Iterative/chained transitions along line graph with discrete points and delay - javascript

I created a jsfiddle here.
I do have a graph - in this case a sine wave - and want to move a circle along this line (triggered by a click event), stop at certain x and y value pairs that are on this graph and then move on to the last point of the graph from where it jumps to the first again (ideally this should go on until I press a stop button).
My current problem is that the circle only moves horizontally but not in the ordinate direction and also the delay is visible only once (in the very beginning).
The relevant code is this one (the entire running example can be found in the link above):
Creation of the circle:
// the circle I want to move along the graph
var circle = svg.append("circle")
.attr("id", "concindi")
.attr("cx", x_scale(xval[0]))
.attr("cy", y_scale(yval[0]))
.attr("transform", "translate(" + (0) + "," + (-1 * padding + 15) + ")")
.attr("r", 6)
.style("fill", 'red');
The moving process:
var coordinates = d3.zip(xval, yval);
svg.select("#concindi").on("click", function() {
coordinates.forEach(function(ci, indi){
//console.log(ci[1] + ": " + indi);
//console.log(coordinates[indi+1][1] + ": " + indi);
if (indi < (coordinates.length - 1)){
//console.log(coordinates[indi+1][1] + ": " + indi);
console.log(coordinates[indi + 1][0]);
console.log(coordinates[indi + 1][1]);
d3.select("#concindi")
.transition()
.delay(2000)
.duration(5000)
.ease("linear")
.attr("cx", x_scale(coordinates[indi + 1][0]))
.attr("cy", y_scale(coordinates[indi + 1][1]));
}
});
I am pretty sure that I use the loop in a wrong manner. The idea is to start at the first x/y pair, then move to the next one (which takes 5s), wait there for 2s and move on to the next and so on. Currently, the delay is only visible initially and then it just moves horizontally.
How would this be done correctly?

Why don't you use Bostock's translateAlong function?
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
Here is the demo:
// function to generate some data
function get_sin_val(value) {
return 30 * Math.sin(value * 0.25) + 35;
}
var width = 400;
var height = 200;
var padding = 50;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var xrange_min = 0;
var xrange_max = 50;
var yrange_min = 0;
var yrange_max = 100;
var x_scale = d3.scale.linear()
.domain([xrange_min, xrange_max])
.range([padding, width - padding * 2]);
var y_scale = d3.scale.linear()
.domain([yrange_min, yrange_max])
.range([height - padding, padding]);
// create the data
var xval = d3.range(xrange_min, xrange_max, 1);
var yval = xval.map(get_sin_val);
// just for convenience
var coordinates = d3.zip(xval, yval);
//defining line graph
var lines = d3.svg.line()
.x(function(d) {
return x_scale(d[0]);
})
.y(function(d) {
return y_scale(d[1]);
})
.interpolate("linear");
//draw graph
var sin_graph = svg.append("path")
.attr("d", lines(coordinates))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
// the circle I want to move along the graph
var circle = svg.append("circle")
.attr("id", "concindi")
.attr("transform", "translate(" + (x_scale(xval[0])) + "," + (y_scale(yval[0])) + ")")
.attr("r", 6)
.style("fill", 'red');
svg.select("#concindi").on("click", function() {
d3.select(this).transition()
.duration(5000)
.attrTween("transform", translateAlong(sin_graph.node()));
});
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You have to understand that forEach will loop to the end of the array almost instantaneously. Thus, you cannot make the circle jumping to one coordinate to the other with your approach right now (thus, unfortunately, you are correct here:"I am pretty sure that I use the loop in a wrong manner").
If you want to add the 2s waiting period between one point and another, the best idea is chaining the transitions. Something like this (I'm reducing the delay and the duration times in the demo, so we can better see the effect):
var counter = 0;
transit();
function transit() {
counter++;
d3.select(that).transition()
.delay(500)
.duration(500)
.attr("transform", "translate(" + (x_scale(coordinates[counter][0]))
+ "," + (y_scale(coordinates[counter][1])) + ")")
.each("end", transit);
}
Here is the demo:
// function to generate some data
function get_sin_val(value) {
return 30 * Math.sin(value * 0.25) + 35;
}
var width = 400;
var height = 200;
var padding = 50;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var xrange_min = 0;
var xrange_max = 50;
var yrange_min = 0;
var yrange_max = 100;
var x_scale = d3.scale.linear()
.domain([xrange_min, xrange_max])
.range([padding, width - padding * 2]);
var y_scale = d3.scale.linear()
.domain([yrange_min, yrange_max])
.range([height - padding, padding]);
// create the data
var xval = d3.range(xrange_min, xrange_max, 1);
var yval = xval.map(get_sin_val);
// just for convenience
var coordinates = d3.zip(xval, yval);
//defining line graph
var lines = d3.svg.line()
.x(function(d) {
return x_scale(d[0]);
})
.y(function(d) {
return y_scale(d[1]);
})
.interpolate("linear");
//draw graph
var sin_graph = svg.append("path")
.attr("d", lines(coordinates))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
// the circle I want to move along the graph
var circle = svg.append("circle")
.attr("id", "concindi")
.attr("transform", "translate(" + (x_scale(xval[0])) + "," + (y_scale(yval[0])) + ")")
.attr("r", 6)
.style("fill", 'red');
svg.select("#concindi").on("click", function() {
var counter = 0;
var that = this;
transit();
function transit() {
counter++;
d3.select(that).transition()
.delay(500)
.duration(500)
.attr("transform", "translate(" + (x_scale(coordinates[counter][0])) + "," + (y_scale(coordinates[counter][1])) + ")")
.each("end", transit);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

Migrating d3 v2 to d3 v4 for Chord Diagram

I am using this example to guide my foray into chord diagrams with d3. I have it working with my data in v2 (the version the example is in), and now I am attempting to upgrade to v4.
Here is the code that works with v2:
<script src="http://d3js.org/d3.v2.min.js?2.8.1"></script>
<script>
var width = 900,
height = 900,
outerRadius = Math.min(width, height) / 2 - 10,
innerRadius = outerRadius - 24;
var formatPercent = d3.format(",.0f");
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var layout = d3.layout.chord()
.padding(.02)
.sortSubgroups(d3.descending)
.sortChords(d3.ascending);
var path = d3.svg.chord()
.radius(innerRadius);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("id", "circle")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("circle")
.attr("r", outerRadius);
d3.csv("teams.csv", function(cities) {
d3.json("matrix.json", function(matrix) {
// Compute the chord layout.
layout.matrix(matrix);
// Add a group per neighborhood.
var group = svg.selectAll(".group")
.data(layout.groups)
.enter().append("g")
.attr("class", "group")
.on("mouseover", mouseover);
// Add a mouseover title.
group.append("title").text(function(d, i) {
return cities[i].name + ": " + formatPercent(d.value) + " as business unit";
});
// Add the group arc.
var groupPath = group.append("path")
.attr("id", function(d, i) { return "group" + i; })
.attr("d", arc)
.style("fill", function(d, i) { return cities[i].color; });
// Add the chords.
var chord = svg.selectAll(".chord")
.data(layout.chords)
.enter().append("path")
.attr("class", "chord")
.style("fill", function(d) { return cities[d.source.index].color; })
.attr("d", path);
// Add an elaborate mouseover title for each chord.
chord.append("title").text(function(d) {
return cities[d.source.index].name
+ " → " + cities[d.target.index].name
+ ": " + formatPercent(d.source.value)
+ "\n" + cities[d.target.index].name
+ " → " + cities[d.source.index].name
+ ": " + formatPercent(d.target.value);
});
function mouseover(d, i) {
chord.classed("fade", function(p) {
return p.source.index != i
&& p.target.index != i;
});
}
});
});
</script>
Here is the same code midway through my attempt to migrate to v4:
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var width = 900,
height = 900,
outerRadius = Math.min(width, height) / 2 - 10,
innerRadius = outerRadius - 24;
var formatPercent = d3.format(",.0f");
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var layout = d3.chord()
.padAngle(.02)
.sortSubgroups(d3.descending)
.sortChords(d3.ascending);
var path = d3.ribbon()
.radius(innerRadius);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("id", "circle")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("circle")
.attr("r", outerRadius);
d3.csv("teams.csv", function(cities) {
d3.json("matrix.json", function(matrix) {
// Compute the chord layout.
layout.matrix(matrix);
// Add a group per neighborhood.
var group = svg.selectAll(".group")
.data(layout.groups)
.enter().append("g")
.attr("class", "group")
.on("mouseover", mouseover);
// Add a mouseover title.
group.append("title").text(function(d, i) {
return cities[i].name + ": " + formatPercent(d.value) + " as business unit";
});
// Add the group arc.
var groupPath = group.append("path")
.attr("id", function(d, i) { return "group" + i; })
.attr("d", arc)
.style("fill", function(d, i) { return cities[i].color; });
// Add the chords.
var chord = svg.selectAll(".chord")
.data(layout.chords)
.enter().append("path")
.attr("class", "chord")
.style("fill", function(d) { return cities[d.source.index].color; })
.attr("d", path);
// Add an elaborate mouseover title for each chord.
chord.append("title").text(function(d) {
return cities[d.source.index].name
+ " → " + cities[d.target.index].name
+ ": " + formatPercent(d.source.value)
+ "\n" + cities[d.target.index].name
+ " → " + cities[d.source.index].name
+ ": " + formatPercent(d.target.value);
});
function mouseover(d, i) {
chord.classed("fade", function(p) {
return p.source.index != i
&& p.target.index != i;
});
}
});
});
</script>
So far, I've flattened the namespaces (up to d3.csv), changed padding to padAngle, and changed var path = d3.chord() to var path = d3.ribbon(). As I make each change, I am checking the error messages in Chrome Developer. After making those changes, the current error is layout.matrix is not a function. This makes sense, based on v4 standards. To combat this, I tried adding .data(layout.matrix) to the var svg creation. I tried a few other routes gleaned from other chord diagram examples, to no avail.
How should I access and bind the data in v4?
Edit: I added .data(layout(matrix)) instead, and now the g elements with class=group are being created. However, this error is occurring: attribute d: Expected number, "MNaN,NaNLNaN,NaNZ". So I'm thinking this means the location is not being populated correctly.
Edit #2: Now I've gotten everything to show up except for the bars around the outside. Here is my current code. I believe the d attribute of the path elements within the g groups are wrong. They are set to be the same as the path elements outside the g groups. When I attempt to set them to arc instead (.attr("d", arc), the error attribute d: Expected number, "MNaN,NaNLNaN,NaNZ". occurs.
<script>
var width = 900,
height = 900,
outerRadius = Math.min(width, height) / 2 - 10,
innerRadius = outerRadius - 24;
var formatPercent = d3.format(",.0f");
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var layout = d3.chord()
.padAngle(.02)
.sortSubgroups(d3.descending)
.sortChords(d3.ascending);
var ribbon = d3.ribbon()
.radius(innerRadius);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("id", "circle")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
svg.append("circle")
.attr("r", outerRadius);
d3.csv("teams.csv", function(cities) {
d3.json("matrix.json", function(matrix) {
// Add a group per neighborhood.
var group = svg.selectAll(".group")
.data(layout(matrix))
.enter()
.append("g")
.attr("class", "group");
// Add the group arc.
var groupRibbon = group.append("path")
.attr("id", function(d, i) { return "group" + i; })
.attr("d", ribbon)
//.style("fill", function(d, i) { return cities[i].color; })
;
// Add the chords.
var chord = svg.selectAll(".chord")
.data(layout(matrix))
.enter().append("path")
.attr("class", "chord")
.style("fill", function(d) { return cities[d.source.index].color; })
.attr("d", ribbon);
});
});
</script>

Specify startpoint to interpolate a circle on an arc when clicked on by the user

How do I provide the starting point of an arc for moving a circle along the path of the arc. I have a world map with several arcs displayed on it. I wish to interpolate the movement of a circle on the arc that has been selected by the user using the .on('click') event. I wish to know how I can identify the startPoint of the arc in question.
Specifically, I am not able to understand what parameters to provide in .attr("transform", "translate(" + startPoint + ")") attribute of the circle to enable the circle to start from the starting position of the arc.
At present, it passes the entire path and I receive the following error
d3.v3.min.js:1 Error: attribute transform: Expected number, "translate(M1051.5549785289…".
Although, surprisingly, the circle marker appears on the screen and interpolates along the first arc that has been drawn. However, I wish to change this interpolation to an arc that has been clicked by the user. In other words, how do I feed a new startPoint to the circle marker every time the user clicks on a different arc and to have a subsequent interpolation of the same.
var path3 = arcGroup.selectAll(".arc"),
startPoint = pathStartPoint(path3)
var marker = arcGroup.append("circle")
marker.attr("r", 7)
.attr("transform", "translate(" + startPoint + ")")
transition();
function pathStartPoint(path) {
var d = path.attr("d")
console.log(path)
dsplitted = d.split(" ");
return dsplitted[0].split(",");
}
function transition() {
marker.transition()
.duration(7500)
.attrTween("transform", translateAlong(path3.node()))
.each("end", transition);// infinite loop
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";//Move marker
}
}
}
As #altocumulus states in the their comment, getPointAtLength doesn't need a starting point. It takes as an argument a distance from 0 to path length where 0 is the starting point. Here's a quick example, click on any path below:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var w = 400,
h = 400;
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h);
var data = []
for (var i = 0; i < 5; i++) {
data.push({
x0: Math.random() * w,
y0: Math.random() * h,
x1: Math.random() * w,
y1: Math.random() * h
});
}
var marker = svg.append("circle")
.attr("r", 20)
.style("fill", "steelblue")
.style("opacity", 0);
svg.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", function(d) {
var dx = d.x1 - d.x0,
dy = d.y1 - d.y0,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.x0 + "," + d.y0 + "A" + dr + "," + dr +
" 0 0,1 " + d.x1 + "," + d.y1;
})
.style("stroke", function(d, i) {
return d3.schemeCategory10[i];
})
.style("stroke-width", "10px")
.style("fill", "none")
.on("click", function(d){
marker
.style("opacity", 1)
.transition()
.duration(1000)
.attrTween("transform", translateAlong(this))
.on("end", function(d) {
marker.style("opacity", 0);
});
});
function translateAlong(path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")"; //Move marker
}
}
}
</script>
</body>
</html>

