Fair warning: I'm a D3 rookie here. I'm building a donut chart using D3 and all is well so far, except that the labels on the slices aren't aligning with the slices. Using the code below, the labels for each slice are rendered in the middle of the chart, stacked on top of each other so they're unreadable. I've dropped the arc.centroid in my transform attribute, but it's returning "NaN,NaN" instead of actual coordinates, and I can't understand where it's reading from that it's not finding a number. My innerRadius and outerRadius are defined in the arc variable. Any help?
(pardon the lack of a jsfiddle but I'm pulling data from a .csv here)
var width = 300,
height = 300,
radius = Math.min(width, height) / 2;
var color = ["#f68b1f", "#39b54a", "#2772b2"];
var pie = d3.layout.pie()
.value(function(d) { return d.taskforce1; })
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 85)
.outerRadius(radius);
var svg = d3.select("#pieplate").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("data.csv", type, function(error, data) {
var path = svg.datum(data).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color[i]; })
.attr("d", arc)
.each(function(d) { this._current = d; }); // store the initial angles
var text = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text( function (d) { return d.taskforce1; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "black");
d3.selectAll("a")
.on("click", switcher);
function switcher() {
var value = this.id;
var j = value + 1;
pie.value(function(d) { return d[value]; }); // change the value function
path = path.data(pie); // compute the new angles
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
textLabels = text.text( function (d) { return d[value]; });
}
});
function type(d) {
d.taskforce1 = +d.taskforce1;
d.taskforce2 = +d.taskforce2;
d.taskforce3 = +d.taskforce3;
return d;
}
// Store the displayed angles in _current.
// Then, interpolate from _current to the new angles.
// During the transition, _current is updated in-place by d3.interpolate.
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
Finally got it. The arc.centroid function expects data with precomputed startAngle and endAngle which is the result of pie(data). So the following helped me:
var text = svg.selectAll("text")
.data(pie(data))
followed by the rest of the calls. Note that you might have to change the way to access the text data that you want to display. You can always check it with
// while adding the text elements
.text(function(d){ console.log(d); return d.data.textAttribute })
Related
I have an assortment of data, and I have used it to create a donut chart. I want to make a pie chart using a breakdown of the data, which I've acquired using d3.nest to subdivide the data I had (it was currently in 3 categories: nest breaks it down into 129). Basically, I have Olympic data based on medals awarded, and I want to subdivide the data on interaction into which sports they were earned in.
I'm just not sure how to use nested data to create a pie chart, particularly if the keys are variable. I'll include my implementation for the donut chart.
var pie = d3.pie();
// color based on medal awarded
// order: gold, silver, bronze
var color = d3.scaleOrdinal()
.range(['#e5ce0c', '#e5e4e0', '#a4610a']);
var arcs = d3.select(svg).selectAll("g.arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + (w/2) + "," + ((h-25)/2) + ")");
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.attr("stroke", "white")
.style("stroke-width", "0.5px")
.on('mouseover', function(d) {
d3.select(this).attr('opacity', .7);
})
.on('mouseleave', function(d) {
d3.select(this).attr('opacity', 1);
});
// title
d3.select(svg).append('text')
.attr('x', function(d) {
return ((w/2) - 85);
})
.attr('y', '20')
.text(function(d) {
return ('Medal breakdown for ' + country);
})
.attr('font-size', '16px');
arcs.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
Can you please confirm is this the format of data you have ?
Please find the code below and let me know if you have any doubt.
var w = 400;
var h = 400;
var r = h/2;
var color = d3.scale.category20c();
var data = [
{name:"football", medal:1},
{name:"hockey", medal:2},
{name:"cricket", medal:3},
{name:"tennis", medal:4},
{name:"table tennis", medal:5},
];
var vis = d3.select('#chart').append("svg:svg").data([data]).attr("width", w).attr("height", h).append("svg:g").attr("transform", "translate(" + r + "," + r + ")");
var pie = d3.layout.pie().value(function(d){return d.medal;});
// declare an arc generator function
var arc = d3.svg.arc().outerRadius(r);
// select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice");
arcs.append("svg:path")
.attr("fill", function(d, i){
return color(i);
})
.attr("d", function (d) {
// log the result of the arc generator to show how cool it is :)
return arc(d);
});
// add the text
arcs.append("svg:text").attr("transform", function(d){
d.innerRadius = 0;
d.outerRadius = r;
return "translate(" + arc.centroid(d) + ")";}).attr("text-anchor", "middle").text( function(d, i) {
return data[i].name;}
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.15/d3.min.js"></script>
<div id="chart"></div>
I've been trying to get a pie chart to update and it works in CodePen but when I try to make it larger scale it completely fails.
This is the code I'm having trouble with:
var d2_data, d2_path, d2_pie, d2_text, d2_arc;
function D2_Update()
{
d2_data = [d2_employed,d2_student,d2_unemployed,d2_retired];
d2_path.data(d2_pie(d2_data))
.transition().duration(1000)
.attrTween("d", arcTween(null, d2_arc)); // Redraw the arcs
d2_text.data(d2_pie(d2_data))
.transition().duration(1000)
.attr("transform", function (d) {
return "translate(" + d2_arc.centroid(d) + ")";
})
.text(function(d){return d.data;});
}
function arcTween(a, arc) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
When this update function is called the text on the chart correctly updates--once--and then it fails to actually change the arcs like in the CodePen I linked above. All I get in return is a console error reading TypeError: r is not a function coming from my D3js JavaScript file.I am completely confused here because I'm not really sure what I should be doing differently.
Any help is appreciated in understanding my issue. I will also display my code for this graph pie chart:
// D2 pie
d2_data = [d2_employed,d2_student,d2_unemployed,d2_retired];
var d2_dataKey = ["Employed","Student","Unemployed","Retired"];
var d2_width = 400,
d2_height = 400,
d2_radius = Math.min(d2_width, d2_height) / 2;
d2_arc = d3.svg.arc()
.outerRadius(d2_radius - 10)
.innerRadius(d2_radius - 85);
d2_pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d;
});
var d2_svg = d3.select("#general_d2-graph").append("svg")
.attr("width", d2_width)
.attr("height", d2_height)
.append("g")
.attr("id", "pieChart")
.attr("transform", "translate(" + d2_width / 2 + "," + d2_height / 2 + ")");
d2_path = d2_svg.selectAll("path")
.data(d2_pie(d2_data))
.enter()
.append("path");
d2_text = d2_svg.selectAll("text")
.data(d2_pie(d2_data))
.enter()
.append("text")
.attr("text-anchor", "middle")
.attr("transform", function (d) {
return "translate(" + d2_arc.centroid(d) + ")";
})
.text(function(d){return d.data;});
d2_path.transition()
.duration(1000)
.attr("fill", function(d, i) {
return color(d.data);
})
.attr("d", d2_arc)
.each(function(d) {
this._current = d;
}); // Store the initial angles
Thank you.
I'm using dc.js to draw some charts.
In the d3 code I'm calculating dynamicly the total sum of a few columns and add them then to the pie chart which I draw with d3.js.
This is the code which calculates the total sum of the columns:
var pieChart = [];
classesJson.forEach(function(classJson){
var memDegree = ndx.groupAll().reduceSum(function(d){
return d[classJson.name];
}).value();
//console.log(memDegree);
pieChart.push({name:classJson.name, memDegree:memDegree});
});
The drawing for the first time works fine. But when I click elements on the dc.js bar charts the d3.js pie chart didn't update. How can accomplish that the GroupAll values from the above code also update in the d3.js pie chart?
This is the total d3 code for the pie chart:
radius = Math.min(300, 234) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.memDegree; });
var svg = d3.select("#membership-degree-pie-chart").append("svg")
.attr("width", 300)
.attr("height", 234)
.append("g")
.attr("transform", "translate(" + 300 / 2 + "," + 234 / 2 + ")");
var pieChart = [];
classesJson.forEach(function(classJson){
var memDegree = ndx.groupAll().reduceSum(function(d){
return d[classJson.name];
}).value();
//console.log(memDegree);
pieChart.push({name:classJson.name, memDegree:memDegree});
});
pieChart.forEach(function(d) {
d.memDegree = +d.memDegree;
});
var g = svg.selectAll(".arc")
.data(pie(pieChart))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.name); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.name; });
You can use a listener on the dc chart to detect that is has been filtered and then call your update function for the d3 chart.
yourDCChart.on("filtered", function (chart, filter) {
// update function for d3
updateD3Chart();
});
Without fiddle or plnkr it's difficult to tell.
But I have edited your code without testing. Please check if it helps, I have created the change function to update the graph. you can call change function where you want to update the graph. Hope it helps.
var g = svg.selectAll(".arc")
.data(pie(pieChart))
.enter().append("g")
.attr("class", "arc")
.append("path")
.attr("d", arc)
.style("fill", function (d) { return color(d.data.name); })
.each(function(d) { this._current = d; }); // store the initial angles;
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) { return d.data.name; });
//For updating change in data
function change() {
pie.value(function(d) { return d.memDegree; }); // change the value function
g = g.data(pie); // compute the new angles
g.transition().duration(750).attrTween("d", function (a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return arc(i(t));
};
}); // redraw the arcs
}
I attached D3 draw function for my custom visualizations to dc chart, each time the chart was updated/rendered D3 chart got drawn again :
dcTable
.on("renderlet.<renderletKey>", function (d3ChartData) {
drawD3(d3ChartData)
}
I'm using d3 to try to recreate a pie chart. Whenever I pass my data in (3 values), I get the aforementioned error. I believe I know the problem, the problem is that the 3 values are not mapped to anything. For example, when I tried this previously I mapped the values to SupplierID's and therefore it split into multiple different segments which was correct. However the values were wrong. I have now remedied the values, however they now are not mapped to anything and therefore will not display. Is there a way to get around this?
For example could I manually assign them an ID to map to? Or is there another way to get around this?
D3:
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.QueryTotal; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var data = #Html.Raw(#Model.output);
console.log(data);
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d) { return color("QueryTotal"); });
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) { return "QueryTotal"; });
</script>
Data:
NormalTotal: 0
QueryTotal: 131058.34
StrongTotal: 0
LINQ:
public TotalValueBySupplierAndClaimTypeModel GetTotalValueBySupplierAndClaimType(int ClientID, int ReviewPeriodID, int StatusCategoryID) {
var rt =
this.GetValueBySupplierAndClaimType(ClientID, ReviewPeriodID, StatusCategoryID);
TotalValueBySupplierAndClaimTypeModel x = new TotalValueBySupplierAndClaimTypeModel() {
NormalTotal = rt.Sum(c=>c.Normal) ?? 0,
QueryTotal = rt.Sum( c => c.Query) ?? 0,
StrongTotal = rt.Sum( c => c.Strong) ?? 0
};
return x;
}
UPDATE:
I need to assign an ID to each one of my values in my LINQ statement. I have the values in my viewModel. However, I'm not sure how to do this. Does anyone else?
I have a dynamic data source that creates a new json in the browser frequently.
I was able to create a pie chart from this json using the code below (also at this fiddle)
var data=[{"crimeType":"mip","totalCrimes":24},{"crimeType":"theft","totalCrimes":558},{"crimeType":"drugs","totalCrimes":81},{"crimeType":"arson","totalCrimes":3},{"crimeType":"assault","totalCrimes":80},{"crimeType":"burglary","totalCrimes":49},{"crimeType":"disorderlyConduct","totalCrimes":63},{"crimeType":"mischief","totalCrimes":189},{"crimeType":"dui","totalCrimes":107},{"crimeType":"resistingArrest","totalCrimes":11},{"crimeType":"sexCrimes","totalCrimes":24},{"crimeType":"other","totalCrimes":58}];
var width = 800,
height = 250,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.totalCrimes;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d) {
return color(d.data.crimeType);
});
g.append("text")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) {
return d.data.crimeType;
});
This data updates frequenty so what would be the best way to update the pie? Look at this fiddle. Here I have another json called data2.
How could I simply replace data with data2 and have the pie animate/update?
Note: on some updates values could == 0
I have created a working version and have posted it here: http://www.ninjaPixel.io/StackOverflow/doughnutTransition.html (for some reason I couldn't get the transitions to play ball in fiddle, so have just posted it to my website instead).
To make the code clearer I have omitted your labelling, renamed 'data' to 'data1', and have stuck in some radio buttons to flip between the data arrays. The following snippet shows the important bits. You can get the whole code from my page above.
var svg = d3.select("#chartDiv").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("id", "pieChart")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(data1))
.enter()
.append("path");
path.transition()
.duration(500)
.attr("fill", function(d, i) { return color(d.data.crimeType); })
.attr("d", arc)
.each(function(d) { this._current = d; }); // store the initial angles
function change(data){
path.data(pie(data));
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
// Store the displayed angles in _current.
// Then, interpolate from _current to the new angles.
// During the transition, _current is updated in-place by d3.interpolate.
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
You may find this code of Mike Bostock's helpful, it is where I learned how to do this.
Here are some other similar questions that might help:
How to update pie chart using d3.js
d3 pie chart transition with attrtween
simple d3.js pie chart transitions *without* data joins?
Adding new segments to a Animated Pie Chart in D3.js
https://groups.google.com/forum/#!msg/d3-js/2o5NTVjVJgA/AslmRSxXUAgJ