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

D3 v7堆叠面积图垂直准线Tooltip实现方法

D3 v7 堆叠面积图垂直准线+多系列Tooltip实现方案

核心实现原则(完全适配v7 API,无旧版本冗余逻辑)

  • 垂直准线与事件捕获分离:单独添加覆盖整个绘图区的透明矩形作为事件接收层,避免事件被上层面积路径遮挡,准线元素提前创建仅更新位置,不要在鼠标移动时反复增删DOM
  • 数值匹配用二分查找替代DOM遍历:通过d3.bisector直接在原始数据集中匹配离鼠标位置最近的x轴坐标点,从d3.stack()生成的结构化数据中读取各系列值,从根源避免数据绑定错位
  • Tooltip全局复用:提前初始化Tooltip容器,鼠标移动时仅更新内容与坐标,避免重复创建DOM导致的样式异常、性能损耗

关键实现代码

1. 初始化固定元素(图表渲染阶段执行一次即可)

// 垂直定位准线,默认隐藏
const hoverLine = svg.append("line")
  .attr("class", "hover-guide-line")
  .attr("y1", 0)
  .attr("y2", chartHeight) // 与绘图区高度一致
  .attr("stroke", "#666")
  .attr("stroke-width", 1)
  .attr("stroke-dasharray", "4 2")
  .style("opacity", 0);

// Tooltip容器,默认隐藏
const tooltip = d3.select("body").append("div")
  .attr("class", "stack-area-tooltip")
  .style("position", "absolute")
  .style("background", "#fff")
  .style("border", "1px solid #e5e5e5")
  .style("border-radius", "4px")
  .style("padding", "8px 12px")
  .style("box-shadow", "0 2px 8px rgba(0,0,0,0.1)")
  .style("pointer-events", "none")
  .style("font-size", "12px")
  .style("line-height", "1.5")
  .style("opacity", 0);

// 透明事件捕获层,铺满整个绘图区
const eventLayer = svg.append("rect")
  .attr("width", chartWidth)
  .attr("height", chartHeight)
  .attr("fill", "transparent")
  .style("cursor", "crosshair");

2. 配置二分查找器

// 适配数值/时间类型x轴,自动匹配离鼠标位置最近的数据点索引
const bisectX = d3.bisector(d => d.xValue).center;

3. 绑定鼠标交互事件

注意D3 v7的事件回调参数顺序为(event, datum),和v6之前版本顺序不同,这是旧版本代码数据绑定错误的核心原因。

eventLayer
  .on("mousemove", (event) => {
    // 反算鼠标位置对应的x轴数值
    const [mouseXPos] = d3.pointer(event);
    const currentX = xScale.invert(mouseXPos);
    // 找到最近的原始数据点
    const closestIndex = bisectX(rawData, currentX);
    const closestPoint = rawData[closestIndex];
    const matchedXPos = xScale(closestPoint.xValue);

    // 更新垂直准线位置并显示
    hoverLine
      .attr("x1", matchedXPos)
      .attr("x2", matchedXPos)
      .style("opacity", 1);

    // 组装Tooltip内容,遍历所有堆叠系列取值
    let content = `<div style="font-weight: 600;margin-bottom: 6px">${closestPoint.xLabel}</div>`;
    stackedSeries.forEach(series => {
      const val = closestPoint[series.key];
      content += `
      <div style="display: flex;align-items: center;gap: 6px;margin: 2px 0">
        <span style="width: 10px;height: 10px;border-radius: 2px;background: ${colorScale(series.key)}"></span>
        <span>${series.key}</span>
        <span style="margin-left: auto;font-weight: 500">${val.toFixed(2)}</span>
      </div>
      `
    })

    // 更新Tooltip位置与内容并显示
    tooltip
      .html(content)
      .style("left", `${event.pageX + 12}px`)
      .style("top", `${event.pageY - 8}px`)
      .style("opacity", 1);
  })
  .on("mouseleave", () => {
    // 鼠标离开绘图区时隐藏所有交互元素
    hoverLine.style("opacity", 0);
    tooltip.style("opacity", 0);
  })

常见问题对应修正

  • 数据绑定错误:不要从面积路径的DOM元素上取绑定数据,直接通过二分查找从原始数据集取点,完全规避事件冒泡、元素层级导致的数据取错问题
  • 垂直准线不显示:不要将鼠标事件绑定在堆叠面积的path元素上,上层path会拦截下层元素的事件,必须使用独立的透明捕获层
  • Tooltip内容异常:不要在mousemove回调中重复创建Tooltip DOM节点,全局复用单个实例即可,避免DOM堆积导致的样式错乱、内容不更新问题

若x轴为时间类型,仅需将bisector取值字段替换为对应的时间字段,其余逻辑无需调整即可直接使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 07:18:23