日期和时间从行数据过渡

时间:2019-06-26 05:08:35

标签: javascript css d3.js charts transition

我使用D3 V5创建了此图表。另外,我将示例数据附加到了view by clicking here的小提琴上。

我已经包含了tick功能代码块,该代码块在向左滑动的路径上附加了x和y标度以及线/数据的新域:

执行tick函数时,将进行重新生成的行排序,使其看起来像在弹跳。
重建线条时,如何平滑无弹跳?

var tr = d3
  .transition()
  .duration(obj.tick.duration)
  .ease(d3.easeLinear);

function tick() {
  return setInterval(function() {
    var newData = [];
    var tickFunction = obj.tick.fnTickData;
    if (tickFunction !== undefined && typeof tickFunction === "function") {
      newData = tickFunction();
      for (var i = 0; i < newData.length; i++) {
        obj.data.push(newData[i]);
      }
    }

    if (newData.length > 0) {
      var newMaxDate, newMinDate, newDomainX;
      if (isKeyXDate) {
        newMaxDate = new Date(
          Math.max.apply(
            null,
            obj.data.map(function(e) {
              return new Date(e[obj.dataKeys.keyX]);
            })
          )
        );
        newMinDate = new Date(
          Math.min.apply(
            null,
            obj.data.map(function(e) {
              return new Date(e[obj.dataKeys.keyX]);
            })
          )
        );
        newDomainX = [newMinDate, newMaxDate];
      } else {
        newDomainX = [
          d3.min(obj.data, function(d) {
            return d[obj.dataKeys.keyX];
          }),
          d3.max(obj.data, function(d) {
            return d[obj.dataKeys.keyX];
          })
        ];
      }

      // update the domains
      //x.domain([newMinDate, newMaxDate]);
      if (obj.tick.updateXDomain) {
        newDomainX = obj.tick.updateXDomain;
      }
      x.domain(newDomainX);
      if (obj.tick.updateYDomain) {
        y.domain(obj.tick.updateYDomain);
      }

      path.attr("transform", null);

      // slide the line left
      if (obj.area.allowArea) {
        areaPath.attr("transform", null);
        areaPath
          .transition()
          .transition(tr)
          .attr("d", area);
      }
      path
        .transition()
        .transition(tr)
        .attr("d", line);
      svg
        .selectAll(".x")
        .transition()
        .transition(tr)
        .call(x.axis);
      svg
        .selectAll(".y")
        .transition()
        .transition(tr)
        .call(y.axis);

      // pop the old data point off the front
      obj.data.shift();
    }
  }, obj.tick.tickDelay);
}
this.interval = tick();

2 个答案:

答案 0 :(得分:3)

当您过渡d属性(只是一个字符串)时,反弹实际上是预期的结果。

这里有几种解决方案。在不过度重构代码的情况下,一个简单的方法就是使用Mike Bostock在此bl.ocks中编写的pathTween函数:https://bl.ocks.org/mbostock/3916621。在这里,我要对其进行一些更改,以便您可以像这样传递原点:

path.transition()
    .transition(tr)
    .attrTween("d", function(d) {
        var self = this;
        var thisd = line(d);
        return pathTween(thisd, 1, self)()
    })

这是分叉的矮人:https://plnkr.co/edit/aAqpdSb9JozwHsErpqa9?p=preview

答案 1 :(得分:2)

如Gerardo所述,除非您修改方法,否则转换路径的d属性将无法很好地工作。这是一个简单的示例,说明如果仅更新路径的d属性,就会出现的摆动/弹跳:

enter image description here

Pᴏɪɴᴛsᴛʀᴀɴsɪᴛɪᴏɴɪɴɢᴀᴄʀᴏsssᴄʀᴇᴇɴ,ᴡɪᴘᴀᴛʜᴛʀᴀɴsɪᴛɪᴏɴɪᴏɴғʀᴏᴍᴅᴀᴛᴀɴᴇɴᴇxᴛ。

