Slice and bounce effect not working properly in pie chart - d3.js

As per below code I am expecting bounce effect when the pie chart loads for the first time which do not work as expected and expand the arc slice when on mouseenter but slicing the selected arc overlaps the adjacent arcs while it should work as red arc as in the below example it should only expand and displace other arcs. Can any give pointer on where exactly I am doing wrong.
Pie Chart
var width = 960,
height = 500,
radius = Math.min(width, height) / 2 - 10;
var data=[
{
"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
}
];
var color = d3.scale.category20();
var arc = d3.svg.arc()
.outerRadius(radius);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
var labelArc = d3.svg.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var svg = d3.select("body").append("svg")
.datum(data)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var arcs = svg.selectAll("g.arc")
.data(pie)
.enter().append("g")
.attr("class", "arc");
arcs.append("path")
.attr("fill", function(d, i) { return color(i); }).on("mouseenter", function(d) {
var endAngle = d.endAngle + 0.2;
var startAngle = d.startAngle - 0.2;
var arcOver = d3.svg.arc()
.outerRadius(radius + 10).endAngle(endAngle).startAngle(startAngle);
d3.select(this)
.attr("stroke","white")
.transition()
.ease("bounce")
.duration(1000)
.attr("d", arcOver)
.attr("stroke-width",6);
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arc)
.attr("stroke","none");
})
.transition()
.ease("bounce")
.duration(2000)
.attrTween("d", tweenPie).attr("d", arc);
function tweenPie(b) {
b.innerRadius = 0;
var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
return function(t) { return arc(i(t)); };
}
arcs.append("text")
.attr("transform", function(d) { return "translate(" + labelArc.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.data.age; });
function type(d) {
d.population = +d.population;
return d;
}
</script>
Here is the bin of what I have tried so far.

The arcs overlap because of the order they were appended in the SVG. As you know, the SVG order defines what element goes over its siblings. So, when you expand the hovered arc (using the new startAngle and endAngle), the arc expands under a sibling that sits on top of it in the SVG order.
One solution is sorting the elements inside the mouseenter function, in such a way that the hovered element is the first one in the SVG order. This is the function:
svg.selectAll("path").sort(function (a, b) {
if (a != d) return -1;
else return 1;
});
The element that you hovered is the d, and a is the first one. Using this function, all the paths are sorted when you hover over them.
This is the Bin: http://jsbin.com/hotifepiko/1/edit?html,output
PS: This other Bin solves the problem of the disappearing texts (because of the sort function). I just created new groups for the texts: http://jsbin.com/mifejasiyo/1/edit?html,output

Related

place nodes generated from a force to a radial scatter plot

