实现效果展示
画布展示
UML(统一建模语言,Unified Modeling Language)关系线是用来在UML图中表示不同类、对象或其他模型元素之间关系的图形符号。
泛化关系
组合关系
交互展示
移动线预览
移动元素预览
移动后效果
动图展示
SVG 绘制箭头连线
在 SVG 中绘制箭头有多种方法,最常见的是使用 <path> 元素来定义箭头的形状。此外,还可以使用 <line>、<polyline> 或 <polygon> 元素来绘制箭头,并通过 marker 元素为线条添加箭头标记。
path 绘制一个基础的箭头连线
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<path d="M80 115 L110 115 M110,110 L110,120 L120,115 Z"
fill="black" stroke="black" stroke-width="2"/>
</svg>
path 效果图
路径数据解析
-
M80 115 L110 115:-
M80 115: 移动到起始点 (80, 115),不绘制任何线条。 -
L110 115: 从当前点 (80, 115) 绘制一条直线到 (110, 115)。
-
-
M110,110 L110,120 L120,115 Z:-
M110,110: 移动到新的起始点 (110, 110),不绘制任何线条。注意这里的移动不会影响之前的路径部分。 -
L110,120: 从当前点 (110, 110) 绘制一条垂直线到 (110, 120)。 -
L120,115: 从当前点 (110, 120) 绘制一条斜线到 (120, 115)。 -
Z: 闭合路径,连接当前点 (120, 115) 到该子路径的起始点 (110, 110)。
-
样式属性
-
fill="black": 设置图形内部填充颜色为黑色。 -
stroke="black": 设置图形边框颜色为黑色。 -
stroke-width="2": 设置边框宽度为2个单位。
use 复用样式
- defs 标签内的坐标是在画布中的坐标
<use>的x和y属性 会为#arrow1的局部坐标系添加一个偏移量。 例如,<use href="#arrow1" x="50" y="100" />表示将#arrow1的坐标系原点移动到(50, 100)。
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<!-- <defs> 标签用于定义可重用的图形元素。这些元素不会直接显示在画布上,但可以在其他地方通过引用来使用。 -->
<defs>
<!-- 箭头样式 -->
<g id="arrow1">
<path d="M80 115 L110 115 M110,110 L110,120 L120,115 Z" />
</g>
<!-- 定义星星样式 -->
<g id="arrow2">
<path d="M30,3 L36,21 L54,21 L39,33 L45,51 L30,39 L15,51 L21,33 L6,21 L24,21 Z"
fill="gold"
stroke="orange"
stroke-width="0.9"/>
</g>
</defs>
<!-- 箭头,将 #arrow1 图形移动到 x、y 坐标作为起点 -->
<use href="#arrow1" x="0" y="0" fill="red" stroke-width="2" stroke="red"/>
<use href="#arrow1" x="0" y="20" fill="orange" stroke-width="2" stroke="orange" />
<use href="#arrow1" x="50" y="0" fill="green" stroke-width="2" stroke="green"/>
<!-- 星星 -->
<use href="#arrow2" x="0" y="0" />
<use href="#arrow2" x="0" y="50" />
</svg>
use 复用效果图
marker
适合需要自动附加到路径、方向动态适配的场景(如箭头、标记)。
- 通过 path 绘制 d 路径的折线。
- 在 defs 中定义需要复用的形状,如箭头、圆。使用
marker标签包裹。 marker-start="url(#arrow)":在折线的起点添加箭头标记,marker-mid="url(#dot)":在折线的每个中间点添加圆点标记,marker-end="url(#arrow)":在折线的终点添加箭头标记。
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- 箭头 marker 定义 -->
<marker
id="arrow"
viewBox="0 0 10 10"
refX="5"
refY="5"
markerWidth="6"
markerHeight="6"
orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
<!-- 点 marker 定义 -->
<marker
id="dot"
viewBox="0 0 10 10"
refX="5"
refY="5"
markerWidth="5"
markerHeight="5">
<circle cx="5" cy="5" r="5" fill="red" />
</marker>
</defs>
<!-- 绘制折线 ->
<path d="M15 80 L29 50 L43 60 L57 30 L 71 40 L85 15" stroke="black" fill="none"
marker-mid="url(#dot)" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
</svg>
markder 效果图
自定义 SVG 图形库
上面一节介绍了 SVG 基础图形的绘制的常见方法,如果我们需要 SVG 绘制各种图形,需要对常用的绘制操作和算法进行封装。
下图是项目中目前自定义开发的自定义库。
@hfdraw/elbow 库
monorepo 项目中使用自定义库
如何使用 @hfdraw/elbow 绘制折线
- 通过 getKeyPoints(rect1, rect2, {source: [0, 1], target: [1, 0]}) 获取形成折线的点数组 [Point1,Point2,Point3]。
- 将返回的数组连接起来形成折线。
/**
* 计算两个矩形之间折线连接的关键路径点
* @param rect1 第一个矩形的顶点坐标数组(例如矩形的四个角坐标)
* @param rect2 第二个矩形的顶点坐标数组
* @param element 包含连接线源端肘点在 rect1 上的比例,和目标端肘点在 rect2 上的比例
* 例如 { source: [0, 1], target: [1, 0] } 表示源肘点在 rect1 (x1, y1+height) 的位置。目标肘点在 rect2 (x2+width, y2) 位置。
* @returns Point[] 关键路径点数组,表示从 rect1源肘点 到 rect2 肘点的折线路径
*/
export function getKeyPoints(
rect1: Point[],
rect2: Point[],
element: PlaitElement
): Point[] {
const handleRefPair = getArrowLineHandleRefPair(rect1, rect2, element);
// 2. 生成折线路径的配置参数:包含路径生成所需参数的对象(例如起点、终点、障碍物信息等)
const params = getElbowLineRouteOptions(rect1, rect2, element, handleRefPair);
// 3. 根据参数生成折线路径点数组
const route = generateElbowLineRoute(params);
// 4. 去除路径中的重复点
const keyPoints = removeDuplicatePoints(route);
return keyPoints;
}
/**
* 二维坐标点类型(例如 [x, y])
*/
export type Point = [number, number];
/**
* 连接线元素定义,包含源端和目标端处理信息
*/
export interface PlaitElement {
source: ArrowLineHandle; // 源端箭头处理点(如起点)
target: ArrowLineHandle; // 目标端箭头处理点(如终点)
}
/**
* 矩形内相对坐标点(取值范围 0~1)
* - 例如 [0.5, 0.5] 表示矩形中心
* - 例如 [0, 1] 表示左下角
*/
export type PointOfRectangle = [number, number];
/**
* 箭头连接线处理点定义
*/
export interface ArrowLineHandle {
boundId?: string; // 绑定的元素ID(可选,用于关联特定图形)
connection: PointOfRectangle; // 连接点在矩形内的相对位置(归一化坐标)
}
如何使用 @hfdraw/elbow 设置箭头样式
我们可以通过起点、终点设置线的箭头样式。
- 可以通过两个 path 路径分别绘制对应的箭头样式,然后用对应变量控制是否显示。
- 因为箭头本身有大小,在绘制箭头的时候线需要裁剪掉箭头的宽度。所以在计算
computedData.pathData如果有箭头需要对应的处理。
<template>
<g style="cursor: pointer;">
<!-- 开始箭头 -->
<path :d="computedData.startArrow" :stroke="StrokeColor" :stroke-width="computedData.style.strokeWidth"
:fill="computedData.style.arrowStyle?.fillStart || 'none'" />
<!-- 结束箭头 -->
<path :d="computedData.endArrow" :stroke="StrokeColor" :stroke-width="computedData.style.strokeWidth"
:fill="computedData.style.arrowStyle?.fillEnd || 'none'" />
<!-- 折线 -->
<path :d="computedData.pathData" stroke="rgba(21,71,146,0.5)" :stroke-width="computedData.style.strokeWidth"
fill="none" />
<!-- 加粗的折线,不展示只用于事件监听 -->
<path :d="computedData.pathData" stroke="rgba(0,0,0,0)" fill="none" stroke-width="4" v-on="eventHandler" />
</g>
</template>
总结
本文介绍了使用 SVG 绘制关系连线几种基础的方法。并封装了 @hfdraw/elbow 库用于计算图形任意位置连线的点。最终可以通过各种连线的标识了描述 UML 图中各种关系。
往期相关文章
感谢看到完整结尾,如果你对手写 UML 系列其他文章感兴趣可以查看其他相关文章。我也会持续分享我的开发过程。如果喜欢可以点赞和关注,不错过更多分享!