D3 JS make polygon on click

Consider the following code
var width = 960,
height = 500;
var vertices = d3.range(100).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
var voronoi = d3.geom.voronoi()
.clipExtent([[0, 0], [width, height]]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousemove", function() { vertices[0] = d3.mouse(this); redraw(); });
var path = svg.append("g").selectAll("path");
svg.selectAll("circle")
.data(vertices.slice(1))
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + d + ")"; })
.attr("r", 1.5);
redraw();
function redraw() {
path = path
.data(voronoi(vertices), polygon);
path.exit().remove();
path.enter().append("path")
.attr("class", function(d, i) { return "q" + (i % 9) + "-9"; })
.attr("d", polygon);
path.order();
}
function polygon(d) {
return "M" + d.join("L") + "Z";
}
How can I add a new Polygon with a CLICK & at the same time draw a center dot as well ?
You have a good start. In addition to the mousemove listener on the svg you also need a click listener. With this you can just add a new vertex each time the user clicks. I've done this by adding a variable to the redraw function to distinguish between redraws triggered by a click. You might be able to find a cleaner way to do this, but hopefully this helps!
var width = 960,
height = 500;
var vertices = d3.range(100).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
var voronoi = d3.geom.voronoi()
.clipExtent([[0, 0], [width, height]]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousemove", function() { vertices[0] = d3.mouse(this); redraw(); })
.on('click', function() {
vertices.push(d3.mouse(this));
redraw(true);
});
var path = svg.append("g").selectAll("path");
var circle = svg.selectAll("circle");
redraw();
function redraw(fromClick) {
var data = voronoi(vertices);
path = path
.data(data, polygon);
path.exit().remove();
path.enter().append("path")
.attr("class", function(d, i) { return "q" + (i % 9) + "-9"; })
.attr("d", polygon);
path.order();
circle = circle.data(vertices)
circle.attr("transform", function(d) { return "translate(" + d + ")"; })
circle.enter().append("circle")
.attr("transform", function(d) { return "translate(" + d + ")"; })
.attr("r", 1.5)
.attr('fill', fromClick ? 'white' : '')
circle.exit().remove();
}
function polygon(d) {
return d ? "M" + d.join("L") + "Z" : null;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Transition of triangle svg path in D3.js

I would like to create 30 equilateral triangles of different dimensions and colors, in a random position inside the body, that are moving away from the current mouse position, stopping at a short distance from the border of the body.
Here is the code:
var body = d3.select("body");
var mouse = [];
var width = 1000;
var height = 600;
var numberOfTriangles = 30;
var isMouseMoving = false;
var triangle = d3.symbol()
.type(d3.symbolTriangle);
function drawTriangles(number) {
for(var i=0;i<number;i++){
var dim = Math.random()*400;
svg.append("path")
.attr("d", triangle.size(dim))
.attr("transform", function(d) { return "translate(" + Math.random()*width + "," + Math.random()*height + ")"; })
.attr("fill", "rgb("+parseInt(Math.random()*255)+","+parseInt(Math.random()*255)+","+parseInt(Math.random()*255)+")")
.attr("opacity", 2)
.attr("class", "path"+i);
}
}
function moveMouse(number) {
if (isMouseMoving) {
console.log("posix: x="+mouse[0]+",y="+mouse[1]+"\n");
for(var i=0;i<number;i++){
svg.select('.path'+i).transition().duration(5)
.attr({'x':-mouse[0],'y':-mouse[1]})
}
}
}
var svg = body.append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
.on("mousemove", function() {
mouse = d3.mouse(this);
isMouseMoving=true;
});
drawTriangles(numberOfTriangles);
d3.timer(function(){moveMouse(numberOfTriangles)});
I have few questions:
1) How to position the triangles inside the body? In this way that I have done, a little part of some triangles is outside the body. Math.random()*weight and Math.random()*height are not enough?
2) I saw some examples that implement the transition like I have done, but in my case doesn't work. How can I implement the transition of the triangles so that they move away from the current position of the mouse and they stop at a short distance from the border of the body?
Thank you in advice.
1) It depends on how the triangles are drawn.
If d3.symbolTriangle draws lineTo into negative x territory (i think it does), then you have the potential that the left half of your triangle will be cut off by the bounds of your body.
Try adding 1/2 of the triangle's width when you translate it on the x direction.
2) At the moment, your code is attempting to move the triangles to the position of the mouse, not away from it. Here is that working:
var body = d3.select("body");
var mouse = [];
var width = 1000;
var height = 600;
var numberOfTriangles = 30;
var isMouseMoving = false;
var triangle = d3.symbol()
.type(d3.symbolTriangle);
function drawTriangles(number) {
for (var i = 0; i < number; i++) {
var dim = Math.random() * 400;
svg.append("path")
.attr("d", triangle.size(dim))
.attr("transform", function(d) {
return "translate(" + Math.random() * width + "," + Math.random() * height + ")";
})
.attr("fill", "rgb(" + parseInt(Math.random() * 255) + "," + parseInt(Math.random() * 255) + "," + parseInt(Math.random() * 255) + ")")
.attr("opacity", 2)
.attr("class", "path" + i);
}
}
function moveMouse() {
if (isMouseMoving) {
//console.log("posix: x="+mouse[0]+",y="+mouse[1]+"\n");
svg.selectAll('path').each(function(d, i) {
var self = d3.select(this);
self.attr('transform', function() {
return "translate(" + mouse[0] + "," + mouse[1] + ")";
})
})
}
}
var svg = body.append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
.on("mousemove", function() {
mouse = d3.mouse(this);
isMouseMoving = true;
});
drawTriangles(numberOfTriangles);
d3.timer(function() {
moveMouse()
});
Fiddle: https://jsfiddle.net/hLcvdpjk/
D3 triangle: https://github.com/d3/d3-shape/blob/master/src/symbol/triangle.js

