本文介绍了当向d3流图添加新数据时的过渡的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用d3绘制流图,与官方示例非常相似



按更新按钮查看效果

  test_data0 = [{0:0.0,1:0.0,-1:0.0},{0:0.0,1:0.6, :0.0,{0:0.0,1:0.3,-1:0.0},{0:0.0,1:0.0,-1:0.6} :0.3,1:0.0,-1:0.0},{0:0.0,1:0.3,-1:0.3} ,-1:0.0},{0:0.3,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} 1:0.0,-1:0.0},{0:0.0,1:0.3,-1:0.3} :0.0},{0:0.3,1:0.0,-1:0.0},{0:0.0,1:0.0,-1: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 '},
feedback = [-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])$ ​​b $ b .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函数(f,i){
return {layer:layers [i],feedback:f,color:colors [f]}
})

var x = d3。 scale.linear()
.domain([0,data.length - 1])$ ​​b $ b .range([0,width]);

var area = d3.svg.area()。interpolate(basis)
.x(function(d){return x(dx);})
。 y0(function(d){return y(d.y0);})
.y1(function(d){return y(d.y0 + dy);});

//输入
svg.selectAll(path)
.data(layers)
.enter()。append(path)
.attr(d,function(d){return area(d.layer);})
.style(fill,function(d){return d.color;});

//更新
d3.selectAll(path)
.data(layers)
.transition()
.duration(2000)
.attr(d,function(d){return area(d.layer);});
}


解决方案

对于SVG动画,你只能将点推到路径的末端。



首先,修复(这只会在图形总是垂直密集的情况下工作,具有最高图的一致排序):

  ... 
var area = d3.svg.area ).interpolate(basis)
...
.y0(function(d){return y(null);})//这里的null是超重要的!
...
...
//添加此函数
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())//颠倒顺序!
.attr(d,function(d){return fixPath(area(d.layer));})//必须加倍右下角才能避开工件
...
...
d3.selectAll(path)
.data(layers)
.attr(d,function(d){return fixPath(area layer));})//更新时!
...

这里的工作示例:



现在,解释。 ..



图中的每个颜色带都是封闭路径,d3.js构造它们,使得这些颜色带中没有一个彼此重叠。问题是,这意味着这些路径的每一个开始在左下角,并循环一路返回到自己。当你在这些路径上添加一个新的点时,你在末尾添加它,它将逆时针推动剩余的路径(创造奇怪的动画效果)。



我最初尝试使用SVG裁剪和 fill-rule:evenodd 属性解决这个问题,但是似乎使用裁剪你必须创建复合路径,新点在这个复合路径的末尾推送它们(例如,不能将点推到复合路径的第一个路径上),因此问题仍然存在。



相反,这个解决方案没有d3.js的聪明,而是让所有的颜色带扩展到图的底部(这是 y(null); 线在做)。它然后排序路径,所以最先绘制的是最先的。如果一个图形的高度低于另一个图形的高度,这种方法会崩溃。



最后,当点被推到右下角时,可能会有一些奇怪的伪影。为了解决这个问题,我使用 fixPath 函数将右下角的点数加倍。



,这个解决方案适用于你有的例子。我希望这有助于。


I use d3 to draw a stream graph very similar to the official example http://bl.ocks.org/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 become compressed 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.net/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);});
}
解决方案

This 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.net/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 compound paths, and adding new points pushes them on the end of this compound path (you can't, for example, push a point onto the first path of the compound 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.

这篇关于当向d3流图添加新数据时的过渡的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-20 16:43