I have a radar chart working in a regular js file, and a react app component that can render and return a simple bar chart. I'm trying to get the much more complex radar chart to render and return in react's component. I'm currently getting an error: ./src/App.js Attempted import error: 'scale' is not exported from 'd3' (imported as 'd3'). Any other feedback/suggestions in general would be appreciated. I'm new to D3. Thanks so much in advance.
import * as d3 from "d3";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
/////////////////////////////////////////////////////////
/////////////// The Radar Chart Function ////////////////
/////////////// Written by Nadieh Bremer ////////////////
////////////////// VisualCinnamon.com ///////////////////
/////////// Inspired by the code of alangrafu ///////////
/////////////////////////////////////////////////////////
/* Radar chart design created by Nadieh Bremer - VisualCinnamon.com */
componentDidMount() {
//////////////////////// Set-Up //////////////////////////////
var margin = { top: 100, right: 100, bottom: 100, left: 100 },
width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right,
height = Math.min(
width,
window.innerHeight - margin.top - margin.bottom - 20
);
////////////////////////// Data //////////////////////////////
var data = [
[
//Tsircon
{ axis: "BALLISTIC", value: 65 },
{ axis: "Glide", value: 39 },
{ axis: "20G TURN", value: 43 },
{ axis: "FISH HOOK DIVE", value: 50 },
{ axis: "PHUGOID MOTION", value: 60 },
{ axis: "ENERGY SCRUB", value: 52 },
],
// [
// //Kinzhal
// { axis: "BALLISTIC", value: 0.27 },
// { axis: "Glide", value: 0.16 },
// { axis: "20G TURN", value: 0.35 },
// { axis: "FISH HOOK DIVE", value: 0.13 },
// { axis: "PHUGOID MOTION", value: 0.2 },
// { axis: "ENERGY SCRUB", value: 0.13 },
// ],
// [
// //Example Missile Overlay
// { axis: "BALLISTIC", value: 0.26 },
// { axis: "Glide", value: 0.1 },
// { axis: "20G TURN", value: 0.3 },
// { axis: "FISH HOOK DIVE", value: 0.14 },
// { axis: "PHUGOID MOTION", value: 0.22 },
// { axis: "ENERGY SCRUB", value: 0.04 },
// ],
];
//////////////////// Draw the Chart //////////////////////////
var color = d3.scale.ordinal().range(["#739CC4", "#CC333F", "#00A0B0"]);
var radarChartOptions = {
w: width,
h: height,
margin: margin,
maxValue: 0.5,
levels: 5,
roundStrokes: false,
color: color,
};
//Call function to draw the Radar chart
RadarChart(".radarChart", data, radarChartOptions);
function RadarChart(id, data, options) {
var cfg = {
w: 600, //Width of the circle
h: 600, //Height of the circle
margin: { top: 20, right: 20, bottom: 20, left: 20 }, //The margins of the SVG
levels: 3, //How many levels or inner circles should there be drawn
maxValue: 0, //What is the value that the biggest circle will represent
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
opacityArea: 0.35, //The opacity of the area of the blob
dotRadius: 4, //The size of the colored circles of each blog
opacityCircles: 0.1, //The opacity of the circles of each blob
strokeWidth: 2, //The width of the stroke around each blob
roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
color: d3.scale.category10(), //Color function
};
//Put all of the options into a variable called cfg
if ("undefined" !== typeof options) {
for (var i in options) {
if ("undefined" !== typeof options[i]) {
cfg[i] = options[i];
}
} //for i
} //if
//If the supplied maxValue is smaller than the actual one, replace by the max in the data
var maxValue = Math.max(
cfg.maxValue,
d3.max(data, function (i) {
return d3.max(
i.map(function (o) {
return o.value;
})
);
})
);
var allAxis = data[0].map(function (i, j) {
return i.axis;
}), //Names of each axis
total = allAxis.length, //The number of different axes
radius = Math.min(cfg.w / 2, cfg.h / 2), //Radius of the outermost circle
Format = d3.format("%"), //Percentage formatting
angleSlice = (Math.PI * 2) / total; //The width in radians of each "slice"
//Scale for the radius
var rScale = d3.scale.linear().range([0, radius]).domain([0, maxValue]);
//////////// Create the container SVG and g /////////////
//Remove whatever chart with the same id/class was present before
d3.select(id).select("svg").remove();
//Initiate the radar chart SVG
var svg = d3
.select(id)
.append("svg")
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr("class", "radar" + id);
//Append a g element
var g = svg
.append("g")
.attr(
"transform",
"translate(" +
(cfg.w / 2 + cfg.margin.left) +
"," +
(cfg.h / 2 + cfg.margin.top) +
")"
);
////////// Glow filter ///////////
//Filter for the outside glow
var filter = g.append("defs").append("filter").attr("id", "glow"),
feGaussianBlur = filter
.append("feGaussianBlur")
.attr("stdDeviation", "2.5")
.attr("result", "coloredBlur"),
feMerge = filter.append("feMerge"),
feMergeNode_1 = feMerge.append("feMergeNode").attr("in", "coloredBlur"),
feMergeNode_2 = feMerge.append("feMergeNode").attr("in", "SourceGraphic");
/////////////// Draw the Circular grid //////////////////
//Wrapper for the grid & axes
var axisGrid = g.append("g").attr("class", "axisWrapper");
//Draw the background circles
axisGrid
.selectAll(".levels")
.data(d3.range(1, cfg.levels + 1).reverse())
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", function (d, i) {
return (radius / cfg.levels) * d;
})
.style("fill", "#CDCDCD")
.style("stroke", "#CDCDCD")
.style("fill-opacity", cfg.opacityCircles)
.style("filter", "url(#glow)");
//Text indicating at what % each level is
axisGrid
.selectAll(".axisLabel")
.data(d3.range(1, cfg.levels + 1).reverse())
.enter()
.append("text")
.attr("class", "axisLabel")
.attr("x", 4)
.attr("y", function (d) {
return (-d * radius) / cfg.levels;
})
.attr("dy", "0.4em")
.style("font-size", "10px")
.attr("fill", "#737373")
.text(function (d, i) {
return Format((maxValue * d) / cfg.levels);
});
//////////////////// Draw the axes //////////////////////
//Create the straight lines radiating outward from the center
var axis = axisGrid
.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis");
//Append the lines
axis
.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", function (d, i) {
return rScale(maxValue * 1.1) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("y2", function (d, i) {
return rScale(maxValue * 1.1) * Math.sin(angleSlice * i - Math.PI / 2);
})
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
//Append the labels at each axis
axis
.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", function (d, i) {
return (
rScale(maxValue * cfg.labelFactor) *
Math.cos(angleSlice * i - Math.PI / 2)
);
})
.attr("y", function (d, i) {
return (
rScale(maxValue * cfg.labelFactor) *
Math.sin(angleSlice * i - Math.PI / 2)
);
})
.text(function (d) {
return d;
})
.call(wrap, cfg.wrapWidth);
svg.append("text")
.attr("x", (width / 4.8))
.attr("y", 75 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "24px")
.style("font-weight", "bold")
.style("fill", "white")
.text("MANEUVERS");
svg.append("text")
.attr("x", (width / 4.2))
.attr("y", 95 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "white")
.text("initiate: % ground track");
svg.append("text")
.attr("x", (width / 3.6))
.attr("y", 115 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#8CD9FF")
.text("% variability");
svg.append("text")
.attr("x", (width / 8))
.attr("y", 135 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "white")
.text("sample:");
svg.append("text")
.attr("x", (width / 3.4))
.attr("y", 135 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#76ADDB")
.text("% trajectories");
///////////// Draw the radar chart blobs ////////////////
//The radial line function
var radarLine = d3.svg.line
.radial()
.interpolate("linear-closed")
.radius(function (d) {
return rScale(d.value);
})
.angle(function (d, i) {
return i * angleSlice;
});
if (cfg.roundStrokes) {
radarLine.interpolate("cardinal-closed");
}
//Create a wrapper for the blobs
var blobWrapper = g
.selectAll(".radarWrapper")
.data(data)
.enter()
.append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", function (d, i) {
return radarLine(d);
})
.style("fill", function (d, i) {
return cfg.color(i);
})
.style("fill-opacity", cfg.opacityArea)
.on("mouseover", function (d, i) {
//Dim all blobs
d3.selectAll(".radarArea")
.transition()
.duration(200)
.style("fill-opacity", 0.1);
//Bring back the hovered over blob
d3.select(this).transition().duration(200).style("fill-opacity", 0.7);
})
.on("mouseout", function () {
//Bring back all blobs
d3.selectAll(".radarArea")
.transition()
.duration(200)
.style("fill-opacity", cfg.opacityArea);
});
//Create the outlines
blobWrapper
.append("path")
.attr("class", "radarStroke")
.attr("d", function (d, i) {
return radarLine(d);
})
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", function (d, i) {
return cfg.color(i);
})
.style("fill", "none")
.style("filter", "url(#glow)");
//Append the circles
blobWrapper
.selectAll(".radarCircle")
.data(function (d, i) {
return d;
})
.enter()
.append("circle")
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", function (d, i) {
return rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function (d, i) {
return rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style("fill", function (d, i, j) {
return cfg.color(j);
})
.style("fill-opacity", 0.8);
//////// Append invisible circles for tooltip ///////////
//Wrapper for the invisible circles on top
var blobCircleWrapper = g
.selectAll(".radarCircleWrapper")
.data(data)
.enter()
.append("g")
.attr("class", "radarCircleWrapper");
//Append a set of invisible circles on top for the mouseover pop-up
blobCircleWrapper
.selectAll(".radarInvisibleCircle")
.data(function (d, i) {
return d;
})
.enter()
.append("circle")
.attr("class", "radarInvisibleCircle")
.attr("r", cfg.dotRadius * 1.5)
.attr("cx", function (d, i) {
return rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function (d, i) {
return rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function (d, i) {
newX = parseFloat(d3.select(this).attr("cx")) - 10;
newY = parseFloat(d3.select(this).attr("cy")) - 10;
tooltip
.attr("x", newX)
.attr("y", newY)
.text(Format(d.value))
.transition()
.duration(200)
.style("opacity", 1);
})
.on("mouseout", function () {
tooltip.transition().duration(200).style("opacity", 0);
});
//Set up the small tooltip for when you hover over a circle
var tooltip = g.append("text").attr("class", "tooltip").style("opacity", 0);
/////////////////// Helper Function /////////////////////
//Taken from http://bl.ocks.org/mbostock/7555321
//Wraps SVG text
function wrap(text, width) {
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text
.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
} //wrap
} //RadarChart
} //componentDidMount
render() {
const styles = {
container: {
display: "grid",
justifyItems: "center"
}
};
return (
// <div ref="chart" style={styles.container}>
<div className=".radarChart" style={styles.container}>
<h1 style={{ textAlign: "center" }}>Hi, I'm the radar chart</h1>
</div>
);
}
}
export default App;
Edit: update: so I found some answers elsewhere, thank you #Michael Rovinsky for pointing me in the right direction. Some of this code was pre D3 version 4.
d3.scale.linear() vs d3.scaleLinear()
What is the d3.js v4.0 equivalent for d3.scale.category10()?
Current error: I am now getting
src\App.js
Line 406:8: 'newX' is not defined no-undef
Line 407:8: 'newY' is not defined no-undef
Line 410:21: 'newX' is not defined no-undef
Line 411:21: 'newY' is not defined no-undef
Which was fixed by defining them with "let" so the new code is
let newX = parseFloat(d3.select(this).attr("cx")) - 10; let newY = parseFloat(d3.select(this).attr("cy")) - 10;
Finally I was getting an error TypeError: Cannot read property 'radial' of undefined
which was solved by updating var radarLine = d3.svg.line.radial().interpolate("linear-closed").radius(...
to radarLine = d3.lineRadial().radius(...
The graph rendered once I fixed the typo className=".radarChart" to className="radarChart"
Related
I am trying to create responsive donut chart in d3.js.
I wrote the code for chart in d3.js, but I am not able to create according to expected chart.
Here's the actual chart, while I'm trying to achieve is this one.
My d3.js script below:
document.addEventListener('DOMContentLoaded', function(e) {
var dataset = {
oranges: [200, 200, 200, 200]
};
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var enterClockwise = {
startAngle: 0,
endAngle: 0
};
var enterAntiClockwise = {
startAngle: Math.PI * 2,
endAngle: Math.PI * 2
};
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 20);
var svg = d3.select('#Donut-chart').append('svg')
.attr('id', 'Donut-chart-render')
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox', (-width / 2) + ' ' + (-height / 2) + ' ' + width + ' ' + height)
.attr('preserveAspectRatio', 'xMinYMin')
var path = svg.selectAll("path")
.data(pie(dataset.apples))
.enter().append("path")
.attr("fill", function (d, i) { return color(i); })
.attr("d", arc(enterClockwise))
.each(function (d) {
this._current = {
data: d.data,
value: d.value,
startAngle: enterClockwise.startAngle,
endAngle: enterClockwise.endAngle
}
});
path.transition()
.duration(750)
.attrTween("d", arcTween);
d3.selectAll("input").on("change", change);
var timeout = setTimeout(function () {
d3.select("input[value=\"oranges\"]").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(timeout);
path = path.data(pie(dataset[this.value]));
path.enter().append("path")
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", arc(enterAntiClockwise))
.each(function (d) {
this._current = {
data: d.data,
value: d.value,
startAngle: enterAntiClockwise.startAngle,
endAngle: enterAntiClockwise.endAngle
};
}); // store the initial values
path.exit()
.transition()
.duration(750)
.attrTween('d', arcTweenOut)
.remove() // now remove the exiting arcs
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return arc(i(t));
};
}
function arcTweenOut(a) {
var i = d3.interpolate(this._current, { startAngle: Math.PI * 2, endAngle: Math.PI * 2, value: 0 });
this._current = i(0);
return function (t) {
return arc(i(t));
};
}
function type(d) {
d.value = +d.value;
return d;
}
});
I'm stuck with this problem, so helpful answers will be appreciated.
Thanks in advance!
To add labels to a pie chart, use arc.centroid(). You'll need an arc generator first. See this example for reference.
svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 12)
.attr("text-anchor", "middle")
.selectAll("text")
.data(arcs)
.join("text")
.attr("transform", d => `translate(${arc.centroid(d)})`)
.call(text => text.append("tspan")
.attr("y", "-0.4em")
.attr("font-weight", "bold")
.text(d => d.data.name))
.call(text => text.filter(d => (d.endAngle - d.startAngle) > 0.25).append("tspan")
.attr("x", 0)
.attr("y", "0.7em")
.attr("fill-opacity", 0.7)
.text(d => d.data.value.toLocaleString()));
I'm a newbie both in JSON and js. I'm trying to load json data based on this radarchart. If I add the data just after var data = MY DATA, it works but if I load json file, it doesn't. For loading data, I follow the last suggestion entitled “What does d3.json return?” (v5) of this post--in fact the others didn't worked too--; thus, I have added <script src="https://d3js.org/d3.v5.min.js"></script> in addition to <script src="https://d3js.org/d3.v4.min.js"></script> which I used for other barplots and piecharts in the same webpage.
It works
<head>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
<!-- source: https://gist.github.com/mthh/7e17b680b35b83b49f1c22a3613bd89f -->
<script src="../other/radarChart.js" charset="utf-8"></script>
</head>
<body>
<!-- a few barplots and piecharts -->
<!-- afterwards the following radarChart -->
<table>
<tr>
<td style="radarChart">
<div class="radarChart" style="display: inline-flex;"></div>
<script>
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var marginRadar = { top: 150, right: 80, bottom: 50, left: 80 },
widthRadar = Math.min(700, window.innerWidth / 4) - marginRadar.left - marginRadar.right,
heightRadar = Math.min(width, window.innerHeight - marginRadar.top - marginRadar.bottom);
//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////
var data = [
{
"name": "Clan A",
"axes": [
{"axis": "cognition", "value": 3.12},
{"axis": "communication","value": 9.38},
{"axis": "competition","value": 18.75},
{"axis": "consumption","value": 6.25},
{"axis": "contact","value": 21.88},
{"axis": "emotion","value": 6.25},
{"axis": "motion","value": 18.75},
{"axis": "perception","value": 3.12},
{"axis": "possession","value": 3.12},
{"axis": "social","value": 3.12}
]
},
{"name": "clan B",
"axes": [
{"axis": "cognition","value": 3.12},
{"axis": "communication","value": 0.00},
{"axis": "competition","value": 0.00},
{"axis": "consumption","value": 0.00},
{"axis": "contact","value": 9.38},
{"axis": "emotion","value": 0.00},
{"axis": "motion","value": 0.00},
{"axis": "perception","value": 0.00},
{"axis": "possession","value": 0.00},
{"axis": "social","value": 0.00}
]
}
];
var radarChartOptions = {
w: 290,
h: 350,
margin: marginRadar,
levels: 6,
roundStrokes: false,
color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
format: '.0f',
legend: { title: 'Legend', translateX: 150, translateY: 100 },
unit: '%'
};
// Draw the chart, get a reference the created svg element :
let svg_radar = RadarChart(".radarChart", data, radarChartOptions);
</script>
</td>
</tr>
</table>
</body>
It doesn't work: loading json file [note: everything is the same before and after the following suggestion, except that I have added <script src="https://d3js.org/d3.v5.min.js"></script> between <td> and <div class="radarChart"..> so that there is no conflict with barplots and piecharts which need v4 instead of v5]:
<table>
<tr>
<td style="radarChart">
<!-- add v5 here -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<div class="radarChart" style="display: inline-flex;"></div>
<!-- [same data ] -->
[...]
var data = d3.json("../other/radarChart-data.json");
console.log(data)
<!-- [same data after] -->
[...]
Thus my question is: how to load JSON file for this radarchart? I prefer to load a file since "value: XX" are not fixed, but changed according to new values adding in related xml files.
In advance, thanks so much for your kind help.
The solution suggested here works:
https://www.quora.com/How-can-I-load-data-from-a-JSON-file-into-a-variable-in-JavaScript-without-using-Ajax?fbclid=IwAR1ilYFGKBaNsUupYOwIBSVMHBDP24o7j87WP5GzZSrlwIsyLK1riU5JCRQ
It bypasses the constraints of Ajax in a beautifully simple way!
move the declaration of your var data into a separate file
load this file as a javascript
use the variable as you need to!
Important: Both imports of d3 edit the global scope, so one will override the other. I recommend to just choose v4 for the radar chart.
My answer is very long, because I had to include the RadarChart code to make it work. Just scroll down to the very end to see what I did. d3.json needs a function as a second argument, and will call that function with the result:
/////////////////////////////////////////////////////////
/////////////// The Radar Chart Function ////////////////
/// mthh - 2017 /////////////////////////////////////////
// Inspired by the code of alangrafu and Nadieh Bremer //
// (VisualCinnamon.com) and modified for d3 v4 //////////
/////////////////////////////////////////////////////////
const max = Math.max;
const sin = Math.sin;
const cos = Math.cos;
const HALF_PI = Math.PI / 2;
const RadarChart = function RadarChart(parent_selector, data, options) {
//Wraps SVG text - Taken from http://bl.ocks.org/mbostock/7555321
const wrap = (text, width) => {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
} //wrap
const cfg = {
w: 600, //Width of the circle
h: 600, //Height of the circle
margin: {
top: 20,
right: 20,
bottom: 20,
left: 20
}, //The margins of the SVG
levels: 3, //How many levels or inner circles should there be drawn
maxValue: 0, //What is the value that the biggest circle will represent
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
opacityArea: 0.35, //The opacity of the area of the blob
dotRadius: 4, //The size of the colored circles of each blog
opacityCircles: 0.1, //The opacity of the circles of each blob
strokeWidth: 2, //The width of the stroke around each blob
roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
color: d3.scaleOrdinal(d3.schemeCategory10), //Color function,
format: '.2%',
unit: '',
legend: false
};
//Put all of the options into a variable called cfg
if ('undefined' !== typeof options) {
for (var i in options) {
if ('undefined' !== typeof options[i]) {
cfg[i] = options[i];
}
} //for i
} //if
//If the supplied maxValue is smaller than the actual one, replace by the max in the data
// var maxValue = max(cfg.maxValue, d3.max(data, function(i){return d3.max(i.map(function(o){return o.value;}))}));
let maxValue = 0;
for (let j = 0; j < data.length; j++) {
for (let i = 0; i < data[j].axes.length; i++) {
data[j].axes[i]['id'] = data[j].name;
if (data[j].axes[i]['value'] > maxValue) {
maxValue = data[j].axes[i]['value'];
}
}
}
maxValue = max(cfg.maxValue, maxValue);
const allAxis = data[0].axes.map((i, j) => i.axis), //Names of each axis
total = allAxis.length, //The number of different axes
radius = Math.min(cfg.w / 2, cfg.h / 2), //Radius of the outermost circle
Format = d3.format(cfg.format), //Formatting
angleSlice = Math.PI * 2 / total; //The width in radians of each "slice"
//Scale for the radius
const rScale = d3.scaleLinear()
.range([0, radius])
.domain([0, maxValue]);
/////////////////////////////////////////////////////////
//////////// Create the container SVG and g /////////////
/////////////////////////////////////////////////////////
const parent = d3.select(parent_selector);
//Remove whatever chart with the same id/class was present before
parent.select("svg").remove();
//Initiate the radar chart SVG
let svg = parent.append("svg")
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr("class", "radar");
//Append a g element
let g = svg.append("g")
.attr("transform", "translate(" + (cfg.w / 2 + cfg.margin.left) + "," + (cfg.h / 2 + cfg.margin.top) + ")");
/////////////////////////////////////////////////////////
////////// Glow filter for some extra pizzazz ///////////
/////////////////////////////////////////////////////////
//Filter for the outside glow
let filter = g.append('defs').append('filter').attr('id', 'glow'),
feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation', '2.5').attr('result', 'coloredBlur'),
feMerge = filter.append('feMerge'),
feMergeNode_1 = feMerge.append('feMergeNode').attr('in', 'coloredBlur'),
feMergeNode_2 = feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
/////////////////////////////////////////////////////////
/////////////// Draw the Circular grid //////////////////
/////////////////////////////////////////////////////////
//Wrapper for the grid & axes
let axisGrid = g.append("g").attr("class", "axisWrapper");
//Draw the background circles
axisGrid.selectAll(".levels")
.data(d3.range(1, (cfg.levels + 1)).reverse())
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", d => radius / cfg.levels * d)
.style("fill", "#CDCDCD")
.style("stroke", "#CDCDCD")
.style("fill-opacity", cfg.opacityCircles)
.style("filter", "url(#glow)");
//Text indicating at what % each level is
axisGrid.selectAll(".axisLabel")
.data(d3.range(1, (cfg.levels + 1)).reverse())
.enter().append("text")
.attr("class", "axisLabel")
.attr("x", 4)
.attr("y", d => -d * radius / cfg.levels)
.attr("dy", "0.4em")
.style("font-size", "10px")
.attr("fill", "#737373")
.text(d => Format(maxValue * d / cfg.levels) + cfg.unit);
/////////////////////////////////////////////////////////
//////////////////// Draw the axes //////////////////////
/////////////////////////////////////////////////////////
//Create the straight lines radiating outward from the center
var axis = axisGrid.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis");
//Append the lines
axis.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", (d, i) => rScale(maxValue * 1.1) * cos(angleSlice * i - HALF_PI))
.attr("y2", (d, i) => rScale(maxValue * 1.1) * sin(angleSlice * i - HALF_PI))
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
//Append the labels at each axis
axis.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", (d, i) => rScale(maxValue * cfg.labelFactor) * cos(angleSlice * i - HALF_PI))
.attr("y", (d, i) => rScale(maxValue * cfg.labelFactor) * sin(angleSlice * i - HALF_PI))
.text(d => d)
.call(wrap, cfg.wrapWidth);
/////////////////////////////////////////////////////////
///////////// Draw the radar chart blobs ////////////////
/////////////////////////////////////////////////////////
//The radial line function
const radarLine = d3.radialLine()
.curve(d3.curveLinearClosed)
.radius(d => rScale(d.value))
.angle((d, i) => i * angleSlice);
if (cfg.roundStrokes) {
radarLine.curve(d3.curveCardinalClosed)
}
//Create a wrapper for the blobs
const blobWrapper = g.selectAll(".radarWrapper")
.data(data)
.enter().append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", d => radarLine(d.axes))
.style("fill", (d, i) => cfg.color(i))
.style("fill-opacity", cfg.opacityArea)
.on('mouseover', function(d, i) {
//Dim all blobs
parent.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", 0.1);
//Bring back the hovered over blob
d3.select(this)
.transition().duration(200)
.style("fill-opacity", 0.7);
})
.on('mouseout', () => {
//Bring back all blobs
parent.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", cfg.opacityArea);
});
//Create the outlines
blobWrapper.append("path")
.attr("class", "radarStroke")
.attr("d", function(d, i) {
return radarLine(d.axes);
})
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", (d, i) => cfg.color(i))
.style("fill", "none")
.style("filter", "url(#glow)");
//Append the circles
blobWrapper.selectAll(".radarCircle")
.data(d => d.axes)
.enter()
.append("circle")
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", (d, i) => rScale(d.value) * cos(angleSlice * i - HALF_PI))
.attr("cy", (d, i) => rScale(d.value) * sin(angleSlice * i - HALF_PI))
.style("fill", (d) => cfg.color(d.id))
.style("fill-opacity", 0.8);
/////////////////////////////////////////////////////////
//////// Append invisible circles for tooltip ///////////
/////////////////////////////////////////////////////////
//Wrapper for the invisible circles on top
const blobCircleWrapper = g.selectAll(".radarCircleWrapper")
.data(data)
.enter().append("g")
.attr("class", "radarCircleWrapper");
//Append a set of invisible circles on top for the mouseover pop-up
blobCircleWrapper.selectAll(".radarInvisibleCircle")
.data(d => d.axes)
.enter().append("circle")
.attr("class", "radarInvisibleCircle")
.attr("r", cfg.dotRadius * 1.5)
.attr("cx", (d, i) => rScale(d.value) * cos(angleSlice * i - HALF_PI))
.attr("cy", (d, i) => rScale(d.value) * sin(angleSlice * i - HALF_PI))
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function(d, i) {
tooltip
.attr('x', this.cx.baseVal.value - 10)
.attr('y', this.cy.baseVal.value - 10)
.transition()
.style('display', 'block')
.text(Format(d.value) + cfg.unit);
})
.on("mouseout", function() {
tooltip.transition()
.style('display', 'none').text('');
});
const tooltip = g.append("text")
.attr("class", "tooltip")
.attr('x', 0)
.attr('y', 0)
.style("font-size", "12px")
.style('display', 'none')
.attr("text-anchor", "middle")
.attr("dy", "0.35em");
if (cfg.legend !== false && typeof cfg.legend === "object") {
let legendZone = svg.append('g');
let names = data.map(el => el.name);
if (cfg.legend.title) {
let title = legendZone.append("text")
.attr("class", "title")
.attr('transform', `translate(${cfg.legend.translateX},${cfg.legend.translateY})`)
.attr("x", cfg.w - 70)
.attr("y", 10)
.attr("font-size", "12px")
.attr("fill", "#404040")
.text(cfg.legend.title);
}
let legend = legendZone.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 200)
.attr('transform', `translate(${cfg.legend.translateX},${cfg.legend.translateY + 20})`);
// Create rectangles markers
legend.selectAll('rect')
.data(names)
.enter()
.append("rect")
.attr("x", cfg.w - 65)
.attr("y", (d, i) => i * 20)
.attr("width", 10)
.attr("height", 10)
.style("fill", (d, i) => cfg.color(i));
// Create labels
legend.selectAll('text')
.data(names)
.enter()
.append("text")
.attr("x", cfg.w - 52)
.attr("y", (d, i) => i * 20 + 9)
.attr("font-size", "11px")
.attr("fill", "#737373")
.text(d => d);
}
return svg;
}
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var marginRadar = {
top: 150,
right: 80,
bottom: 50,
left: 80
},
widthRadar = Math.min(700, window.innerWidth / 4) - marginRadar.left - marginRadar.right,
heightRadar = Math.min(widthRadar, window.innerHeight - marginRadar.top - marginRadar.bottom);
//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////
var radarChartOptions = {
w: 290,
h: 350,
margin: marginRadar,
levels: 6,
roundStrokes: false,
color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
format: '.0f',
legend: {
title: 'Legend',
translateX: 150,
translateY: 100
},
unit: '%'
};
// Just some example JSON data I found at https://support.oneskyapp.com/hc/en-us/articles/208047697-JSON-sample-files
d3.json('https://support.oneskyapp.com/hc/en-us/article_attachments/202761727/example_2.json', function(fakeData) {
// Which I then map into your format - you don't need to do this
const data = Object.entries(fakeData['quiz']).map(([topic, questionObject]) => ({
name: topic,
axes: [
{ axis: 'difficulty', value: Object.keys(questionObject).length },
{ axis: 'depth', value: Object.keys(questionObject).length },
{ axis: 'duration', value: Object.keys(questionObject).length },
{ axis: 'delicacy', value: Object.keys(questionObject).length }
]
}));
// Draw the chart, get a reference the created svg element :
let svg_radar = RadarChart(".radarChart", data, radarChartOptions);
});
<head>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
<!-- source: https://gist.github.com/mthh/7e17b680b35b83b49f1c22a3613bd89f -->
</head>
<body>
<table>
<tr>
<td style="radarChart">
<div class="radarChart" style="display: inline-flex;"></div>
</td>
</tr>
</table>
I am trying to add checkboxes and text in a node. See below image of what I am trying to achieve.
I can see the checkbox in elements view but cannot view it in page.
But this is what I am getting for now.
Below is the code used.
(function() {
var width, height, rules, map, tasks, links, nodes, svg, tick, radius, force, link, node;
width = 960;
height = 500;
rules = [
['root', 'iot'],
['root', 'points'],
['root', 'camnative'],
['root', 'classifier'],
['points', 'classifier2'],
['camnative', 'classifier3'],
['classifier', 'consec'],
['iot', 'classifier1'],
['cloudclassif', 'schedule'],
['schedule', 'privacy'],
['privacy', 'roi'],
['roi', 'flooding'],
['classifier1', 'cloudclassif'],
['classifier2', 'cloudclassif'],
['classifier3', 'cloudclassif'],
['consec', 'cloudclassif']
];
map = d3.map();
rules.forEach(function(rule) {
map.set(rule[0], {
fixed: false
});
return map.set(rule[1], false);
});
map.set('root', {
fixed: true,
x: 100,
y: height / 2
});
// map.set('P4', {
// fixed: true,
// x: width / 2 - 100,
// y: height / 2
// });
tasks = map.keys();
links = rules.map(function(rule) {
return {
source: tasks.indexOf(rule[0]),
target: tasks.indexOf(rule[1])
};
});
nodes = tasks.map(function(k) {
var entry;
entry = {
name: k
};
if (map.get(k).fixed) {
entry.fixed = true;
entry.x = map.get(k).x;
entry.y = map.get(k).y;
}
return entry;
});
svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.append("svg:defs")
.append("svg:marker")
.attr("id", "arrow")
.attr("viewBox", "0 0 10 10")
.attr("refX", 0)
.attr("refY", 5)
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", 8)
.attr("markerHeight", 6)
// .attr("orient", "auto")
.append("svg:path")
.attr("d", "M 0 0 L 10 5 L 0 10 z");
svg.append("line")
.attr("x1", 5)
.attr("x2", 50)
.attr("y1", 5)
.attr("y2", 50)
.style("stroke", "black")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrow)");
tick = function() {
var arrowheadLength = 8, // from markerWidth
nodeRadius = 10;
link.each(function(d) {
var x1 = d.source.x,
y1 = d.source.y,
x2 = d.target.x,
y2 = d.target.y,
angle = Math.atan2(y2 - y1, x2 - x1);
d.targetX = x2 - Math.cos(angle) * (nodeRadius + arrowheadLength);
d.targetY = y2 - Math.sin(angle) * (nodeRadius + arrowheadLength);
});
link.selectAll("line").attr("x1", function(d) {
return d.source.x;
}).attr("y1", function(d) {
return d.source.y;
}).attr("x2", function(d) {
return d.targetX;
}).attr("y2", function(d) {
return d.targetY;
}).attr("marker-end", "url(#arrow)");
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
};
radius = d3.scale.sqrt().range([0, 6]);
force = d3.layout.force().size([width / 2, height]).charge(-900).linkDistance(function(d) {
return 40;
});
force.nodes(nodes).links(links).on("tick", tick).start();
link = svg.selectAll(".link").data(links).enter().append("g").attr("class", "link");
link.append("line").style("stroke-width", 1).attr("marker-end", "url(#arrow)");
node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.call(force.drag);
node.append("rect")
.attr("class", "node")
.attr("width", 100)
.attr("height", 50);
node.append("input")
.attr("type", "checkbox")
.attr("class", "mycheck")
.attr("fill", "black");
node.append("text")
.attr("x", function(d) { return (d) - 3; })
.attr("y", 50 / 2)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
console.log("HERE2");
console.log("HERE5");
}).call(this);
I checked D3 v3 appending checkbox?, but that solution does not work.
Update
Added
node.append("foreignObject")
.attr("width", 100)
.attr("height", 100)
.append("xhtml:chart")
.append("div")
.append("input")
.attr("type", "checkbox");
Add the checkboxes are added to the node.
I played around with this and got a bit further. I found discovered a few things:
As mentioned by others, put the checkbox inside of a <foreignObject>
Use the "xhtml:" prefix before the html objects inside of the foreignObject.
You have some bad math in your data functions. For example: return (d) - 3; returns NaN. You need something like return d.x - 3;
Items inside of the <g> element are positioned relative to the group. In my fiddle example (link at bottom), I positioned the top-lefts of the rectangles at (0,-16).
// Create the <foreignObject>:
let ddiv = node.append("foreignObject")
.attr("x", -10)
.attr("y", -34)
.attr("width", 50)
.attr("height",50)
.append("xhtml:div") // <<<---- xhtml: prefix!
.classed("roundedOne", true)
ddiv.append("xhtml:input") // <<<---- xhtml: prefix!
.attr("type", "checkbox")
.attr("id", (d) => d.name);
ddiv.append("xhtml:label") // <<<---- xhtml: prefix!
.attr("for", (d) => d.name);
Check out my jsFiddle: https://jsfiddle.net/visioguy/yz5tfgjm/
I added some fancy checkbox styling that fit better into your boxes, which is why I added the extra <div> and <label> to your code. You'll have to fiddle with more of the margins and sizes and offsets, and perhaps some of the force-layout parameters to get the layout to work properly.
I have js code that plots a simple polar chart in the HTML page. I am trying to have it update the data on the plot every 1 second.
I was able to update the data and plot the new points, however, there are two things I have not figured out yet.
Right now every second I am cleaning the plot area and plotting everything new, instead I would like to update the data by creating a smooth sliding transition from the previous position to the new one.
The second issue is that the labeling of the points is not being deleted with the old point position, which creates multiple labeling on top of each other.
What should I add in order to create this smooth transition for the plotted points and their labels?
Here is my code snippet:
<html>
<head>
<meta charset='ISO-8859-1'>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style='background-color:lightgray'>
<div id="chart" style='width: 400px; height: 400px; padding-left: 5px; padding-bottom: 5px;'></div>
<script>
var color = d3.scale.category20();
var deg2rad = Math.PI / 180;
var width = 400,
height = 350,
radius = Math.min(width, height) / 2 - 30;
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var r = d3.scale.linear()
.domain([90, 0])
.range([0, radius]);
var line = d3.svg.line.radial()
.radius(function(d) {
return r(d[1]);
})
.angle(function(d) {
return -d[0] + Math.PI / 2;
});
var gr = null;
createSkyplot();
updateSkyPlot();
function createSkyplot() {
//////////////////////
gr = svg.append("g")
.attr("class", "r axis")
.selectAll("g")
.data(r.ticks(5))
.enter().append("g");
gr.append("circle").attr("r", r).style('fill', 'white');
gr.append("text")
.attr("y", function(d) {
return -r(d) - 4;
})
.attr("transform", "rotate(20)")
.style("text-anchor", "middle")
.style('fill', 'blue')
.text(function(d) {
return d;
});
/////////////////////
/////////////////////
var ga = svg.append("g")
.attr("class", "a axis")
.selectAll("g")
.data(d3.range(0, 360, 45))
.enter().append("g")
.attr("transform", function(d) {
return "rotate(" + (d - 90) + ")";
});
ga.append("line").attr("x2", radius).style('stroke', 'black').style('stroke-dasharray', '1,8');
ga.append("text")
.attr("x", radius + 6)
.attr("dy", ".35em")
.style("text-anchor", function(d) {
return d < 360 && d > 90 ? "end" : null;
})
.attr("transform", function(d) {
return d < 360 && d > 90 ? "rotate(180 " + (radius + 3) + ",0)" : null;
})
.text(function(d) {
return d + "°";
});
/////////////////////
}
function updateSkyPlot() {
var pos = [{
"position": [1, Math.random() * 20, Math.random() * 20],
"label": 1
}, {
"position": [3, Math.random() * 20, Math.random() * 20],
"label": 5
}];
var r = d3.scale.linear()
.domain([90, 0])
.range([0, radius]);
var line = d3.svg.line.radial()
.radius(function(d) {
return r(d[1]);
})
.angle(function(d) {
return -d[0] + Math.PI / 2;
});
svg.selectAll('circle').remove();
gr.append("circle").attr("r", r).style('fill', 'white');
var points = svg.selectAll("point")
.data(pos)
.enter()
.append("a") // The container
.attr("transform", function(d) {
var coors = line([d.position]).slice(1).slice(0, -1);
return "translate(" + coors + ")"
});
points.append("circle")
.attr("class", "point")
.attr("r", 10)
.attr("fill", function(d, i) {
return color(i);
});
points.append("text")
.attr("class", "label")
.text(function(d) {
return d.label
})
.style("font-size", "10")
.attr("transform", "translate(-4,5)");
setTimeout(updateSkyPlot, 1000);
}
</script>
</body>
</html>
Code can also be seen here.
I have tried adding
svg.selectAll("label").remove(); or svg.selectAll('point.label').remove();
but it does not work and when i add svg.selectAll("text").remove(); it removes all the text in the plot which is obviously something i don't want.
Any help on fixing those issues would be appreciated. Thank you.
I have a graph that uses d3.radialLine that can be seen in this fiddle. My data points on the line are visualized by plotting the circle on the proper axis value. I need to have a line down the middle of the svg element that will give me the reading of the plotted data point that is touching it. You can see a graphic image of what I'm trying to accomplish:
I would like to programmatically rotate, for example clockwise so that December will be inline with the red line, and be able to read that data point. I know that I'll have to get the x2 value of the red line and use 'x2' of the December value to find out how many degrees I would need to rotate the graph, but I can't seem to figure that part out. The code was taken from Radar Chart.
I know that formula for rotating..d3.select(...).attr('transform', 'rotate(degree, x, y)'
Code
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var margin = {top: 100, right: 100, bottom: 100, left: 100},
width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right,
height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 20);
//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////
var data = [
// Yearly
[{axis:"Jan",value: 700},
{axis:"Feb",value: 1453},
{axis:"March",value: 1300},
{axis:"April",value: 1534},
{axis:"May",value: 1534},
{axis:"June",value: 1547},
{axis:"July",value: 1100},
{axis:"August",value: 1800},
{axis:"September",value: 1700},
{axis:"October",value: 1500},
{axis:"November",value: 1000},
{axis:"December",value: 1200}
]
];
//////////////////////////////////////////////////////////////
//////////////////// Draw the Chart //////////////////////////
//////////////////////////////////////////////////////////////
var color = d3.scaleOrdinal()
.range(["#58D6C7","#CC333F","#00A0B0"]);
var radarChartOptions = {
w: width,
h: height,
margin: margin,
maxValue: 0.5,
levels: 5,
roundStrokes: true,
color: color,
opacityCircles: 0.1
};
//Call function to draw the Radar chart
RadarChart(".radarChart", data, radarChartOptions);
/////////////////////////////////////////////////////////
/////////////// The Radar Chart Function ////////////////
/////////////// Written by Nadieh Bremer ////////////////
////////////////// VisualCinnamon.com ///////////////////
/////////// Inspired by the code of alangrafu ///////////
/////////////////////////////////////////////////////////
function RadarChart(id, data, options) {
var cfg = {
w: 600, //Width of the circle
h: 600, //Height of the circle
margin: {top: 20, right: 20, bottom: 20, left: 20}, //The margins of the SVG
levels: 3, //How many levels or inner circles should there be drawn
maxValue: 0, //What is the value that the biggest circle will represent
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
opacityArea: 0.35, //The opacity of the area of the blob
dotRadius: 4, //The size of the colored circles of each blog
opacityCircles: 0.1, //The opacity of the circles of each blob
strokeWidth: 2, //The width of the stroke around each blob
roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
color: d3.scaleOrdinal(d3.schemeCategory10) //Color function
};
//Put all of the options into a variable called cfg
if('undefined' !== typeof options){
for(var i in options){
if('undefined' !== typeof options[i]){ cfg[i] = options[i]; }
}//for i
}//if
//If the supplied maxValue is smaller than the actual one, replace by the max in the data
var maxValue = Math.max(cfg.maxValue, d3.max(data, function(i){return d3.max(i.map(function(o){return o.value;}))}));
var allAxis = (data[0].map(function(i, j){return i.axis})), //Names of each axis
total = allAxis.length, //The number of different axes
radius = Math.min(cfg.w/2, cfg.h/2), //Radius of the outermost circle
Format = d3.format(''), //Percentage formatting
angleSlice = Math.PI * 2 / total; //The width in radians of each "slice"
//Scale for the radius
var rScale = d3.scaleLinear()
.range([0, radius])
.domain([0, maxValue]);
/////////////////////////////////////////////////////////
//////////// Create the container SVG and g /////////////
/////////////////////////////////////////////////////////
//Remove whatever chart with the same id/class was present before
d3.select(id).select("svg").remove();
//Initiate the radar chart SVG
var svg = d3.select(id).append("svg")
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr("class", "radar"+id);
//Append a g element
var g = svg.append("g")
.attr("transform", "translate(" + (cfg.w/2 + cfg.margin.left) + "," + (cfg.h/2 + cfg.margin.top) + ")");
/////////////////////////////////////////////////////////
////////// Glow filter for some extra pizzazz ///////////
/////////////////////////////////////////////////////////
//Filter for the outside glow
var filter = g.append('defs').append('filter').attr('id','glow'),
feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation','2.5').attr('result','coloredBlur'),
feMerge = filter.append('feMerge'),
feMergeNode_1 = feMerge.append('feMergeNode').attr('in','coloredBlur'),
feMergeNode_2 = feMerge.append('feMergeNode').attr('in','SourceGraphic');
/////////////////////////////////////////////////////////
/////////////// Draw the Circular grid //////////////////
/////////////////////////////////////////////////////////
//Wrapper for the grid & axes
var axisGrid = g.append("g").attr("class", "axisWrapper");
//Draw the background circles
axisGrid.selectAll(".levels")
.data(d3.range(1,(cfg.levels+1)).reverse())
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", function(d, i){return radius/cfg.levels*d;})
.style("fill", "#CDCDCD")
.style("stroke", "#CDCDCD")
.style("fill-opacity", cfg.opacityCircles)
.style("filter" , "url(#glow)");
//Text indicating at what % each level is
axisGrid.selectAll(".axisLabel")
.data(d3.range(1,(cfg.levels+1)).reverse())
.enter().append("text")
.attr("class", "axisLabel")
.attr("x", 4)
.attr("y", function(d){return -d*radius/cfg.levels;})
.attr("dy", "0.4em")
.style("font-size", "10px")
.attr("fill", "#737373")
.text(function(d,i) { return Format(maxValue * d/cfg.levels); });
/////////////////////////////////////////////////////////
//////////////////// Draw the axes //////////////////////
/////////////////////////////////////////////////////////
//Create the straight lines radiating outward from the center
var axis = axisGrid.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis");
//Append the lines
axis.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", function(d, i){ return rScale(maxValue*1.1) * Math.cos(angleSlice*i - Math.PI/2); })
.attr("y2", function(d, i){ return rScale(maxValue*1.1) * Math.sin(angleSlice*i - Math.PI/2); })
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
//Append the labels at each axis
axis.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.cos(angleSlice*i - Math.PI/2); })
.attr("y", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.sin(angleSlice*i - Math.PI/2); })
.text(function(d){return d})
.call(wrap, cfg.wrapWidth);
/////////////////////////////////////////////////////////
///////////// Draw the radar chart blobs ////////////////
/////////////////////////////////////////////////////////
//The radial line function
var radarLine = d3.radialLine()
.curve(d3.curveLinearClosed)
.radius(function(d) { return rScale(d.value); })
.angle(function(d,i) { return i*angleSlice; });
if(cfg.roundStrokes) {
radarLine.curve(d3.curveCardinalClosed);
}
//Create a wrapper for the blobs
var blobWrapper = g.selectAll(".radarWrapper")
.data(data)
.enter().append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", function(d,i) { return radarLine(d); })
.style("fill", function(d,i) { return cfg.color(i); })
.style("fill-opacity", cfg.opacityArea)
.on('mouseover', function (d,i){
//Dim all blobs
d3.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", 0.1);
//Bring back the hovered over blob
d3.select(this)
.transition().duration(200)
.style("fill-opacity", 0.7);
})
.on('mouseout', function(){
//Bring back all blobs
d3.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", cfg.opacityArea);
});
//Create the outlines
blobWrapper.append("path")
.attr("class", "radarStroke")
.attr("d", function(d,i) { return radarLine(d); })
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", function(d,i) { return cfg.color(i); })
.style("fill", "none")
.style("filter" , "url(#glow)");
//Append the circles
blobWrapper.selectAll(".radarCircle")
.data(function(d,i) { return d; })
.enter().append("circle")
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2); })
.attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2); })
.style("fill", function(d,i,j) { return cfg.color(j); })
.style("fill-opacity", 0.8);
/////////////////////////////////////////////////////////
//////// Append invisible circles for tooltip ///////////
/////////////////////////////////////////////////////////
//Wrapper for the invisible circles on top
var blobCircleWrapper = g.selectAll(".radarCircleWrapper")
.data(data)
.enter().append("g")
.attr("class", "radarCircleWrapper");
//Append a set of invisible circles on top for the mouseover pop-up
blobCircleWrapper.selectAll(".radarInvisibleCircle")
.data(function(d,i) { return d; })
.enter().append("circle")
.attr("class", "radarInvisibleCircle")
.attr("r", cfg.dotRadius*1.5)
.attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2); })
.attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2); })
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function(d,i) {
newX = parseFloat(d3.select(this).attr('cx')) - 10;
newY = parseFloat(d3.select(this).attr('cy')) - 10;
tooltip
.attr('x', newX)
.attr('y', newY)
.text(Format(d.value))
.transition().duration(200)
.style('opacity', 1);
})
.on("mouseout", function(){
tooltip.transition().duration(200)
.style("opacity", 0);
});
//Set up the small tooltip for when you hover over a circle
var tooltip = g.append("text")
.attr("class", "tooltip")
.style("opacity", 0);
/////////////////////////////////////////////////////////
/////////////////// Helper Function /////////////////////
/////////////////////////////////////////////////////////
//Taken from http://bl.ocks.org/mbostock/7555321
//Wraps SVG text
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}//wrap
}//RadarChart
For rotating you just need to increase your initial angle offset over time.
Here's an example:
https://bl.ocks.org/tezzutezzu/c9d8706587e8f5b5d72084b083b502f8
As we know the angle is dependent to the index of the datum, you can calculate the value of the red dots under the red line in this way.
var currentAngle = (offset - Math.PI/2) % (Math.PI*2);
var currentIndex = Math.floor( (currentAngle/(Math.PI*2)) * data[0].length);
var currentValue = data[0][currentIndex].value;