迈克·波斯托克(Mike Bostock)在短片here中指出了上述行为,以下是复制上述动画的摘要:

var n = 10;
var data = d3.range(n).map(function(d) {
  return {x: d, y:Math.random() }
})

var x = d3.scaleLinear()
  .domain(d3.extent(data, function(d) { return d.x; }))
  .range([10,490])
  
var y = d3.scaleLinear()
  .range([290,10]);
  
var line = d3.line()
  .x(function(d) { return x(d.x); })
  .y(function(d) { return y(d.y); })
  
var svg = d3.select("body")
  .append("svg")
  .attr("width",500)
  .attr("height", 400)
  .append("g");
  
var path = svg.append("path")
  .datum(data)
  .attr("d", line);

var points = svg.selectAll("circle")
  .data(data, function(d) { return d.x; })
  .enter()
  .append("circle")
  .attr("cx", function(d) { return x(d.x); })
  .attr("cy", function(d) { return y(d.y); })
  .attr("r", 5);


function tick() {
 
  var transition = d3.transition()
    .duration(1000);
  
  var newPoint = {x:n++, y: Math.random() };
  data.shift()
  data.push(newPoint);
  
  x.domain(d3.extent(data,function(d) { return d.x; }))
  
  points = svg.selectAll("circle").data(data, function(d) { return d.x; })
    
   points.exit()
     .transition(transition)
     .attr("cx", function(d) { return x(d.x); })
     .attr("cy", function(d) { return y(d.y); }) 
     .remove(); 
     
  points.enter().append("circle")
     .attr("cx", function(d) { return x(d.x)+30; })
     .attr("cy", function(d) { return y(d.y); })
     .merge(points)
     .transition(transition)
     .attr("cx", function(d) { return x(d.x); })
     .attr("r", 5);

  path.datum(data)
    .transition(transition)
    .attr("d", line)
    .on("end", tick);
}

tick();

  

   
path {
  fill: none;
  stroke: black;
  stroke-width: 2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

对此摆动/弹跳的一种解决方案是:

  1. 向数据添加一个附加点,
  2. 使用最近添加到数据数组中的行重绘
  3. 找出下一个数据范围
  4. 将行左移
  5. 更新比例并转换轴
  6. 删除第一个数据点

在我链接到的Mike的文章中也对此提出了建议。这将是代码的基本实现:

我通过在最后一次转换结束时递归调用该函数来避免了setInterval函数

    function slide() {
     // Stop any ongoing transitions:
     d3.selectAll().interrupt();

      // A transition:
      var transition = d3.transition()
        .duration(2000)
        .ease(d3.easeLinear)

      // 1. add an additional point(s) to the data
      var newData = obj.tick.fnTickData();
      obj.data.push(...newData);

      // 2. redraw the line with the recently added to data array
      path.datum(obj.data)
      areaPath.datum(obj.data)

      // Redraw the graph, without the translate, with less data:
      path.attr("transform","translate(0,0)")
        .attr("d", line)

      areaPath.attr("transform","translate(0,0)")
        .attr("d", area)          

      // 3. find out the next extent of the data                     
      // Assuming data is in chronological order:
      var min = obj.data[newData.length][obj.dataKeys.keyX];
      var max = obj.data[obj.data.length-1][obj.dataKeys.keyX];

      // 4. transition the line to the left
      path.datum(obj.data)
        .transition(transition)
        .attr("transform", "translate("+(-x(new Date(min)))+",0)");
      areaPath.datum(obj.data)
        .transition(transition)
        .attr("transform", "translate("+(-x(new Date(min)))+",0)");

      // 5. update the scale and transition the axis
      x.domain([new Date(min),new Date(max)])

      // Update the xAxis:
      svg.selectAll('.x')
         .transition(transition)
         .call(x.axis)
         .on("end",slide); // Trigger a new transition at the end.

      // 6. remove the first data point(s)  
      obj.data.splice(0,newData.length)
    }
    slide();

这是更新的plunkr