D3 简介

交互与缩放

掌握 D3 事件绑定和 tooltip 提示框的实现,并用 zoom 为图表添加缩放平移能力。

🎯 引言

图表能看能动,还差最后一步:能交互。鼠标悬停时高亮柱子、显示数值提示框、滚轮缩放图表,这些都是真实项目的标配。学会这篇文章,你能掌握 D3 的事件绑定、tooltip 实现和 zoom 缩放,让图表真正“活”起来。


🧱 绑定事件 .on()

D3 用 .on() 给元素绑定事件,和原生 JS 的 addEventListener 很像:

svg.selectAll('rect').on('mouseenter', function (event, d) {
    console.log(d); // { day: '周一', value: 120 } —— 这根柱子绑定的数据
});

事件回调会收到两个参数:

  • event:浏览器原生事件对象,鼠标位置等信息都在里面。
  • d这个元素绑定的数据。悬停哪根柱子,d 就是哪根柱子的数据。

想修改“当前被悬停的这一根”柱子,通过 this 拿到它:

svg.selectAll('rect')
    .on('mouseenter', function (event, d) {
        d3.select(this).attr('fill', 'orange'); // 只改变当前这根
    })
    .on('mouseleave', function (event, d) {
        d3.select(this).attr('fill', '#5470c6'); // 移开后还原
    });
两个极易踩的坑:
  1. 回调参数是 (event, d),事件在前、数据在后。网上老教程写的 (d, i) 是 D3 v5 及以前的写法,v7 已经改了,照抄会拿到错误的数据。
  2. 想用 this 拿当前元素,必须用 function 普通函数,不能用箭头函数——箭头函数没有自己的 this

把鼠标悬停到柱子上,试试高亮效果:

把鼠标悬停到柱子上试试


🛠 实现 tooltip 提示框

tooltip 是最常见的交互:悬停柱子时,鼠标旁边显示一个数值小卡片。实现思路是用一个绝对定位的 div,三个事件分工配合:

事件干什么
mouseenter显示 tooltip,填入数据
mousemove让 tooltip 跟着鼠标走
mouseleave隐藏 tooltip
views/TooltipChart.vue
<template>
    <div class="wrapper">
        <svg ref="svgRef" class="chart"></svg>
        <!-- tooltip 容器,默认隐藏 -->
        <div ref="tooltipRef" class="tooltip"></div>
    </div>
</template>

<script setup>
import { onMounted, ref } from 'vue';
import * as d3 from 'd3';

const svgRef = ref(null);
const tooltipRef = ref(null);

onMounted(() => {
    const svg = d3.select(svgRef.value);
    const tooltip = d3.select(tooltipRef.value);

    // ……比例尺和画柱子的代码同上一篇,此处省略,重点是事件部分
    svg.selectAll('rect')
        .on('mouseenter', function (event, d) {
            tooltip.style('opacity', 1).text(`${d.day}${d.value}`); // 显示数值
        })
        .on('mousemove', function (event, d) {
            // offsetX/offsetY 是鼠标相对容器的位置,让提示框跟在鼠标右下方
            tooltip
                .style('left', event.offsetX + 10 + 'px')
                .style('top', event.offsetY + 10 + 'px');
        })
        .on('mouseleave', function (event, d) {
            tooltip.style('opacity', 0); // 隐藏
        });
});
</script>

<style scoped>
.wrapper {
    position: relative; /* 让 tooltip 相对容器定位 */
}

.tooltip {
    position: absolute;
    opacity: 0; /* 默认隐藏 */
    pointer-events: none; /* 关键:tooltip 自己不响应鼠标 */
    background: rgba(0, 0, 0, 0.7);
    color: #fff;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 12px;
}
</style>

pointer-events: none 是关键细节:没有它,tooltip 会挡住鼠标,柱子反复触发“进入/离开”,提示框就会疯狂闪烁。

把鼠标悬停到下面的柱子上,试试真实的 tooltip 效果:


🧱 d3.zoom 缩放平移

数据点多或者做大屏时,用户会想滚轮放大看细节、按住拖拽平移。D3 的 zoom 开箱即用:

const g = svg.append('g'); // 所有图形画在这个 g 里

const zoom = d3
    .zoom()
    .scaleExtent([0.5, 8]) // 缩放范围:最小 0.5 倍、最大 8 倍
    .on('zoom', (event) => {
        g.attr('transform', event.transform); // 把缩放结果应用到 g 上
    });

d3.select(svgRef.value).call(zoom);

原理很直白:zoom 捕获滚轮和拖拽操作,换算成一个 transform(平移 + 缩放),回调里把它应用到装图形的 g 上,整个图表就跟着缩放平移了。手机上的双指缩放、单指拖动也自动支持。

scaleExtent 一定要设置,否则用户可以无限缩小,图表一下子就“飞没了”。

下面是一个可以缩放平移的散点图,滚轮缩放、按住拖拽试试:

滚轮缩放,按住拖拽平移(范围 0.5 ~ 8 倍)


🧾 小节总结

  • .on() 绑定事件,回调参数是 (event, d):事件在前、数据在后。
  • 用普通 function 写回调,通过 d3.select(this) 操作当前元素。
  • tooltip 三件套:mouseenter 显示、mousemove 跟随、mouseleave 隐藏,记得 pointer-events: none
  • d3.zoom() 实现滚轮缩放和拖拽平移,scaleExtent 限制缩放范围。
  • zoom 回调里把 event.transform 应用到装图形的 g 容器上。

❓ 知识问答

Q1:tooltip 为什么不用 SVG 的 title 元素?

title 是浏览器原生提示,样式无法定制、显示还有延迟。用 div 自己实现可以任意美化,这也是主流图表库的做法。

Q2:点击事件能拿到数据吗?

能,.on('click', function (event, d) {}) 里的 d 就是该元素绑定的数据,常用于点击柱子跳转详情页。

Q3:zoom 缩放后坐标轴文字会跟着变形吗?

会,因为坐标轴也在被变换的 g 里。想保持刻度文字清晰,需要在回调里重新计算比例尺并重绘坐标轴,属于进阶内容,初学先掌握整体缩放。

Q4:事件里的 d 是 undefined 是怎么回事?

两种常见原因:用了箭头函数导致参数错位,或者照抄了 v5 老教程的 (d, i) 写法。v7 的正确写法是 function (event, d)


🧪 小练习

给之前的柱状图加上完整交互:

  1. 悬停柱子时高亮为橙色,并在 tooltip 中显示“星期 + 数值”。
  2. tooltip 跟随鼠标移动,离开后隐藏。
  3. 给图表加上 d3.zoom,缩放范围限制在 0.8 到 5 倍。
svg.selectAll('rect')
    .data(data)
    .join('rect')
    .attr('x', (d) => xScale(d.day))
    .attr('y', (d) => yScale(d.value))
    .attr('width', xScale.bandwidth())
    .attr('height', (d) => height - yScale(d.value))
    .attr('fill', '#5470c6');

// 请在这里编写代码(事件绑定 + tooltip + zoom)

🎉 恭喜你已经掌握 D3 的交互技能!下一篇我们把所有知识收拢,在 Vue3 项目中封装一个可复用的图表组件。