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

解决D3 7.8.5散点图中标签与图片的重叠问题

D3散点图:解决图片与标签重叠问题

问题说明

我用D3 7.8.5绘制了一个散点图,每个数据点是一张图片,同时附带描述标签,但目前标签和其他标签、图片严重重叠。需要实现:

  • 标签自动避开图片和其他标签,可放置在图片周围任意位置
  • 当没有可用空间时自动隐藏标签

当前实现代码如下:

const width = 1300;
const height = 800;
const padding = { top: 10, bottom: 50, left: 20, right: 50 };

// X/Y 比例尺
var xScale = d3.scaleSymlog()
  .domain([0, 110])
  .range([0, width]);
var yScale = d3.scaleSymlog()
  .domain([-50, 1000])
  .range([height, 0]);

// 挂载SVG容器
var svg = d3.select("#my_dataviz")
  .append("svg")
  .attr("id", "chartSvg")
  .attr("width", width + padding.left + padding.right)
  .attr("height", height + padding.top + padding.bottom);

// 绘图区域
var plotArea = svg.append("g")
  .attr("transform", `translate(${padding.left}, ${padding.top})`)
  .attr("id", "plotArea");

// 可平移内容容器
var panableContent = plotArea.append('g')
  .attr('id', 'panableContent');

// 绑定数据,创建图片+标签的组
var logosAndText = panableContent.selectAll("g")
  .data(jsonData.Items)
  .enter()
  .append("g");

// 添加图片
var logos = logosAndText.append("image")
  .attr("width", 32)
  .attr("height", 32)
  .attr("x", (d) => xScale(d.XValue) - 16)
  .attr("y", (d) => yScale(d.YValue) - 16)
  .attr("href", () => "https://d3js.org/logo.svg");

// 添加标签
var texts = logosAndText.append("text")
  .attr("x", (d) => xScale(d.XValue) - 16)
  .attr("y", (d) => yScale(d.YValue) - 16)
  .text(() => "Dies ist ein Text");

解决方案:用力导向布局实现自动避障

使用d3.forceSimulation给每个图片+标签组添加斥力(避免重叠)和向心力(保持原始散点位置),同时检测标签位置,隐藏无法放置的标签。

修改后的完整代码

const width = 1300;
const height = 800;
const padding = { top: 10, bottom: 50, left: 20, right: 50 };

// X/Y 比例尺
var xScale = d3.scaleSymlog()
  .domain([0, 110])
  .range([0, width]);
var yScale = d3.scaleSymlog()
  .domain([-50, 1000])
  .range([height, 0]);

// 挂载SVG容器
var svg = d3.select("#my_dataviz")
  .append("svg")
  .attr("id", "chartSvg")
  .attr("width", width + padding.left + padding.right)
  .attr("height", height + padding.top + padding.bottom);

// 绘图区域
var plotArea = svg.append("g")
  .attr("transform", `translate(${padding.left}, ${padding.top})`)
  .attr("id", "plotArea");

// 可平移内容容器
var panableContent = plotArea.append('g')
  .attr('id', 'panableContent');

// 预处理数据:添加原始位置信息
jsonData.Items.forEach(d => {
  d.x = xScale(d.XValue);
  d.y = yScale(d.YValue);
  d.originalX = d.x;
  d.originalY = d.y;
});

// 绑定数据,创建图片+标签的组
var logosAndText = panableContent.selectAll("g")
  .data(jsonData.Items)
  .enter()
  .append("g");

// 添加图片(不再单独设置x/y,通过组的transform定位)
var logos = logosAndText.append("image")
  .attr("width", 32)
  .attr("height", 32)
  .attr("href", () => "https://d3js.org/logo.svg");

// 添加标签(初始不设置位置,后续由力模拟调整)
var texts = logosAndText.append("text")
  .text(() => "Dies ist ein Text")
  .attr("dominant-baseline", "middle"); // 垂直居中

// 先计算所有标签的实际宽度,用于碰撞检测
texts.each(function(d) {
  d.labelWidth = this.getBBox().width;
});

// 创建力模拟
const simulation = d3.forceSimulation(jsonData.Items)
  // 向心力:拉回原始散点位置,strength控制拉力大小
  .force("x", d3.forceX(d => d.originalX).strength(0.3))
  .force("y", d3.forceY(d => d.originalY).strength(0.3))
  // 斥力:避免组之间重叠,radius为图片半宽+标签半宽+间距
  .force("collide", d3.forceCollide()
    .radius(d => 16 + (d.labelWidth / 2) + 8)
    .iterations(6)) // 迭代次数越高,避障效果越精准
  .on("tick", ticked);

// 力模拟更新回调:调整组和标签位置
function ticked() {
  // 调整组的位置,让图片居中(偏移16,即32/2)
  logosAndText.attr("transform", d => `translate(${d.x - 16}, ${d.y - 16})`);
  
  // 将标签放在图片右侧,添加间距
  texts.attr("x", 32 + 8)
    .attr("text-anchor", "start");
  
  // 检测标签是否超出画布,隐藏超出的标签
  texts.each(function(d) {
    const bbox = this.getBBox();
    // 计算标签相对于画布的位置
    const groupTransform = d3.select(this.parentNode).attr("transform");
    const [tx, ty] = groupTransform.match(/translate\(([^,]+),([^)]+)\)/).slice(1).map(Number);
    const absoluteX = tx + bbox.x;
    const absoluteY = ty + bbox.y;
    
    const isOutOfBounds = 
      absoluteX + bbox.width > width || 
      absoluteX < 0 || 
      absoluteY + bbox.height > height || 
      absoluteY < 0;
    
    d3.select(this).style("display", isOutOfBounds ? "none" : "inline");
  });
}

关键说明

  1. 力模拟配置:

    • forceX/forceY:让每个组保持在原始散点位置附近,strength值越大,拉力越强,组偏离原始位置的幅度越小。
    • forceCollide:设置碰撞半径,确保组之间不会重叠,半径需要包含图片和标签的尺寸。
  2. 标签位置调整:

    • 将标签固定在图片右侧(可根据需求改为上方、下方、左侧),通过text-anchor和dominant-baseline确保对齐。
  3. 隐藏超出画布的标签:

    • 通过获取标签和组的边界框,计算标签在画布中的绝对位置,超出范围则隐藏。
  4. 优化方向:

    • 如果需要更精准的重叠检测,可以结合d3-quadtree检测相邻标签的重叠,隐藏优先级较低的标签。
    • 可以尝试d3-annotation库的自动标签定位功能,内置了多种避障策略。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 21:10:10