D3 JS make polygon on click - javascript

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>

Related

D3.js v5 modular swarm clusters (variable radius?)

I want to create a visual whereby a swarm contains one big circle and a bunch of satellite circles clinging around it. For a simple demonstration, I have prepared a small version of the data set; each item in the array should have one big circle and then however many smaller circles clinging to it:
var data = [
{'wfoe':'wfoe1','products':d3.range(20)},
{'wfoe':'wfoe2','products':d3.range(40)},
{'wfoe':'wfoe3','products':d3.range(10)}
];
Here is a snippet of my progress:
var margins = {
top: 100,
bottom: 300,
left: 100,
right: 100
};
var height = 250;
var width = 900;
var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate(" + margins.left + "," + margins.top + ")");
var data = [
{'wfoe':'wfoe1','products':d3.range(20)},
{'wfoe':'wfoe2','products':d3.range(40)},
{'wfoe':'wfoe3','products':d3.range(10)}
];
var columns = 4;
var spacing = 250;
var vSpacing = 250;
var fmcG = graphGroup.selectAll('.fmc')
.data(data)
.enter()
.append('g')
.attr('class', 'fmc')
.attr('id', (d, i) => 'fmc' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var xScale = d3.scalePoint()
.range([0, width])
.domain([0, 100]);
var rScale = d3.scaleThreshold()
.range([50,5])
.domain([0,1]);
data.forEach(function(d, i) {
d.x = (i % columns) * spacing;
d.y = ~~((i / columns)) * vSpacing;
});
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d,i) {
return (i % columns) * spacing;
}).strength(0.1))
.force("y", d3.forceY(function(d,i) {
return ~~((i / columns)) * vSpacing;
}).strength(0.01))
.force("collide", d3.forceCollide(function(d,i) { return rScale(i)}))
.stop();
simulation.tick(75);
fmcG.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", function(d,i) {
return rScale(i)
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.style('fill',"#003366");
<script src="https://d3js.org/d3.v5.min.js"></script>
I want to quickly point out that the big circle doesn't represent any data point (they are just going to house a name / logo). I just thought that including it in the simulation data would be the easiest way to introduce the needed force logic for the swarm circles. I thought that an elegant solution would be to use a threshold scale and let the first (i=0) datum always be the biggest circle. Here is what I mean:
var rScale = d3.scaleThreshold()
.range([0, 1])
.domain([50, 5]);
fmcG.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", function(d,i) {
return rScale(i)
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.style('fill',"#003366");
The result I mentioned above (three big circles with little circles all around them) was not achieved, and in fact very few circles were appended and the variable radius component didn't seem to be working as I thought it would. (also no errors displayed in the log).
Question
How can I iteratively create swarms that start with one big circle and append subsequent smaller circles around the initial big circle, as applicable to the sample data set?
You could use a force simulation, like below, only this gives non-deterministic results. However, it's really good when you want to gradually add more nodes. In the below solution, I gave all related nodes a link to the center node, but didn't draw it. This made it possible for linked nodes to attract heavily.
On the other hand, you could also use a bubble chart if you want D3 to find the optimal packing solution for you, without the force working on them. Only downside is you'd have to call the packing function with all nodes every time, and the other nodes might shift because of the new one.
var margins = {
top: 100,
bottom: 300,
left: 100,
right: 100
};
var height = 250;
var width = 900;
var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate(" + margins.left + "," + margins.top + ")");
var data = [{
'wfoe': 'wfoe1',
'products': d3.range(20).map(function(v) {
return v.toString() + '_wfoe1';
})
},
{
'wfoe': 'wfoe2',
'products': d3.range(40).map(function(v) {
return v.toString() + '_wfoe2';
})
},
{
'wfoe': 'wfoe3',
'products': d3.range(10).map(function(v) {
return v.toString() + '_wfoe3';
})
}
];
var columns = 4;
var spacing = 250;
var vSpacing = 250;
function dataToNodesAndLinks(d) {
// Create one giant array of points and
// one link between each wfoe and each product
var nodes = [{
id: d.wfoe,
center: true
}];
var links = [];
d.products.forEach(function(p) {
nodes.push({
id: p,
center: false
});
links.push({
source: d.wfoe,
target: p
});
});
return {
nodes: nodes,
links: links
};
}
var fmcG = graphGroup.selectAll('.fmc')
.data(data.map(function(d, i) {
return dataToNodesAndLinks(d, i);
}))
.enter()
.append('g')
.attr('class', 'fmc')
.attr('id', (d, i) => 'fmc' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var xScale = d3.scalePoint()
.range([0, width])
.domain([0, 100]);
var rScale = d3.scaleThreshold()
.range([50, 5])
.domain([0, 1]);
fmcG.selectAll("circle")
.data(function(d) {
return d.nodes;
})
.enter()
.append("circle")
.attr("id", function(d) {
return d.id;
})
.attr("r", function(d, i) {
return d.center ? rScale(i) * 5 : rScale(i);
})
.style('fill', function(d) { return d.center ? "darkred" : "#003366"; })
fmcG
.each(function(d, i) {
d3.forceSimulation(d.nodes)
.force("collision", d3.forceCollide(function(d) {
return d.center ? rScale(i) * 5 : rScale(i);
}))
.force("center", d3.forceCenter(0, 0))
.force("link", d3
.forceLink(d.links)
.id(function(d) {
return d.id;
})
.distance(0)
.strength(2))
.on('tick', ticked);
});
function ticked() {
fmcG.selectAll("circle")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
<script src="https://d3js.org/d3.v5.js"></script>

How to plot markers using OSM + d3-tile?

I'm working on a map project where we render a map using OSM tiles and d3-tile project. I'm trying to put markers on it. However projection(long,lat) returns weird values which misplaces the markers for instance -0.4777943611111111, -0.3832333211677277 for New York:
newyork = [-74.2605518, 40.6971478];
svg.selectAll("circle")
.data([newyork]).enter()
.append("circle")
.attr("cx", function (d) { console.log(projection(d)); return -projection(d)[0]; })
.attr("cy", function (d) { return -projection(d)[1]; })
. attr("r", "20px")
.attr("fill", "red")
Full source code below
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
margin: 0;
}
</style>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-tile#0.0.4/build/d3-tile.js"></script>
<script>
var tau = 2 * Math.PI;
var width = 960;
height = 500;
// Initialize the projection to fit the world in a 1×1 square centered at the origin.
var projection = d3.geoMercator()
.scale(1 / tau)
.translate([0, 0]);
var path = d3.geoPath()
.projection(projection);
var tile = d3.tile()
.size([width, height]);
var zoom = d3.zoom()
.on("zoom", zoomed);
var svg = d3.select("svg")
.attr("width", width)
.attr("height", height);
var raster = svg.append("g");
// Center at US
var center = projection([-98.5, 39.5]);
console.log("Center " + center[0]);
// Apply a zoom transform equivalent to projection.{scale,translate,center}.
svg.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(1 << 12)
.translate(-center[0], -center[1]));
newyork = [-74.2605518, 40.6971478];
console.log(projection(newyork))
svg.selectAll("circle")
.data([newyork]).enter()
.append("circle")
.attr("cx", function (d) { console.log(projection(d)); return -projection(d)[0]; })
.attr("cy", function (d) { return -projection(d)[1]; })
. attr("r", "20px")
.attr("fill", "red")
function zoomed() {
var transform = d3.event.transform;
var tiles = tile
.scale(transform.k)
.translate([transform.x, transform.y])
();
var image = raster
.attr("transform", stringify(tiles.scale, tiles.translate))
.selectAll("image")
.data(tiles, function(d) {
return d;
});
image.exit().remove();
// enter:
var entered = image.enter().append("image");
// update:
image = entered.merge(image)
.attr('xlink:href', function(d) {
return 'http://' + 'abc' [d.y % 3] + '.tile.openstreetmap.org/' +
d.z + '/' + d.x + '/' + d.y + '.png';
})
.attr('x', function(d) {
return d.x * 256;
})
.attr('y', function(d) {
return d.y * 256;
})
.attr("width", 256)
.attr("height", 256);
}
function stringify(scale, translate) {
var k = scale / 256,
r = scale % 1 ? Number : Math.round;
return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
</script>
Any help is appreciated. Thanks!
For anyone looking for the answer found it here.: D3 cartography: lon/lat circles in wrong place on map (projection)
The trick is in the zoomed function transform the circle:
function zoomed() {
...
vector
.attr("transform", transform)
.attr("r", 5/transform.k);
...
}

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>

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

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>

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