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

如何在D3.js中实现类似GoJS的图表概览图?求代码示例

嘿,我正好做过类似的功能!其实用D3.js完全可以实现GoJS那样的概览小地图(就是那种能看到整个图表范围,还能拖动视口导航的小面板)。核心思路就是创建一个缩小版的图表副本,再加上一个可交互的视口矩形,同步主图表的平移和缩放状态。下面是一个完整的可运行示例,你可以直接复用或者根据自己的图表需求调整:


实现步骤与代码

1. HTML结构

先准备两个SVG容器,一个放主图表,一个放概览图:

<div class="chart-container">
  <svg id="main-chart" width="800" height="600"></svg>
  <svg id="overview" width="200" height="150"></svg>
</div>

2. 基础样式

给概览图加个边框,视口矩形设置半透明样式,方便区分:

#overview {
  border: 1px solid #ccc;
  margin-top: 10px;
}
.viewport {
  fill: rgba(0, 123, 255, 0.2);
  stroke: #007bff;
  stroke-width: 2;
  cursor: move;
}

3. D3.js核心逻辑

// ----------------------
// 主图表初始化
// ----------------------
const mainSvg = d3.select("#main-chart");
const mainWidth = +mainSvg.attr("width");
const mainHeight = +mainSvg.attr("height");

// 创建主图表的缩放平移行为
const zoom = d3.zoom()
  .scaleExtent([0.5, 4]) // 限制缩放范围
  .on("zoom", handleMainZoom);

// 主图表的容器层(所有图表元素都放在这里,用来应用缩放变换)
const mainGroup = mainSvg.append("g")
  .call(zoom);

// 👉 替换成你自己的图表内容:这里模拟了50个随机节点
const nodes = d3.range(50).map(() => ({
  x: Math.random() * mainWidth,
  y: Math.random() * mainHeight
}));

mainGroup.selectAll("circle")
  .data(nodes)
  .enter()
  .append("circle")
  .attr("cx", d => d.x)
  .attr("cy", d => d.y)
  .attr("r", 5)
  .attr("fill", "#333");

// ----------------------
// 概览图初始化
// ----------------------
const overviewSvg = d3.select("#overview");
const overviewWidth = +overviewSvg.attr("width");
const overviewHeight = +overviewSvg.attr("height");

// 计算概览图的缩放比例:确保完整显示主图表,保持宽高比
const scaleX = overviewWidth / mainWidth;
const scaleY = overviewHeight / mainHeight;
const overviewScale = Math.min(scaleX, scaleY);

// 概览图的容器层,居中放置缩小后的图表内容
const overviewGroup = overviewSvg.append("g")
  .attr("transform", `translate(${overviewWidth/2 - mainWidth*overviewScale/2}, ${overviewHeight/2 - mainHeight*overviewScale/2})`);

// 复制主图表元素到概览图(如果你的图表有复杂元素,也可以直接克隆DOM节点)
overviewGroup.selectAll("circle")
  .data(nodes)
  .enter()
  .append("circle")
  .attr("cx", d => d.x * overviewScale)
  .attr("cy", d => d.y * overviewScale)
  .attr("r", 2)
  .attr("fill", "#666");

// 添加视口矩形:用来指示主图表当前的可见区域
const viewport = overviewSvg.append("rect")
  .attr("class", "viewport")
  .attr("width", mainWidth * overviewScale)
  .attr("height", mainHeight * overviewScale)
  .attr("x", overviewWidth/2 - mainWidth*overviewScale/2)
  .attr("y", overviewHeight/2 - mainHeight*overviewScale/2);

// ----------------------
// 主图表与概览图的同步逻辑
// ----------------------
// 主图表缩放/平移时,更新概览图的视口位置和大小
function handleMainZoom(event) {
  const transform = event.transform;
  
  // 根据主图表的变换参数,计算视口在概览图中的位置
  const viewportX = overviewWidth/2 - (mainWidth * transform.k * overviewScale)/2 + transform.x * overviewScale;
  const viewportY = overviewHeight/2 - (mainHeight * transform.k * overviewScale)/2 + transform.y * overviewScale;
  const viewportW = mainWidth * transform.k * overviewScale;
  const viewportH = mainHeight * transform.k * overviewScale;
  
  viewport
    .attr("x", viewportX)
    .attr("y", viewportY)
    .attr("width", viewportW)
    .attr("height", viewportH);
}

// 拖动概览图的视口,同步更新主图表的位置
let isDragging = false;
let startPos = {};

viewport
  .on("mousedown", function(event) {
    isDragging = true;
    startPos = { x: event.clientX, y: event.clientY };
    d3.select(this).style("cursor", "grabbing");
  })
  .on("mousemove", function(event) {
    if (!isDragging) return;
    
    const dx = event.clientX - startPos.x;
    const dy = event.clientY - startPos.y;
    
    // 将概览图的拖动距离转换为主图表的平移距离
    const mainDx = dx / overviewScale;
    const mainDy = dy / overviewScale;
    
    // 更新主图表的平移位置
    zoom.translateBy(mainGroup, mainDx, mainDy);
    
    startPos = { x: event.clientX, y: event.clientY };
  })
  .on("mouseup", function() {
    isDragging = false;
    d3.select(this).style("cursor", "move");
  })
  .on("mouseleave", function() {
    isDragging = false;
    d3.select(this).style("cursor", "move");
  });

关键细节说明

  1. 缩放比例计算:用Math.min(scaleX, scaleY)保证概览图能完整显示主图表的全部内容,不会出现裁剪。
  2. 视口同步:主图表的zoom事件会触发handleMainZoom函数,根据当前的缩放倍数(transform.k)和平移量(transform.x/y)实时更新视口的大小和位置。
  3. 拖动导航:拖动视口时,把概览图的像素偏移量转换为主图表的平移量,调用zoom.translateBy来同步主图表的位置,体验和GoJS的概览图一致。

如果你的主图表有更复杂的元素(比如连线、分组、自定义图形),可以直接克隆主图表的DOM节点到概览图,再调整缩放比例,这样不用重复写绘制逻辑,更高效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:58:07