完全指南:定义、定位、旋转与样式定制)
X6 边标签Edge Label完全指南定义、定位、旋转与样式定制【免费下载链接】X6 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6导读X6 是一个使用 SVG 与 HTML 进行渲染的 JavaScript 图编辑引擎其边的标签Edge Label系统是内置的最灵活能力之一一条边上可以挂载多个标签每个标签拥有独立的标记结构markup、样式attrs与位置position。本文以 labels.en.md 为核心骨架结合 src/model/edge.ts 与 src/view/edge/index.ts 的源码实现系统讲解 X6 边标签的完整用法——从 Label 数据结构、默认标签合并机制到距离/偏移/旋转三种定位方式再到全局与单标签两种样式定制方案、字符串标签语法糖与单标签快捷选项。读完本文你将能够熟练地为任意边添加、查询、插入、替换与删除标签并精确控制标签在边上的落点与姿态。Edge 实例上的标签操作方法在深入了解 Label 的数据结构之前先掌握 Edge 实例上提供的 7 个标签操作方法。这些方法定义在 src/model/edge.ts 的labels区域全部围绕内部存储的labels数组展开其中appendLabel与setLabels都接受字符串或 Label 对象字符串会经由parseLabel转换成 Label 对象见 src/model/edge.ts。方法签名说明edge.getLabels()获取全部标签返回经过解析后的 Label 对象数组。edge.setLabels(labels)一次性设置全部标签传入数组或单个标签/字符串覆盖原有列表。edge.insertLabel(label, index)在指定索引处插入一个标签索引支持负数从末尾倒数。edge.appendLabel(label)在末尾追加一个标签等价于insertLabel(label, -1)。edge.setLabelAt(index, label)替换指定索引位置的标签。edge.getLabelAt(index)读取指定索引位置的标签。edge.removeLabelAt(index)删除指定索引位置的标签并返回被删除项。从源码看appendLabel直接委托给insertLabel(label, -1)src/model/edge.ts而insertLabel会先通过getLabels()取出当前列表将字符串标签用parseLabel标准化后splice进数组再调用setLabels写回 storesrc/model/edge.ts。这些方法的测试用例见tests/model/edge.spec.ts覆盖了 set/get/insert/append/remove 的完整链路。另外edge.labels也提供了get/set访问器属性与getLabels()/setLabels()等价src/model/edge.ts。Label 定义一个完整的 Label 对象由三部分组成标记结构markup、样式attrs和位置position。interface Label { markup?: Markup attrs?: Attr.CellAttrs position?: | number | { distance: number offset?: | number | { x?: number y?: number } angle?: number options?: { absoluteDistance?: boolean reverseDistance?: boolean absoluteOffset?: boolean keepGradient?: boolean ensureLegibility?: boolean } } }各字段含义markup标签的标记结构决定标签由哪些 SVG 元素rect、text、ellipse、circle等组成。attrs标签的样式以 selector 为键设置元素属性。position标签位置。当它的值是number时等价于设置position.distance。distance标签在边长度方向上的位置见下文「Position」。offset标签相对边的垂直偏移见下文「Offset」。angle标签的旋转角度见下文「Rotation」。LabelPosition类型在 src/model/edge.ts 中被定义为number | LabelPositionObject对应文档中的两种写法。视图层渲染时会把数字形式的 position 通过normalizeLabelPosition归一化为{ distance: number }对象见 src/view/edge/index.ts。默认标签创建 Edge 时可通过defaultLabel选项设置默认标签其默认值定义在 src/model/edge.ts 的Edge.defaultLabel静态属性上{ markup: [ { tagName: rect, selector: body, }, { tagName: text, selector: label, }, ], attrs: { text: { fill: #000, fontSize: 14, textAnchor: middle, textVerticalAnchor: middle, pointerEvents: none, }, rect: { ref: label, fill: #fff, rx: 3, ry: 3, refWidth: 1, refHeight: 1, refX: 0, refY: 0, }, }, position: { distance: 0.5, }, }这个默认标签由两个元素组成textselector 为label即标签文字和rectselector 为body即白色圆角背景。它默认居中显示在边的中点distance: 0.5背景为白色、圆角半径3。Edge.defaultLabel的取值在tests/model/edge.spec.ts 中有对应断言。关键在于所有自定义标签都会与默认标签做合并merge。在视图层渲染时defaultLabel.attrs与单个标签的attrs会被合并见 src/view/edge/index.ts 附近因此我们可以只提供文字属性其余沿用默认edge.appendLabel({ attrs: { text: { text: Hello Label, }, }, })这行代码即可产出一个带白色圆角背景、居中显示的 Hello Label 标签。完整的运行示例见 site/src/api/label/append-label/index.tsx。标签位置Position沿边距离通过position.distance指定标签在边长度方向上的位置默认值0.5表示边长的中点。根据取值的不同分为三种计算方式取值在[0, 1]区间内表示标签位于从起点出发、沿边长方向的比例位置相对长度。正数表示标签距离起点沿边长方向的绝对距离。负数表示标签距离终点沿边长方向的绝对距离。edge.appendLabel({ attrs: { text: { text: 0.25 } }, position: { distance: 0.25 }, }) edge.appendLabel({ attrs: { text: { text: 150 } }, position: { distance: 150 }, }) edge.appendLabel({ attrs: { text: { text: -100 } }, position: { distance: -100 }, })视图层在计算最终位置时会先判断distance是否落在(0, 1]区间内来决定是否按比例换算isDistanceRelative labelDistance 0 labelDistance 1若为相对值则乘上连接线总长度得到绝对距离见 src/view/edge/index.ts。完整示例见 site/src/api/label/label-position/index.tsx。Offset垂直偏移通过position.offset设置标签相对边的偏移默认值0表示不偏移。三种取值情况正数沿垂直于边的方向向下做绝对偏移。负数沿垂直于边的方向向上做绝对偏移。坐标对象{ x: number; y: number }在x、y两个方向上做绝对偏移。edge.appendLabel({ attrs: { text: { text: offset: 40 } }, position: { distance: 0.66, offset: 40 }, }) edge.appendLabel({ attrs: { text: { text: offset: -40 } }, position: { distance: 0.66, offset: -40 }, }) edge.appendLabel({ attrs: { text: { text: offset: { x: -40, y: 80 } } }, position: { distance: 0.66, offset: { x: -40, y: 80 }, }, })在实现层面数字偏移会先取路径在标签处的切线的法向量再把法向量旋转 -90° 并设置为偏移长度从而得到垂直于边的平移终点见 src/view/edge/index.ts。完整示例见 site/src/api/label/label-offset/index.tsx。Rotation旋转通过position.angle设置标签顺时针方向的旋转角度默认值0表示不旋转。此外还有两个配套选项position.options.keepGradient为true时标签的初始旋转角为该位置处边的切线角度后续设置的position.angle是相对这个初始角度的增量。position.options.ensureLegibility为true时会在必要时额外旋转 180°以保证标签文字的可读性例如避免文字倒置。edge.appendLabel({ attrs: { text: { text: 70°\nkeepGradient } }, position: { distance: 0.05, angle: 70, options: { keepGradient: true }, }, }) edge.appendLabel({ attrs: { text: { text: 0°\nkeepGradient } }, position: { distance: 0.3, options: { keepGradient: true }, }, }) edge.appendLabel({ attrs: { text: { text: 45° } }, position: { distance: 0.8, angle: 45 }, }) edge.appendLabel({ attrs: { text: { text: 135° } }, position: { distance: 0.9, angle: 135 }, }) edge.appendLabel({ attrs: { text: { text: 270°\nkeepGradient } }, position: { distance: 0.66, offset: 80, angle: 270, options: { keepGradient: true }, }, }) edge.appendLabel({ attrs: { text: { text: 270°\nkeepGradient\nensureLegibility } }, position: { distance: 0.66, offset: -80, angle: 270, options: { keepGradient: true, ensureLegibility: true }, }, })源码中keepGradient生效时最终角度为tangent.angle() labelAngle若同时开启ensureLegibility还会执行normalize(((angle 90) % 180) - 90)将角度归一化到 ±90° 以内避免文字上下颠倒见 src/view/edge/index.ts。完整示例见 site/src/api/label/label-rotate/index.tsx。标签样式使用markup与attrs可以自定义标签样式支持两个维度的定制。方式一创建 Edge 时全局覆盖默认标签通过defaultLabel重定义默认标签的 markup 与 attrs影响该边上的所有标签const edge graph.addEdge({ source: { x: 100, y: 40 }, target: { x: 400, y: 40 }, defaultLabel: { markup: [ { tagName: ellipse, selector: bg }, { tagName: text, selector: txt }, ], attrs: { txt: { fill: #7c68fc, textAnchor: middle, textVerticalAnchor: middle, }, bg: { ref: txt, refRx: 70%, refRy: 80%, stroke: #7c68fc, fill: white, strokeWidth: 2, }, }, }, }) edge.appendLabel({ attrs: { txt: { text: First } }, position: { distance: 0.3 }, }) edge.appendLabel({ attrs: { txt: { text: Second } }, position: { distance: 0.7 }, })这里把默认的背景元素换成ellipse两个标签都会自动获得紫色描边的椭圆背景。注意此时 selector 从默认的label/body变为了txt/bg后续设置文字必须使用新的 selector。完整示例见 site/src/api/label/label-markup/index.tsx。方式二创建单个标签时覆盖在创建单个标签时传入自己的markup与attrs只影响该标签本身。例如制作一个带红色星号角标的标签edge.appendLabel({ markup: [ { tagName: circle, selector: body }, { tagName: text, selector: label }, { tagName: circle, selector: asteriskBody }, { tagName: text, selector: asterisk }, ], attrs: { label: { text: ½, fill: #000, fontSize: 12, textAnchor: middle, textVerticalAnchor: middle, pointerEvents: none, }, body: { ref: label, fill: #fff, stroke: #000, strokeWidth: 1, refR: 1, refCx: 0, refCy: 0, }, asterisk: { ref: label, text: , fill: #ff0000, fontSize: 8, textAnchor: middle, textVerticalAnchor: middle, pointerEvents: none, refX: 16.5, refY: -2, }, asteriskBody: { ref: asterisk, fill: #fff, stroke: #000, strokeWidth: 1, refR: 1, refCx: 50%, refCy: 50%, refX: 0, refY: 0, }, }, })这个例子展示了 label markup 的核心能力通过ref属性把元素相对定位到其他元素body相对label居中、asterisk相对label偏移、asteriskBody相对asterisk居中实现多元素组合标签。完整示例见 site/src/api/label/label-attrs/index.tsx。字符串标签默认情况下创建一个带文字的标签需要写嵌套对象{ attrs: { label: { text: edge label } } }比较繁琐const edge graph.addEdge({ source, target, labels: [ { attrs: { label: { text: edge label } } }, ], }) edge.setLabels([ { attrs: { label: { text: edge label } } }, ]) edge.appendLabel({ attrs: { label: { text: edge label } }, })X6 为此提供了字符串标签语法糖直接传入字符串即可const edge graph.addEdge({ source, target, labels: [edge label], }) edge.setLabels([edge label]) edge.appendLabel(edge label)该语法糖的实现是Edge上的静态方法parseStringLabel负责把字符串转换成 Label 对象默认实现如下见 src/model/edge.tsfunction parseStringLabel(label: string): Label { return { attrs: { label: { text: label } }, } }注意这个语法糖只对系统默认标签有效。也就是说如果你用defaultLabel重定义了默认标签的markup就需要同步重写parseStringLabel否则字符串标签无法命中新的 selectorEdge.config({ defaultLabel: { markup: [ { tagName: rect, selector: body }, { tagName: text, selector: my-label }, // 修改了默认 selector ], }, }) // 同时重定义 parseStringLabel保证字符串标签可用 Edge.parseStringLabel (label: string) { return { attrs: { my-label: { text: label } }, } }parseStringLabel的默认行为与改写逻辑在测试tests/model/edge.spec.ts 中亦有覆盖。此外在setLabels、insertLabel、appendLabel、setLabelAt等入口处字符串都会经过parseLabel调用对应构造器的parseStringLabel完成转换见 src/model/edge.ts。单标签快捷方式绝大多数边最多只有一个标签因此 X6 为Edge定义了自定义选项label来支持直接传入单个标签graph.addEdge({ source, target, label: { attrs: { label: { text: edge label } }, }, })只设置文字时还可以直接使用字符串形式graph.addEdge({ source, target, label: edge label, })从源码看label是一个propHook在Edge.config的propHooks中若元数据里存在label会把它字符串经parseStringLabel转换push 进labels数组见 src/model/edge.ts。因此label本质上只是创建边时往labels里追加第一个标签的语法糖与labels: [...]是等价的。小结X6 的边标签体系可以概括为三层能力数据模型层Label对象由markup、attrs、position构成与Edge.defaultLabel深合并后渲染position支持相对/绝对 distance、数字或坐标 offset、基于切线的角度旋转及keepGradient/ensureLegibility等可读性选项。实例操作层getLabels/setLabels/insertLabel/appendLabel/setLabelAt/getLabelAt/removeLabelAt七个方法覆盖了标签生命周期的全部增删改查字符串入参统一由parseStringLabel语法糖处理。定制扩展层既可在建边时通过defaultLabel全局定制所有标签也可在单个标签内自定义 markup 组合出任意复杂结构配合单标签快捷选项label日常绘图只需一行代码即可完成标签标注。以上全部 API 的默认行为均可从 src/model/edge.ts 与 src/view/edge/index.ts 的源码中得到印证对应的交互示例分别位于 site/src/api/label 目录下的append-label、label-position、label-offset、label-rotate、label-markup、label-attrs六个子目录可直接在站点示例中查看运行效果。【免费下载链接】X6 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考