How to update d3.js pie chart with updated dataset

I've mixed and matched a lot of code from other sources.
Currently, the pie chart displays fine, but when I select a different server in the drop-down menu, it fails to update the picture. The console logs show that dataset is indeed being changed. I checked online and it seems like my elements are not being removed correctly? However, whenever I try to use path.exit().remove() it fails to display anything.
Here is my code and jsfiddle: http://jsfiddle.net/36q95k1c/2/
var ds1 = [1,1,1,1,1,1,1,3,0,0];
var ds2 = [0,0,0,1,1,1,1,1,1,1];
var ds3 = [0,0,1,1,0,0,0,0,0,0];
var ds4 = [0,0,2,0,5,3,0,0,0,0];
var ds5 = [0,0,0,0,0,0,0,0,0,0];
var ds6 = [0,0,0,0,0,0,0,0,0,0];
var ds7 = [0,0,0,0,0,0,0,0,0,0];
var ds8 = [0,0,0,0,0,0,0,0,0,0];
var ds9 = [0,0,0,0,0,0,0,0,0,0];
var ds10 = [0,0,0,0,0,0,0,0,0,0];
var ds11 = [0,0,0,0,0,0,0,0,0,0];
var ds12 = [0,0,0,0,0,0,0,0,0,0];
var ds13 = [0,0,0,0,0,0,0,0,0,0];
var ds14 = [0,0,0,0,0,0,0,0,0,0];
var dataset = {inner: [4,1,31,28,13,65,6,6,4,3],
middle: ds1,
outer: [1175,1802,8126,11926,37264,4267,2961,2909,850,12432]};
var victim_total = 4+1+31+28+13+65+6+6+4+3;
var killer_total = 22+4+37+72+2+20+2+11+3+3;
var general_total = 1175+1802+8126+11926+37264+4267+2961+2909+850+12432;
var legendRectSize = 25;
var legendSpacing = 6;
//Width and height
var w = 750;
var h = 750;
var r = 100;
var donutWidth = 225
var outerRadius = w / 2;
var innerRadius = donutWidth;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var pie = d3.layout.pie()
.sort(null);
// Easy colors accessible via a 10-step ordinal scale
var color = d3.scale.category20();
// Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", 2*w)
.attr("height", h)
.append('g')
.attr('transform', 'translate(' + (w / 2) +
',' + (h / 2) + ')');
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
function updateLegend(newData) {
//https://dl.dropboxusercontent.com/s/hfho50s0xd2dcpn/craftinggeneralstats1.csv?dl=1"
//https://dl.dropboxusercontent.com/s/i152v8ccetr5gj0/craftinggeneralstats.csv?dl=1
//Import CSV file
d3.csv("https://dl.dropboxusercontent.com/s/i152v8ccetr5gj0/craftinggeneralstats.csv?dl=1", function(data) {
data.forEach(function(d) {
d.outer = +d.count;
d.middle = +d.countkiller;
d.inner = +d.countvictim;
d.label = d.label;
});
// function updateLegend(newData) {
/*var step;
for (step = 0; step < 10; step++) {
// Runs 5 times, with values of step 0 through 4.
data[step].countkiller = 1;
}*/
//data[i].countkiller = 0;
console.log(dataset.middle);
// Set up groups
var arcs = svg.selectAll("g.arc")
.data(d3.values(dataset));
arcs.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" +0+ "," +0+ ")");
var path = arcs.selectAll("path")
.data(function(d) { return pie(d); });
path.enter().append("path")
.on("mouseover", function(d, i, j) {
// console.log(d);
//console.log(dataset.middle[3]);
div.transition()
.duration(200)
.style("opacity", .9)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
if (j ==0)
div.html("Victim" + "<br/>" + Math.round(1000 * d.value / victim_total) / 10 +"%")
if (j ==1)
div.html("Killer" + "<br/>" + Math.round(1000 * d.value / killer_total) / 10 +"%")
if (j ==2)
{
div.html("Overall" + "<br/>" + Math.round(1000 * d.value / general_total) / 10 +"%")
}
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d, i, j) { return arc.innerRadius(10+r*j).outerRadius(r*(j+1))(d); });
// .attr("text-anchor", "middle")
/* .text(function (d, i) {
return data[i].label;
});*/
// Setup legend
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + (horz +(w/2)) + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color)
.on('click', function(label) {
var rect = d3.select(this);
var enabled = true;
if (rect.attr('class') === 'disabled') {
rect.attr('class', '');
} else {
if (totalEnabled < 2) return;
rect.attr('class', 'disabled');
enabled = false;
}
pie.value(function(d) {
if (d.label === label) d.enabled = enabled;
return (d.enabled) ? d.count : 0;
});
arcs = arcs.data(d3.values(dataset))
});
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d, i) {
return data[i].label;
});
path.exit().remove();
d3.select('#opts')
.on('change', function(d) {
var server = eval(d3.select(this).property('value'));
dataset.middle = server;
updateLegend(dataset);
});
});
}
updateLegend(dataset);
In the current version, you just add declarations for the enter selection, which affects only arcs that are inserted. You need to guide D3 how to treat the update selection, which affects arcs that are updated and the exit selection, which affects arcs that are removed. You can add these lines of code before the path.enter() statement.
path.attr("d", function(d, i, j) {
return arc.innerRadius(10 + r * j).outerRadius(r * (j + 1))(d);
});
path.exit().remove();
The updated fiddle: http://jsfiddle.net/36q95k1c/5/
To add to Hieu Le's nice answer, for your query about animations, it can be a bit tricky to figure out the right point at which to add the transition() call. Try putting it on line 100 of Hieu Le's jsfiddle to see if it's what you're after:
path.transition().attr("d", function(d, i, j) {
return arc.innerRadius(10 + r * j).outerRadius(r * (j + 1))(d);
});
Once you've got it animating, check out the docs for information on different interpolations and tweens.
Cheers!

Categories