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

如何使用d3.js v6实现点击触发的逐年自动过渡散点图

D3.js v6 散点图年度自动过渡实现方案

你要的效果核心由三部分组成:年份数组预处理、散点更新过渡函数、自动轮播定时器逻辑,具体实现如下:

1. 基础初始化代码

首先是页面结构和基础D3配置,你可以直接套自己的散点图尺寸、比例尺:

<!-- 页面元素 -->
<button id="playBtn">开始按年份切换</button>
<svg id="scatterPlot" width="800" height="600"></svg>
// 假设你的原始数据格式为 [{x: 数值, y: 数值, year: 2000}, ...]
const rawData = 你的全量数据集;

// 第一步:提取所有不重复年份并升序排序
const allYears = Array.from(new Set(rawData.map(d => d.year))).sort((a,b) => a - b);
let currentYearIndex = 0; // 记录当前展示的年份索引,初始是第一个年份
let playTimer = null; // 存轮播定时器id,方便后续控制

// 初始化svg、比例尺,这里用你自己的业务配置就行
const svg = d3.select('#scatterPlot');
const margin = {top: 20, right: 20, bottom: 30, left: 40};
const width = +svg.attr('width') - margin.left - margin.right;
const height = +svg.attr('height') - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);

// x/y比例尺,根据你的数据值域调整
const xScale = d3.scaleLinear()
  .domain(d3.extent(rawData, d => d.x))
  .range([0, width]);
const yScale = d3.scaleLinear()
  .domain(d3.extent(rawData, d => d.y))
  .range([height, 0]);

// 初始化坐标轴,只需要渲染一次
g.append('g')
  .attr('transform', `translate(0,${height})`)
  .call(d3.axisBottom(xScale));
g.append('g')
  .call(d3.axisLeft(yScale));

2. 核心更新函数(带过渡效果)

遵循D3的enter/update/exit模式实现年份切换的平滑过渡:

function updateChart(targetYear) {
  // 过滤出当前年份的所有数据
  const yearData = rawData.filter(d => d.year === targetYear);
  
  // 选择所有现有散点,第二个参数为数据唯一标识,没有独立id可以用d.x+''+d.y组合,避免过渡错乱
  const circles = g.selectAll('circle.scatter-point')
    .data(yearData, d => d.id);

  // 退出态:上一年有、当前年没有的散点,淡出后移除
  circles.exit()
    .transition()
    .duration(1000) // 过渡时长,单位ms
    .attr('opacity', 0)
    .remove();

  // 进入态:当前年新增的散点,初始透明+位置偏移,淡入到对应位置
  const newCircles = circles.enter()
    .append('circle')
    .attr('class', 'scatter-point')
    .attr('r', 4)
    .attr('fill', 'steelblue')
    .attr('opacity', 0)
    .attr('cx', d => xScale(d.x))
    .attr('cy', height) // 初始位置可自定义,也可以设为上一年的坐标

  // 更新态:两年都有的散点,平滑移动到新位置
  newCircles.merge(circles)
    .transition()
    .duration(1000)
    .attr('opacity', 1)
    .attr('cx', d => xScale(d.x))
    .attr('cy', d => yScale(d.y));
}

// 页面初始化先渲染第一个年份的数据
updateChart(allYears[currentYearIndex]);

3. 按钮点击自动轮播逻辑

d3.select('#playBtn').on('click', function() {
  // 避免重复点击生成多个定时器,点击先清掉现有定时器
  if (playTimer) clearInterval(playTimer);
  // 重置索引,从第一个年份开始轮播,不需要重置可以删掉这行
  currentYearIndex = 0;
  updateChart(allYears[currentYearIndex]);
  // 每隔1.5s切下一个年份,间隔可以自行调整,注意要大于过渡的1000ms避免动画冲突
  playTimer = setInterval(() => {
    currentYearIndex++;
    // 所有年份播完就停止
    if (currentYearIndex >= allYears.length) {
      clearInterval(playTimer);
      playTimer = null;
      return;
    }
    updateChart(allYears[currentYearIndex]);
  }, 1500);
});

常见调整项

  • 过渡效果可自定义,比如散点大小、颜色变化,直接写在transition后的attr配置里即可
  • 需要暂停功能的话,新增按钮点击事件调用clearInterval(playTimer)即可
  • 年份不连续的情况不需要额外处理,预处理时已经做了去重排序,会按实际存在的年份顺序轮播

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 18:15:02