Location>code7788 >text

Front-end implementation of the visual designer using Konva (22) - Drawing shapes (rectangles, lines, polylines)

Popularity:602 ℃/2024-09-10 18:20:02

This chapter shares how to use Konva to draw basic shapes: rectangles, straight lines, and polylines, and I hope you will continue to follow and support it haha!

Please give me a free Star!

If you find any bugs, please feel free to raise an Issue!

github source code

gitee source code

sample address (computing)

rectangles

First on the effect!

image
image

The realization is basically the same asFront-end Visual Designer with Konva (21) - Drawing Graphs (Ellipses)is consistent, the main difference between the size of the rectangle and the size of the ellipse is not the same way to set the size of the rectangle, in particular, do not need to set the offset. other than not to go into detail ha.

Straight lines, polylines

First on the effect!

image
image

Briefly describe the above interaction:

First, draw a straight line, fade out to draw a straight line or relatively simple, according to the record of the position of the mouse down and mouse release position, it is easy to get the points should be set to the value.

Then, along the lines of an ellipse or rectangle, there are only 2 specific "adjustment points" which represent the start and end points.

// src/Render/graphs/

// omitted

/**
 * Straight lines, polylines
 */
export class Line extends BaseGraph {
  // Omit

  constructor(render: , dropPoint: Konva.Vector2d) {
    super(render, dropPoint, {
      type: ,
      // Defines 2 adjustment points
      anchors: [{ adjustType: 'start' }, { adjustType: 'end' }].map((o) => ({
        adjustType: // Adjustment Point Type Definition
      })), { adjustType: 'start' }
      linkAnchors: [
        { x: 0, y: 0, alias: 'start' }, { x: 0, y: 0, alias: 'end' }
        { x: 0, y: 0, alias: 'end' }
      ] as []
    })

    // Create new straight and polyline
     = new ({
      name: 'graph',
      x: 0,
      y: 0,
      stroke: 'black',
      strokeWidth: 1,
      hitStrokeWidth: (5)
    })

    // Give 1 pixel to prevent the exported image toDataURL from failing
    ({
      width: 1,
      height: 1
    })

    // Add
    ()
    // Mouse down position as starting point
    ()
  }

  // Implementation: Drag-and-drop
  override drawMove(point: Konva.Vector2d): void {
    // Mouse drag offset
    const offsetX = - ,
      offsetY = -

    // Start and end points
    const linkPoints = [
      [(), ()], [() + offsetX, () + offsetY]
      [() + offsetX, () + offsetY]
    ]

    // Straight, polyline paths
    (_.flatten(linkPoints))

    // Update the anchor position of the graphic's flatten points.
    (, , )

    // Update the anchor position of the graph's link points.
    (, , )

    // Redraw
    ([, , ])
  }

  // Implementation: drag end
  override drawEnd(): void {
    if (() <= 1 && () <= 1) {
      // Add click only, no drag

      // Default size
      const width = ,
        height = width

      // Start and end points
      const linkPoints = [
        [(), ()], [() + width, () + height]
        [() + width, () + height]
      ]

      // Straight lines, polylines, position sizes
      (_.flatten(linkPoints))
    }

    // Update Adjustment points (inflection points)
    (, )

    // Update the anchor position of the graphic's adjustment point.
    (, , )

    // Update the anchor position of the graph's join point.
    (, , )

    // Alignment line clear
    ()

    // Update history
    ()

    // Redraw
    ([, , ])
  }

  // Omit
}

Adjustment points allow you to change the start and end points of a straight line or polyline.

// summarize

/**
 * straightness、broken line (continuous figure made up of straight line segments)
 */
export class Line extends BaseGraph {
  // realization:update depiction (used form a nominal expression) adjustment point (used form a nominal expression) anchor location
  static override updateAnchorShadows(
    graph: ,
    anchorShadows: [],
    shape?:
  ): void {
    if (shape) {
      const points = ()
      //
      for (const shadow of anchorShadows) {
        switch () {
          case 'start':
            ({
              x: points[0],
              y: points[1]
            })
            break
          case 'end':
            ({
              x: points[ - 2],
              y: points[ - 1]
            })
            break
        }
      }
    }
  }
  
  // summarize

