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

求助:使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 10:01:44