d3实时图形同步问题

时间:2019-03-31 22:25:06

标签: javascript d3.js data-visualization real-time

我花了几天时间尝试创建一个实时时间轴,在该时间轴上异步添加数据。似乎不可能每次都不会破坏其他东西而使事情顺利进行。

我看了几个例子,但似乎都不符合我的情况。也就是说,大多数实时示例要么依靠数据每次使时间轴递增一步,要么都假设数据以固定间隔连续出现。

问题在于:

  1. 点滑动很好。但是,如果我切换选项卡然后再切换回去,它们将从它们离开的位置继续,因此与x轴上的当前刻度不匹配
  2. 时间轴上的刻度线不时出现一些奇怪的过渡,看起来像是在拖曳。改组后,实际点与时间轴不同步。

这里是fiddle

<!doctype html><html lang="en">
<head><script src="https://d3js.org/d3.v5.min.js"></script></head>
<body><script>

    const LENGTH = 10 // in seconds
    const TICKS = 10 // num of ticks in time axis
    const HEIGHT = 240
    const WIDTH = 950
    const MARGIN_LEFT = 40
    const MARGIN_TOP = 40
    var datapoints = []

    // Create root element + background rect
    svg = d3.select('body').append('svg')
      .attr('width', WIDTH)
      .attr('height', HEIGHT)
    svg.append('rect')
      .attr('width', '100%')
      .attr('height', '100%')
      .attr('fill', 'rgba(59, 58, 52, 0.8)')
    $graphs = svg.append('g')
    $slidables = $graphs.append('g')

    // We use two scalers for x. This solves the issue with the axis being out
    // of sync
    scaleX = d3.scaleTime().range([MARGIN_LEFT, WIDTH-MARGIN_LEFT])
    scaleY = d3.scaleLinear().range([MARGIN_TOP, HEIGHT-MARGIN_TOP])
    updateTimeScale()

    // -------------------------------------------------------------------------

    function logDate(date){
      console.log(date.toTimeString().split(' GMT')[0] + ' ' + date.getMilliseconds() + 'ms')
    }

    function logPoint(point){
      const date = point[0]
      console.log(
          date.toTimeString().split(' GMT')[0] + ' ' + date.getMilliseconds() + 'ms, ',
          point[1]
      )
    }

    function oneSecondAgo(){
      d = new Date()
      d.setSeconds(d.getSeconds() - 1)
      return d
    }

    function leftDate(){
      d = new Date()
      d.setSeconds(d.getSeconds() - LENGTH)
      return d
    }

    function tickDist(){
        return scaleX(new Date()) - scaleX(oneSecondAgo())
    }


    // -------------------------------- Init -----------------------------------

    /* Resets timescale to the current time */
    function updateTimeScale(){
      right = new Date()
      left = new Date()
      right.setSeconds(right.getSeconds())
      left.setSeconds(right.getSeconds()-LENGTH)
      scaleX.domain([left, right])
    }

    function init(){
      // Setup axis
      xaxis = d3.axisBottom(scaleX).ticks(TICKS)
      $xaxis = svg.append('g')
        .attr('transform', 'translate(0, ' + (HEIGHT - MARGIN_TOP) + ')')
        .call(xaxis)
      yaxis = d3.axisLeft(scaleY)
      $yaxis = svg.append('g')
        .attr('transform', 'translate(' + MARGIN_LEFT + ', 0)')
        .call(yaxis)

      // Garbage collect old points every second
      setInterval(function(){
          while (datapoints.length > 0 && scaleX(datapoints[0][0]) <= MARGIN_LEFT){
            datapoints.shift()
          }
          $slidables.selectAll('circle')
            .data(datapoints, d=>d)
            .exit()
            .remove()
      }, 1000)

      // Slide axis at interval
      function tick(){
        right = new Date()
        left = new Date()
        right.setSeconds(right.getSeconds()+1)
        left.setSeconds(right.getSeconds()-LENGTH)
        scaleX.domain([left, right])
        $xaxis.transition()
          .ease(d3.easeLinear)
          .duration(new Date() - oneSecondAgo())
          .call(xaxis)
      }
      tick()
      setInterval(tick, 1000)
    }

    // ------------------------------ Update -----------------------------------

    /* Update graph with points

    We always set right edge to current time
    */
    function update(points){
      datapoints = datapoints.concat(points)
      logPoint(points[0])
      updateTimeScale()

      // Add new points, transition until left edge and then remove
      $slidablesEnter = $slidables.selectAll('circle')
        .data(datapoints, d=>d)
        .enter()
      $slidablesEnter
        .append("circle")
        .style("fill", "rgb(74, 255, 0)")
        .attr("r", 2)
        .attr("cx", p=>scaleX(p[0]))  // put it at right
        .attr("cy", p=>scaleY(p[1]))
      .transition()
        .duration(function(p){
          remainingTime = p[0] - leftDate()
          return remainingTime
        })
        .ease(d3.easeLinear)
        .attr("cx", p => MARGIN_LEFT)
        .remove()
    }

    // Start everything with two random datapoints
    init()
    d1 = new Date()
    d2 = new Date()
    d2.setMilliseconds(d2.getMilliseconds()-1500)
    update([[d1, Math.random()]])
    update([[d2, Math.random()]])

</script></body></html>

1 个答案:

答案 0 :(得分:0)

只需一些更新。我的解决方法是在很小的间隔(即20毫秒)内重新粉刷所有内容。

这解决了所有同步问题,但不确定性能是否会有所不同。

类似这样的东西

function tick(){
        // Redraw all elements
        scaleX.domain([now(-TIMELINE_LENGTH_MS), now()])
        $slidables.selectAll('circle')
          .data(datapoints, d=>d)
          .attr("cx", p =>  scaleX(p[0]))
          .transition()
          .duration(0)
}
setInterval(tick, REFRESH_INTERVAL)

REFRESH_INTERVAL设置为20毫秒,看起来与进行过渡几乎一样。上面的所有内容看上去都显得断断续续,但至少比以前更准确。