求助:使用JavaScript/CSS实现基于时序变化数据的图表动画
实现每日更新的排名条形图动画(JavaScript)
核心思路
基于D3.js实现动态排名条形图,支持每日数据更新时的平滑过渡动画,核心步骤包括:结构化数据解析、画布初始化、比例尺动态适配、条形与标签的过渡动画、自动更新逻辑。
数据解析函数
先将你的原始数据转换为便于处理的结构化格式:
// 原始数据示例 const rawData = `2023-01-01 user1,10 user2,12 2023-01-02 user1,11 user2,12 user3,1 2023-01-03 user1,11 user2,14 user3,12 user4,5`; // 解析数据为按日期分组的结构化数组 function parseRawData(raw) { return raw.split('\n').map(line => { const parts = line.trim().split(' '); const date = parts[0]; const users = parts.slice(1).map(item => { const [name, value] = item.split(','); return { name, value: parseInt(value) }; }); // 按数值降序排序,确定当日排名 users.sort((a, b) => b.value - a.value); return { date, users }; }); } const dailyData = parseRawData(rawData);
完整动画实现代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>每日排名动画图表</title> <script src="https://d3js.org/d3.v7.min.js"></script> <style> .bar { fill: #4285F4; } .bar-label { fill: #333; font-size: 12px; } .value-label { fill: #fff; font-size: 12px; font-weight: bold; } .date-label { font-size: 18px; font-weight: bold; text-anchor: middle; } </style> </head> <body> <div id="chart-container"></div> <script> // 1. 数据解析 const rawData = `2023-01-01 user1,10 user2,12 2023-01-02 user1,11 user2,12 user3,1 2023-01-03 user1,11 user2,14 user3,12 user4,5`; function parseRawData(raw) { return raw.split('\n').map(line => { const parts = line.trim().split(' '); const date = parts[0]; const users = parts.slice(1).map(item => { const [name, value] = item.split(','); return { name, value: parseInt(value) }; }); users.sort((a, b) => b.value - a.value); return { date, users }; }); } const dailyData = parseRawData(rawData); // 2. 图表基础配置 const margin = { top: 60, right: 100, bottom: 40, left: 80 }; const width = 800 - margin.left - margin.right; const height = 400 - margin.top - margin.bottom; // 3. 初始化SVG画布 const svg = d3.select("#chart-container") .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})`); // 4. 初始化比例尺 let xScale = d3.scaleLinear() .range([0, width]); let yScale = d3.scaleBand() .range([0, height]) .padding(0.2); // 5. 添加顶部日期标签 const dateLabel = svg.append("text") .attr("class", "date-label") .attr("x", width / 2) .attr("y", -20); // 6. 渲染单日数据的核心函数 function renderDay(dayData) { // 更新比例尺定义域 const maxValue = d3.max(dayData.users, d => d.value); xScale.domain([0, maxValue]); yScale.domain(dayData.users.map(d => d.name)); // 更新条形元素 const bars = svg.selectAll(".bar") .data(dayData.users, d => d.name); // 新增条形的入场动画 bars.enter() .append("rect") .attr("class", "bar") .attr("y", d => yScale(d.name)) .attr("height", yScale.bandwidth()) .attr("x", 0) .attr("width", 0) .merge(bars) .transition() .duration(800) .attr("y", d => yScale(d.name)) .attr("width", d => xScale(d.value)) .attr("height", yScale.bandwidth()); // 移除已消失用户的条形 bars.exit() .transition() .duration(800) .attr("width", 0) .remove(); // 更新用户名称标签 const nameLabels = svg.selectAll(".bar-label") .data(dayData.users, d => d.name); nameLabels.enter() .append("text") .attr("class", "bar-label") .attr("y", d => yScale(d.name) + yScale.bandwidth() / 2 + 4) .attr("x", -10) .text(d => d.name) .merge(nameLabels) .transition() .duration(800) .attr("y", d => yScale(d.name) + yScale.bandwidth() / 2 + 4); nameLabels.exit() .transition() .duration(800) .attr("opacity", 0) .remove(); // 更新数值标签 const valueLabels = svg.selectAll(".value-label") .data(dayData.users, d => d.name); valueLabels.enter() .append("text") .attr("class", "value-label") .attr("y", d => yScale(d.name) + yScale.bandwidth() / 2 + 4) .attr("x", d => xScale(d.value) - 8) .text(d => d.value) .merge(valueLabels) .transition() .duration(800) .attr("x", d => xScale(d.value) - 8) .attr("y", d => yScale(d.name) + yScale.bandwidth() / 2 + 4) .text(d => d.value); valueLabels.exit() .transition() .duration(800) .attr("opacity", 0) .remove(); // 更新顶部日期文本 dateLabel.text(dayData.date); } // 7. 模拟每日数据自动更新 let currentDayIndex = 0; function playNextDay() { renderDay(dailyData[currentDayIndex]); currentDayIndex = (currentDayIndex + 1) % dailyData.length; setTimeout(playNextDay, 2000); // 每2秒切换一次数据 } // 启动动画 playNextDay(); // 8. 实际场景中新增数据的方法 // 示例:传入新的单日原始数据即可添加 function addNewDay(newDayRaw) { const newDay = parseRawData(newDayRaw)[0]; dailyData.push(newDay); } </script> </body> </html>
关键说明
- 数据处理:自动拆分日期与用户数据,按数值降序排序生成当日排名
- 平滑动画:通过D3的
transition()实现条形宽度、位置的过渡效果,新增/移除元素时带有渐入渐出动画 - 动态适配:自动识别每日新增用户,实时调整Y轴比例尺
- 扩展能力:可将模拟更新的
setTimeout替换为接口请求或WebSocket监听,实现真实的实时数据更新
内容的提问来源于stack exchange,提问作者GLB Project
相关产品推荐
相关产品推荐

