Javascript 向d3 streamgraph添加新数据时的转换

Javascript 向d3 streamgraph添加新数据时的转换,javascript,svg,d3.js,transition,stream-graph,Javascript,Svg,D3.js,Transition,Stream Graph,我使用d3绘制了一个与官方示例非常相似的流图: 唯一的区别是我如何用新数据更新它。我不仅想要垂直(y值)转换,还想在右侧添加新的数据点。整个图形应在水平方向上压缩 实现预期结果没有问题,唯一的问题是两国之间的过渡看起来不像预期的那样 您可以在JSFIDLE上找到一个奇怪的转换效果的最小示例: 按更新按钮查看效果 test_data0 = [{"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.6, "-1": 0.0}, {"0": 0.0, "

我使用d3绘制了一个与官方示例非常相似的流图:

唯一的区别是我如何用新数据更新它。我不仅想要垂直(y值)转换,还想在右侧添加新的数据点。整个图形应在水平方向上压缩

实现预期结果没有问题,唯一的问题是两国之间的过渡看起来不像预期的那样

您可以在JSFIDLE上找到一个奇怪的转换效果的最小示例:

按更新按钮查看效果

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);});
}

这个问题的核心是,对于SVG动画,只能将点推到路径的末端

首先,修复(只有当图形始终垂直密集且具有一致的顺序,哪个图形最高时,这才有效):

此处的工作示例:

现在,解释

图形中的每个色带都是一条闭合路径,d3.js构造它们以使这些色带彼此不重叠。问题是,这意味着这些路径中的每一条都从左下角开始,并一直循环到自身。当你在这些路径上添加一个新的点时,你是在末端添加它,它逆时针推动路径的其余部分(创建奇怪的动画效果)

我最初尝试使用SVG剪裁和
填充规则:evenodd
属性来解决这个问题,但是使用剪裁似乎必须创建复合路径,并且添加新点会将它们推到复合路径的末尾(例如,不能将点推到复合路径的第一条路径上),因此问题仍然存在

相反,这个解决方案消除了d3.js的聪明,使所有的色带扩展到图形的底部(这就是
y(null);
行所做的)。然后对路径进行排序,以便首先绘制最高的路径。如果一个图形的高度低于另一个图形的高度,这种方法就会失效

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


无论如何,这个解决方案适用于您的示例案例。我希望这能有所帮助。

JSFIDLE不起作用……非常感谢,这真的解决了我的问题。一开始我不明白你说的最高路径是什么意思。但现在我明白了,你指的是堆叠图中最高的那个。因为这个命令并没有改变你完美地解决了我的问题。也谢谢你的解释
...
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!
    ...