You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

D3多轨迹实时滚动绘图过渡卡顿及x轴速度异常问题问询

D3多轨迹实时滚动绘图卡顿&速度异常问题解决方案

问题根因

动画速度翻倍、卡顿的核心原因是过渡结束事件被重复触发:

  • 原代码给所有N_CH条轨迹单独绑定了transition().on("end", tick)事件,每有一条轨迹过渡完成,就会调用一次tick()函数
  • 当N_CH=4时,1秒的过渡周期内会触发4次tick(),相当于数据刷新、平移操作都执行了4次,所以滚动速度变成单轨迹的4倍,同时重复执行逻辑导致动画卡顿

解决方案

把平移操作从单条轨迹改为平移轨迹的父容器traces组,过渡和结束事件只绑定1次,避免重复触发。同时该方案还有额外性能优势:仅操作一个DOM元素的变换属性,轨迹数量越多,相比单条轨迹单独变换的性能优势越明显。

修正后完整代码

JavaScript部分

// 配置参数
const N_CH = 4;
const N_PTS = 40;
const margin = {top: 20, right: 30, bottom: 30, left: 40};
const width = 800;
const height = 300;
const colors = ['steelblue', 'red', 'orange', 'magenta']

// 初始化多通道数据
var data = [];
var channelData = [];
for (let ch = 0; ch < N_CH; ch++) {
  channelData = [];
  for (let i = 0; i < N_PTS; i++) {
    channelData.push({
      x: Date.now() + i * 1000,
      y: ch + Math.random()
    })
  }
  data.push({
    name: "CH" + ch,
    values: channelData
  });
}

// 初始化画布
var svg = d3
  .select("#chart")
  .append("svg")
  .attr("viewBox", `0 0 ${width} ${height}`)

// 添加裁剪路径实现平滑出入效果
svg.append("defs").append("clipPath")
  .attr("id", "clip")
  .append("rect")
  .attr("x", margin.left)
  .attr("y", margin.bottom)
  .attr("width", width - margin.left - margin.right)
  .attr("height", height - margin.top - margin.bottom);

// 数据索引到像素的映射比例尺
var iScale = d3.scaleLinear()
  .range([0, width - margin.right])
  .domain([0, data[0].values.length - 1]);

// 时间轴比例尺
var xScale = d3.scaleUtc()
  .range([margin.left, width - margin.right])

// 渲染x轴
var xAxis = svg.append("g")
  .attr("class", "x-axis")
  .attr("transform", `translate(0, ${height - margin.top})`)
  .call(d3.axisBottom(xScale));

// y轴比例尺
var yScale = d3.scaleLinear()
  .range([height - margin.top, margin.bottom]);

// 渲染y轴
var yAxis = svg.append("g")
  .attr("class", "y-axis")
  .attr("transform", `translate(${margin.left}, 0)`)
  .call(d3.axisLeft(yScale));

// 初始化轴域
xScale.domain(d3.extent(data[0].values, d => d.x));
// 计算全局y轴范围
var flatten = [].concat.apply([], data.map(o => o.values))
yScale.domain(d3.extent(flatten, d => d.y));

// 线生成器
var line = d3.line()
  .x((d, i) => iScale(i))
  .y(d => yScale(d.y));

// 创建轨迹容器组
const traces = svg.append("g")
  .attr("clip-path", "url(#clip)")

// 初始化多轨迹路径
for (let ch=0; ch<N_CH; ch++) {
  traces.append("path")
    .datum(data[ch].values)
    .attr("id", `trace-${ch}`)
    .attr("class", "trace")
    .attr("d", line)
    .attr("stroke", colors[ch])
    .attr("fill", "none")
    .attr("stroke-width", 1.5)
}

// 启动动画
tick();

function tick() {
  // 给所有通道追加新数据
  let lastData;
  for (let ch = 0; ch < N_CH; ch++) {
    lastData = data[ch].values[data[ch].values.length - 1];
    data[ch].values.push({
      x: lastData.x + 1000,
      y: ch + Math.random()
    });
  }

  // 更新所有轨迹的路径数据
  for (let ch = 0; ch < N_CH; ch++) {
    traces.select(`#trace-${ch}`)
      .attr("d", line)
  }

  // 仅对轨迹容器做平移,结束事件仅绑定一次
  traces
    .attr("transform", "translate(0)")
    .transition().duration(1000).ease(d3.easeLinear)
    .attr("transform", `translate(${iScale(-1)}, 0)`)
    .on("end", tick)
    
  // 更新x轴域
  xScale.domain(d3.extent(data[0].values, d => d.x));
  
  // 平滑过渡x轴
  xAxis
    .transition().duration(1000).ease(d3.easeLinear)
    .call(d3.axisBottom(xScale));

  // 移除过期数据
  for (let ch=0; ch<N_CH; ch++) {
    data[ch].values.shift();
  }
}

HTML部分

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chart"></div>

内容的提问来源于stack exchange,提问作者tdpu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.10 02:36:02