I am working on a sunburst viz based off of Mike Bostock's Zoomable Sunburst example.
I want to be able to change the underlying data using a whole new JSON (which has the same structure but different 'size' values), and have the sunburst animate a transition to reflect the updated data.
If I change the data of the path elements using .data(), and then attempt to update in the following fashion:
path.data(partition.nodes(transformed_json))
.transition()
.duration(750)
.attrTween("d", arcTween(transformed_json));
(..which is pretty much the exact same code as the click fn)
function click(d) {
path.transition()
.duration(750)
.attrTween("d", arcTween(d));
}
..I find that the sunburst does correctly change to reflect the new data, but it snaps into place rather than smoothly transitioning, like it does when you zoom in.
http://jsfiddle.net/jTV2y/ <-- Here is a jsfiddle with the issue isolated (the transition happens one second after you click 'Run')
I'm guessing that I need to create a different arcTween() fn, but my d3 understanding is not there yet. Many thanks!
Your example is quite similar to the sunburst partition example, which also updates data with a transition. The difference is that in this example it's the same underlying data with different value accessors. This means that you can't save the previous value in the data (as that will be different), but need to put it somewhere else (e.g. the DOM element).
The updated tween function looks like this:
function arcTweenUpdate(a) {
var i = d3.interpolate({x: this.x0, dx: this.dx0}, a);
return function(t) {
var b = i(t);
this.x0 = b.x;
this.dx0 = b.dx;
return arc(b);
};
}
This requires, as in the original example, to save the original x and dx values:
.enter().append("path")
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
Complete example here. This one has a kind of weird transition which is cause by the different order of the data in the layout. You can disable that by calling .sort(null), see here.
Related
I am currently using the d3js Bilevel partition because our data is too large to show all at once. I first had the sunburst partition with the last layers hidden with css, but then the chart wouldn't have the same size every time, which annoyed me.
A user can adjust values with a range slider, this should update the graph in real-time. This worked using this in the sunburst partition
path.data(partition.nodes)
.transition()
.duration(1000)
.attrTween("d", arcTweenData);
Is it also possible to do something similar in the bilevel partition?
So basicly this sunburst partition but with the bilevel partition or only 2 rows showing each time(like the bilevel partition).
FOUND THE SOLUTION
I've finally found the solution, the bilevel partition uses the sum by default so doesn't update with the changes made to the value. Specifying the value again before updating did it for me.
path = path.data(partition.value(function (d) { return d.value}).nodes(current).slice(1));
path.transition().duration(750)
.attrTween("d", function (d) { return arcTween.call(this, updateArc(d)); });
Bilevel is different from sunburst in the sense that although it reads and stores the data/nodes into a partition, it only displays two layers by using the "children" element. Sunburst displays them all (default children).
In your case, what you really want is to update/refresh the path data using transitions. This can be easily be done by calling the transition on the path whenever you want to update:
path.transition()
.style("fill-opacity", 1)
.attrTween("d", function(d) { return arcTween.call(this, updateArc(d)); });
You could make it update on an interval or place it within a function that is called whenever is button is clicked. If you're not updating the d values, but updating an external data variable instead; you could simply pass that new data value using a global variable.
function updateVisual(d,newData){
path.transition()
.style("fill-opacity", 1)
.attrTween("d", function(d) { return arcTween.call(this, updateArc(newData));});
} // If d is updated, remove newData and use updateArc(d)
Note: This is assuming you're using Bilevel Partition
I'm following the General Update Pattern but having an issue with regards to layering.
Using a circle-pack layout, I pack the new data, update, enter and exit the circle elements. However, when new elements enter, they overlap the updated circles.
Data key function is based on element name:
.data(nodes, function(d, i) { return d.name; });
So my circle pack has a spot for the updated circle (of the correct location and size) but it's hidden behind its newly entered parent circle.
Is there a way to send these updated nodes to the front or redraw them over the entered circles?
--UPDATE--
As suggested by the person who closed this issue, I've tried implementing the linked to solution using moveToFront.
I added the following code in my update section (which didn't change anything) and then tried adding it after the enter and exit code, which also didn't make any difference.
.each("end", function(d){ d3.select(this).moveToFront(); });
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
For clarity, this is what the selection and update looks like:
// Load data into svg, join new data with old elements, if any.
var nodes = pack.nodes(postData);
node = root = postData;
groupNodes = svg.selectAll("g")
.data(nodes, function(d, i) { return d.name; });
// Update and transition existing elements
groupNodes.select("circle")
.transition()
.duration(duration)
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
.attr('r', function(d) { return d.r; })
.each("end", function(d){ d3.select(this).moveToFront(); });
This moveToFront code does not make a difference to my output, and the updated circles remain behind the entered selection circles.
To summarize: the issue seems to be caused by a hierarchy layout (circle-packing) which expects the circles to be drawn in the order of the data's hierarchy. The d3 update pattern (using enter, update and exit selections) causes selected update elements to remain in the svg when the hierarchy is re-drawn, and the new layers are drawn over it. The parents of those nodes are already correctly set, so parentNode.appendChild doesn't do anything in this case, because it's not the cause of the issue.
Here is a fiddle to demonstrate my issue. I've tried putting the moveToFront code in various places, with no visible difference.
When you hit the "Change Data" button, it'll redraw the circles, but any circles whose names overlap between the two data sets are not nested properly in the circle-pack. Children of "Group A" are hidden behind one of the parent circles. You can verify the nodes are there via Inspect Element.
Another pic from the updated fiddle:
D3 provides a way to reorder elements based on the data bound to them with the .sort() function. In your case, the condition to check is the .depth attribute of the elements -- "deeper" elements should appear in front:
svg.selectAll("g")
.sort(function (a, b) {
if (a.depth < b.depth) return -1;
else return 1;
});
Complete demo here.
Using the D3 Sankey plugin, I'm updating a Sankey diagram with new values (on changing the data, passing new values for the nodes and links -- keeping all of them consistent). Is there functionality like d3.treemap's sticky to maintain node and link orders on the page? If not, is there an approach to building this?
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
.sticky(true)
I'm following the pattern here: http://bost.ocks.org/mike/sankey/
No, there isn't. If you want to dive into the layout, here's where you want to look:
function computeNodeDepths(iterations) {
var nodesByBreadth = d3.nest()
.key(function(d) { return d.x; })
.sortKeys(d3.ascending)
.entries(nodes)
.map(function(d) { return d.values; });
initializeNodeDepth();
...
Notice that sortKeys points at d3.ascending. You'd want this to point to some kind of hard-wired value, which you'd need to compute either in the first iteration or in your data preparation. It will still get adjusted when the collision detection function is run, so you might see your nodes pushed out of position but this will give you the best chance to maintain some control.
I'm a complete noop to D3 and partly SVG, so I got a few basic questions.
First off, my code in question can be viewed at http://dotnetcarpenter.github.io/d3-test/ and I've used Simple Pie Chart example with D3.js and Pie Chart Update, II as examples to get a running start.
As you can see, the animation gets skewed in the end when the low path values switch to the higher values. This is obviously not what I want. I think I'm getting the order of calculations wrong but I'm not sure what to do. I'm using the code from the last example:
function change() {
//...
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
// where arcTween is
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
Another issue is placing labels on the sectors. I've put the update stuff in the change function and is able to read out and only render them if the value is between 0 and 100. I can't however place them in any way. Looking at the first example, I figure that I could do something like this:
text.data(data)
.text(setText)
.attr("transform", function (d) {
// we have to make sure to set these before calling arc.centroid
d.innerRadius = 0;
d.outerRadius = radius;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
Where text is a d3 selection and arc is: d3.svg.arc().outerRadius(radius)
But I get "Unexpected value translate(NaN,NaN) parsing transform attribute." warning in Firefox and the labels are written on top of each other.
I appreciate any help and hints. Thanks!
I finally figured it out.
Maintain sector order throughout an animation.
You'd be forgiven for thinking that object contancy had something do with it. I did. But it turns out to be much simpler than that.
Every pie chart is by default sorted by value. If you don't want to sort by value but e.g. by data list order, you just have to disable sorting.
var pie = d3.layout.pie() // get a pie object structure
.value(function(d) { // define how to get your data value
return d.value; // (based on your data set)
})
.sort(null); // disable sort-by-value
Positioning labels according to your chart
Basically, you need to calculate your label positions depending on the type of chart or graph, your trying to connect them to. In my case, it's a pie chart. So if I want d3 to help with the calculations, I need to tell centroid the inner and outer radius and, most importantly to my issue, the start and end angles. The latter was missing from my code. Getting these values is as simple as, calling our pie layout above with our dataset and then do a transform.
Note that you don't have to call .data() again if you created the SVG with d3 and already supplied your data wrapped in .pie() structure. That is, that you didn't select any existing SVG from your page.
var svg = d3.select("svg")
// do stuff with your svg
var pie = d3.layout.pie()
// set stuff on your layout
var text = svg.selectAll("text")
.data(pie(dataset)) // where dataset contains your data
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
});
I have to give credit to Philip Pedruco for helping me along the way.
Bonus info
Use viewBox if you want to position your SVG cross browser, not transform/translate.
// move pie to center
d3.select("svg").attr("viewBox", -radius + ","+ -radius +"," + size + "," + size)
I'm learning D3.js and trying to get my head around data keys used with streamgraphs. I would like to adapt the official streamgraph example:
...so that each path has an explicit data key, and so that the mouseover logs the data key.
The official example adds paths as follows:
var area = d3.svg.area()
.x(function(d) { console.log('x', d.data); return d.x * w / mx; })
.y0(function(d) { return h - d.y0 * h / my; })
.y1(function(d) { return h - (d.y + d.y0) * h / my; });
vis.selectAll("path")
.data(data0)
.enter().append("path")
.style("fill", function() { return color(Math.random()); })
.attr("d", area);
I tried adapting the code as follows, but I'm not sure how to change the structure of data0 (currently an array of arrays) to achieve what I want:
vis.selectAll("path")
.data(data0, function(d) { return d.name }) // Add key function
.enter().append("path")
.style("fill", function() { return color(Math.random()); })
.attr("d", area)
.on("mouseover", function (d,i) {
console.log("mouseover", d.name); // Log name property on mouseover
});
As it stands, without my having made any changes to the structure of data0, it unsurprisingly does not work. How can I add a name property to data0 without also messing up the area and .data() functions?
UPDATE: To be a bit clearer: the D3 docs say that the area function is expecting a two-dimensional array. So if I change data0 from a two-dimensional array, to an array of objects, each with a name key and a data key, how can I also change what I pass to area?
The data in the example doesn't have a "name" property, so you would need to add that to the data to use it. The data keys you refer to are used when merging/updating data, i.e. you have drawn some paths already and then update (some of them). The .data() function will try to figure out what data is updated and what data is new. If that doesn't work for you, you can use the data key to help it, i.e. in your case tell it that things with the same name are the same data.
If what you mean by data keys are "data legends", then you might want to take a look at the following examples where I've completely separated the placement of magnitudes, legend bullets and legend text in different areas of the charts.
Multiple D3 Pie Charts Mixed In With HTML Layout Constructs
Multiple D3 Horizontal Bar Charts Mixed In With HTML Layout Constructs
In each of the examples, you'll clearly see how the data is labeled, structured, passed in, and used.
I also tied them together through mouseover and mouseout events so that mousing over or out of any element causes all elements in a set to change color simultaneously.
I hope this helps.
Frank