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;
Related
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"
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 facing issue for getting more than 30 legends and that legends could not be shown in vertical way nor horizontal way.
I would like to add scrollbar to only legend box so that all legends would be visible with scroll or is there any way to add legends side by side like 3 in a row something like that
I tried adding overflow property but could not work.
Below is my code
var data =[];
for(var p = 0 ;p <unique.length;p++)
{
data.push({
legendLabel:unique[p],
magnitude:uniquecount[p]
});
}
var canvasWidth = this.getWidth(), //width
canvasHeight = this.getHeight(), //height
outerRadius = 60, //radius
color = d3.scale.category20(); //builtin range of colors
var vis = d3.select("#"+this.htmlObject)
.append("svg:svg") //create the SVG element inside the <body>
.data([data]) //associate our data with the document
.attr("width", canvasWidth) //set the width of the canvas
.attr("height", canvasHeight) //set the height of the canvas
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + 1.5*outerRadius + "," + 1.5*outerRadius + ")") // relocate center of pie to 'outerRadius,outerRadius'
.attr('transform', 'translate(' + (canvasWidth/2 - 50) + ',' + canvasHeight/2 +')');
vis.append("text")
.attr("x",50)
.attr("y", -110)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Response Code vs Count(Last 20 Mins)");
if(unique.length === 0)
{
vis.append("text")
.attr("x",50)
.attr("y", 0)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.text("No Failure Transactions");
}
// This will create <path> elements for us using arc data...
var arc = d3.svg.arc()
.outerRadius(outerRadius);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.magnitude; }); // Binding each value to the pie
// Select all <g> elements with class slice (there aren't any yet)
var arcs = vis.selectAll("g.slice")
// Associate the generated pie data (an array of arcs, each having startAngle,
// endAngle and value properties)
.data(pie)
// This will create <g> elements for every "extra" data element that should be associated
// with a selection. The result is creating a <g> for every object in the data array
.enter()
// Create a group to hold each slice (we will have a <path> and a <text>
// element associated with each slice)
.append("svg:g")
.attr("class", "slice"); //allow us to style things in the slices (like text)
arcs.append("svg:path")
//set the color for each slice to be chosen from the color function defined above
.attr("fill", function(d, i) { return color(i); } )
.attr("data-legend",function(d) { return d.data.legendLabel +"->" + d.data.magnitude})
//this creates the actual SVG path using the associated data (pie) with the arc drawing function
.attr("d", arc);
// Add a magnitude value to the larger arcs, translated to the arc centroid and rotated.
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
//.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.outerRadius = outerRadius; // Set Outer Coordinate
d.innerRadius = outerRadius/2; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
})
.style("fill", "White")
.style("font", "bold 12px Arial")
.text(function(d) { return d.data.magnitude; });
legend = vis.append("g")
.attr("class","legend")
.attr("overflow-y","auto")
.attr("transform","translate(70,-50)")
.style("font-size","13px")
.call(d3.legend);
// Computes the angle of an arc, converting from radians to degrees.
function angle(d) {
var a = 180;
return a > 90 ? a - 180 : a;
}
}
else
{
var canvasWidth = this.getWidth(), //width
canvasHeight = this.getHeight(), //height
outerRadius = 75, //radius
color = d3.scale.category20(); //builtin range of colors
var viN = d3.select("#"+this.htmlObject)
.append("svg:svg") //create the SVG element inside the <body>
.attr("width", canvasWidth) //set the width of the canvas
.attr("height", canvasHeight) //set the height of the canvas
.append("svg:g"); //make a group to hold our pie chart
viN.append("text")
.attr("x",200)
.attr("y", 30)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Response Code vs Count(Last 20 Mins)");
viN.append("text")
.attr("x",200)
.attr("y", 100)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.text("No data Found");
}
I have a JavaScript code where the dataset is hard-coded in a variable, like this -
var dataset = [
{category: "Dept 1", measure: 0.30},
{category: "Dept 2", measure: 0.25},
{category: "Dept 4", measure: 0.15},
{category: "Dept 3", measure: 0.05},
{category: "Dept 5", measure: 0.18},
{category: "Dept 6", measure: 0.04},
{category: "Dept 7", measure: 0.03}
]
;
Now I want to use the json data which is getting returned from a php file (fetching through mysql query).
Whats the effective way to do this. does getJSON works well in this case?
Note- I am working on a pie chart in d3.js and this dataset requirement is for that chart.
EDIT -
This is how the code looks after suggested changes-
function dsPieChart(){
var width = 400,
height = 400,
outerRadius = Math.min(width, height) / 2,
innerRadius = outerRadius * .999,
// for animation
innerRadiusFinal = outerRadius * .5,
innerRadiusFinal3 = outerRadius* .45,
color = d3.scale.category20() //builtin range of colors
;
d3.json("data/mixchart.php", function(error, dataset) {
if (error) return console.warn(error);
else
{
var vis = d3.select("#pieChart")
.append("svg:svg") //create the SVG element inside the <body>
.data([dataset]) //associate our data with the document
.attr("width", width) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", height)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")") //move the center of the pie chart from 0, 0 to radius, radius
;
var arc = d3.svg.arc() //this will create <path> elements for us using arc data
.outerRadius(outerRadius).innerRadius(innerRadius);
// for animation
var arcFinal = d3.svg.arc().innerRadius(innerRadiusFinal).outerRadius(outerRadius);
var arcFinal3 = d3.svg.arc().innerRadius(innerRadiusFinal3).outerRadius(outerRadius);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.measure; }); //we must tell it out to access the value of each element in our data array
var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
.data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
.enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
.append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
.attr("class", "slice") //allow us to style things in the slices (like text)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", up)
;
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } ) //set the color for each slice to be chosen from the color function defined above
.attr("d", arc) //this creates the actual SVG path using the associated data (pie) with the arc drawing function
.append("svg:title") //mouseover title showing the figures
.text(function(d) { return d.data.category + ": " + formatAsPercentage(d.data.measure); });
d3.selectAll("g.slice").selectAll("path").transition()
.duration(750)
.delay(10)
.attr("d", arcFinal )
;
// Add a label to the larger arcs, translated to the arc centroid and rotated.
// source: http://bl.ocks.org/1305337#index.html
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; })
.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "translate(" + arcFinal.centroid(d) + ")rotate(" + angle(d) + ")"; })
//.text(function(d) { return formatAsPercentage(d.value); })
.text(function(d) { return d.data.category; })
;
// Computes the label angle of an arc, converting from radians to degrees.
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
// Pie chart title
vis.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text("Revenue Share 2012")
.attr("class","title")
;
}
});
function mouseover() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","red")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal3)
;
}
function mouseout() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","blue")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal)
;
}
function up(d, i) {
/* update bar chart when user selects piece of the pie chart */
//updateBarChart(dataset[i].category);
updateBarChart(d.data.category, color(i));
updateLineChart(d.data.category, color(i));
}
}
dsPieChart();
Edit 2 -
<script type="text/javascript">
/*
################ FORMATS ##################
-------------------------------------------
*/
var formatAsPercentage = d3.format("%"),
formatAsPercentage1Dec = d3.format(".1%"),
formatAsInteger = d3.format(","),
fsec = d3.time.format("%S s"),
fmin = d3.time.format("%M m"),
fhou = d3.time.format("%H h"),
fwee = d3.time.format("%a"),
fdat = d3.time.format("%d d"),
fmon = d3.time.format("%b")
;
var width = 400,
height = 400,
outerRadius = Math.min(width, height) / 2,
innerRadius = outerRadius * .999,
// for animation
innerRadiusFinal = outerRadius * .5,
innerRadiusFinal3 = outerRadius* .45,
color = d3.scale.category20() //builtin range of colors
;
d3.json("data/mixchart.php", function(error,data) {
data.forEach(function(d) {
d.category =d.category;
d.measure = d.measure;
});
//if (err) return console.warn(err);
var vis = d3.select("#pieChart")
.append("svg:svg") //create the SVG element inside the <body>
.data(data) //associate our data with the document
.attr("width", width) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", height)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")") //move the center of the pie chart from 0, 0 to radius, radius
;
var arc = d3.svg.arc() //this will create <path> elements for us using arc data
.outerRadius(outerRadius).innerRadius(innerRadius);
// for animation
var arcFinal = d3.svg.arc().innerRadius(innerRadiusFinal).outerRadius(outerRadius);
var arcFinal3 = d3.svg.arc().innerRadius(innerRadiusFinal3).outerRadius(outerRadius);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.measure; }); //we must tell it out to access the value of each element in our data array
var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
.data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
.enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
.append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
.attr("class", "slice") //allow us to style things in the slices (like text)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", up)
;
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } ) //set the color for each slice to be chosen from the color function defined above
.attr("d", arc) //this creates the actual SVG path using the associated data (pie) with the arc drawing function
.append("svg:title") //mouseover title showing the figures
.text(function(d) { return d.data.category + ": " + formatAsPercentage(d.data.measure); });
d3.selectAll("g.slice").selectAll("path").transition()
.duration(750)
.delay(10)
.attr("d", arcFinal )
;
// Add a label to the larger arcs, translated to the arc centroid and rotated.
// source: http://bl.ocks.org/1305337#index.html
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; })
.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "translate(" + arcFinal.centroid(d) + ")rotate(" + angle(d) + ")"; })
//.text(function(d) { return formatAsPercentage(d.value); })
.text(function(d) { return d.data.category; })
;
// Computes the label angle of an arc, converting from radians to degrees.
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
// Pie chart title
vis.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text("Revenue Share 2012")
.attr("class","title")
;
function mouseover() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","red")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal3)
;
}
function mouseout() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","blue")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal)
;
}
function up(d, i) {
/* update bar chart when user selects piece of the pie chart */
//updateBarChart(dataset[i].category);
updateBarChart(d.data.category, color(i));
updateLineChart(d.data.category, color(i));
}
</script>
var dataset = [];
$.getJSON("your_php_file", function(result){
dataset = result;
});
this will work but keep in mind that your php file is returning only json... rest you can play with the options.
There are numerous ways of fetching JSON, but as you're already working with d3, d3.json would be a good way to go.
E.g.
d3.json('your/json.json', function(error, json) {
if (error) return console.warn(error);
doSomethingWithJson(json)
});
Also see the d3 API
I created a pie chart and it is showing great.
Only, I noticed that some text is hidden by pie slice. By looking carefully, I noticed that each text can drawn over with the next slice.
so the rendering order goes like this : slice 1, text 1, slice 2, text 2, slice 3, text 3, etc...
How can I make it so the rendering order is slice 1, slice 2, slice 3...slice n.
Then text 1, text 2, text 3...text n
and then the text will always show since it will be drawn on top of every slice of the pie.
Thanks, here is my code
function createChart(dom, newData, title) {
d3.select(dom).selectAll("*").remove();
// Define size & radius of donut pie chart
var width = 450,
height = 800,
radius = Math.min(width, height) / 2;
// Define arc colours
var colour = d3.scale.category20();
// Define arc ranges
var arcText = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, .3);
// Determine size of arcs
var arc = d3.svg.arc()
.innerRadius(radius - 130)
.outerRadius(radius - 10);
// Create the donut pie chart layout
var pie = d3.layout.pie()
.value(function (d) { return d.count; })
.sort(null);
// Append SVG attributes and append g to the SVG
var mySvg = d3.select(dom).append("svg")
.attr("width", width)
.attr("height", height);
var svg = mySvg
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var svgText = mySvg
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
// Define inner circle
svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 100)
.attr("fill", "#fff") ;
// Calculate SVG paths and fill in the colours
var g = svg.selectAll(".arc")
.data(pie(newData))
.enter().append("g")
.attr("class", "arc");
// Append the path to each g
g.append("path")
.attr("d", arc)
//.attr("data-legend", function(d, i){ return parseInt(newData[i].count) + ' ' + newData[i].emote; })
.attr("fill", function(d, i) {
return colour(i);
});
// Append text labels to each arc
g.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.attr("fill", "#fff")
.text(function(d,i) { return newData[i].count > 0 ? newData[i].emote : ''; })
// Append text to the inner circle
svg.append("text")
.style("text-anchor", "middle")
.attr("fill", "#36454f")
.text(function(d) { return title; })
.style("font-size","16px")
.style("font-weight", "bold");
}
Simplest approach is to give the text labels there own g and rebind the data:
// there own g
var textG = svg.selectAll(".labels")
.data(pie(newData))
.enter().append("g")
.attr("class", "labels");
// Append text labels to each arc
textG.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.attr("fill", "#fff")
.text(function(d, i) {
return d.data.count > 0 ? d.data.emote : ''; // you can use d.data instead of indexing
});
Full example:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var newData = [{
count: 1,
emote: "OneTwoThree"
}, {
count: 1,
emote: "FourFiveSix"
}, {
count: 1,
emote: "SevenEightNine"
}, {
count: 15,
emote: "TenElevenTwelve"
},
]
// Define size & radius of donut pie chart
var width = 450,
height = 800,
radius = Math.min(width, height) / 2;
// Define arc colours
var colour = d3.scale.category20();
// Define arc ranges
var arcText = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, .3);
// Determine size of arcs
var arc = d3.svg.arc()
.innerRadius(radius - 130)
.outerRadius(radius - 10);
// Create the donut pie chart layout
var pie = d3.layout.pie()
.value(function(d) {
return d.count;
})
.sort(null);
// Append SVG attributes and append g to the SVG
var mySvg = d3.select('body').append("svg")
.attr("width", width)
.attr("height", height);
var svg = mySvg
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var svgText = mySvg
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
// Define inner circle
svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 100)
.attr("fill", "#fff");
// Calculate SVG paths and fill in the colours
var g = svg.selectAll(".arc")
.data(pie(newData))
.enter().append("g")
.attr("class", "arc");
// Append the path to each g
g.append("path")
.attr("d", arc)
//.attr("data-legend", function(d, i){ return parseInt(newData[i].count) + ' ' + newData[i].emote; })
.attr("fill", function(d, i) {
return colour(i);
});
var textG = svg.selectAll(".labels")
.data(pie(newData))
.enter().append("g")
.attr("class", "labels");
// Append text labels to each arc
textG.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.attr("fill", "#fff")
.text(function(d, i) {
return d.data.count > 0 ? d.data.emote : '';
});
</script>
</body>
</html>