如何在没有奇怪的动画故障的情况下将数据准备到D3堆叠区域图

How can I prepend data to a D3 stacked area graph without weird animation glitches?

本文关键字:数据 D3 区域 情况下 故障 动画      更新时间:2023-09-26

我有一个堆叠区域图,当数据发生变化时,它会设置动画。

该数据显示了给定日期范围(例如,12月10-15日)的值。当我添加并扩展我的日期范围以包括更早的日期(例如,12月9日至15日)时,数据数组会在前面预先准备额外的数据。

不过,当路径设置动画时,效果会非常奇怪且不受欢迎。

请参阅显示效果的jsfiddle(单击"更新"按钮):http://jsfiddle.net/qprn9ta9/

这似乎是因为我在SVG区域的前面添加了新的点,并且插值是基于索引来设置它们的动画。

我该如何避免这种情况?D3中有什么可以帮助的东西吗?我找了一些类似于data()的键函数,但没有找到类似的函数。还有其他想法吗?

如果您需要向其中一个数据集0添加点,但数据集1没有更改,请添加一个"间隔值";,重复数据集1的最后一个值。

数组长度需要匹配,否则动画会出现故障,因为SVG的标记结构需要更改以适应数据,所以随着时间的推移,您不会只更改位置。

之前(数据集的长度不同):

test_data0 = [{"0": 0.1, "1": 0.1}, {"0": 0.2, "1": 0.6}, {"0": 0.3, "1": 0.4}, {"0": 0.1, "1": 0.6}, {"0": 0.3, "1": 0.1}, {"0": 0.0, "1": 0.3}, {"0": 0.3, "1": 0.1}, {"0": 0.3, "1": 0.2}, {"0": 0.2, "1": 0.3}]
test_data1 = [{"0": 0.2, "1": 0.2}, {"0": 0.0, "1": 0.0}, {"0": 0.2, "1": 0.6}, {"0": 0.3, "1": 0.4}, {"0": 0.1, "1": 0.6}, {"0": 0.3, "1": 0.1}, {"0": 0.0, "1": 0.3}, {"0": 0.3, "1": 0.1}, {"0": 0.3, "1": 0.2}, {"0": 0.2, "1": 0.3}]

之后(两个数据集的长度相同):

test_data0 = [{"0": 0.1, "1": 0.1}, {"0": 0.2, "1": 0.6}, {"0": 0.3, "1": 0.4}, {"0": 0.1, "1": 0.6}, {"0": 0.3, "1": 0.1}, {"0": 0.0, "1": 0.3}, {"0": 0.3, "1": 0.1}, {"0": 0.3, "1": 0.2}, {"0": 0.2, "1": 0.3}, {"0": 0.2, "1": 0.3}]
test_data1 = [{"0": 0.2, "1": 0.2}, {"0": 0.0, "1": 0.0}, {"0": 0.2, "1": 0.6}, {"0": 0.3, "1": 0.4}, {"0": 0.1, "1": 0.6}, {"0": 0.3, "1": 0.1}, {"0": 0.0, "1": 0.3}, {"0": 0.3, "1": 0.1}, {"0": 0.3, "1": 0.2}, {"0": 0.2, "1": 0.3}]

因此,该解决方案迫使您提前更加小心地处理数据集。

jsfiddle:

http://jsfiddle.net/2o372wu3/

所以,这个问题整晚都让我抓狂。从一条路径到另一条路径时,在数据前面添加新点会抛出d3interpolateString方法,这是正确的。我能想到的最好的(黑客)修复方法是在转换开始前在路径中插入一个点:

// enter
var mp = svg.selectAll("path")
    .data(layers);
mp
    .enter().append("path")
    .style("fill", function(d) { return d.color; });
//update
mp
  .transition()
  .duration(2000)
  .tween("path", function(d) {
    var self = d3.select(this),
        cP = self.attr("d"),
        i = null;
    if (!cP){
      i = d3.interpolateString("", area(d.layer));
    } else {
        var idx = cP.indexOf("L"),
          fP = cP.substring(0, idx),
          lP = cP.substring(idx);          
                cP = fP + "L" + fP.slice(1) + lP;          
        i = d3.interpolateString(cP, area(d.layer));
    }
    return function(t) {
            self.attr("d", i(t));
    };
  });

完整代码:

test_data0 = [{"0": 0.1, "1": 0.1}, {"0": 0.2, "1": 0.6}, {"0": 0.3, "1": 0.4}, {"0": 0.1, "1": 0.6}, {"0": 0.3, "1": 0.1}, {"0": 0.0, "1": 0.3}, {"0": 0.3, "1": 0.1}, {"0": 0.3, "1": 0.2}, {"0": 0.2, "1": 0.3}]
test_data1 = [{"0": 0.2, "1": 0.2}, {"0": 0.1, "1": 0.1}, {"0": 0.2, "1": 0.6}, {"0": 0.3, "1": 0.4}, {"0": 0.1, "1": 0.6}, {"0": 0.3, "1": 0.1}, {"0": 0.0, "1": 0.3}, {"0": 0.3, "1": 0.1}, {"0": 0.3, "1": 0.2}, {"0": 0.2, "1": 0.3}]
$('#update').click(function(){
    console.log('test')
    streamed_history(test_data1)
});
var width = 300,
    height = 200,
    colors = {'0': '#6ff500', '1': '#ffad0a'},
    feedbacks = [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()
        .x(function(d) { return x(d.x); })
        .y0(function(d) { return y(d.y0); })
        .y1(function(d) { return y(d.y0 + d.y); });
        
    //enter
    var mp = svg.selectAll("path")
        .data(layers);
        
    mp
        .enter().append("path")
        .style("fill", function(d) { return d.color; });
    //update
    mp
      .transition()
      .duration(2000)
      .tween("path", function(d) {
        var self = d3.select(this),
            cP = self.attr("d"),
            i = null;
        if (!cP){
          i = d3.interpolateString("", area(d.layer));
        } else {
        	var idx = cP.indexOf("L"),
              fP = cP.substring(0, idx),
              lP = cP.substring(idx);          
					cP = fP + "L" + fP.slice(1) + lP;          
        	i = d3.interpolateString(cP, area(d.layer));
        }
        return function(t) {
        		self.attr("d", i(t));
        };
      });
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="timeline"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<button id='update'>Update</button>

因此,正如经常发生的那样,Mike Bostock已经在这里解决了这个问题:

https://bost.ocks.org/mike/path/

他只说明了追加到末尾的情况,但预处理的情况可以通过类似于以下的一些调整来完成:

// push a new data point onto the front
data.unshift(random());
// redraw the line shifted to the left by one data point, and then slide it to the right
path
    .attr("d", line)
    .attr("transform", 'translate(" + x(-1) + ")')
  .transition()
    .attr("transform", null);
// pop the old data point off the back
data.pop();