You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在保留旋转功能的同时为d3-3d添加平移与缩放功能的问题

解决d3-3D柱状图中旋转、平移、缩放的交互冲突问题

我明白你遇到的困扰了——直接同时用d3.drag()d3.zoom()确实会因为事件监听重叠导致冲突,出现抖动或者操作优先级混乱的情况。核心原因是d3.zoom()本身就内置了拖拽行为,两个独立的行为会争抢同一批鼠标事件,自然会出问题。

解决思路是放弃单独的d3.drag(),把旋转、平移、缩放逻辑全部整合到d3.zoom()的事件处理中,通过区分鼠标操作类型(按键、滚轮)来分配不同的交互功能,这样所有交互由zoom统一管理,就不会有冲突了。

下面是具体的修改方案:

1. 替换拖拽行为为Zoom行为

首先移除原来的d3.drag(),改用d3.zoom(),同时阻止右键默认菜单避免干扰平移操作:

// 定义zoom行为,绑定三个阶段的事件
const zoom = d3.zoom()
  .on("start", zoomStart)
  .on("zoom", zoomed)
  .on("end", zoomEnd);

// 绑定zoom到svg,并阻止右键菜单
const svg = d3.select('svg')
  .call(zoom)
  .on("contextmenu", (event) => event.preventDefault());

2. 定义状态变量与辅助函数

我们需要记录交互开始时的初始状态,方便计算增量:

// 新增初始状态变量,用于交互时的增量计算
let startAlpha, startBeta, startOrigin, startScale;

// 你的原有变量保持不变
var origin = [100, 85], scale = 5, j = 10, cubesData = [];
var alpha = 0, beta = 0, startAngle = Math.PI/6;

// 新增更新图表的函数(你原有绘制逻辑可以整合到这里)
function updateCubes() {
  // 用d3-3d配置当前的旋转、缩放、平移参数
  const cubeGenerator = d3._3d()
    .shape('cube')
    .rotateY(alpha)
    .rotateX(beta)
    .scale(scale)
    .origin(origin);

  // 更新3D数据
  cubesData = cubeGenerator(cubesData);

  // 这里放入你原有绘制/更新柱状图的逻辑(比如enter/update/exit选择器)
  // 示例:
  // const faces = svg.selectAll('.cube-face').data(cubesData);
  // faces.enter().append('path').attr('class', 'cube-face')...
  // faces.attr('d', d => d.d)...
  // faces.exit().remove();
}

3. 实现Zoom事件逻辑

在三个zoom事件中,根据鼠标操作类型分配旋转、平移、缩放功能:

3.1 交互开始时记录初始状态

function zoomStart(event) {
  // 记录当前所有状态的初始值,用于后续计算增量
  startAlpha = alpha;
  startBeta = beta;
  startOrigin = [...origin];
  startScale = scale;
}

3.2 交互过程中处理不同操作

function zoomed(event) {
  const transform = event.transform;
  const dx = transform.x - transform.previousX;
  const dy = transform.y - transform.previousY;

  // 区分操作类型:
  // 1. 滚轮操作:处理缩放
  if (event.sourceEvent.type === "wheel") {
    // 根据zoom的缩放因子调整scale,同时限制范围避免过度缩放
    scale = startScale * transform.k;
    scale = Math.max(0.5, Math.min(20, scale)); // 可根据需求调整范围
  } 
  // 2. 右键拖动:处理平移
  else if (event.sourceEvent.button === 2) {
    // 根据鼠标移动距离平移origin
    origin[0] = startOrigin[0] + dx;
    origin[1] = startOrigin[1] + dy;
  } 
  // 3. 左键拖动:处理旋转(对应你原来的drag逻辑)
  else if (event.sourceEvent.button === 0) {
    // 根据鼠标移动距离调整旋转角度,系数可控制旋转速度
    alpha = startAlpha + dx * 0.01;
    beta = startBeta - dy * 0.01;
    // 限制beta角度,避免柱状图翻转(可选)
    beta = Math.max(-Math.PI/2, Math.min(Math.PI/2, beta));
  }

  // 每次状态更新后重新绘制图表
  updateCubes();
}

3.3 交互结束时重置Zoom状态

因为我们自己维护了originscale等参数,不需要zoom内置的transform累积,所以在结束时重置:

function zoomEnd(event) {
  // 重置zoom的transform,避免影响下一次交互
  svg.call(zoom.transform, d3.zoomIdentity);
}

关键说明

  • 这样所有交互逻辑都由d3.zoom()统一处理,不会出现事件争抢的情况,也就解决了抖动问题。
  • 操作逻辑:左键拖动旋转,右键拖动平移,滚轮缩放(你可以根据需求修改按键或操作方式)。
  • 如果需要修改操作触发条件(比如用Ctrl+左键平移),只需要在zoomed函数里判断event.sourceEvent.ctrlKey等修饰键即可。

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

火山引擎 最新活动