如何在d3中使用定时器转换?

时间:2017-01-23 06:38:46

标签: d3.js

我正在尝试动画(转换)一个项目进入实时图表。

如您所见,单击“添加圆圈”按钮时,圆圈会停留在图表的底部,而不是向上设置动画。

有趣的是,在转换中添加.ease(d3.easeLinear)可以解决问题。

为什么?

在d3中使用带定时器的转换在概念上是错误的吗?

const width = 300;
const height = 100;
const margin = { top: 0, right: 30, bottom: 30, left: 50 };

const main = d3.select('.chart')
  .append('svg')
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
  .append('g')
    .attr('transform', `translate(${margin.left}, ${margin.top})`);

const now = Date.now();
const xScale = d3.scaleTime().domain([now - 10000, now]).range([0, width]);
const xAxis = d3.axisBottom(xScale).ticks(5);

const xAxisG = main.append('g')
  .attr('transform', `translate(0, ${height})`)
  .call(xAxis);

let data = [];

function drawCircles() {
  const t = d3.transition().duration(1000); //.ease(d3.easeLinear);
  const update = main.selectAll('circle').data(data);
  
  update.enter()
      .append('circle')
      .attr('r', d => d.value)
    .merge(update)
      .attr('cx', d => xScale(d.timestamp))
      .attr('cy', height)
      .transition(t)
      .attr('cy', height / 2);
}

d3.timer(() => {
  const now = Date.now();

  xScale.domain([now - 10000, now]);
  xAxisG.call(xAxis);
  
  drawCircles();
});

d3.select('.add-circle').on('click', () => {
  data.push({
    timestamp: Date.now() - 3000,
    value: 10
  });
});
svg {
  margin: 30px;
}
.add-circle {
  margin-left: 200px;
}
<script src="https://unpkg.com/d3@4.4.1/build/d3.js"></script>
<button class="add-circle">Add circle</button>
<div class="chart"></div>

1 个答案:

答案 0 :(得分:1)

这里的问题不是缺少easing。问题很可能是d3.timerselection.transition的混合。

让我们一步一步看。你这样说:

const t = d3.transition().duration(1000);

不能工作,而这个:

const t = d3.transition().duration(1000).ease(d3.easeLinear);

作品。嗯,事实并非如此:它不起作用。使用easeLinear的人很容易发现这不是一个线性过渡!这里有点搞笑......

现在,让我们改变宽松。默认缓动(即,如果您未设置任何),则为easeCubic。因此,如果我们这样做:

const t = d3.transition().duration(1000).ease(d3.easeCubic);

它与第一个代码具有相同的效果,没有ease。因此,我们设置ease并不会导致过渡发生。

我使用您的代码测试了所有缓动,其中一些&#34;工作&#34;,如easeLineareaseCircle。这里的工作是在引号之间,因为转换是正常的,即预期的转换。其中一些,如easeCubic,根本没有效果。

那是因为你不能将d3.timer与过渡混合在一起。我没有机会看一下源代码,但很可能他们使用相同的方法(比如requestAnimationFrame())并且是冲突的。

解决方案:将转换移至d3.timer调用的函数外部。

实施该提议的解决方案有不同的方法(不同的编码器将创建不同的替代方案),但这是我的。首先,我们定义一个转换函数:

function transitioning(elem) {
    const t = d3.transition().duration(1000).ease(d3.easeCubic);
    d3.select(elem)
        .transition(t)
        .attr('cy', height / 2);
}

然后,在您的函数drawCircles中,我们为每个圈子调用transitioning。但是,我们必须设置一个标记,因此我们不会再次将transitioning函数调用到相同的元素:

function drawCircles() {

    const update = main.selectAll('circle').data(data);

    update.enter()
        .append('circle')
        .attr('r', d => d.value)
        .attr('cy', height)
        .attr("flag", 0)
        .merge(update)
        .attr('cx', d => xScale(d.timestamp))
        .each(function() {
            if (!(+d3.select(this).attr("flag"))) {
                transitioning(this)
            }
            d3.select(this).attr("flag", 1);
        });
}

这是您更新的CodePen:http://codepen.io/anon/pen/ZLyXma?editors=0010

在上面的CodePen中,我使用easeCubic。但是你可以在其他笔中看到,它可以在不设置任何宽松的情况下工作:http://codepen.io/anon/pen/apwLxR?editors=0010