  // realization:generating adjustment point
  static override createAnchorShapes(
    render: ,
    graph: ,
    anchorAndShadows: {
      anchor:
      anchorShadow:
      shape?:
    }[],
    adjustAnchor?:
  ): {
    anchorAndShadows: {
      anchor:
      anchorShadow:
      shape?:  | undefined
    }[]
  } {
    // stage state of affairs
    const stageState = ()

    const graphShape = ('.graph') as

    if (graphShape) {
      const points = ()

      for (const anchorAndShadow of anchorAndShadows) {
        let rotate = 0
        const { anchor, anchorShadow } = anchorAndShadow

        const x = (().x - ),
          y = (().y - )

        if ( === 'manual') {
          // summarize
        } else {
          if ( === 'start') {
            rotate = (points[2] - points[0], points[3] - points[1])
          } else if ( === 'end') {
            rotate = (
              points[ - 2] - points[ - 4],
              points[ - 1] - points[ - 3]
            )
          }

          const cos = ((rotate * ) / 180)
          const sin = ((rotate * ) / 180)

          const offset = ( + 5)

          const offsetX = offset * sin
          const offsetY = offset * cos

          const anchorShape = new ({
            name: 'anchor',
            anchor: anchor,
            //
            fill:
              adjustAnchor?.adjustType === && adjustAnchor?.groupId === ()
                ? 'rgba(0,0,255,0.8)'
                : 'rgba(0,0,255,0.2)',
            radius: (3),
            strokeWidth: 0,
            // placement
            x: x,
            y: y,
            offsetX:
               === 'start' ? offsetX : === 'end' ? -offsetX : 0,
            offsetY:
               === 'start' ? offsetY : === 'end' ? -offsetY : 0,
            // angle of rotation
            rotation: ()
          })

          ('mouseenter', () => {
            ('rgba(0,0,255,0.8)')
             = 'move'
          })
          ('mouseleave', () => {
            (
               ? 'rgba(0,0,255,0.8)' : 'rgba(0,0,255,0.2)'
            )
             = ? 'move' : 'default'
          })

           = anchorShape
        }
      }
    }

    return { anchorAndShadows }
  }

  // summarize

  // realization:align depiction
  static override adjust(
    render: ,
    graph: ,
    graphSnap: ,
    adjustShape: ,
    anchorAndShadows: {
      anchor:
      anchorShadow:
      shape?:  | undefined
    }[],
    startPoint: Konva.Vector2d,
    endPoint: Konva.Vector2d
  ) {
    // goal straightness、broken line (continuous figure made up of straight line segments)
    const line = ('.graph') as
    // mirroring
    const lineSnap = ('.graph') as

    // adjustment point anchor point
    const anchors = (('.anchor') ?? []) as []
    // mirroring
    const anchorsSnap = (('.anchor') ?? []) as []

    // connection point anchor point
    const linkAnchors = (('.link-anchor') ?? []) as []

    if (line && lineSnap) {
      // stage state of affairs
      const stageState = ()

      {
        const [graphRotation, adjustType, ex, ey] = [
          (()),
          ?.adjustType,
          ,
          
        ]

        const { x: cx, y: cy, width: cw, height: ch } = ()

        const { x, y } = ()

        const [centerX, centerY] = [cx + cw / 2, cy + ch / 2]

        const { x: sx, y: sy } = (ex, ey, centerX, centerY, -graphRotation)
        const { x: rx, y: ry } = (x, y, centerX, centerY, -graphRotation)

        const points = ()
        const manualPoints = ( ?? []) as []

        if (adjustType === 'manual') {
          // summarize
        } else {
          const anchor = ((o) => === adjustType)
          const anchorShadow = ((o) => === adjustType)

          if (anchor && anchorShadow) {
            {
              const linkPoints = [
                [points[0], points[1]],
                ...((a, b) => - ).map((o) => [, ]),
                [points[ - 2], points[ - 1]]
              ]

              switch (adjustType) {
                case 'start':
                  {
                    linkPoints[0] = [sx - rx, sy - ry]
                    (_.flatten(linkPoints))
                  }
                  break
                case 'end':
                  {
                    linkPoints[ - 1] = [sx - rx, sy - ry]
                    (_.flatten(linkPoints))
                  }
                  break
              }
            }
          }
        }
      }

      // update adjustment point(inflexion point (math., a point of a curve at which the curvature changes sign))
      (render, graph)

      // update adjustment point (used form a nominal expression) anchor point placement
      (graph, anchors, line)

      // update depiction (used form a nominal expression) connection point (used form a nominal expression) anchor location
      (graph, linkAnchors, line)

      // update adjustment point placement
      for (const anchor of anchors) {
        for (const { shape } of anchorAndShadows) {
          if (shape) {
            if (?.adjustType === ) {
              const anchorShadow = graph
                .find(`.anchor`)
                .find((o) => === )

              if (anchorShadow) {
                ({
                  x: (().x - ),
                  y: (().y - )
                })
                (())
              }
            }
          }
        }
      }

      // repaint
      ([, , ])
    }
  }

