update d3 pie chart with new data.json - javascript

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

Related

Dc.js and D3.js chart update

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)
}

How to use forEach in d3 using Javascript

I'm trying to make a pie chart using d3. To do this I am send my data in a JSON format to my view and then I'd like to try and total up three of the parameters and then use these in the pie chart. So far I have managed to get the data to the view but now I need to use a foreach to total up the parameters. I'm only working on one at the moment.
Script:
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.Query; });
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);
var QueryTotal = data.forEach(function(d) {
d.Query = +d.Query;
});
console.log(QueryTotal);
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("Query"); });
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) { return "Query"; });
How do I use a for each to total up the values, I've given it an attempt which you can see above. It just returns undefined.
To sum the values, use reduce()
var QueryTotal = data.reduce(function(prev, d) {
return prev + d.Query;
}, 0);
Or, using your existing structure:
var QueryTotal = 0;
data.forEach(function(d) {
QueryTotal += d.Query;
});
The D3 way to do this is to use d3.sum():
var QueryTotal = d3.sum(data, function(d) { return d.Query; });

Uncaught TypeError: data.map is not a function

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?

arc.centroid returning (NaN, NaN) in D3

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 })

d3.js transition of donuts created by multi-arcs doesn't work

I have created a donut using multi-arcs and I want to update my donut with new data(arcs).
var width = 300;
var height = 300;
var p = Math.PI * 2;
var vis = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var group = vis.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var arcs = [];
arcs[0] = d3.svg.arc()
.innerRadius(50)
.outerRadius(70)
.startAngle(0)
.endAngle(p - 2);
arcs[1] = d3.svg.arc()
.innerRadius(50)
.outerRadius(70)
.startAngle(p - 2)
.endAngle(p);
group.append("path")
.attr("d", arcs[0])
.attr("class", "first")
.attr("fill", "green");
group.append("path")
.attr("d", arcs[1])
.attr("class", "second")
.attr("fill", "grey");
The new data(arcs - functions) must be in arrays and I have to pass them using the .data(dataset) method.
// New data
var data1 = [];
data1[0] = d3.svg.arc()
.innerRadius(60)
.outerRadius(100)
.startAngle(0)
.endAngle(p - 1);
var data2 = [];
data2[0] = d3.svg.arc()
.innerRadius(60)
.outerRadius(100)
.startAngle(p - 1)
.endAngle(p);
-I can update my donut with the new arcs but the issue that I have is that the transition doesn't work.
-I want just to make the transition work following the steps that I described before.
I know already that if i don't use the .data(dataset) method and I use the .attr("d", arc) instead of .attrTween method then the transition will work.
-However that is not what I want because I want to apply the solution to multi-donuts.
//On click, update with new data
d3.select("p")
.on("click", function () {
//Update all rects
vis.selectAll("path.first")
.data(data1)
.transition()
.duration(1000)
.attrTween("d", function (d) { return d; });
vis.selectAll("path.second")
.data(data2)
.transition()
.duration(1000)
.attrTween("d", function (d) { return d; });
Here is an example, click update to see the changes: example

Categories