本章开始补充一些基础的图形绘制,比如绘制:直线、曲线、圆/椭形、矩形。这一章主要分享一下本示例是如何开始绘制一个图形的,并以绘制圆/椭形为实现目标。
请大家动动小手,给我一个免费的 Star 吧~
大家如果发现了 Bug,欢迎来提 Issue 哟~
接下来主要说说:
- UI
- Graph(图形)
- canvas2svg 打补丁
- 拐点旋转修复
UI - 图形绘制类型切换
先找几个图标,增加按钮,分别代表绘制图形:直线、曲线、圆/椭形、矩形:
选中图形类型后,即可通过拖动绘制图形(绘制完成后,清空选择):
定义图形类型:
// src/Render/types.ts
/**
* 图形类型
*/
export enum GraphType {
Line = 'Line', // 直线
Curve = 'Curve', // 曲线
Rect = 'Rect', // 矩形
Circle = 'Circle' // 圆/椭圆形
}
在 Render 中记录当前图形类型,并提供修改方法与事件:
// src/Render/index.ts
// 略
// 画图类型
graphType: Types.GraphType | undefined = undefined
// 略
// 改变画图类型
changeGraphType(type?: Types.GraphType) {
this.graphType = type
this.emit('graph-type-change', this.graphType)
}
工具栏按钮通讯:
// src/components/main-header/index.vue
// 略
const emit = defineEmits([/* 略 */, 'update:graphType'])
const props = withDefaults(defineProps<{
// 略
graphType?: Types.GraphType
}>(), {
// 略
});
// 略
watch(() => props.render, () => {
if (props.render) {
// 略
props.render?.on('graph-type-change', (value) => {
emit('update:graphType', value)
})
}
}, {
immediate: true
})
// 略
function onGraph(type: Types.GraphType) {
emit('update:graphType', props.graphType === type ? undefined : type)
以上就是绘制图形的工具栏入口。
Graph - 图形定义及其相关实现
相关代码文件:
1、src/Render/graphs/BaseGraph.ts - 抽象类:定义通用属性、逻辑、外部接口定义。
2、src/Render/graphs/Circle.ts 继承 BaseGraph - 构造 圆/椭形 ;处理创建部分交互信息;关键逻辑的实现。
3、src/Render/handlers/GraphHandlers.ts - 收集图形创建所需交互信息,接着交给 Circle 静态处理方法处理。
4、src/Render/draws/GraphDraw.ts - 绘制图形、调整点 - 绘制 调整点 的锚点;收集并处理交互信息,接着并交给 Circle 静态处理方法处理。
BaseGraph 抽象类
// src/Render/graphs/BaseGraph.ts
// 略
/**
* 图形类
* 实例主要用于新建图形时,含新建同时的大小拖动。
* 静态方法主要用于新建之后,通过 调整点 调整的逻辑定义
*/
export abstract class BaseGraph {
/**
* 更新 图形 的 调整点 的 锚点位置
* @param width 图形 的 宽度
* @param height 图形 的 高度
* @param rotate 图形 的 旋转角度
* @param anchorShadows 图形 的 调整点 的 锚点
*/
static updateAnchorShadows(
width: number,
height: number,
rotate: number,
anchorShadows: Konva.Circle[]
) {
console.log('请实现 updateAnchorShadows', width, height, anchorShadows)
}
/**
* 更新 图形 的 连接点 的 锚点位置
* @param width 图形 的 宽度
* @param height 图形 的 高度
* @param rotate 图形 的 旋转角度
* @param anchors 图形 的 调整点 的 锚点
*/
static updateLinkAnchorShadows(
width: number,
height: number,
rotate: number,
linkAnchorShadows: Konva.Circle[]
) {
console.log('请实现 updateLinkAnchorShadows', width, height, linkAnchorShadows)
}
/**
* 生成 调整点
* @param render 渲染实例
* @param graph 图形
* @param anchor 调整点 定义
* @param anchorShadow 调整点 锚点
* @param adjustingId 正在操作的 调整点 id
* @returns
*/
static createAnchorShape(
render: Render,
graph: Konva.Group,
anchor: Types.GraphAnchor,
anchorShadow: Konva.Circle,
adjustType: string,
adjustGroupId: string
): Konva.Shape {
console.log('请实现 createAnchorShape', render, graph, anchor, anchorShadow, adjustingId, adjustGroupId)
return new Konva.Shape()
}
/**
* 调整 图形
* @param render 渲染实例
* @param graph 图形
* @param graphSnap 图形 的 备份
* @param rect 当前 调整点
* @param rects 所有 调整点
* @param startPoint 鼠标按下位置
* @param endPoint 鼠标拖动位置
*/
static adjust(
render: Render,
graph: Konva.Group,
graphSnap: Konva.Group,
rect: Types.GraphAnchorShape,
rects: Types.GraphAnchorShape[],
startPoint: Konva.Vector2d,
endPoint: Konva.Vector2d
) {
console.log('请实现 updateAnchorShadows', render, graph, rect, startPoint, endPoint)
}
//
protected render: Render
group: Konva.Group
id: string // 就是 group 的id
/**
* 鼠标按下位置
*/
protected dropPoint: Konva.Vector2d = {
x: 0, y: 0 }
/**
* 调整点 定义
*/
protected anchors: Types.GraphAnchor[] = []
/**
* 调整点 的 锚点
*/
protected anchorShadows: Konva.Circle[] = []
/**
* 调整点 定义
*/
protected linkAnchors: Types.LinkDrawPoint[] = []
/**
* 连接点 的 锚点
*/
protected linkAnchorShadows: Konva.Circle[] = []
constructor(
render: Render,
dropPoint: Konva.Vector2d,
config: {
anchors: Types.GraphAnchor[]
linkAnchors: Types.AssetInfoPoint[]
}
) {
this.render = render
this.dropPoint = dropPoint
this.id = nanoid()
this.group = new Konva.Group({
id: this.id,
name: 'asset',
assetType: Types.AssetType.Graph
})
// 调整点 定义
this.anchors = config.anchors.map((o) => ({
...o,
// 补充信息
name: 'anchor',
groupId: this.group.id()
}))
// 记录在 group 中
this.group.setAttr('anchors', this.anchors)
// 新建 调整点 的 锚点
for (const anchor of this.anchors) {
const circle = new Konva.Circle({
adjustType: anchor.adjustType,
name: anchor.name,
radius: 0
// radius: this.render.toStageValue(1),
// fill: 'red'
})
this.anchorShadows.push(circle)
this.group.add(circle)
}
// 连接点 定义
this.linkAnchors = config.linkAnchors.map(
(o) =>
({
...o,
id: nanoid(),
groupId: this.group.id(),
visible: false,
pairs: [],
direction: o.direction,
alias: o.alias
}) as Types.LinkDrawPoint
)
// 连接点信息
this.group.setAttrs({
points: this.linkAnchors
})
// 新建 连接点 的 锚点
for (const point of this.linkAnchors) {
const circle = new Konva.Circle({
name: 'link-anchor',
id: point.id,
x: point.x,
y: point.y,
radius: this.render.toStageValue(1),
stroke: 'rgba(0,0,255,1)',
strokeWidth: this.render.toStageValue(2),
visible: false,
direction: point.direction,
alias: point.alias
})
this.linkAnchorShadows.push(circle)
this.group.add(circle)
}
this.group.on('mouseenter', () => {
// 显示 连接点
this.render.linkTool.pointsVisible(true, this.group)
})
this.group.on('mouseleave', () => {
// 隐藏 连接点
this.render.linkTool.pointsVisible(false, this.group)
// 隐藏 hover 框
this.group.findOne('#hoverRect')?.visible(false)
})
this.render.layer.add(this.group)
this.render.redraw()
}
/**
* 调整进行时
* @param point 鼠标位置 相对位置
*/
abstract drawMove(point: Konva.Vector2d): void
/**
* 调整结束
*/
abstract drawEnd(): void
}
这里的:
- 静态方法,相当定义了绘制图形必要的工具方法,具体实现交给具体的图形类定义;
- 接着是绘制图形必要的属性及其初始化;
- 最后,抽象方法约束了图形实例必要的方法。
绘制 圆/椭形
图形是可以调整的,这里 圆/椭形 拥有 8 个 调整点:
还要考虑图形被旋转后,依然能合理调整:
调整本身也是支持磁贴的:
图形也支持 连接点:
图形类 - Circle
// src/Render/graphs/Circle.ts
// 略
/**
* 图形 圆/椭圆
*/
export class Circle extends BaseGraph {
// 实现:更新 图形 的 调整点 的 锚点位置
static override updateAnchorShadows(
width: number,
height: number,
rotate: number,
anchorShadows: Konva.Circle[]
): void {
for (