  // summarize
}

broken line (continuous figure made up of straight line segments)

Unlike drawing ellipses and rectangles, where the "adjustment points" are fixed, for bisectors, 2 new adjustment points are added for each new point of inflection, and the overall interaction is similar to that of a manually connected line.

image

// src/Render/draws/

// summarize

export interface GraphDrawState {
  // summarize

  /**
   * in the pipeline adjustment point
   */
  adjustAnchor?:

  /**
   * Mouse over adjustment point placement
   */
  startPointCurrent: Konva.Vector2d

  /**
   * depiction group
   */
  graphCurrent?:

  /**
   * depiction group mirroring,用于计算placement、Offset in size
   */
  graphCurrentSnap?:
}

// summarize

export class GraphDraw extends implements {
  // summarize

  state: GraphDrawState = {
    adjusting: false,
    adjustGroupId: '',
    startPointCurrent: { x: 0, y: 0 }
  }

  // summarize

  override draw() {
    ()
    // 所有depiction
    const graphs =
      .find('.asset')
      .filter((o) => === ) as []

    for (const graph of graphs) {
      // Displayed only when not selected adjustment point
      if (!) {
        // summarize

        for (const anchorAndShadow of anchorAndShadows) {
          const { shape } = anchorAndShadow

          if (shape) {
            // Mouse over
            ('mousedown', () => {
              const pos = ()
              if (pos) {
                 = true
                 =
                 = ()

                 = pos

                 = graph
                 = ()

                ('adjusting', true)

                if () {
                  switch (?.type) {
                    case :
                      // utilization straightness、broken line (continuous figure made up of straight line segments) Static processing methods
                      (, graph, , pos)
                      break
                  }
                }
              }
            })

            // summarize

            // End of adjustment
            ('mouseup', () => {
              // summarize
              
               = false
               = undefined
               = ''

              // Restore the display of all adjustment point
              for (const { shape } of anchorAndShadows) {
                if (shape) {
                  (1)
                  ('adjusting', false)
                  if (?.type === ) {
                    if () {
                      ('rgba(0,0,0,0.4)')
                    } else {
                      ('rgba(0,0,255,0.2)')
                    }
                  } else {
                    ('rgba(0,0,255,0.2)')
                  }
                }

                // summarize
              }

              // summarize
            })

            // summarize
          }
        }
      }
    }
  }
}

In addition to the need for more state logging adjustment information, the Line-specific adjustStart method needs to be defined:

// src/Render/graphs/

// summarize

/**
 * straightness、broken line (continuous figure made up of straight line segments)
 */
export class Line extends BaseGraph {
  // summarize

  /**
   * Before adjustment
   */
  static adjustStart(
    render: ,
    graph: ,
    adjustAnchor: & { manualIndex?: number; adjusted?: boolean },
    endPoint: Konva.Vector2d
  ) {
    const { x: gx, y: gy } = ()

    const shape = ('.graph') as

    if (shape && typeof === 'number') {
      const manualPoints = ( ?? []) as []
      if () {
        //
      } else {
        ({
          x: - gx,
          y:  - gy,
          index:
        })
        ('manualPoints', manualPoints)
      }

      // update adjustment point(inflexion point (math., a point of a curve at which the curvature changes sign))
      (render, graph)
    }
  }
}

// summarize

Dynamic adjustment points are recorded in the line's attrs manualPoints, each time a point of inflection is adjusted for the first time, a new point of inflection is added, which is mainly used:

// summarize

/**
 * straightness、broken line (continuous figure made up of straight line segments)
 */
export class Line extends BaseGraph {
  // summarize

