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 中local、graph、client、page四种坐标系的含义以及pageToLocal、localToPage、clientToLocal、localToClient、localToGraph、graphToLocal、snapToGrid七个转换方法的签名、底层原理与实战用法。读完本文你将能够准确判断任意场景下该用哪个转换方法并理解每一步换算背后的矩阵运算。一、X6 中的四种坐标系在 X6 中位置计算经常需要坐标转换。文档中明确了四种坐标系理解它们是使用一切转换方法的前提local画布本地坐标系默认情况下与graph坐标系一致但会随着画布的缩放scale和平移translate发生改变。画布中所有节点的位置x、y都以local坐标系为准因此它是节点、边等模型数据实际存储所用的坐标系。graph画布坐标系即我们看到的画布视口所对应的坐标系它不会随画布缩放和平移而改变。可以把它理解为未被变换矩阵作用过的原始画布坐标系。client浏览器坐标系鼠标事件中的e.clientX、e.clientY就是相对于浏览器坐标系即相对浏览器视口左上角。page页面坐标系与client相比page额外考虑了页面在水平和垂直方向的滚动。鼠标事件中的e.pageX、e.pageY就是相对于页面坐标系即相对文档左上角。从源码结构看这四种坐标系并非凭空定义而是由 src/graph/coord.ts 中的CoordManager类统一管理。该类通过两个基础数据完成所有换算getClientMatrix()调用this.view.stage.getScreenCTM()获取 SVG 舞台相对文档的屏幕 CTMCurrent Transformation Matrix这是 local 到 client 的核心桥梁getClientOffset()/getPageOffset()分别通过svg.getBoundingClientRect()得到画布视口相对窗口的偏移再叠加window.scrollX、window.scrollY得到页面偏移。对应的单元测试见tests/graph/coord.spec.ts其中通过getBoundingClientRect的 mock 返回值如left: 10, top: 20验证了偏移计算的正确性并通过scrollX/scrollY的桩数据验证getPageOffset确实等于 client 偏移加滚动量。二、坐标系之间的关系图四个坐标系之间的换算关系可以用一句话概括page与client之间差一个页面滚动偏移graph与local之间差一个画布变换矩阵local与client之间差一个屏幕 CTM其本身已包含画布变换矩阵。X6 提供的全部转换方法本质上就是在这四个坐标系之间两两搭桥pageToLocalpage → local先减页面偏移得到 graph再用画布矩阵逆变换localToPagelocal → page先用画布矩阵变换到 graph再加页面偏移clientToLocalclient → local用屏幕 CTM 的逆矩阵变换localToClientlocal → client用屏幕 CTM 变换localToGraphlocal → graph用画布矩阵变换graphToLocalgraph → local用画布矩阵的逆变换其中clientToGraph虽然未在原文档方法列表中单独列出但实现中同样存在见 graph.ts它通过graph.matrix().multiply(clientMatrix.inverse())组合两个矩阵完成换算可视为clientToLocal与localToGraph的合成。三、坐标转换方法详解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.pageX、e.pageY这类坐标换算为节点可以直接使用的x、y。这是实现点击画布空白处添加节点右键菜单定位等交互的标配方法。底层实现coord.ts 与 coord.tspageToLocalPoint(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.tslocalToPagePoint(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.clientX、e.clientY转换为画布本地坐标。与pageToLocal的区别在于这里不再关心页面滚动直接利用屏幕 CTM 的逆矩阵完成换算。底层实现coord.tsclientToLocalPoint(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.tslocalToGraphPoint(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.tsgraphToLocalPoint(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.tssnapToGrid(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 divPage(pageX, pageY): ${pageX} x ${pageY}/div divClient(clientX, clientY): ${clientX} x ${clientY}/div divLocal Point: ${p1.x} x ${p1.y}/div divGraph Point: ${p2.x} x ${p2.y}/div 配套的 settings.tsx 提供了scale0.12.0和translateX/translateY-5050三个滑块通过graph.scale()与graph.translate()实时改变画布变换。你会发现当拖动滑块改变缩放或平移后local与graph坐标开始分离而client坐标始终保持不变——这正是四种坐标系性质最直观的体现。4.2 实战点击画布空白处添加节点这是最典型的应用通过鼠标事件拿到client或page坐标转换后作为节点位置。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)也可以达到同样效果两者的差异正是client与page坐标系的区别。4.3 实战在画布外渲染跟随节点的 DOM 元素需要把节点坐标映射到页面/DOM 层时使用localToPageconst 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.tslocalToPagelocal → page画布矩阵变换 加页面偏移coord.ts、coord.tsclientToLocalclient → local屏幕 CTM 逆变换coord.ts、coord.tslocalToClientlocal → client屏幕 CTM 变换coord.ts、coord.tslocalToGraphlocal → graph画布矩阵变换coord.ts、coord.tsgraphToLocalgraph → local画布矩阵逆变换coord.ts、coord.tssnapToGridclient → local吸附网格client 转 local 网格吸附coord.ts各方法的 Graph 层入口统一委托实现见 graph.tsCoordManager作为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),仅供参考