X6 坐标系完全指南:local、graph、client 与 page 四种坐标系的转换方法详解
2026/9/17 18:21:01 网站建设 项目流程

X6 坐标系完全指南:local、graph、client 与 page 四种坐标系的转换方法详解

【免费下载链接】X6🚀 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6

坐标系转换是图编辑类应用中最高频、也最容易出错的环节——无论是把鼠标点击位置换算成节点坐标、把节点位置换算成拖拽辅助线坐标,还是实现"右键菜单跟随节点"这类交互,本质都是坐标系之间的映射。本文基于 X6 官方文档 坐标系 展开,结合仓库中 CoordManager 实现 与 坐标转换测试 的源码证据,系统讲解 X6 中localgraphclientpage四种坐标系的含义,以及pageToLocallocalToPageclientToLocallocalToClientlocalToGraphgraphToLocalsnapToGrid七个转换方法的签名、底层原理与实战用法。读完本文,你将能够准确判断任意场景下该用哪个转换方法,并理解每一步换算背后的矩阵运算。

一、X6 中的四种坐标系

在 X6 中,位置计算经常需要坐标转换。文档中明确了四种坐标系,理解它们是使用一切转换方法的前提:

  • local(画布本地坐标系):默认情况下与graph坐标系一致,但会随着画布的缩放(scale)和平移(translate)发生改变。画布中所有节点的位置(xy)都以local坐标系为准,因此它是节点、边等模型数据实际存储所用的坐标系。
  • graph(画布坐标系):即我们看到的画布视口所对应的坐标系,它不会随画布缩放和平移而改变。可以把它理解为"未被变换矩阵作用过的原始画布坐标系"。
  • client(浏览器坐标系):鼠标事件中的e.clientXe.clientY就是相对于浏览器坐标系(即相对浏览器视口左上角)。
  • page(页面坐标系):与client相比,page额外考虑了页面在水平和垂直方向的滚动。鼠标事件中的e.pageXe.pageY就是相对于页面坐标系(即相对文档左上角)。

从源码结构看,这四种坐标系并非凭空定义,而是由 src/graph/coord.ts 中的CoordManager类统一管理。该类通过两个基础数据完成所有换算:

  1. getClientMatrix():调用this.view.stage.getScreenCTM()获取 SVG 舞台相对文档的屏幕 CTM(Current Transformation Matrix),这是 local 到 client 的核心桥梁;
  2. getClientOffset()/getPageOffset():分别通过svg.getBoundingClientRect()得到画布视口相对窗口的偏移,再叠加window.scrollXwindow.scrollY得到页面偏移。

对应的单元测试见tests/graph/coord.spec.ts,其中通过getBoundingClientRect的 mock 返回值(如left: 10, top: 20)验证了偏移计算的正确性,并通过scrollX/scrollY的桩数据验证getPageOffset确实等于 client 偏移加滚动量。

二、坐标系之间的关系图

四个坐标系之间的换算关系可以用一句话概括:pageclient之间差一个页面滚动偏移,graphlocal之间差一个画布变换矩阵,localclient之间差一个屏幕 CTM(其本身已包含画布变换矩阵)。X6 提供的全部转换方法,本质上就是在这四个坐标系之间两两搭桥:

  • pageToLocal:page → local(先减页面偏移得到 graph,再用画布矩阵逆变换)
  • localToPage:local → page(先用画布矩阵变换到 graph,再加页面偏移)
  • clientToLocal:client → local(用屏幕 CTM 的逆矩阵变换)
  • localToClient:local → client(用屏幕 CTM 变换)
  • localToGraph:local → graph(用画布矩阵变换)
  • graphToLocal:graph → local(用画布矩阵的逆变换)

其中clientToGraph虽然未在原文档方法列表中单独列出,但实现中同样存在(见 graph.ts),它通过graph.matrix().multiply(clientMatrix.inverse())组合两个矩阵完成换算,可视为clientToLocallocalToGraph的合成。

三、坐标转换方法详解

