admin管理员组文章数量:1403452
I use d3 to draw a stream graph very similar to the official example :
The only difference is how I updated it with new data. I don't only want a vertical (y-value) transition, but also want to add new data points on the right. The whole graph should bee pressed in the horizontal direction.
It was no problem to achieve the desired result, the only problem is that the transition between the two states does not look as expected.
You can find a a minimal example of the strange transition effect on JSfiddle: /
Press the update button to see the effect
test_data0 = [{"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.6, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.6}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.3}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}]
test_data1 = [{"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.6, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.6}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.3}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}]
$('#update').click(function(){
streamed_history(test_data1)
});
var width = 300,
height = 200,
colors = {'0': '#6ff500', '1': '#ffad0a', '-1': '#f90035'},
feedbacks = [-1, 0, 1],
stack = d3.layout.stack();
var svg = d3.select("#timeline").append("svg")
.attr("width", width)
.attr("height", height);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
streamed_history(test_data0)
function streamed_history(data) {
data_array = feedbacks.map(function (f) {
return data.map(function(element, i) { return {x: i, y: element[f]}; })
}),
layers = stack(data_array)
layers = feedbacks.map(function (f, i) {
return {layer: layers[i], feedback: f, color: colors[f]}
})
var x = d3.scale.linear()
.domain([0, data.length - 1])
.range([0, width]);
var area = d3.svg.area().interpolate("basis")
.x(function(d) { return x(d.x); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
//enter
svg.selectAll("path")
.data(layers)
.enter().append("path")
.attr("d", function (d) {return area(d.layer);})
.style("fill", function(d) { return d.color; });
//update
d3.selectAll("path")
.data(layers)
.transition()
.duration(2000)
.attr("d", function (d) {return area(d.layer);});
}
I use d3 to draw a stream graph very similar to the official example http://bl.ocks/mbostock/4060954:
The only difference is how I updated it with new data. I don't only want a vertical (y-value) transition, but also want to add new data points on the right. The whole graph should bee pressed in the horizontal direction.
It was no problem to achieve the desired result, the only problem is that the transition between the two states does not look as expected.
You can find a a minimal example of the strange transition effect on JSfiddle: http://jsfiddle/jaYJ9/4/
Press the update button to see the effect
test_data0 = [{"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.6, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.6}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.3}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}]
test_data1 = [{"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.6, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.6}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.3}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}]
$('#update').click(function(){
streamed_history(test_data1)
});
var width = 300,
height = 200,
colors = {'0': '#6ff500', '1': '#ffad0a', '-1': '#f90035'},
feedbacks = [-1, 0, 1],
stack = d3.layout.stack();
var svg = d3.select("#timeline").append("svg")
.attr("width", width)
.attr("height", height);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
streamed_history(test_data0)
function streamed_history(data) {
data_array = feedbacks.map(function (f) {
return data.map(function(element, i) { return {x: i, y: element[f]}; })
}),
layers = stack(data_array)
layers = feedbacks.map(function (f, i) {
return {layer: layers[i], feedback: f, color: colors[f]}
})
var x = d3.scale.linear()
.domain([0, data.length - 1])
.range([0, width]);
var area = d3.svg.area().interpolate("basis")
.x(function(d) { return x(d.x); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
//enter
svg.selectAll("path")
.data(layers)
.enter().append("path")
.attr("d", function (d) {return area(d.layer);})
.style("fill", function(d) { return d.color; });
//update
d3.selectAll("path")
.data(layers)
.transition()
.duration(2000)
.attr("d", function (d) {return area(d.layer);});
}
Share
Improve this question
edited Feb 3, 2014 at 9:43
VividD
10.5k8 gold badges66 silver badges112 bronze badges
asked Mar 8, 2013 at 14:46
dedandedan
3374 silver badges15 bronze badges
1
- The jsfiddle doesn't work... – Lars Kotthoff Commented Mar 8, 2013 at 14:59
1 Answer
Reset to default 9 +50This problem revolves around the fact that, for SVG animations, you can only push points onto the end of a path.
First, the fix (this will only work if the graphics are always vertically dense and have consistent ordering for which graph is highest):
...
var area = d3.svg.area().interpolate("basis")
...
.y0(function(d) { return y(null); }) // The null here is super important!
...
...
// Add this function
function fixPath (path) {
var Lidx = path.indexOf('L');
var Cidx = path.slice(Lidx).indexOf('C');
var PCidx = path.slice(0,Lidx).lastIndexOf('C');
var lp = path.substr(PCidx, Lidx-PCidx);
var ss = path.substr(Lidx, Cidx);
return (path.slice(0,Lidx) + lp + ss + path.slice(Lidx));
}
...
svg.selectAll("path")
.data(layers.reverse()) // Gotta reverse the order!
.attr("d", function (d) { return fixPath(area(d.layer)); }) // Have to double up the bottom right corner to avoid artifacts
...
...
d3.selectAll("path")
.data(layers)
.attr("d", function (d) { return fixPath(area(d.layer)); }) // When updating too!
...
Working example here: http://jsfiddle/f5JSR/2/
Now, the explanation...
Each of the color bands in your graph is a closed path, and d3.js constructs them so that none of these color bands overlaps with each other. The problem is, this means each of these paths starts at the bottom left corner, and loops around all the way back to itself. When you add a new point on these paths, you are adding it at the end, and it is pushing the rest of the path counter-clockwise around (creating that weird animation effect).
I initially tried solving this using SVG clipping and the fill-rule: evenodd
property, but it seems that to use clipping you have to create pound paths, and adding new points pushes them on the end of this pound path (you can't, for example, push a point onto the first path of the pound path), so the problem persists.
Instead, this solution does away with d3.js's cleverness and instead makes all of your color bands expand to the bottom of the graph (that's what the y(null);
line is doing). It then orders the paths so the highest ones are drawn first. This approach breaks down if one graph's height falls below another graph's height.
Finally, to when points are pushed around the bottom right corner, there can be some weird artifacting. To fix that, I double up the number of points at the bottom right corner using the fixPath
function.
Anyways, this solution works for the example case you had. I hope this helps.
本文标签: javascripttransition when adding new data to d3 streamgraphStack Overflow
版权声明:本文标题:javascript - transition when adding new data to d3 streamgraph - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744377001a2603309.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论