I'm building a very complex visualization with pie charts on a Choropleth world map. You can cycle through the years and the data gets updated. I managed to update the size of the pies but I have difficulties updating the angles because my data structure is quite different from this example: http://bl.ocks.org/mbostock/1346410. I don't know how the data update would work in my case.
Thank you for any help
here is my code to build the pies:
var arc = d3.svg.arc()
(some more code here ...)
function ready(error, world, water_pc,cotton_pc,water_total,cotton_total) {
(some more code here ...)
var pie = d3.layout.pie();
var piegroup = g.selectAll(".pie")
.data(water_pc)
.enter().append("g")
.attr("class","pie")
.attr("transform", function (d) {
return ("translate(" + projection([d.lon,d.lat])[0] + "," +
projection([d.lon,d.lat])[1] + ")");
});
arc
.outerRadius(function (d,i) {
return radius(data_water_pc()[this.parentNode.__data__.id][0]+data_water_pc()[this.parentNode.__data__.id][1]);
})
.innerRadius(0);
var arcs = piegroup.selectAll("path")
.data(function(d) {return pie(data_water_pc()[d.id]); })
.enter().append("svg:path")
.attr("d", arc)
.each(function(d) { this._current = d; }) // store the initial angles
.attr("class","slice")
.style("fill", function(d, i) { return colorWater(i); });
(some more code here ...)
and this is my update function
var update_total = function() {
var piegroup = g.selectAll(".pie")
.transition()
arc
.outerRadius(function (d,i) {
return radius_total(data_water_total()[this.parentNode.__data__.id][0]+data_water_total()[this.parentNode.__data__.id][1]);
})
var arcs = piegroup.selectAll("path")
.transition().duration(150)
.attr("d", arc)
//.attrTween("d", arcTween); // when I use this the size of the pies is not calculated anymore
}
(some more code here ...)
and at the end I have my arc tween function:
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
Related
I have looked for possible solutions but nothing worked for me.
Problem is when I try to update the data and the pie chart accordingly, the transition does not work and prints error, mentioned in the topic, more than once. I am kinda new to JS, so I am looking for some help.
Code:
var pie = d3.pie();
var pathArc = d3.arc()
.innerRadius(200)
.outerRadius(250);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var t = d3.transition()
.duration(500);
var path = piesvg.selectAll("path")
.data(pie(gdp_values));
path.exit()
.transition(t)
.remove();
path.transition(t)
.attr("d",function (d) {
return pathArc(d);
})
.attr("fill",function(d, i){return color(i);});
path.enter()
.append("path")
.transition(t)
.attr("d",pathArc)
.attr("fill",function(d, i){return color(i);});
Initial dataset(gdp_values);
[407500000000, 417300000000, 439800000000, 680900000000, 980900000000, 1160000000000, 1727000000000, 2249000000000, 2389000000000, 3074000000000]
It does work when data changed to the another similar data, however when changes to the data as follows, transitions doesnot work and throws the same error 40 times.
[7714000000, 123900000000, 846200000000]
Any thoughts?
You have to invert the order of your selections: the enter selection should come before the update selection:
path.enter()
.append("path")
.transition(t)
.attr("d", pathArc)
.attr("fill", function(d, i) {
return color(i);
});
path.transition(t)
.attr("d", function(d) {
return pathArc(d);
})
.attr("fill", function(d, i) {
return color(i);
});
Here is the demo:
var piesvg = d3.select("svg").append("g").attr("transform", "translate(250,250)")
var gdp_values = [407500000000, 417300000000, 439800000000, 680900000000, 980900000000, 1160000000000, 1727000000000, 2249000000000, 2389000000000, 3074000000000];
var gdp_values2 = [7714000000, 123900000000, 846200000000];
var pie = d3.pie();
var pathArc = d3.arc()
.innerRadius(200)
.outerRadius(250);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var t = d3.transition()
.duration(500);
update(gdp_values)
setTimeout(function() {
update(gdp_values2);
}, 1000)
function update(data) {
var path = piesvg.selectAll("path")
.data(pie(data));
path.exit()
.transition(t)
.remove();
path.enter()
.append("path")
.transition(t)
.attr("d", pathArc)
.attr("fill", function(d, i) {
return color(i);
});
path.transition(t)
.attr("d", function(d) {
return pathArc(d);
})
.attr("fill", function(d, i) {
return color(i);
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="500"></svg>
I got this error when making data updates to a chart. Solution in my case was to prevent drawing of the chart during the time the data was loading. Guessing the arcTween code doesn't handle data changes gracefully.
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 am new to programming so apologies if the answer to this is obvious but after hours of searching I can't find out what's wrong.
I simply want to tween an arc in D3.js (in this case change the endAngle to 0). I've been through lots of examples but I must be missing something. I have built a function to change arc colour on clicking which works but it is the second function 'arcTween' to change the arc endAngle of the outermost arcs that doesn't work. Can you help?
Many thanks
Full JS fiddle here: http://jsfiddle.net/vaaa052h/
Extracts below
var chartArea = d3.select("#chart")
.append("svg") // d3 SVG function
.attr("width", 210)
.attr("height", 210);
var arcGroup = chartArea.append("g") // d3 g grouping function
.attr("transform", "translate(" + transX + "," + transY + ")")
.attr("class", "arc");
var arc = d3.svg.arc()
.innerRadius(function (d) {
return radius[level];
})
.outerRadius(function (d) {
return radius[level + 1];
})
.startAngle(function (d) {
return minAngArc;
})
.endAngle(function (d) {
return maxAngArc;
});
//////// chart building ///////////////
arcGroup.append("path")
.attr("d", arc)
.attr("fill", color(0, random, 0, i, j, k))
.attr("opacity", opacity(rating))
.on("click", arcTween());
////// click functions //////////
function arcTween(d) {
d3.select(this).transition().duration(1000)
.attrTween("d", function (d) {
var interpolate = d3.interpolate(d.endAngle, 0);
return function (t) {
d.endAngle = interpolate(t);
return arc(d);
};
});
};
I made a couple of changes in this updated fiddle: http://jsfiddle.net/henbox/a8r326m5/1/
First, when you set up the click handler, avoid calling it on page load by using:
.on("click", arcTween);
instead of
.on("click", arcTween());
as per Lars' explanation here. This will stop you getting "Object [object global] has no method 'getAttribute'" errors in the console
Second, bind some data to the path elements so we can manipulate it later:
arcGroup.append("path")
.datum({endAngle:maxAngArc, startAngle:minAngArc})
....
And thirdly, use this data in the arcTween function. By setting maxAngArc and minAngArc, and then tweening the value of maxAngArc to minAngArc (I've asumed you mean to do this rather than tweening to 0), you should get the behaviour you want. The tween function:
function arcTween(d) {
maxAngArc = d.endAngle;
minAngArc = d.startAngle;
d3.select(this).transition().duration(1000)
.attrTween("d", function (d) {
var interpolate = d3.interpolate(d.endAngle, d.startAngle);
return function (t) {
maxAngArc = interpolate(t);
return arc(d);
};
});
};
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 })