X6 的Graph实例上直接暴露了以下坐标转换方法(均在 graph.ts 的// #region coord区块中声明并委托给CoordManager实现)。所有方法都支持矩形两种输入形态:

  • 传点:(p: Point.PointLike)(x: number, y: number),返回Point
  • 传矩形:(rect: Rectangle.RectangleLike)(x, y, width, height)四个数值,返回Rectangle

3.1 pageToLocal(...):页面坐标 → 画布本地坐标

pageToLocal(rect: Rectangle.RectangleLike): Rectangle pageToLocal(x: number, y: number, width: number, height: number): Rectangle pageToLocal(p: Point.PointLike): Point pageToLocal(x: number, y: number): Point

将页面坐标转换为画布本地坐标,即把e.pageXe.pageY这类坐标换算为节点可以直接使用的xy。这是实现"点击画布空白处添加节点""右键菜单定位"等交互的标配方法。

底层实现(coord.ts 与 coord.ts):

pageToLocalPoint(x, y) { const pagePoint = Point.create(x, y) const graphPoint = pagePoint.diff(this.getPageOffset()) return this.graphToLocalPoint(graphPoint) }

先减去页面偏移得到graph坐标,再调用graphToLocalPoint用画布矩阵的逆矩阵变换到local坐标。测试用例 coord.spec.ts 验证了该流程:pageToLocalPoint确实先 diff 页面偏移再做逆变换。

3.2 localToPage(...):画布本地坐标 → 页面坐标

localToPage(rect: Rectangle.RectangleLike): Rectangle localToPage(x: number, y: number, width: number, height: number): Rectangle localToPage(p: Point.PointLike): Point localToPage(x: number, y: number): Point

将画布本地坐标转换为页面坐标,是pageToLocal的逆运算。典型场景:已知某节点的本地坐标,需要在页面层(如 DOM 定位的浮动面板)渲染一个跟随它的元素。

底层实现(coord.ts):

localToPagePoint(x, y) { const p = this.localToGraphPoint(x, y) return p.translate(this.getPageOffset()) }

先用画布矩阵变换到graph坐标,再加上getPageOffset()(client 偏移 + 滚动量)。测试 coord.spec.ts 中,local 点(5,5)叠加页面偏移(2,3)得到(7,8),直接印证了这一加法运算。

3.3 clientToLocal(...):浏览器坐标 → 画布本地坐标

clientToLocal(rect: Rectangle.RectangleLike): Rectangle clientToLocal(x: number, y: number, width: number, height: number): Rectangle clientToLocal(p: Point.PointLike): Point clientToLocal(x: number, y: number): Point

将浏览器坐标(e.clientXe.clientY)转换为画布本地坐标。与pageToLocal的区别在于:这里不再关心页面滚动,直接利用屏幕 CTM 的逆矩阵完成换算。

底层实现(coord.ts):

clientToLocalPoint(x, y) { const clientPoint = Point.create(x, y) return Util.transformPoint(clientPoint, this.getClientMatrix().inverse()) }

由于getClientMatrix()本身来自stage.getScreenCTM(),其逆矩阵可以同时抵消画布视口偏移与画布变换,一步到位得到 local 坐标。

3.4 localToClient(...):画布本地坐标 → 浏览器坐标

localToClient(rect: Rectangle.RectangleLike): Rectangle localToClient(x: number, y: number, width: number, height: number): Rectangle localToClient(p: Point.PointLike): Point localToClient(x: number, y: number): Point

将画布本地坐标转换为浏览器坐标,是clientToLocal的逆运算。当需要把画布内部坐标映射为视口内的绝对定位(如自定义 Tooltip、ContextMenu 挂载到 body)时使用。

底层实现(coord.ts):直接用屏幕 CTM 变换本地点,即Util.transformPoint(localPoint, this.getClientMatrix())

3.5 localToGraph(...):画布本地坐标 → 画布坐标

localToGraph(rect: Rectangle.RectangleLike): Rectangle localToGraph(x: number, y: number, width: number, height: number): Rectangle localToGraphPoint(p: Point.PointLike): Point localToGraphPoint(x: number, y: number): Point

将画布本地坐标转换为画布坐标。graph坐标不受缩放和平移影响,因此在"判断两个节点在当前视口下是否重叠""把 local 坐标归一化到无变换坐标系中参与算法计算"等场景下非常有用。

底层实现(coord.ts):

localToGraphPoint(x, y) { const localPoint = Point.create(x, y) return Util.transformPoint(localPoint, this.graph.matrix()) }

直接应用画布当前的变换矩阵this.graph.matrix()。该矩阵由 transform.ts 中的getMatrix()提供——它读取 viewport 元素的transform属性并通过viewport.getCTM()获取矩阵,同时做了缓存(viewportTransformString变化时才重新读取)。

注意:原文档中该方法内部点版本命名为localToGraphPoint,与localToGraph等价,只是命名上更明确地表达"只处理点"。

3.6 graphToLocal(...):画布坐标 → 画布本地坐标

graphToLocal(rect: Rectangle.RectangleLike): Rectangle graphToLocal(x: number, y: number, width: number, height: number): Rectangle graphToLocal(p: Point.PointLike): Point graphToLocal(x: number, y: number): Point

将画布坐标转换为画布本地坐标,是localToGraph的逆运算。

底层实现(coord.ts):

graphToLocalPoint(x, y) { const graphPoint = Point.create(x, y) return Util.transformPoint(graphPoint, this.graph.matrix().inverse()) }

localToGraphPoint完全对称,区别仅在于使用画布矩阵的逆矩阵inverse()

3.7 snapToGrid(...):对齐到网格

snapToGrid(p: Point.PointLike): Point snapToGrid(x: number, y: number): Point

浏览器坐标转换为画布本地坐标,并对齐到画布网格。注意文档特别注明:输入是浏览器坐标(client 坐标),输出是吸附到网格后的 local 坐标。

底层实现(coord.ts):

snapToGrid(x, y) { const p = typeof x === 'number' ? this.clientToLocalPoint(x, y) : this.clientToLocalPoint(x.x, x.y) return p.snapToGrid(this.graph.getGridSize()) }

内部先执行clientToLocalPoint完成 client → local 转换,再调用Point.snapToGrid(gridSize)按当前网格大小吸附。网格大小通过graph.getGridSize()获取(见 graph.ts)。测试 coord.spec.ts 验证:网格大小为 10 时,(12, 18)吸附后横纵坐标均能被 10 整除。

四、实战场景与示例

4.1 官方演示 playground

官方文档顶部内嵌的演示代码位于 site/src/api/coord/playground/index.tsx。该演示创建了一张包含两个节点和一条边的画布,并监听mousemove事件,在鼠标移动时实时显示四种坐标系下的坐标值:

const p1 = this.graph.pageToLocal(pageX, pageY) const p2 = this.graph.localToGraph(p1) this.c.innerHTML = ` <div>Page(pageX, pageY): ${pageX} x ${pageY}</div> <div>Client(clientX, clientY): ${clientX} x ${clientY}</div> <div>Local Point: ${p1.x} x ${p1.y}</div> <div>Graph Point: ${p2.x} x ${p2.y}</div> `

配套的 settings.tsx 提供了scale(0.1~2.0)和translateX/translateY(-50~50)三个滑块,通过graph.scale()graph.translate()实时改变画布变换。你会发现:当拖动滑块改变缩放或平移后,localgraph坐标开始分离,而client坐标始终保持不变——这正是四种坐标系性质最直观的体现。

4.2 实战:点击画布空白处添加节点

这是最典型的应用:通过鼠标事件拿到clientpage坐标,转换后作为节点位置。

graph.on('blank:click', ({ e }) => { const node = graph.addNode({ x: 80, // 以 local 坐标系为准 y: 80, width: 80, height: 40, label: 'new node', }) // 把新节点的中心移动到鼠标点击处(画布已缩放平移时依然准确) const p = graph.clientToLocal(e.clientX, e.clientY) node.position(p.x - node.getSize().width / 2, p.y - node.getSize().height / 2) })

如果页面存在滚动条,用graph.pageToLocal(e.pageX, e.pageY)也可以达到同样效果;两者的差异正是clientpage坐标系的区别。

4.3 实战:在画布外渲染跟随节点的 DOM 元素

需要把节点坐标映射到页面/DOM 层时,使用localToPage

const node = graph.getCellById('node-1')! const pos = node.getPosition() // local 坐标 const rect = graph.localToPage(pos.x, pos.y, node.getSize().width, node.getSize().height) tooltip.style.left = `${rect.x}px` tooltip.style.top = `${rect.y}px`

五、方法速查与源码索引

方法方向核心底层运算实现位置
pageToLocalpage → local减页面偏移 + 画布矩阵逆变换coord.ts、coord.ts
localToPagelocal → page画布矩阵变换 + 加页面偏移coord.ts、coord.ts
clientToLocalclient → local屏幕 CTM 逆变换coord.ts、coord.ts
localToClientlocal → client屏幕 CTM 变换coord.ts、coord.ts
localToGraphlocal → graph画布矩阵变换coord.ts、coord.ts
graphToLocalgraph → local画布矩阵逆变换coord.ts、coord.ts
snapToGridclient → local(吸附网格)client 转 local + 网格吸附coord.ts

各方法的 Graph 层入口统一委托实现,见 graph.ts;CoordManager作为Graph的公开只读成员graph.coord被实例化(graph.ts),画布销毁时随之一并dispose()。全部方法的单元测试覆盖于tests/graph/coord.spec.ts,是阅读底层行为的最佳参考。

六、小结

X6 的坐标转换体系可以归纳为三个关键等式:

  • local → graph:应用graph.matrix()
  • graph → client:应用stage.getScreenCTM()
  • client → page:加上window.scrollX / window.scrollY

文档中提供的七个方法覆盖了这四个坐标系之间的主要转换路径。实际开发中,只需记住两条判断准则:处理鼠标事件坐标(e.clientX/e.clientY)优先用clientToLocal,处理页面滚动相关的定位(e.pageX/e.pageY)用pageToLocal;需要把节点位置映射回 DOM 层时反向使用localToClient/localToPage。再结合 官方演示源码 亲手拖动缩放滑块观察数值变化,就能彻底掌握这套坐标体系。

【免费下载链接】X6🚀 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询