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
相关产品推荐
相关产品推荐

