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

求助:用D3.js复现带Y轴悬浮提示的分组连接散点图

解决D3.js连接散点图的两个问题:分组Y轴 + Y轴tooltip

一、处理分组Y轴数值

如果你的数据按类别分组(不同组别对应Y轴上的独立区块),可以用嵌套比例尺实现分组布局:

  1. 提取并去重所有分组名称:
const groups = [...new Set(data.map(d => d.group))];
  1. 定义分组主比例尺(控制每个组在Y轴的整体位置):
const yGroupScale = d3.scaleBand()
  .domain(groups)
  .range([height, 0]) // height为图表总高度,坐标从下到上
  .padding(0.3); // 组与组之间的间距
  1. 若每个分组内有多个数据点,定义组内子比例尺(控制组内点的垂直分布):
// 先计算每个分组的最大数据量
const maxPointsPerGroup = d3.max(groups.map(g => data.filter(d => d.group === g).length));
const ySubScale = d3.scalePoint()
  .domain(d3.range(maxPointsPerGroup)) // 用索引作为定义域
  .range([0, yGroupScale.bandwidth()]) // 范围限制在当前组的带宽内
  .padding(0.1);
  1. 绘制散点时计算Y坐标:
svg.selectAll('.dot')
  .data(data)
  .enter()
  .append('circle')
  .attr('class', 'dot')
  .attr('cx', d => xScale(d.xValue))
  .attr('cy', d => {
    // 获取当前点在组内的索引
    const groupIndex = data.filter(item => item.group === d.group).indexOf(d);
    return yGroupScale(d.group) + ySubScale(groupIndex);
  })
  .attr('r', 5);
  1. 基于分组比例尺绘制Y轴:
const yAxis = d3.axisLeft(yGroupScale);
svg.append('g')
  .attr('transform', `translate(${margin.left},0)`)
  .call(yAxis);

二、实现鼠标悬停沿Y轴的tooltip效果

这里实现「鼠标移动时显示水平辅助线+Y轴旁提示框」的效果,也支持散点悬停触发:

方案1:鼠标全局移动触发

  1. 创建辅助线和tooltip元素:
// 水平辅助线(初始隐藏)
const hoverLine = svg.append('line')
  .attr('class', 'hover-line')
  .attr('stroke', '#999')
  .attr('stroke-width', 1)
  .attr('stroke-dasharray', '4,2')
  .style('opacity', 0);

// 提示框(用div实现,初始隐藏)
const tooltip = d3.select('body').append('div')
  .attr('class', 'tooltip')
  .style('position', 'absolute')
  .style('background', 'rgba(0,0,0,0.8)')
  .style('color', '#fff')
  .style('padding', '8px 12px')
  .style('border-radius', '4px')
  .style('pointer-events', 'none')
  .style('opacity', 0);
  1. 监听svg鼠标事件更新元素:
svg.on('mousemove', function(event) {
  const [, mouseY] = d3.pointer(event);
  // 将鼠标Y坐标转换为对应的分组
  const currentGroup = yGroupScale.invert(mouseY);

  // 更新辅助线位置
  hoverLine
    .style('opacity', 1)
    .attr('x1', margin.left)
    .attr('x2', width + margin.left)
    .attr('y1', mouseY)
    .attr('y2', mouseY);

  // 更新tooltip内容与位置
  tooltip
    .style('opacity', 1)
    .html(`当前分组: ${currentGroup}`)
    .style('left', `${margin.left - 110}px`) // 定位到Y轴左侧
    .style('top', `${mouseY - 15}px`);
})
.on('mouseout', function() {
  hoverLine.style('opacity', 0);
  tooltip.style('opacity', 0);
});

方案2:悬停在散点上触发

把事件监听绑定到散点元素即可:

svg.selectAll('.dot')
  .on('mouseover', function(event, d) {
    const dotY = d3.select(this).attr('cy');
    // 显示辅助线到当前散点的Y位置
    hoverLine
      .style('opacity', 1)
      .attr('y1', dotY)
      .attr('y2', dotY);
    
    tooltip
      .style('opacity', 1)
      .html(`分组: ${d.group}<br>数值: ${d.xValue}`)
      .style('left', `${event.pageX + 10}px`)
      .style('top', `${event.pageY - 20}px`);
  })
  .on('mouseout', function() {
    hoverLine.style('opacity', 0);
    tooltip.style('opacity', 0);
  });

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 06:05:55