前端使用 Konva 实现可视化设计器(21)- 绘制图形(椭圆)

本章开始补充一些基础的图形绘制,比如绘制:直线、曲线、圆/椭形、矩形。这一章主要分享一下本示例是如何开始绘制一个图形的,并以绘制圆/椭形为实现目标。

请大家动动小手,给我一个免费的 Star 吧~

大家如果发现了 Bug,欢迎来提 Issue 哟~

github源码

gitee源码

示例地址

接下来主要说说:

  • 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 (
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值