D3ForceLayout: 配置的 center / centerX / x / y / radial 坐标及 radius 参数被默认值覆盖失效

Abierto Apto para principiantes
#294 0 comentarios 0 reacciones 0 asignados Ver en GitHub

@chilingling ya está trabajando en esto.

Desde el 9/10/2026.

  • #295 de @chilingling — abierto

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
75/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
d3, typescript

Línea de trabajo

El problema del orden de fusión está en src/algorithm/d3-force/index.ts, en getCenterOptions, getXForceOptions, getYForceOptions y getRadialOptions, donde los valores por defecto se aplican después de las opciones del usuario mediante assignDefined. Empieza ejecutando el script de reproducción del issue para confirmar que el centroide es [350, 350] y que el valor de radial.radius es 100. Se considera terminado cuando se respetan los valores center, centerX/centerY, las coordenadas x/y/radial del force y radial.radius proporcionados por el usuario, y los layouts sin configurar siguen igual.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

版本
  • @antv/layout: 2.0.1(npm beta),v5 分支(62bb479)同样可复现
  • Node.js: 24
问题描述

D3ForceLayout 在合并配置时,默认值后置覆盖了用户传入的参数,导致以下自定义配置失效:

  • 中心点配置失效:center: { x, y } 与文档推荐的快捷字段 centerX / centerY 均不生效,中心点始终固定为视口中心 [width / 2, height / 2](且源码中未读取 centerX / centerY)。
  • 力的坐标被覆盖:中心力开启时(默认开启),x: { x }、y: { y }、radial: { x, y } 均会被中心点坐标覆盖。
  • 径向半径被覆盖:radial: { radius } 始终被默认值 100 覆盖。
复现
const { D3ForceLayout } = require('@antv/layout');

const data = {
  nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }, { id: 'd' }],
  edges: [
    { id: 'e1', source: 'a', target: 'b' },
    { id: 'e2', source: 'b', target: 'c' },
    { id: 'e3', source: 'c', target: 'd' },
  ],
};

const centroid = async (options) => {
  const layout = new D3ForceLayout({ width: 700, height: 700, ...options });
  await layout.execute(data);
  let x = 0, y = 0, n = 0;
  layout.forEachNode((node) => { x += node.x; y += node.y; n += 1; });
  return [Math.round(x / n), Math.round(y / n)];
};

(async () => {
  console.log(await centroid({ center: { x: 300, y: 200 } })); // 实际: [350, 350], 期望: [300, 200]
  console.log(await centroid({ centerX: 300, centerY: 200 })); // 实际: [350, 350], 期望: [300, 200]

  const layout = new D3ForceLayout({
    width: 700,
    height: 700,
    x: { x: 100 },
    radial: { x: 100, y: 100, radius: 50 },
  });
  await layout.execute(data);
  console.log(layout.getForce('x').x()()); // 实际: 350, 期望: 100
  console.log(layout.getForce('radial').x(), layout.getForce('radial').y()); // 实际: 350 350, 期望: 100 100
  console.log(layout.getForce('radial').radius()()); // 实际: 100, 期望: 50
})();
问题原因

在 src/algorithm/d3-force/index.ts 中,getCenterOptions、getXForceOptions、getYForceOptions 和 getRadialOptions 合并配置时使用了如下写法:

return assignDefined({}, options.center || {}, {
  x: viewport.width / 2,
  y: viewport.height / 2,
  strength: options.centerStrength,
});

默认值对象放在了用户配置对象之后,直接覆盖了用户传入的自定义值(如视口中心 width / 2、中心点坐标、radius: 100)。

修复建议

将默认值前置,调整合并顺序为:默认值 → force 对象 → 快捷字段。
这样仅在未配置时使用默认值,未配置相关参数的已有布局不受影响。我稍后会提交对应的修复 PR。

待确认问题
  1. 配置优先级:文档 d3-force-options 说明为“两者混用时以 force 对象为准”,但当前代码实现中所有 getter(如 manyBody 与 nodeStrength、center.strength 与 centerStrength)均为“快捷字段优先覆盖 force 对象”。PR 沿用了现有实现的约定,是否需要后续统一调整文档或实现?
  2. 中心配置文档说明:同一文档的“通用配置”中提到“D3ForceLayout 中心相关配置不使用 center,而是使用 centerX/centerY”,但在“2D 扩展”章节和 TypeScript 类型定义中均支持 center: false | { x?, y?, strength? }。该处文档说明是否需要同步修正?
Lenguaje dominante
TypeScript
Estrellas
280
Forks
61
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de antvis/layout

Todos los issues de antvis/layout

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.