  // realization:align depiction
  static override adjust(
    render: ,
    graph: ,
    graphSnap: ,
    adjustShape: ,
    anchorAndShadows: {
      anchor:
      anchorShadow:
      shape?:  | undefined
    }[],
    startPoint: Konva.Vector2d,
    endPoint: Konva.Vector2d
  ) {
    // goal straightness、broken line (continuous figure made up of straight line segments)
    const line = ('.graph') as
    // mirroring
    const lineSnap = ('.graph') as

    // align点 anchor point
    const anchors = (('.anchor') ?? []) as []
    // mirroring
    const anchorsSnap = (('.anchor') ?? []) as []

    // connection point anchor point
    const linkAnchors = (('.link-anchor') ?? []) as []

    if (line && lineSnap) {
      // stage state of affairs
      const stageState = ()

      {
        const [graphRotation, adjustType, ex, ey] = [
          (()),
          ?.adjustType,
          ,
          
        ]

        const { x: cx, y: cy, width: cw, height: ch } = ()

        const { x, y } = ()

        const [centerX, centerY] = [cx + cw / 2, cy + ch / 2]

        const { x: sx, y: sy } = (ex, ey, centerX, centerY, -graphRotation)
        const { x: rx, y: ry } = (x, y, centerX, centerY, -graphRotation)

        const points = ()
        const manualPoints = ( ?? []) as []

        if (adjustType === 'manual') {
          if (?.manualIndex !== void 0) {
            const index = ?.adjusted
              ? ?.manualIndex
              : ?.manualIndex + 1

            const manualPointIndex = ((o) => === index)

            if (manualPointIndex > -1) {
              manualPoints[manualPointIndex].x = sx - rx
              manualPoints[manualPointIndex].y = sy - ry
            }

            const linkPoints = [
              [points[0], points[1]],
              ...((a, b) => - ).map((o) => [, ]),
              [points[ - 2], points[ - 1]]
            ]

            ('manualPoints', manualPoints)

            (_.flatten(linkPoints))

            //
            const adjustAnchorShadow = (
              (o) => === 'manual' && === index
            )
            if (adjustAnchorShadow) {
              ({
                x: sx - rx,
                y: sy - ry
              })
            }
          }
        } else {
          // summarize
        }
      }

      // summarize
    }
  }

  // summarize

  /**
   * update align点(inflexion point (math., a point of a curve at which the curvature changes sign))
   * @param render
   * @param graph
   */
  static updateAnchor(render: , graph: ) {
    const anchors = ?? []
    const anchorShadows = ('.anchor') ?? []

    const shape = ('.graph') as

    if (shape) {
      // abducted
      let manualPoints = ( ?? []) as []
      const points = ()

      // align点 + inflexion point (math., a point of a curve at which the curvature changes sign)
      const linkPoints = [
        [points[0], points[1]],
        ...((a, b) => - ).map((o) => [, ]),
        [points[ - 2], points[ - 1]]
      ]

      // empty align点(inflexion point (math., a point of a curve at which the curvature changes sign)),reservations start end
      (2)
      const shadows = (2)
      for (const shadow of shadows) {
        ()
        ()
      }

      manualPoints = []

      for (let i = - 1; i > 0; i--) {
        (i, 0, [])
      }

      // align点(inflexion point (math., a point of a curve at which the curvature changes sign))
      for (let i = 1; i < - 1; i++) {
        const anchor = {
          type: ,
          adjustType: 'manual',
          //
          name: 'anchor',
          groupId: (),
          //
          manualIndex: i,
          adjusted: false
        }

        if (linkPoints[i].length === 0) {
           = false

          // additional
          const prev = linkPoints[i - 1]
          const next = linkPoints[i + 1]

          const circle = new ({
            adjustType: ,
            anchorType: ,
            name: ,
            manualIndex: ,
            radius: 0,
            // radius: (2),
            // fill: 'red',
            //
            x: (prev[0] + next[0]) / 2,
            y: (prev[1] + next[1]) / 2,
            anchor
          })

          (circle)
        } else {
           = true

          // abducted
          const circle = new ({
            adjustType: ,
            anchorType: ,
            name: ,
            manualIndex: ,
            adjusted: true,
            radius: 0,
            // radius: (2),
            // fill: 'red',
            //
            x: linkPoints[i][0],
            y: linkPoints[i][1],
            anchor
          })

          (circle)

          ({
            x: linkPoints[i][0],
            y: linkPoints[i][1],
            index:
          })
        }

        (anchor)
      }

      ('manualPoints', manualPoints)

      ('anchors', anchors)
    }
  }

  // summarize
}

The above is simply the algorithm that handles manualPoints, which controls the addition of new points of inflection, then inserts the "points" between the start and end points, and finally handles the value of the points.

Incidentally. The distinction between start, end, and inflection points is made by the adjustType field in attrs; the distinction between whether or not an inflection point has already been manipulated is made by the adjusted field in attrs; and there is a definite order to the inflection points, which is recorded in the manualIndex field in attrs.

Personally, I think, at present, the code structure and variable naming of drawing graphics are easy to produce ambiguity, try to take the time to refactor it later, everyone support support 👇!

Thanks watching~

More Stars please!

source code (computing)

gitee source code

sample address (computing)