I have a responsive line chart made with d3, but have problem resizing the voronoi used for hover state. I suspect I don't refer to it the right way...
I added the voronoi here:
var voronoiGroup = svg.append("g")
.attr("class", "voronoi");
voronoiGroup.selectAll("line")
.data(voronoi(d3.nest()
.key(function(d) { return xScale(d.date) + "," + yScale(d.value); })
.rollup(function(v) { return v[0]; })
.entries(d3.merge(ranksFiltered.map(function(d) { return d.values;})))
.map(function(d) { return d.values; })))
.enter()
.append("path")
.attr("id", "cells")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; });
and in my resize function, I attempt to redraw it:
svg.select("#cells path")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; });;
If someone wants to take a stab at it, there's a plunk here:
http://plnkr.co/edit/Jj4QpF1bqK901WalNMmR
Thanks for you time!
The Voronoi Geom is calculating pixel positions in relation to the clipExtent. Since you are changing your width (the clipextent), you need to rerun the calculations. This is one of the few times with d3 that I'd recommend just remove the paths under your voronoi group and re-adding them:
voronoi.clipExtent([[0, -10], [width+10, height]]);
voronoiGroup.selectAll("path").remove();
voronoiGroup.selectAll("cells")
.data(voronoi(vd))
.enter()
.append("path")
.attr("class", "cells")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; })
.attr("stroke", "red")
.on("mouseover", mouseover)
.on("mouseout", mouseout);
Updated plunker.
Related
I'm trying to create a multi-series line chart (based off the Mike Bostock example) but transitioning between multiple data sets. I have gotten the lines to transition in and out, but the labels for each line stick around after they should have disappeared. Screenshot at this link.
Furthermore, the lines are transitioning in an odd way; almost like they are just extending the same line to create a new one (Second screenshot at this link).
Here is the relevant part of my code (where I draw the lines and add labels):
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.Date); })
.y(function(d) { return y(d.candidate); });
var person = svg_multi.selectAll(".candidate")
.data(people);
var personGroups = person.enter()
.append("g")
.attr("class", "candidate");
person
.enter().append("g")
.attr("class", "candidate");
personGroups.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
var personUpdate = d3.transition(person);
personUpdate.select("path")
.transition()
.duration(1000)
.attr("d", function(d) {
return line(d.values);
});
person
.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.Date) + "," + y(d.value.candidate) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
person.selectAll("text").transition()
.attr("transform", function(d) { return "translate(" + x(d.value.Date) + "," + y(d.value.candidate) + ")"; });
person.exit().remove();
You are appending a new text element to each person every time you render and not removing the old ones. Presumably the code you posted gets run every time you want to draw the chart with new data, so you end up with more text elements every time, rather than updating the existing ones. You need to only append on the "enter" selection. You did this right on the path elements, so you just need to make the text work more like the path. I've updated your example with comments to highlight what I changed.
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.Date); })
.y(function(d) { return y(d.candidate); });
var person = svg_multi.selectAll(".candidate")
// I added a key function here to make sure you always update the same
// line for every person. This ensures that when you re draw with different
// data, the line for Trump doesn't become the line for Sanders, for example.
.data(people, function(d) { return d.name});
var personGroups = person.enter()
.append("g")
.attr("class", "candidate");
// This isn't needed. The path and text get appended to the group above,
// so this one just sits empty and clutters the DOM
// person
// .enter().append("g")
// .attr("class", "candidate");
personGroups.append("path")
.attr("class", "line")
// You do this down below, so no need to duplicate it here
// .attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
// Append the text element to only new groups in the enter selection
personGroups.append("text")
// Set any static attributes here that don't update on data
.attr("x", 3)
.attr("dy", ".35em");
var personUpdate = d3.transition(person);
personUpdate.select("path")
.transition()
.duration(1000)
.attr("d", function(d) {
return line(d.values);
});
person.select("text")
// You don't have to do this datum call because the text element will have
// the same data as its parent, but it does make it easier to get to the last
// value in the list, so you can do it if you want
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.Date) + "," + y(d.value.candidate) + ")"; })
.text(function(d) { return d.name; });
// Remove this. You don't need it anymore since you are updating the text above
// person.selectAll("text").transition()
// .attr("transform", function(d) { return "translate(" + x(d.value.Date) + "," + y(d.value.candidate) + ")"; });
person.exit().remove();
The key to your question was really just doing personGroups.append('text') rather than person.append('text'). The rest was just me going overboard and pointing out some other ways to improve your code that should make it easier to understand and maintain.
I am trying to add interaction in the bubble chart and update data when clicking on the according button. But sth goes wrong when I click the button, the circles go out of the bound of the svg. I can't figure out how to fix it. Please help!
Here is the working Plunk.(Try 2006,2007 or 2008)
function changebubble(i) {
d3.csv("count_s.csv", function(csvData) {
pack.value(function(d){var valuedata=[d.count2006, d.count2007, d.count2008];
return valuedata[i];});
var data = { name: "city", children: csvData };
var node = svg.data([data]).selectAll("circle")
.data(pack.nodes);
var nodeEnter=node.enter().append("g")
.attr("class", "node").attr("cx",0).attr("cy",0)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
nodeEnter.append("title")
.text(function(d) { return d.city+ " : " +format(d.value); });
nodeEnter.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.city); });
nodeEnter.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.city });
node.select("circle")
.transition().duration(1000)
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.city); });
node.transition().attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.select("text")
.transition().duration(1000)
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.city });
node.select("title")
.transition().duration(1000)
.text(function(d) { return d.city+ " : " +format(d.value); });
node.exit().remove();
});
}
function updateBubble1() {changebubble(0);}
function updateBubble2() {changebubble(1);}
function updateBubble3() {changebubble(2);}
d3.select("#count2006").on("click",updateBubble1);
d3.select("#count2007").on("click",updateBubble2);
d3.select("#count2008").on("click",updateBubble3);
Thanks a lot!
There are some problems with your update function, to name a couple of big ones:
The elements you are selecting (var node = svg2.selectAll("circle")) do not match the elements you are 'entering' (var nodeEnter=node.enter().append("g")). This leads to problems when defining key functions and performing data joins
You seem to be trying to rebind the data when transitioning existing elements (node.select("circle").data(pack.nodes,function(d) {return d.city})) This will cause problems -- the data is already bound to these elements and re-binding is un-necessary at this point.
I've made updates to your code here: http://plnkr.co/edit/pYQTCOKWXoRM3ZE0HEt3?p=preview
I have network data for different days which I plot as a force-directed graph for each single day. When I press a button, the network is partially (leaving nodes are removed, new nodes are drawn) updated to the following day. Everything is working fine except for one thing.
For every new day I update some attributes of my nodes-array from my data (e.g. degree of the node). This also works fine, since I can see that the attributes have been updated correctly when I just look at my nodes-array after switching to the next day. However the command
`.append("circle").attr("r", function(d) { return 2*d.Degree+10; })`
is not conducted with the new attributes and the radius of the nodes do not represent by the degree of the node at the date the graph show.
How can I update my graph such that the new values for Degree are used to define the radius of the nodes?
Here is my function start(), which I call after manipulating my data to plot the graph:
function start() {
var force = d3.layout.force()
.charge(-130)
.linkDistance(230)
.size([width, height]);
force.nodes(nodes)
.links(edges)
linkOP = linkOP.data(edges, function(d) { return d.source.id + "-" + d.target.id; });
linkOP.enter().insert("line", ".node1")
.attr("class", "link1")
.style("stroke-width", function(d) {
return 2*d.weight.weight;
});
linkOP.exit()
.remove();
nodeOP = nodeOP.data(nodes, function(d) { return d.id;});
nodeOP.enter()
.append("g")
.append("circle")
.attr("r", function(d) { return 2*d.Degree+10; })
.attr("class", "node")
.style("fill", function(d) { return color(d.bipartite); });
nodeOP.append("title").text(function(d) { return d.name; });
nodeOP.call(force.drag);
nodeOP.append("text")
.text(function(d) { return d.id; });
nodeOP.moveToFront();
nodeOP.exit().remove();
force.start();
clean();
force.on("tick", function() {
linkOP.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; });
nodeOP.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
nodeOP.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
}
I know this is a very simple question... Thank you very much for your help!
Your code doesn't have an update to the radius. Everything called by .enter() only occur for new items. So because you only have the line
.append("circle").attr("r", function(d) { return 2*d.Degree+10; })`
Inside the .enter that portion only occurs on the new nodes.
I built a jsfiddle on enter, update, exit. Here: http://jsfiddle.net/TheMcMurder/H3HTe/
I hope that helps
I'm pretty new to coding in D3. I'm working on a near real-time circle pack chart that gets its underlying data from an ajax call and resizes the nodes based on changes in data values. The challenge I'm facing is likely to be dreadfully simple, but I've not yet found a similar-enough example online to leverage as a solution.
When I run this code, I know that the text values are actually being passed properly as the data changes. However, what's happening is that the code keeps appending text tags to the svg "g" nodes (with the updated values) rather than changing the existing element to reflect the updated value. The result is a layered text mess in the middle of an otherwise attractive bubble.
I have tried using d3.exit().remove() to no avail - it's possible that I misused it and that it's actually the appropriate technique to apply.
Would someone be willing to provide some guidance on how I should accomplish 2 specific things:
1) I'd like to re-use existing "text" elements rather than remove + append unless it's not practical.
2) I'd like to update the values of an existing "text" element with new data without refreshing the page.
The full code for the .js file is here below. I'm aware that I can use "svg" instead of "svg:svg", etc. but I haven't gotten to the tidying-up stage on this file yet.
var Devices = {
setup_devices : function() {
var r = 500,
format = d3.format(",d"),
fill = d3.scale.category10();
var bubble = d3.layout.pack()
.sort(null)
.size([r, r])
.padding(1.5);
var chart = d3.select("#device_info").append("svg:svg")
.attr("width", r)
.attr("height", r)
.attr("class", "bubble")
.append("svg:g")
.attr("transform", "translate(2, 2)");
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Device:</strong> <span style='color:red'>" + d.name + "</span>";
});
chart.call(tip);
setInterval(function() {
console.log("Devices Refreshing");
$.ajax({
type: "GET",
url: "/devices",
dataType: "json",
beforeSend: function() {
},
error: function( jqXHR, textStatus, thrownError ) {
return true;
},
success: function(data) {
update(data);
return true;
}
});
d3.timer.flush();
}, 2000);
function update(data) {
var updated = chart.data([data]).selectAll("g.node")
.data(bubble.nodes);
updated.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("data-name", function(d) {
return d.name;
})
.attr("data-device", function(d) {
return d.device_id;
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.append("svg:circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return fill(d.name); })
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(function(d) { return d.value + "%" });
updated.append("svg:text")
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(function(d) { return d.value + "%" });
updated.transition()
.duration(1000)
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
updated.select("circle").transition()
.duration(1000)
.attr("r", function(d) { return d.r; })
.text(function(d) { return d.value + "%" });
}
}
}
You just need to handle the enter and update selections separately -- to the enter selection you append, for the update selection you reuse existing elements.
var enterGs = updated.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("data-name", function(d) {
return d.name;
})
.attr("data-device", function(d) {
return d.device_id;
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
enterGs.append("circle");
enterGs.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em");
updated.select("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return fill(d.name); });
updated.select("text")
.text(function(d) { return d.value + "%" });
I have circles arranged with the pack layout, from a dataset which periodically updates the radii.
The code I started out with is this standard example for a bubble chart: http://bl.ocks.org/mbostock/4063269
Whenever the circle sizes change, they transition. Often when circles grow, they move to overlap other circles. I don't want them to overlap each other.
I'm still pretty new to d3, have moved the code around a lot and tried everything I can think of, but no luck.
The function makeBubbles is passed raw Json (see below).
function makeBubbles(root){
var diameter = $(window).width(),
diameterh = $(window).height(),
format = d3.format(",d"),
color = d3.scale.category20();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameterh])
.value(function(d){return d.value; })
.padding(1.5);
var svg = d3.select("svg")
.attr("width", diameter)
.attr("height", diameterh)
.attr("class", "bubble");
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root)).filter(function(d) { return !d.children; }), function(d){ console.log(d); return d.className; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.style("fill", function(d) { return color(d.packageName); })
.on("click", function(d) { window.location = d.url; })
.attr("r", 0)
.transition()
.duration(1000)
.attr("r", function(d) { return d.r; });
node.transition().duration(1000).attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.exit().transition().duration(200).attr("transform", "scale(0.001)").remove();
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 6); })
.attr("opacity",0)
.transition().duration(1000)
.attr("opacity",1);
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size, url: node.url});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameterh + "px");
}
Data passed looks something like this (varying as the dataset is updated):
{"name":"bubbles","children":[{"name":"tourism","children":[{"name":"tourism","children":[{"name":"practical","children":[{"name":"ACCOMM","size":13,"url":"#"},{"name":"HIRE","size":2,"url":"#"}]},{"name":"activity","children":[{"name":"EVENT","size":6,"url":"#"},{"name":"TOUR","size":3,"url":"#"}]},{"name":"leisure","children":[{"name":"RESTAURANT","size":168,"url":"#"},{"name":"ATTRACTION","size":8,"url":"#"}]}]}]}]}
I had a similar problem.
I slightly modified (mostly simplified) your code, and here you can find working example.
My approach is not to use transformations. Without them, the code looks more readable and maintainable. So, I propose a simple solution, I hope you can use it in your case.
Label. transition is maybe not the best, but you can change it.
On jsfiddle, its impossible to integrate json files, so the data is inside javascript. In your code, you would need to handle loading json, but the core idea from my example can be applied without change.
The key function is:
function updateVis() {
if (dataSource == 0)
pack.value(function(d) { return d.size; });
if (dataSource == 1)
pack.value(function(d) { return 100; });
if (dataSource == 2)
pack.value(function(d) { return 1 +
Math.floor(Math.random()*501); });
var data1 = pack.nodes(data);
titles.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text(function(d) {
return (d.children ? "" : d.name + ": " + format(d.value));
});
circles.transition()
.duration(5000)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; });
labels.transition()
.duration(5000)
.attr("opacity", 0)
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.each("end", function(d){
d3.select(this).text(function(d) {
return d.children ? "" : d.name.substring(0, d.r / 4);
});
d3.select(this).transition()
.duration(1000)
.attr("opacity", 1);
});
};