I have a force layout that generates some node positions for a given node-link diagram. My task is to place these nodes and links to a radial scatter plot. The nodes should be placed in such a way that their distance from the center node can be visible. For this reason, I calculate the euclidean distance of the nodes from the center and find maximum value from the calculated distance. Then scaled the radius of the radial plot within 0 to maximum distance value. But when I tried This example, the node-link diagram did not place upon the scatter plot. My question is how can I place the node co-ordinates to the plot such that the distance of the nodes from the center node can be seen by the circle line of the scatter plot.
Here is a portion of my input data
{
"nodes": [
{
"id": "A0",
"group": 0,
"degree": 19,
"name": "x"
},
{
"id": "P0",
"group": 0,
"degree": 3,
"name": "y"
},
{
"id": "P1",
"group": 0,
"degree": 3,
"name": "z"
},
{
"id": "P2",
"group": 0,
"degree": 1,
"name": "w"
}
],
"links": [
{
"source": "P0",
"target": "A0"
},
{
"source": "P1",
"target": "A0"
},
{
"source": "P2",
"target": "A0"
}
]
}
Here is a portion of my code.
var width = 1200,
height = 800,
radius = Math.min(width, height) / 2 - 30;
//console.log(");
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var rd_at = 3,
rd_pp = 2.5;
var author_cord = [];author_cord.push({x:width / 2,y:height / 2});
var given_person = 'x';
d3.json("json_file.json", function(graph) {
var simulation = d3.forceSimulation(graph.nodes)
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(width / 2, height / 2))//ATTRACT NODES TO CENTER(width / 2, height / 2)
.force("x", d3.forceX(width / 2).strength(1))//X ALLIGN
.force("y", d3.forceY(height / 2).strength(1))//Y ALLIGN
.force("link", d3.forceLink(graph.links).id(function(d) {return d.id; ).distance(40).strength(1))
.stop();
var loading = svg.append("text")
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.text("Simulating. One moment please…");
d3.timeout(function() {
loading.remove();
for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) {
simulation.tick();
}
var all_dist = [];
graph.nodes.forEach(function(d){
var d_x = d.x - author_cord[0].x;
var d_y = d.y - author_cord[0].y;
var dist = Math.sqrt(d_x*d_x + d_y+d_y);
all_dist.push(dt);
});
var max_dist = d3.max(all_dist, function(d) {
return d;
});
var r = d3.scaleLinear()
.domain([0,12])
.range([0, max_dist]);
var line = d3.lineRadial()
.radius(function(d) {
return r(d[1]);
});
var gr = svg.append("g")
.attr("class", "r axis")
.selectAll("g")
.data(r.ticks(10).slice(1))
.enter().append("g");
gr.append("circle")
.attr("r", r);
var links = svg.append("g")
.attr("stroke", "grey")
.attr("stroke-width", 1)
.selectAll("line")
.data(graph.links)
.enter().append("line")
.style("opacity", function(d) { return d.target.name == given_author? 0 : 1; } )
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
var n = svg.append("g")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", function(d) {return d.group == 0 ? rd_at : rd_pp;})
.attr("fill", function(d) { return color(d.group); })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("stroke", "#000")//.attr("stroke", function(d) {return d.group == 0 ? "red": "blue";})
.attr("stroke-width", function(d) {return d.group == 0 ? 1.5 : 1;});
});
});
Here is my output so far. . Here, in the picture green nodes are group 0 node and orange nodes are group 1. Here, center node is the ego node that should be place in the inner most center position of the radial plot
Your svg selection actually corresponds to a group already translated by half width and height:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Therefore, you can either change the "center", "x" and "y" in the simulation or, alternatively, you can just remove that group from the svg selection and translating the circles' groups instead:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var gr = svg.append("g")
.attr("class", "r axis")
.selectAll("g")
.data(r.ticks(10).slice(1))
.enter()
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

Use svg as icon with D3 and htmlwidgets

