【问题标题】:Date and time transition from data with line带线数据的日期和时间转换
【发布时间】:2019-11-07 23:19:49
【问题描述】:

我使用 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();

【问题讨论】:

    标签: javascript css d3.js charts transition


    【解决方案1】:

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

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

    Mike Bostock 在一篇短文here 中指出了上述行为,这里是再现上述动画的 sn-p:

    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;
    }
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"&gt;&lt;/script&gt;

    这种摆动/反弹的一种解决方案是:

    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

    【讨论】:

    • 我从代码中观察到了很多,谢谢它给了我安静的知识和很好的解释,Gerardo 也是如此。虽然我想问一些我发现新的东西。 “data.push(...newData)”尤其是 3 个前置点,我正在阅读它。
    • 还有一个问题需要理解,line: 269, var min = obj.data[newData.length][obj.dataKeys.keyX]; 为什么你认为它是最小值而不是最大值?我在想最后收到的数据应该是最大值(我现在知道我错了,但你能告诉我这背后的逻辑吗?拜托。)
    • ...(javascript 扩展运算符)是这样的,如果有多个新点,则每个新点都被单独推送(newData = [a,b]data.push(...newData)data.push(a), data.push(b) 或@987654338 相同@. 此外,新的最小值应该从原来的位置上移 n 个点,其中 n 是新数据点的数量。如果 newData.length ==1 (n ==1),obj.data[newData.length] 是第二个数据项: 在我们删除第一个数据项(使用 data.splice)后,第二个数据项将是第一个数据项。
    • 非常感谢,我很抱歉在那里误解了它。您使用newData.Lengthobj.data 获取索引1,这是偏离路线1。
    【解决方案2】:

    那个bounce实际上是你转换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)()
        })
    

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

    【讨论】:

    • 您提供的 plunker 运行不佳,即路径向左过渡。就不能很流畅,完全没有反弹吗?
    • 你能告诉我哪些地方可以修改我的代码以使其更流畅吗?我会这样做,并感谢您分享“补间路径”,这对我来说是一个新知识,但在这种情况下它并没有帮助我。
    • “就不能平滑且完全没有反弹吗?”...在这种情况下,您有一个不同的问题。我刚刚回答了你的问题,做了同样的转换,但没有 bouce 效果。我建议你编辑你的问题,更好地解释你期望什么样的解决方案。
    • 感谢您的帮助、工作和时间兄弟,非常感谢您,如果我冒犯了您,我深表歉意。但我确实添加了“如何避免它弹跳”。但如果不清楚,我也编辑并添加了平滑词。
    • @ARr0w 你没有冒犯我。我告诉过您编辑您的问题,使其更清晰,以便其他用户能够准确了解您正在寻找什么样的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2021-11-23
    • 1970-01-01
    • 1970-01-01
    • 2020-08-18
    • 2018-03-05
    • 1970-01-01
    • 2019-08-12
    • 1970-01-01
    相关资源
    最近更新 更多