How to update a pie chart? - javascript

I'm trying to make a pie chart that is updated when I press the button "2016" but instead of updating I create a new pie chart, how can I change the values of my pie chart? Thanks in advance. I tried to search a question but all of them are so specific.
var dataset = [{
key: "Alumnos",
value: 15
}, {
key: "AlumnosFCT",
value: 12
}];
var w = 300;
var h = 300;
var outerRadius = w / 2;
var innerRadius = 0;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var color = d3.scale.ordinal()
.domain([15, 12])
.range(["#FF4081", "#3F51B5"]);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + ", " + outerRadius + ")");
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
arcs.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.text(function(d) {
return d.value;
});
d3.selectAll("button").on("click", function() {
var paragraphID = d3.select(this).attr("id");
if (paragraphID == "2016") {
dataset.push({
key: "Alumnos",
value: 20
}, {
key: "AlumnosFCT",
value: 18
});
dataset.shift();
dataset.shift();
}
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var color = d3.scale.ordinal()
.domain([15, 12])
.range(["#FF4081", "#3F51B5"]);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + ", " + outerRadius + ")");
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
arcs.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.text(function(d) {
return d.value;
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="2016">2016</button>

In general, d3 is pretty good about managing DOM elements as long as you work within their API. In that way you can write a function that can create new elements for new data, or update existing elements with new data pertaining to those elements.
See the following updated version of your code snippet, specifically pulling out the data dependent DOM manipulations into a function called update:
/***
* Original Code
***/
var dataset = [{
key: "Alumnos",
value: 15
}, {
key: "AlumnosFCT",
value: 12
}];
var w = 300;
var h = 300;
var outerRadius = w / 2;
var innerRadius = 0;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var color = d3.scale.ordinal()
.domain([15, 12])
.range(["#FF4081", "#3F51B5"]);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
/***
* update function for data dependent manipulations
***/
function update(data) {
var arcs = svg.selectAll("g.arc").data(pie(data));
arcs.exit().remove();
arcs.enter().append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + ", " + outerRadius + ")");
var paths = arcs.selectAll('path').data(function (d, i) {
d.idx = i;
return [d];
})
paths.enter().append('path');
paths
.attr("fill", function(d) {
return color(d.idx);
})
.attr("d", arc);
var texts = arcs.selectAll('text').data(function (d) {
return [d];
})
texts.enter().append('text');
texts.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.text(function(d) {
return d.value;
});
}
update(dataset);
/***
* Handler to set new data based on button clicked
***/
d3.select('button').on('click', function() {
var newData;
if (this.id === '2016') {
newData = [{
key: "Alumnos",
value: 20
}, {
key: "AlumnosFCT",
value: 18
}];
update(newData);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="2016">2016</button>
(depending on your browser, you might need to scroll the view to see the "2016" button)
Note the following advantages:
only elements whose data need to change are updated when update is called.
if you add a new data point when updating, a new element will be added without touching elements that should remain unchanged (via enter)
if you remove a data point when updating, that element will be removed (via exit)
d3 version: 3.4.11

Related

D3 -- Nested piechart not displaying all wedges

I've been having a bunch of trouble with a pie chart I've been trying to make. I finally have the outer ring working, but the inner ring only displays a few of the pieces (out ring has 3, inner ring has 6 but displays 3).
Does anyone know what might be wrong with this code? Both systems work fine on their own, but for whatever reason they don't work when I put them together.
The wedges for 20, 10 and 5 are the ones that don't display, and it happens that way every single time.
The name of the class ("arc") doesn't seem to matter, either.
function makeDonut(svg) {
var boundingBox = d3.select(svg).node().getBoundingClientRect();
var h = boundingBox.height;
var w = boundingBox.width;
/***** donut chart *****/
var data = [25, 40, 55];
// arbitrary data
var outerRadius = w/3;
var innerRadius = 3*(outerRadius/4);
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var pie = d3.pie();
// 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);
});
arcs.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
/************ piechart ************/
var dataset = [ 5, 10, 20, 45, 6, 25 ];
// arbitrary dataset
var outerRadius2 = 0.75 * (w/3);
var innerRadius2 = 0;
var arc2 = d3.arc()
.innerRadius(innerRadius2)
.outerRadius(outerRadius2);
var pie2 = d3.pie();
var color2 = d3.scaleOrdinal(d3.schemeCategory10);
var arcs2 = d3.select(svg).selectAll("g.arc")
.data(pie2(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + (w/2) + "," + ((h-25)/2) + ")");
//Draw arc paths
arcs2.append("path")
.attr("fill", function(d, i) {
return color2(i);
})
.attr("d", arc2);
arcs2.append("text")
.attr("transform", function(d) {
return "translate(" + arc2.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
}
The D3 enter method creates elements in the DOM where needed so that for every item in the data array there is an appropriate element in the DOM.
For your donut chart, which you draw first, you selectAll("g.arc") - there are no g elements with the class arc, you have an empty selection. So when you use the enter method, D3 creates one element for every item in the data array. Everything chained to .enter(), without a .merge() method, only affects these entered elements.
For your pie chart, which you draw second, you selectAll("g.arc") - however, now there are three g elements with the class arc. So when you use the enter method here, the enter selection does not included elements for the first three items in the data array: they already exist. Instead these first three elements are included in the update selection.
This functionality is core to the D3 enter/update/exit cycle.
If you want to enter everything, and aren't updating or exiting data points, then you can simply use .selectAll(null) which will create an empty selection, for which an enter selection will create an element for every item in the data array.
If you wanted to modify these wedges/arcs later, you could differentiate the two, either by entering them in different g elements (eg: g1.selectAll("g.arc") and g2.selectAll("g.arc"). Alternatively, you could give them different class names based on whether pie or donut, as below:
var svg = d3.select("svg");
var boundingBox = svg.node().getBoundingClientRect();
var h = boundingBox.height;
var w = boundingBox.width;
/***** donut chart *****/
var data = [25, 40, 55];
// arbitrary data
var outerRadius = w/3;
var innerRadius = 3*(outerRadius/4);
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var pie = d3.pie();
// order: gold, silver, bronze
var color = d3.scaleOrdinal()
.range(['#e5ce0c', '#e5e4e0', '#a4610a']);
var arcs = svg.selectAll("donut")
.data(pie(data))
.enter()
.append("g")
.attr("class", "donut")
.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);
});
arcs.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
/************ piechart ************/
var dataset = [ 5, 10, 20, 45, 6, 25 ];
// arbitrary dataset
var outerRadius2 = 0.75 * (w/3);
var innerRadius2 = 0;
var arc2 = d3.arc()
.innerRadius(innerRadius2)
.outerRadius(outerRadius2);
var pie2 = d3.pie();
var color2 = d3.scaleOrdinal(d3.schemeCategory10);
var arcs2 = svg.selectAll(".pie")
.data(pie2(dataset))
.enter()
.append("g")
.attr("class", "pie")
.attr("transform", "translate(" + (w/2) + "," + ((h-25)/2) + ")");
//Draw arc paths
arcs2.append("path")
.attr("fill", function(d, i) {
return color2(i);
})
.attr("d", arc2);
arcs2.append("text")
.attr("transform", function(d) {
return "translate(" + arc2.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width=500 height=400></svg>

d3js scroll visibility - series animation for pie chart

I am working on a d3 applicaton - with a pie chart -- I would like to get animation onload and on a call to action. Like when the chart becomes visible during a scroll.
Where the pie segments grow around the central pivot. So tween or snap to the other segment like a relay race
http://jsfiddle.net/pg886/192/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<div class="piechart" data-role="piechart" data-width=400 data-height=400 data-radius=30 data-innerradius=20
data-data=x>
</div>
<style>
.piechart{
/*border: 1px solid black;*/
/*text-align: center;
font-size: 12px;*/
}
</style>
<script>
$( document ).ready(function() {
console.log("test")
var $this = $('.piechart');
var data = [{
"label": "Apples",
"value": 100
},
{
"label": "Pears",
"value": 120
},
{
"label": "Bananas",
"value": 20
}];
var w = $this.data("width");
var h = $this.data("height");
var ir = $this.data("innerradius");
var r = $this.data("radius");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
//var colores_g = ["#47abd5", "#005a70", "#f5a0a3", "#ff7276", "#a9a19c", "#d0743c", "#ff8c00"];
return colores_g[n % colores_g.length];
}
var radius = Math.min(w, h) / 4;
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var labelArc = d3.svg.arc()
.outerRadius(radius - r)
.innerRadius(radius - ir);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.value; });
var chart = d3.select('.piechart').append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", h)
.attr("transform", "translate(0,0)");
var piechart = chart
.append("g")
.attr("class", "piechart")
.attr("width", (radius*2))
.attr("transform", "translate(0,"+h/4+")");
var path_group = piechart.append("g")
.attr("class", "path_group")
.attr("transform", "translate(90," + ((h / 4) - 20) + ")");
var padding = 45;
var legendPaddingTop = 30;
var legend = chart.append("g")
.attr("class", "legend")
.attr("width", w/2)
.attr("height", h)
.attr("transform", "translate(" + (w - 50) + "," + (h / 4) + ")");
var label_group = legend.append("svg:g")
.attr("class", "label_group")
.attr("transform", "translate(" + (-(w / 3) + 20) + "," + 0 + ")");
var legend_group = legend.append("svg:g")
.attr("class", "legend_group")
.attr("transform", "translate(" + (-(w / 3) - 100) + "," + 0 + ")");
var g = path_group.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return colores_google(i);
});
var legendHeight = legendPaddingTop;
var ySpace = 18;
//draw labels
var labels = label_group.selectAll("text.labels")
.data(data);
labels.enter().append("svg:text")
.attr("class", "labels")
.attr("dy", function(d, i) {
legendHeight+=ySpace;
return (ySpace * i) + 4;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) {
return d.label;
});
labels.exit().remove();
//draw labels
//draw legend
var legend = legend_group.selectAll("circle").data(data);
legend.enter().append("svg:circle")
.attr("cx", 100)
.attr("cy", function(d, i) {
return ySpace * i;
})
.attr("r", 7)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {
return colores_google(i);
});
legend.exit().remove();
//draw legend
//reset legend height
//console.log("optimum height for legend", legendHeight);
$this.find('.legend').attr("height", legendHeight);
function type(d) {
d.value = +d.value;
return d;
}
});
</script>
So you can achieve this pretty easily, and there are a couple of blocks that will help you.
Arc Tween Firstly this block gives you an example of how to tween an arc. Basically you can't get that automatically so you have to write your own attrTween function. Fortunately this is pretty simple and Mike Bostock gives a really good example in there.
Here's a code sample - but the link gives a really good verbose description of what's going on.
.attrTween("d", function(d) {
var interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
}
Next you want something like this Donut with transitions. This is actually the end-result for where you're trying to get to. This effect is really easy to achieve, all you need to do is set your angles correctly and the timings.
Angles: So here you want both the endAngle and the startAngle to be the same at the start (which should be the endAngle value of the previous segment or 0 for the first segment).
Timing: You want to allow 1 animation to complete before you start the next, simply by delaying them. You can see how that's done with this snippet:
.transition()
.delay(function(d,i) { return i * 500; })
.duration(500)
.attrTween(...)
const dataset = [
{ age: "<5", population: 2704659 },
{ age: "5-13", population: 4499890 },
{ age: "14-17", population: 2159981 },
{ age: "18-24", population: 3853788 },
{ age: "25-44", population: 14106543 },
{ age: "45-64", population: 8819342 },
{ age: "≥65", population: 612463 },
];
const TIME = 2000 / dataset.length;
const color = d3.scaleOrdinal(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
const pie = d3.pie()
.sort(null)
.value(function(d) { return d.population; });
const path = d3.arc()
.innerRadius(0)
.outerRadius(350);
d3.select("#container")
.selectAll(".arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.append("path")
.attr("fill", function(d) { return color(d.data.age); })
.transition()
.duration(TIME)
.ease(d3.easeLinear)
.delay(function(d, i) { return i * TIME; })
.attrTween("d", function(d) {
// Note the 0.1 to prevent errors generating the path
const angleInterpolation = d3.interpolate(d.startAngle + 0.1, d.endAngle);
return function(t) {
d.endAngle = angleInterpolation(t);
return path(d);
}
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="800" height="800">
<g id="container" transform="translate(400, 400)"></g>
</svg>

How to correctly draw the text over my pie chart?

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>

d3.js arc.centroid(d) : Error: Invalid value for <text> attribute transform="translate(NaN,NaN)"

I'm trying to center the arc labels with the centroid() function as described in D3 documentation.
arcs.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.text(function(d) { return "labels"; });
but I'm getting an error on the translate css property :
Error: Invalid value for attribute
transform="translate(NaN,NaN)"
one of the console.log of the (d) objects in :
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
returns this:
data: 80
endAngle: 1.9112350744272506
padAngle: 0
startAngle: 0
value: 80
__proto__: Object
Here's my code:
HTML:
<div id="firstChart"></div>
JS:
var myData = [80,65,12,34,72];
var nbElement = myData.length;
var angle = (Math.PI * 2) / nbElement ;
var myColors = ["#e7d90d", "#4fe70d", "#0de7e0", "#f00000", "#00DC85"];
var width = 1500;
var height = 1500;
var radius = 200;
var canvas = d3.select("#firstChart")
.append("svg")
.attr("height", height)
.attr("width", width);
var group = canvas.append("g")
.attr("transform","translate(310, 310)");
var arc = d3.svg.arc()
.innerRadius(0)
.outerRadius(function (d,i) { return (myData[i]*2); })
.startAngle(function (d,i) { return (i*angle); })
.endAngle(function (d,i) { return (i*angle)+(1*angle); });
var pie = d3.layout.pie()
.value(function (d) { return d; });
var arcs = group.selectAll(".arc")
.data(pie(myData))
.enter()
.append("g")
.attr("class", "arc");
arcs.append("path")
.attr("d", arc)
.attr("fill", function (d, i) { return ( myColors[i] ); });
arcs.append("text")
.attr("transform", function(d) {console.log(d); return "translate(" + arc.centroid(d) + ")"; })
.text(function(d) { return d.data; });
var outlineArc = d3.svg.arc()
.innerRadius(0)
.outerRadius(radius)
.startAngle(function (d,i) { return (i*angle); })
.endAngle(function (d,i) { return (i*angle)+(1*angle); });
var outerPath = group.selectAll(".outlineArc")
.data(pie(myData))
.enter().append("path")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", "2")
.attr("class", "outlineArc")
.attr("d", outlineArc);
You need to pass the index of the element to centroid as well as the datum:
.attr("transform", function(d, i) { return "translate(" + arc.centroid(d, i) + ")"; })
During the call to arc.centroid, D3 calls the arc functions you have created for outerRadius, startAngle and endAngle. However, if you don't pass the index to arc.centroid, D3 has got no index to pass on to the arc functions, all of which use the index. Hence the centroid calculations end up with NaNs.

How to generate multiple SVG's in a Meteor.js #each wrapper

Hi there I currently have a template helper that returns me an array with various values used to generate different rows in a table in my HTML.
<template name="stop">
{{#each thumb}}
<tr>
<td>
<h2> Do you like this product? </h2>
<h2>{{text}}</h2>
<svg id="donutChart"> </svg>
</td>
</tr>
{{/each}}
</template>
It also contains a svg tag which I also want to generate a graph for each element generated as a table row and this is what the template helper looks like.
Template.stop.helpers({
'thumb': function(data) {
var result = tweetImages.findOne();
var newResult = [];
for (var i = 0; i < result.data.length; i++) {
newResult[i] = {
data: result.data[i],
text: result.text[i]
};
}
console.log(newResult)
return newResult;
}
I'm trying to create a pie reactive pie chart for each element in the table however I don't seem to be able to access the svg in the stop template.
The d3 code works fine outside that table but cant seem to be generated for each element of the table because it can't access the svg element.
Template.donutChart.rendered = function() {
//my d3 code is here
//Width and height
var w = 300;
var h = 300;
var center = w / 2;
var outerRadius = w / 2;
var innerRadius = 0;
var radius = 150;
var arc = d3.svg.arc()
.innerRadius(40)
.outerRadius(radius + 10 - 25);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.data;
});
//Create SVG element
var svg = d3.select("#donutChart")
.attr("width", w)
.attr("height", h)
.attr("transform", "translate(" + 200 + "," + 100 + ")");
// GROUP FOR CENTER TEXT
var center_group = svg.append("svg:g")
.attr("class", "ctrGroup")
.attr("transform", "translate(" + (w / 2) + "," + (h / 2) + ")");
// CENTER LABEL
var pieLabel = center_group.append("svg:text")
.attr("dy", ".35em").attr("class", "chartLabel")
.attr("text-anchor", "middle")
.text("Clothes")
.attr("fill", "white");
Deps.autorun(function() {
var modifier = {
fields: {
value: 1
}
};
Deps.autorun(function() {
var arcs = svg.selectAll("g.arc")
.data(pie(players))
var arcOutter = d3.svg.arc()
.innerRadius(outerRadius - 10)
.outerRadius(outerRadius);
var arcPhantom = d3.svg.arc()
.innerRadius(-180)
.outerRadius(outerRadius + 180);
var newGroups =
arcs
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + 150 + "," + 150 + ")")
//Set up outter arc groups
var outterArcs = svg.selectAll("g.outter-arc")
.data(pie(players))
.enter()
.append("g")
.attr("class", "outter-arc")
.attr("transform", "translate(" + 150 + ", " + 150 + ")");
//Set up phantom arc groups
var phantomArcs = svg.selectAll("g.phantom-arc")
.data(pie(players))
.enter()
.append("g")
.attr("class", "phantom-arc")
.attr("transform", "translate(" + center + ", " + center + ")");
outterArcs.append("path")
.attr("fill", function(d, i) {
return slickColor[i];
})
.attr("fill-opacity", 0.85)
.attr("d", arcOutter).style('stroke', '#0ca7d2')
.style('stroke-width', 2)
//Draw phantom arc paths
phantomArcs.append("path")
.attr("fill", 'white')
.attr("fill-opacity", 0.1)
.attr("d", arcPhantom).style('stroke', '#0ca7d2')
.style('stroke-width', 5);
//Draw arc paths
newGroups
.append("path")
.attr("fill", function(d, i) {
return slickColor[i];
})
.attr("d", arc);
//Labels
newGroups
.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
})
.style("font-size", function(d) {
return 24;
})
svg.selectAll("g.phantom-arc")
.transition()
.select('path')
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
});
arcs
.transition()
.select('path')
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
});
arcs
.transition()
.select('text')
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.text(function(d) {
return d.value;
})
.attr("fill", function(d, i) {
return textColor[i];
})
arcs
.exit()
.remove();
});
});
}
}
I can't seem to find much information on using d3.js or SVG's within a templates #each wrapper. Any help would be truly appreciated.
I would suggest wrapping your d3 code in a Deps.autorun() function as what's most likely happening is your data isn't available yet when your pie is bound to the data function and thus doesn't render anything.
Template.donutChart.rendered = function() {
Tracker.autorun(function () {
//all d3 code
});
}
You look like you're using autoruns further down but not for the bit that gets your data.

Categories

Resources