I was able to create my first htmlwidget that creates this animated plot:
I would like to replace the "B" and "D" buttons with a single icon that uses an svg as the icon. In particular, I want to use this icon.. The icon should be black when selected, light gray when unselected, and a darker gray when hovering over.
To start, I'm not sure where to save the file so my code can see it.
This is the yaml for my htmlwidget package:
# (uncomment to add a dependency)
dependencies:
- name: D3
version: 4
src: htmlwidgets/lib/D3
script: d3.v4.js
stylesheet: style.css
- name: d3tip
version: 0.7.1
src: htmlwidgets/lib/d3-tip
script: d3-tip.min.js
stylesheet: style.css
And this is the js file:
HTMLWidgets.widget({
name: 'IMPosterior',
type: 'output',
factory: function(el, width, height) {
// TODO: define shared variables for this instance
return {
renderValue: function(opts) {
//transition
var transDuration = 1000;
var dataDiscrete = opts.bars.map((b, i) => {
b.y = Number(b.y);
b.desc = opts.text[i];
return b;
});
var distParams = {
min: d3.min(opts.data, d => d.x),
max: d3.max(opts.data, d => d.x)
};
distParams.cuts = [-opts.MME, opts.MME, distParams.max];
opts.data = opts.data.sort((a,b) => a.x - b.x);
var dataContinuousGroups = [];
distParams.cuts.forEach((c, i) => {
let data = opts.data.filter(d => {
if (i === 0) {
return d.x < c;
} else if (i === distParams.cuts.length - 1) {
return d.x > distParams.cuts[i - 1];
} else {
return d.x < c && d.x > distParams.cuts[i - 1];
}
});
data.unshift({x:data[0].x, y:0});
data.push({x:data[data.length - 1].x, y:0});
dataContinuousGroups.push({
color: opts.colors[i],
data: data
});
});
var margin = {
top: 50,
right: 20,
bottom: 80,
left: 70
},
dims = {
width: width - margin.left - margin.right,
height: height - margin.top - margin.bottom
};
var xContinuous = d3.scaleLinear()
.domain([distParams.min - 1, distParams.max + 1])
.range([0, dims.width]);
var xDiscrete = d3.scaleBand()
.domain(dataDiscrete.map(function(d) { return d.x; }))
.rangeRound([0, dims.width]).padding(0.1);
var y = d3.scaleLinear()
.domain([0, 1])
.range([dims.height, 0]);
var svg = d3.select(el).append("svg")
.attr("width", dims.width + margin.left + margin.right)
.attr("height", dims.height + margin.top + margin.bottom);
var g = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xAxis = d3.axisBottom()
.scale(xDiscrete);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(10)
.tickFormat(d3.format(".0%"));
var yLabel = g.append("text")
.attr("class", "y-axis-label")
.attr("transform", "rotate(-90)")
.attr("y", -52)
.attr("x", -160)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("font-size", 14 + "px")
.text("Probability");
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + dims.height + ")")
.call(xAxis);
g.append("g")
.attr("class", "y axis")
.call(yAxis);
var areas = g.selectAll(".area")
.data(dataDiscrete)
.enter().append("path")
.attr("class", "area")
.style("fill", function(d) { return d.color; })
.attr("d", function(d, i) {
let numPts = dataContinuousGroups[i].data.length - 2;
var path = d3.path()
path.moveTo(xDiscrete(d.x), y(0));
for (j=0; j<numPts; j++) {
path.lineTo(xDiscrete(d.x) + j*xDiscrete.bandwidth()/(numPts-1), y(d.y))
}
path.lineTo(xDiscrete(d.x) + xDiscrete.bandwidth(), y(0));
return path.toString();
});
var tooltip = d3.tip()
.attr('class', 'd3-tip chart-data-tip')
.offset([30, 0])
.direction('s')
.html(function(d, i) {
return "<span>" + dataDiscrete[i].desc + "</span>";
});
g.call(tooltip);
areas
.on('mouseover', tooltip.show)
.on('mouseout', tooltip.hide);
var thresholdLine = g.append("line")
.attr("stroke", "black")
.style("stroke-width", "1.5px")
.style("stroke-dasharray", "5,5")
.style("opacity", 1)
.attr("x1", 0)
.attr("y1", y(opts.threshold))
.attr("x2", dims.width)
.attr("y2", y(opts.threshold));
var updateXAxis = function(type, duration) {
if (type === "continuous") {
xAxis.scale(xContinuous);
} else {
xAxis.scale(xDiscrete);
}
d3.select(".x").transition().duration(duration).call(xAxis);
};
var updateYAxis = function(data, duration) {
var extent = d3.extent(data, function(d) {
return d.y;
});
extent[0] = 0;
extent[1] = extent[1] + 0.2*(extent[1] - extent[0]);
y.domain(extent);
d3.select(".y").transition().duration(duration).call(yAxis);
};
var toggle = function(to, duration) {
if (to === "distribution") {
updateYAxis(dataContinuousGroups[0].data.concat(dataContinuousGroups[1].data).concat(dataContinuousGroups[2].data), 0);
updateXAxis("continuous", duration);
areas
.data(dataContinuousGroups)
.transition()
.duration(duration)
.attr("d", function(d) {
var gen = d3.line()
.x(function(p) {
return xContinuous(p.x);
})
.y(function(p) {
return y(p.y);
});
return gen(d.data);
});
thresholdLine
.style("opacity", 0);
g.select(".y.axis")
.style("opacity", 0);
g.select(".y-axis-label")
.style("opacity", 0);
} else {
y.domain([0, 1]);
d3.select(".y").transition().duration(duration).call(yAxis);
updateXAxis("discrete", duration);
areas
.data(dataDiscrete)
.transition()
.duration(duration)
.attr("d", function(d, i) {
let numPts = dataContinuousGroups[i].data.length - 2;
var path = d3.path()
path.moveTo(xDiscrete(d.x), y(0));
for (j=0; j<numPts; j++) {
path.lineTo(xDiscrete(d.x) + j*xDiscrete.bandwidth()/(numPts-1), y(d.y))
}
path.lineTo(xDiscrete(d.x) + xDiscrete.bandwidth(), y(0));
return path.toString();
});
thresholdLine
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1)
.attr("y1", y(opts.threshold))
.attr("y2", y(opts.threshold));
g.select(".y.axis")
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1);
g.select(".y-axis-label")
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1);
}
};
// Add buttons
//container for all buttons
var allButtons = svg.append("g")
.attr("id", "allButtons");
//fontawesome button labels
var labels = ["B", "D"];
//colors for different button states
var defaultColor = "#E0E0E0";
var hoverColor = "#808080";
var pressedColor = "#000000";
//groups for each button (which will hold a rect and text)
var buttonGroups = allButtons.selectAll("g.button")
.data(labels)
.enter()
.append("g")
.attr("class", "button")
.style("cursor", "pointer")
.on("click", function(d, i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode));
d3.select("#numberToggle").text(i + 1);
if (d === "D") {
toggle("distribution", transDuration);
} else {
toggle("discrete", transDuration);
}
})
.on("mouseover", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill", hoverColor);
}
})
.on("mouseout", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill", defaultColor);
}
});
var bWidth = 40; //button width
var bHeight = 25; //button height
var bSpace = 10; //space between buttons
var x0 = 20; //x offset
var y0 = 10; //y offset
//adding a rect to each toggle button group
//rx and ry give the rect rounded corner
buttonGroups.append("rect")
.attr("class", "buttonRect")
.attr("width", bWidth)
.attr("height", bHeight)
.attr("x", function(d, i) {
return x0 + (bWidth + bSpace) * i;
})
.attr("y", y0)
.attr("rx", 5) //rx and ry give the buttons rounded corners
.attr("ry", 5)
.attr("fill", defaultColor);
//adding text to each toggle button group, centered
//within the toggle button rect
buttonGroups.append("text")
.attr("class", "buttonText")
.attr("x", function(d, i) {
return x0 + (bWidth + bSpace) * i + bWidth / 2;
})
.attr("y", y0 + bHeight / 2)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.attr("fill", "white")
.text(function(d) {
return d;
});
function updateButtonColors(button, parent) {
parent.selectAll("rect")
.attr("fill", defaultColor);
button.select("rect")
.attr("fill", pressedColor);
}
toggle("distribution", 0);
setTimeout(() => {
toggle("discrete", transDuration);
}, 1000);
},
resize: function(width, height) {
// TODO: code to re-render the widget with a new size
}
};
}
});
Once I save the svg in the right folder, I'm also not sure how can I use it to replace the two buttons that I have.
It will probably be easiest and most self contained to grab the svg paths (and in this case a rect) and attach them to the svg with svg.append("defs") - no need to access any image file from the script. Inserting an svg straight from a file makes it trickier, for example, to color, .attr("fill",) won't work in this case.
Open the icon in a text editor, the data we want from the icon is:
<path d="M37.92,42.22c3.78-8,7-14.95,12.08-14.95h0c5,0,8.3,6.93,12.08,14.95,6.12,13,13.73,29.13,33.48,29.13h0v-2h0c-18.48,0-25.79-15.51-31.67-28C59.82,32.74,56.3,25.28,50,25.28h0c-6.3,0-9.82,7.46-13.89,16.09-5.88,12.47-13.19,28-31.67,28h0v2h0C24.18,71.35,31.8,55.2,37.92,42.22Z"/>
<rect y="72.72" width="100" height="2"/>
Then we can append them to the svg as defs, using a parent g, with:
var symbol = svg.append("defs")
.append("g")
.attr("id","bellcurve");
symbol.append("path")
.attr("d", "M37.92,42.22c3.78-8,7-14.95,12.08-14.95h0c5,0,8.3,6.93,12.08,14.95,6.12,13,13.73,29.13,33.48,29.13h0v-2h0c-18.48,0-25.79-15.51-31.67-28C59.82,32.74,56.3,25.28,50,25.28h0c-6.3,0-9.82,7.46-13.89,16.09-5.88,12.47-13.19,28-31.67,28h0v2h0C24.18,71.35,31.8,55.2,37.92,42.22Z" )
symbol.append("rect")
.attr("y", 72.72)
.attr("width",100)
.attr("height",2);
To use the icon, we only need to append it as a child of a g element (this allows us to scale it too, and since it's width is 100 pixels, this allows for easy scaling to any width:
svg.append("g")
.attr("transform","scale(0.4)")
.append("use")
.attr("xlink:href","#bellcurve")
Like any other svg element, we can set the stroke, fill, and stroke-width attributes. If setting the stroke-width to more than 2, you probably won't need to set the fill: the stroke will overlap it.
Here's a quick demonstration using your icon, scaling it and coloring it, and for fun, transitioning it:
var svg = d3.select("body").append("svg")
.attr("width", 400)
.attr("height", 400);
var symbol = svg.append("defs")
.append("g")
.attr("id","bellcurve");
symbol.append("path")
.attr("d", "M37.92,42.22c3.78-8,7-14.95,12.08-14.95h0c5,0,8.3,6.93,12.08,14.95,6.12,13,13.73,29.13,33.48,29.13h0v-2h0c-18.48,0-25.79-15.51-31.67-28C59.82,32.74,56.3,25.28,50,25.28h0c-6.3,0-9.82,7.46-13.89,16.09-5.88,12.47-13.19,28-31.67,28h0v2h0C24.18,71.35,31.8,55.2,37.92,42.22Z" )
symbol.append("rect")
.attr("y", 72.72)
.attr("width",100)
.attr("height",2);
svg.append("g")
.append("use")
.attr("xlink:href","#bellcurve")
.attr("fill","steelblue")
.attr("stroke","steelblue")
svg.append("g")
.attr("transform","translate(100,0)scale(0.5)")
.append("use")
.attr("xlink:href","#bellcurve")
.attr("fill","steelblue")
.attr("stroke","steelblue")
.attr("stroke-width",2)
svg.append("g")
.attr("transform","translate(100,50)scale(0.5)")
.append("use")
.attr("xlink:href","#bellcurve")
.attr("fill","steelblue")
.attr("stroke","steelblue")
.attr("stroke-width",5)
var transition = function() {
d3.select(this)
.transition()
.attr("stroke","orange")
.attr("fill","orange")
.duration(1000)
.transition()
.attr("stroke","steelblue")
.attr("fill","steelblue")
.duration(500)
.on("end",transition)
}
d3.selectAll("g").selectAll("use")
.each(transition);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
With that it should be fairly easy to append the image straight to a button. And when toggling which visualization is showing, you can toggle the button's fill.
Slick looking app by the way.

d3.js - tween numbers on multiple elements

I've built a chart with a series of "small multiples", each representing a percentage of completion, arranged in rows and columns. Each row has a different number that represents "complete".
You can view the chart in this fiddle: http://jsfiddle.net/rolfsf/Lhnm9a9m/
I'm using transitions to grow the bars from 0 to x% width.
I also want to use a transition to show the actual count of each measure (e.g. 10 out of 17) incrementing from 0.
I've got all the numbers incrementing from 0 using a text tween function, but all of the measures stop at the same count, rather than their correct count.
I must be either using the wrong data in the tween, or placing the tween in the wrong part of the script... but I can't figure out where the problem is.
How do I get the numbers to increment properly??
My data looks like this:
var sets = [
{"title": "Set-1", "count": 17, "measures": [10, 13, 16, 14]},
{"title": "Set-2", "count": 23, "measures": [12, 18, 19, 23]},
{"title": "Set-3", "count": 25, "measures": [19, 22, 23, 20]},
{"title": "Set-4", "count": 4, "measures": [4, 4, 4, 4]},
{"title": "Set-5", "count": 8, "measures": [5, 7, 8, 6]}
];
The chart is called like this:
d3 .select('#overview-graph')
.datum(sets)
.call(relativeCompletionChart()
//options
);
and here's the reusable chart script:
function relativeCompletionChart() {
var width = 1200,
margin = {top: 16, right: 16, bottom: 16, left: 16},
onSetMouseOver = null,
onSetClick = null;
function chart(selection) {
selection.each(function(data) {
var titleColWidth = 0.3*width,
setHeight = 24,
barHeight = 22,
setCount = data.length,
colCount = data[0].measures.length,
colWidth = (width - titleColWidth)/colCount,
rangeWidth = colWidth - 4,
height = ((setHeight * setCount) + margin.top + margin.bottom);
var svg = d3.select(this)
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", "0 0 " + width + " " + height )
.attr("preserveAspectRatio", "xMidYMin meet");
svg .append('rect')
.attr("x", 0)
.attr('y', 0)
.attr('height', height)
.attr('width', width)
.attr('class', 'chart-bg');
/**
* Tween functions
*/
function tweenText( newValue ) {
return function() {
// get current value as starting point for tween animation
var currentValue = +this.textContent;
// create interpolator and do not show nasty floating numbers
var i = d3.interpolateRound( currentValue, newValue );
return function(t) {
this.textContent = i(t);
};
}
}
function update(data) {
var set = svg.selectAll("g.set")
.data(data);
set.exit().remove();
var setEnter = set
.enter().append("g")
.attr("class", "set")
.attr('transform', function (d, i) {
return 'translate(0, ' + (margin.top + i*setHeight) + ')';
});
set.append("text")
.attr("class", "title")
.attr("x", (titleColWidth - 80))
.attr("y", 16)
.attr("text-anchor", "end")
.text(function (d){return d.title;});
set.append("text")
.attr("class", "count")
.attr("x", (titleColWidth - 32))
.attr("y", 16)
.attr("text-anchor", "end")
.text(function (d){return d.count;});
var ranges = set.selectAll("rect.range")
.data(function(d, i){return d.measures})
.enter().append('rect')
.attr("class", "range")
.attr("x",2)
.attr("y",0)
.attr('rx', 2)
.attr('ry', 2)
.attr("width", rangeWidth)
.attr("height",barHeight)
.attr("fill", "#CCCCCC")
.attr('transform', function (d, i) {
return 'translate(' + (titleColWidth + i*colWidth) + ', 0)';
});
var measures = set.selectAll("rect.measure")
.data(function(d, i){return d.measures})
.enter().append('rect')
.attr("class", "measure")
.attr('rx', 2)
.attr('ry', 2)
.attr("x",2)
.attr("y",0)
.attr("width", 1)
.attr("height",barHeight)
.attr('transform', function (d, i) {
return 'translate(' + (titleColWidth + i*colWidth) + ', 0)';
});
var markers = set.selectAll("line.marker")
.data(function(d, i){return d.measures})
.enter().append('line')
.attr("class", "marker")
.attr('x1', 2)
.attr('y1', 0)
.attr("x2",2)
.attr("y2",barHeight)
.attr('transform', function (d, i) {
return 'translate(' + (titleColWidth + i*colWidth) + ', 0)';
});
var values = set.selectAll("text.value")
.data(function(d, i){return d.measures})
.enter().append('text')
.text('0')
.attr("class", "value")
.attr("x", 8)
.attr("y", 16)
.attr("text-anchor", "start")
.attr('transform', function (d, i) {
return 'translate(' + (titleColWidth + i*colWidth) + ', 0)';
});
//update widths
set.selectAll("rect.measure")
.transition()
.duration(1000)
.delay(function(d, i) { return i * 20; })
.attr("width", function(d, i) { return d3.round((d/d3.select(this.parentNode).datum().count)*rangeWidth);});
set.selectAll("line.marker")
.transition()
.duration(1000)
.delay(function(d, i) { return i * 20; })
.attr("x1", function(d, i) { return d3.round((d/d3.select(this.parentNode).datum().count)*rangeWidth + 1);})
.attr("x2", function(d, i) { return d3.round((d/d3.select(this.parentNode).datum().count)*rangeWidth + 1);});
set.selectAll('text.value')
.transition()
.duration(1000)
.tween( 'text', function() {
// get current value as starting point for tween animation
var currentValue = +this.textContent;
// create interpolator and do not show nasty floating numbers
var interpolator = d3.interpolateRound( currentValue, 10 );
// this returned function will be called a couple
// of times to animate anything you want inside
// of your custom tween
return function( t ) {
// set new value to current text element
this.textContent = interpolator(t) + '/' + d3.select(this.parentNode).datum().count;
};
});
}
update(data);
});
}
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.onSetClick = function(_) {
if (!arguments.length) return onSetClick;
onSetClick = _;
return chart;
};
chart.onSetMouseOver = function(_) {
if (!arguments.length) return onSetMouseOver;
onSetMouseOver = _;
return chart;
};
return chart;
}
The relevant code for the tweening is pulled out here:
set.selectAll('text.value')
.transition()
.duration(1000)
.tween( 'text', function() {
// get current value as starting point for tween animation
var currentValue = +this.textContent;
// create interpolator and do not show nasty floating numbers
var interpolator = d3.interpolateRound( currentValue, 10 );
// this returned function will be called a couple
// of times to animate anything you want inside
// of your custom tween
return function( t ) {
// set new value to current text element
this.textContent = interpolator(t) + '/' + d3.select(this.parentNode).datum().count;
};
});
Though I've also got an unused helper function in the script that I couldn't get to work:
/**
* Tween functions
*/
function tweenText( newValue ) {
return function() {
// get current value as starting point for tween animation
var currentValue = +this.textContent;
// create interpolator and do not show nasty floating numbers
var i = d3.interpolateRound( currentValue, newValue );
return function(t) {
this.textContent = i(t);
};
}
}

How to start and stop animation of a D3 bubble chart

I am trying to replicate the health & wealth nations chart.
http://bost.ocks.org/mike/nations/:
When i click start the animation of the chart works which works perfectly and if i click stop the animation stops. However if i click the start next time, it is starting from the beginning instead from where i stopped? how do i animate from the place where i left?
Following is the code:
<h1>The Wealth & Health of Nations</h1>
<p id="chart"></p>
<input type="submit" value="Start" onclick=start();>
<input type="submit" value="Stop" onclick=stop();>
<script src="http://d3js.org/d3.v2.js?2.8.1"></script>
<script>
// Various accessors that specify the four dimensions of data to visualize.
function x(d) { return d.income; }
function y(d) { return d.lifeExpectancy; }
function radius(d) { return d.population; }
function color(d) { return d.region; }
function key(d) { return d.name; }
// Chart dimensions.
var margin = {top: 29.5, right: 29.5, bottom: 29.5, left: 59.5},
width = 960 - margin.right,
height = 500 - margin.top - margin.bottom;
// Various scales. These domains make assumptions of data, naturally.
var xScale = d3.scale.log().domain([300, 1e5]).range([0, width]),
yScale = d3.scale.linear().domain([10, 85]).range([height, 0]),
radiusScale = d3.scale.sqrt().domain([0, 5e8]).range([0, 40]),
colorScale = d3.scale.category10();
// The x & y axes.
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(12, d3.format(",d")),
yAxis = d3.svg.axis().scale(yScale).orient("left");
// Create the SVG container and set the origin.
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Add the x-axis.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the y-axis.
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add an x-axis label.
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text("income per capita, inflation-adjusted (dollars)");
// Add a y-axis label.
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("life expectancy (years)");
// Add the year label; the value is set on transition.
var label = svg.append("text")
.attr("class", "year label")
.attr("text-anchor", "end")
.attr("y", height - 24)
.attr("x", width)
.text(2000);
function start()
{
//alert("Start Clicked");
// Load the data.
d3.json("nations_new.json", function(nations) {
// A bisector since many nation's data is sparsely-defined.
var bisect = d3.bisector(function(d) { return d[0]; });
// Add a dot per nation. Initialize the data at 2000, and set the colors.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2000))
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return colorScale(color(d)); })
.call(position)
.sort(order);
// Add a title.
dot.append("title")
.text(function(d) { return d.name; });
// Add an overlay for the year label.
var box = label.node().getBBox();
var overlay = svg.append("rect")
.attr("class", "overlay")
.attr("x", box.x)
.attr("y", box.y)
.attr("width", box.width)
.attr("height", box.height);
//.on("mouseover", enableInteraction);
// Start a transition that interpolates the data based on year.
svg.transition()
.duration(30000)
.ease("linear")
.tween("year", tweenYear)
.each("end", enableInteraction);
// Positions the dots based on data.
function position(dot) {
dot .attr("cx", function(d) { return xScale(x(d)); })
.attr("cy", function(d) { return yScale(y(d)); })
.attr("r", function(d) { return radiusScale(radius(d)); });
}
// Defines a sort order so that the smallest dots are drawn on top.
function order(a, b) {
return radius(b) - radius(a);
}
// After the transition finishes, you can mouseover to change the year.
function enableInteraction() {
var yearScale = d3.scale.linear()
.domain([2000, 2009])
.range([box.x + 10, box.x + box.width - 10])
.clamp(true);
// Cancel the current transition, if any.
svg.transition().duration(0);
overlay
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("mousemove", mousemove)
.on("touchmove", mousemove);
function mouseover() {
label.classed("active", true);
}
function mouseout() {
label.classed("active", false);
}
function mousemove() {
displayYear(yearScale.invert(d3.mouse(this)[0]));
}
}
// Tweens the entire chart by first tweening the year, and then the data.
// For the interpolated data, the dots and label are redrawn.
function tweenYear() {
var year = d3.interpolateNumber(2000, 2009);
return function(t) { displayYear(year(t)); };
}
// Updates the display to show the specified year.
function displayYear(year) {
dot.data(interpolateData(year), key).call(position).sort(order);
label.text(Math.round(year));
}
// Interpolates the dataset for the given (fractional) year.
function interpolateData(year) {
return nations.map(function(d) {
return {
name: d.name,
region: d.region,
income: interpolateValues(d.income, year),
population: interpolateValues(d.population, year),
lifeExpectancy: interpolateValues(d.lifeExpectancy, year)
};
});
}
// Finds (and possibly interpolates) the value for the specified year.
function interpolateValues(values, year) {
var i = bisect.left(values, year, 0, values.length - 1),
a = values[i];
if (i > 0) {
var b = values[i - 1],
t = (year - a[0]) / (b[0] - a[0]);
return a[1] * (1 - t) + b[1] * t;
}
return a[1];
}
});
}
function stop()
{
//alert("stop Clicked");
svg.transition().duration(0);
}
the json file is :
[
{
"name":"Angola",
"region":"Sub-Saharan Africa",
"income":[[2000,2446.65],[2001,2479.69],[2002,2773.29],[2003,2785.39],[2004,3007.11],[2005,3533],[2006,4069.56],[2007,4755.46],[2008,5228.74],[2009,5055.59]],
"population":[[2000,10442812],[2001,10623424],[2002,10866106],[2003,11186202],[2004,11521432],[2005,11827315],[2006,12127071],[2007,12420476],[2008,12707546]],
"lifeExpectancy":[[2000,43.56],[2001,43.86],[2002,44.22],[2003,44.61],[2004,45.05],[2005,45.52],[2006,46.02],[2007,46.54],[2008,47.06],[2009,47.58]]
},
{
"name":"china",
"region":"East Asia & Pacific",
"income":[[2000,12446.65],[2001,12479.69],[2002,12773.29],[2003,12785.39],[2004,12007.11],[2005,12533],[2006,12069.56],[2007,12755.46],[2008,12228.74],[2009,12055.59]],
"population":[[2000,31542812],[2001,31623424],[2002,31866106],[2003,32186202],[2004,31521432],[2005,31827315],[2006,32127071],[2007,32420476],[2008,32707546]],
"lifeExpectancy":[[2000,53.56],[2001,63.86],[2002,64.22],[2003,64.61],[2004,76.05],[2005,66.52],[2006,86.02],[2007,87.54],[2008,89.06],[2009,68.58]]
},
{
"name":"India",
"region":"South Asia",
"income":[[2000,22446.65],[2001,22479.69],[2002,22773.29],[2003,22785.39],[2004,22007.11],[2005,22533],[2006,22069.56],[2007,22755.46],[2008,22228.74],[2009,22055.59]],
"population":[[2000,41542812],[2001,41623424],[2002,41866106],[2003,42186202],[2004,41521432],[2005,41827315],[2006,42127071],[2007,42420476],[2008,42707546],[2009,42707546]],
"lifeExpectancy":[[2000,43.56],[2001,43.86],[2002,44.22],[2003,64.61],[2004,56.05],[2005,56.52],[2006,66.02],[2007,68.54],[2008,67.06],[2009,73.58]]
}
]
In the start function, you would need to keep track of what year you're currently showing, e.g. with a global variable:
var thisYear = 2000;
// lots of code...
function displayYear(year) {
thisYear = year;
dot.data(interpolateData(year), key).call(position).sort(order);
label.text(Math.round(year));
}
Then you would need to modify the year it starts with depending on the value of that variable:
function tweenYear() {
var year = d3.interpolateNumber(thisYear, 2009);
return function(t) { displayYear(year(t)); };
}
I was working on something very similar and the suggestions by Lars got me very far. However, I kept getting my dots duplicated each time I hit start. I have since discovered the following:
If you would like to make it so your dots are not duplicated each time you select the start button, you would need to add the following code as a part of your start() function, and ideally just before the dots are initially added:
svg.selectAll(".dot").remove()
This removes the previous .dot elements; new .dot elements are subsequently created assuming you've setup the thisYear global variable mentioned by Lars.

How to draw multiseries donut chart using d3.js

I am trying to implement donut chart with multiple series (ex:2) using the d3.js library.
I need to use json data as source.
Any Idea how to do this.
This is the source code, i used for donut chart:
var width = 800,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888"]);
var arc = d3.svg.arc()
.outerRadius(radius - 70)
.innerRadius(radius - 120);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.Count; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var jsondata = [{ "id": 1,"age": 5, "population": 2704659 }, { "id": 2, "age": 7, "population": 4499890 }, { "id": 3, "age": 8, "population": 2159981 }];
data = jsondata;
d3.json('talent/data', function (error, data) {
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(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; });
});
</script>
Multi-series donut chart like : http://www.jqplot.com/tests/pie-donut-charts.php

Resources