# 基础图形
插件内置了丰富的基础图形,如下是相关实例,你还可以尝试拖拽他们。
点击以展开或折叠内置图形 Shape 类型定义
 import { Point } from '../core/graph'
import { BezierCurve } from '@jiaminghi/bezier-curve/types/types'
export type CircleShape = {
  rx: number
  ry: number
  r: number
}
export type EllipseShape = {
  rx: number
  ry: number
  hr: number
  vr: number
}
export type RectShape = {
  x: number
  y: number
  w: number
  h: number
}
export type RingShape = {
  rx: number
  ry: number
  r: number
}
export type ArcShape = {
  rx: number
  ry: number
  r: number
  startAngle: number
  endAngle: number
  clockWise: boolean
}
export type SectorShape = {
  rx: number
  ry: number
  r: number
  startAngle: number
  endAngle: number
  clockWise: boolean
}
export type RegPolygonShape = {
  rx: number
  ry: number
  r: number
  side: number
}
export type RegPolygonShapeCache = {
  points?: Point[]
} & Partial<RegPolygonShape>
export type PolylineShape = {
  points: Point[]
  close: boolean
}
export type SmoothlineShape = {
  points: Point[]
  close: boolean
}
export type SmoothlineShapeCache = {
  points?: Point[]
  bezierCurve?: BezierCurve
  hoverPoints?: Point[]
}
export type BezierCurveShape = {
  points: BezierCurve | []
  close: boolean
}
export type BezierCurveShapeCache = {
  points?: BezierCurve
  hoverPoints?: Point[]
}
export type TextShape = {
  content: string
  position: [number, number]
  maxWidth: undefined | number
  rowGap: number
}
export type TextShapeCache = {
  x?: number
  y?: number
  w?: number
  h?: number
}
# 圆形
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| rx | number |  0 |  圆心x轴坐标 | 
| ry | number |  0 |  圆心y轴坐标 | 
| r | number |  0 |  圆半径 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  return {
    name: 'Circle',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      rx: w / 2,
      ry: h / 2,
      r: 50,
    },
    style: {
      fill: '#9ce5f4',
      shadowBlur: 0,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
    },
    onMouseEnter(e) {
      this.animation('shape', { r: 70 }, true)
      this.animation('style', { shadowBlur: 20 })
    },
    onMouseOuter(e) {
      this.animation('shape', { r: 50 }, true)
      this.animation('style', { shadowBlur: 0 })
    },
  }
}
点击以展开或折叠 Circle 实现
 import { CircleShape } from '../types/graphs/shape'
import { checkPointIsInCircle } from '../utils/graphs'
import Graph from '../core/graph.class'
import { GraphConfig, Point } from '../types/core/graph'
class Circle extends Graph<CircleShape> {
  name = 'circle'
  constructor(config: GraphConfig<Partial<CircleShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          rx: 0,
          ry: 0,
          r: 0,
        },
        config,
        ({ shape: { rx, ry, r } }) => {
          if (typeof rx !== 'number' || typeof ry !== 'number' || typeof r !== 'number')
            throw new Error('CRender Graph Circle: Circle shape configuration is invalid!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      render: { ctx },
    } = this
    const { rx, ry, r } = shape
    ctx.beginPath()
    ctx.arc(rx, ry, r > 0 ? r : 0, 0, Math.PI * 2)
    ctx.fill()
    ctx.stroke()
  }
  hoverCheck(point: Point): boolean {
    const { shape } = this
    return checkPointIsInCircle(point, shape)
  }
  setGraphCenter(): void {
    const { shape, style } = this
    const { rx, ry } = shape
    style.graphCenter = [rx, ry]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const { shape } = this
    this.attr('shape', {
      rx: shape.rx + movementX,
      ry: shape.ry + movementY,
    })
  }
}
export default Circle
# 椭圆形
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| rx | number |  0 |  圆心x轴坐标 | 
| ry | number |  0 |  圆心y轴坐标 | 
| hr | number |  0 |  横轴半径 | 
| vr | number |  0 |  竖轴半径 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  return {
    name: 'Ellipse',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      rx: w / 2,
      ry: h / 2,
      hr: 80,
      vr: 30,
    },
    style: {
      fill: '#9ce5f4',
      shadowBlur: 0,
      shadowColor: '#66eece',
      scale: [1, 1],
      hoverCursor: 'pointer',
    },
    onMouseEnter(e) {
      this.animation('style', { scale: [1.5, 1.5], shadowBlur: 20 })
    },
    onMouseOuter(e) {
      this.animation('style', { scale: [1, 1], shadowBlur: 0 })
    },
  }
}
点击以展开或折叠 Ellipse 实现
 import { EllipseShape } from '../types/graphs/shape'
import { getTwoPointDistance } from '../utils/graphs'
import { Point, GraphConfig } from '../types/core/graph'
import Graph from '../core/graph.class'
class Ellipse extends Graph<EllipseShape> {
  name = 'ellipse'
  constructor(config: GraphConfig<Partial<EllipseShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          rx: 0,
          ry: 0,
          hr: 0,
          vr: 0,
        },
        config,
        ({ shape: { rx, ry, hr, vr } }) => {
          if (
            typeof rx !== 'number' ||
            typeof ry !== 'number' ||
            typeof hr !== 'number' ||
            typeof vr !== 'number'
          )
            throw new Error('CRender Graph Ellipse: Ellipse shape configuration is invalid!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      render: { ctx },
    } = this
    const { rx, ry, hr, vr } = shape
    ctx.beginPath()
    ctx.ellipse(rx, ry, hr > 0 ? hr : 0, vr > 0 ? vr : 0, 0, 0, Math.PI * 2)
    ctx.fill()
    ctx.stroke()
  }
  hoverCheck(point: Point): boolean {
    const { shape } = this
    const { rx, ry, hr, vr } = shape
    const a = Math.max(hr, vr)
    const b = Math.min(hr, vr)
    const c = Math.sqrt(a * a - b * b)
    const leftFocusPoint: Point = [rx - c, ry]
    const rightFocusPoint: Point = [rx + c, ry]
    const distance =
      getTwoPointDistance(point, leftFocusPoint) + getTwoPointDistance(point, rightFocusPoint)
    return distance <= 2 * a
  }
  setGraphCenter(): void {
    const { shape, style } = this
    const { rx, ry } = shape
    style.graphCenter = [rx, ry]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const { shape } = this
    this.attr('shape', {
      rx: shape.rx + movementX,
      ry: shape.ry + movementY,
    })
  }
}
export default Ellipse
# 矩形
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| x | number |  0 |  矩形左上角x轴坐标 | 
| y | number |  0 |  矩形左上角y轴坐标 | 
| w | number |  0 |  矩形宽度 | 
| h | number |  0 |  矩形高度 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  const rectWidth = 200
  const rectHeight = 50
  return {
    name: 'Rect',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      x: w / 2 - rectWidth / 2,
      y: h / 2 - rectHeight / 2,
      w: rectWidth,
      h: rectHeight,
    },
    style: {
      fill: '#9ce5f4',
      shadowBlur: 0,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
      translate: [0, 0],
    },
    onMouseEnter(e) {
      this.animation('shape', { w: 400 }, true)
      this.animation('style', { shadowBlur: 20, translate: [-100, 0] })
    },
    onMouseOuter(e) {
      this.animation('shape', { w: 200 }, true)
      this.animation('style', { shadowBlur: 0, translate: [0, 0] })
    },
  }
}
点击以展开或折叠 Rect 实现
 import { RectShape } from '../types/graphs/shape'
import { checkPointIsInRect } from '../utils/graphs'
import Graph from '../core/graph.class'
import { GraphConfig, Point } from '../types/core/graph'
class Rect extends Graph<RectShape> {
  name = 'rect'
  constructor(config: GraphConfig<Partial<RectShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          x: 0,
          y: 0,
          w: 0,
          h: 0,
        },
        config,
        ({ shape: { x, y, w, h } }) => {
          if (
            typeof x !== 'number' ||
            typeof y !== 'number' ||
            typeof w !== 'number' ||
            typeof h !== 'number'
          )
            throw new Error('CRender Graph Rect: Rect shape configuration is invalid!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      render: { ctx },
    } = this
    const { x, y, w, h } = shape
    ctx.beginPath()
    ctx.rect(x, y, w, h)
    ctx.fill()
    ctx.stroke()
  }
  hoverCheck(point: Point): boolean {
    const { shape } = this
    return checkPointIsInRect(point, shape)
  }
  setGraphCenter(): void {
    const { shape, style } = this
    const { x, y, w, h } = shape
    style.graphCenter = [x + w / 2, y + h / 2]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const { shape } = this
    this.attr('shape', {
      x: shape.x + movementX,
      y: shape.y + movementY,
    })
  }
}
export default Rect
# 环形
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| rx | number |  0 |  中心点x轴坐标 | 
| ry | number |  0 |  中心点y轴坐标 | 
| r | number |  0 |  环半径 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  return {
    name: 'Ring',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      rx: w / 2,
      ry: h / 2,
      r: 50,
    },
    style: {
      stroke: '#9ce5f4',
      lineWidth: 20,
      hoverCursor: 'pointer',
      shadowBlur: 0,
      shadowColor: '#66eece',
    },
    onMouseEnter(e) {
      this.animation('style', { shadowBlur: 20, lineWidth: 30 })
    },
    onMouseOuter(e) {
      this.animation('style', { shadowBlur: 0, lineWidth: 20 })
    },
  }
}
点击以展开或折叠 Ring 实现
 import { RingShape } from '../types/graphs/shape'
import { getTwoPointDistance } from '../utils/graphs'
import Graph from '../core/graph.class'
import { GraphConfig, Point } from '../types/core/graph'
class Ring extends Graph<RingShape> {
  name = 'ring'
  constructor(config: GraphConfig<Partial<RingShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          rx: 0,
          ry: 0,
          r: 0,
        },
        config,
        ({ shape: { rx, ry, r } }) => {
          if (typeof rx !== 'number' || typeof ry !== 'number' || typeof r !== 'number')
            throw new Error('CRender Graph Ring: Ring shape configuration is invalid!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      render: { ctx },
    } = this
    const { rx, ry, r } = shape
    ctx.beginPath()
    ctx.arc(rx, ry, r > 0 ? r : 0, 0, Math.PI * 2)
    ctx.stroke()
  }
  hoverCheck(point: Point): boolean {
    const { shape, style } = this
    const { rx, ry, r } = shape
    const { lineWidth } = style
    const halfLineWidth = lineWidth / 2
    const minDistance = r - halfLineWidth
    const maxDistance = r + halfLineWidth
    const distance = getTwoPointDistance(point, [rx, ry])
    return distance >= minDistance && distance <= maxDistance
  }
  setGraphCenter(): void {
    const { shape, style } = this
    const { rx, ry } = shape
    style.graphCenter = [rx, ry]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const { shape } = this
    this.attr('shape', {
      rx: shape.rx + movementX,
      ry: shape.ry + movementY,
    })
  }
}
export default Ring
# 弧形
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| rx | number |  0 |  中心点x轴坐标 | 
| ry | number |  0 |  中心点y轴坐标 | 
| r | number |  0 |  弧半径 | 
| startAngle | number |  0 |  弧起始弧度值 | 
| endAngle | number |  0 |  弧结束弧度值 | 
| clockWise | boolean |  true |  是否顺时针 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  return {
    name: 'Arc',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      rx: w / 2,
      ry: h / 2,
      r: 60,
      startAngle: 0,
      endAngle: Math.PI / 3,
    },
    style: {
      stroke: '#9ce5f4',
      lineWidth: 20,
      shadowBlur: 0,
      rotate: 0,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
    },
    onMouseEnter() {
      this.animation('shape', { endAngle: Math.PI }, true)
      this.animation('style', { shadowBlur: 20, rotate: -30, lineWidth: 30 })
    },
    onMouseOuter() {
      this.animation('shape', { endAngle: Math.PI / 3 }, true)
      this.animation('style', { shadowBlur: 0, rotate: 0, lineWidth: 20 })
    },
  }
}
点击以展开或折叠 Arc 实现
 import Graph from '../core/graph.class'
import { ArcShape } from '../types/graphs/shape'
import { checkPointIsInSector } from '../utils/graphs'
import { GraphConfig, Point } from '../types/core/graph'
class Arc extends Graph<ArcShape> {
  name = 'arc'
  constructor(config: GraphConfig<Partial<ArcShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          rx: 0,
          ry: 0,
          r: 0,
          startAngle: 0,
          endAngle: 0,
          clockWise: true,
        },
        config,
        ({ shape }) => {
          const keys: (keyof ArcShape)[] = ['rx', 'ry', 'r', 'startAngle', 'endAngle']
          if (keys.find(key => typeof shape[key] !== 'number'))
            throw new Error('CRender Graph Arc: Arc shape configuration is invalid!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      render: { ctx },
    } = this
    const { rx, ry, r, startAngle, endAngle, clockWise } = shape
    ctx.beginPath()
    ctx.arc(rx, ry, r > 0 ? r : 0, startAngle, endAngle, !clockWise)
    ctx.stroke()
  }
  hoverCheck(point: Point): boolean {
    const { shape, style } = this
    const { rx, ry, r, startAngle, endAngle, clockWise } = shape
    const { lineWidth } = style
    const halfLineWidth = lineWidth / 2
    const insideRadius = r - halfLineWidth
    const outsideRadius = r + halfLineWidth
    const inSide = checkPointIsInSector(point, {
      rx,
      ry,
      r: insideRadius,
      startAngle,
      endAngle,
      clockWise,
    })
    const outSide = checkPointIsInSector(point, {
      rx,
      ry,
      r: outsideRadius,
      startAngle,
      endAngle,
      clockWise,
    })
    return !inSide && outSide
  }
  setGraphCenter(): void {
    const { shape, style } = this
    const { rx, ry } = shape
    style.graphCenter = [rx, ry]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const { shape } = this
    this.attr('shape', {
      rx: shape.rx + movementX,
      ry: shape.ry + movementY,
    })
  }
}
export default Arc
# 扇形
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| rx | number |  0 |  中心点x轴坐标 | 
| ry | number |  0 |  中心点y轴坐标 | 
| r | number |  0 |  扇形半径 | 
| startAngle | number |  0 |  扇形起始弧度值 | 
| endAngle | number |  0 |  扇形结束弧度值 | 
| clockWise | boolean |  true |  是否顺时针 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  return {
    name: 'Sector',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      rx: w / 2,
      ry: h / 2,
      r: 60,
      startAngle: 0,
      endAngle: Math.PI / 3,
    },
    style: {
      fill: '#9ce5f4',
      shadowBlur: 0,
      rotate: 0,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
    },
    onMouseEnter(e) {
      this.animation('shape', { endAngle: Math.PI, r: 70 }, true)
      this.animation('style', { shadowBlur: 20, rotate: -30, lineWidth: 30 })
    },
    onMouseOuter(e) {
      this.animation('shape', { endAngle: Math.PI / 3, r: 60 }, true)
      this.animation('style', { shadowBlur: 0, rotate: 0, lineWidth: 20 })
    },
  }
}
点击以展开或折叠 Sector 实现
 import { SectorShape } from '../types/graphs/shape'
import { checkPointIsInSector } from '../utils/graphs'
import Graph from '../core/graph.class'
import { GraphConfig, Point } from '../types/core/graph'
class Sector extends Graph<SectorShape> {
  name = 'sector'
  constructor(config: GraphConfig<Partial<SectorShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          rx: 0,
          ry: 0,
          r: 0,
          startAngle: 0,
          endAngle: 0,
          clockWise: true,
        },
        config,
        ({ shape }) => {
          const keys: (keyof SectorShape)[] = ['rx', 'ry', 'r', 'startAngle', 'endAngle']
          if (keys.find(key => typeof shape[key] !== 'number'))
            throw new Error('CRender Graph Sector: Sector shape configuration is invalid!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      render: { ctx },
    } = this
    const { rx, ry, r, startAngle, endAngle, clockWise } = shape
    ctx.beginPath()
    ctx.arc(rx, ry, r > 0 ? r : 0, startAngle, endAngle, !clockWise)
    ctx.lineTo(rx, ry)
    ctx.closePath()
    ctx.stroke()
    ctx.fill()
  }
  hoverCheck(point: Point): boolean {
    const { shape } = this
    return checkPointIsInSector(point, shape)
  }
  setGraphCenter(): void {
    const { shape, style } = this
    const { rx, ry } = shape
    style.graphCenter = [rx, ry]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const { shape } = this
    const { rx, ry } = shape
    this.attr('shape', {
      rx: rx + movementX,
      ry: ry + movementY,
    })
  }
}
export default Sector
# 正多边形
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| rx | number |  0 |  中心点x轴坐标 | 
| ry | number |  0 |  中心点y轴坐标 | 
| r | number |  0 |  外接圆半径 | 
| side | number |  0 |  边数 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  return {
    name: 'RegPolygon',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      rx: w / 2,
      ry: h / 2,
      r: 60,
      side: 6,
    },
    style: {
      fill: '#9ce5f4',
      hoverCursor: 'pointer',
      shadowBlur: 0,
      rotate: 0,
      shadowColor: '#66eece',
    },
    onMouseEnter(e) {
      this.animation('shape', { endAngle: Math.PI, r: 100 }, true)
      this.animation('style', { shadowBlur: 20, rotate: 180 })
    },
    onMouseOuter(e) {
      this.animation('shape', { endAngle: Math.PI / 3, r: 60 }, true)
      this.animation('style', { shadowBlur: 0, rotate: 0 })
    },
  }
}
点击以展开或折叠 RegPolygon 实现
 import { RegPolygonShape, RegPolygonShapeCache } from '../types/graphs/shape'
import { getRegularPolygonPoints, checkPointIsInPolygon } from '../utils/graphs'
import { drawPolylinePath } from '../utils/canvas'
import Graph from '../core/graph.class'
import { GraphConfig, Point } from '../types/core/graph'
class RegPolygon extends Graph<RegPolygonShape> {
  name = 'regPolygon'
  private cache: RegPolygonShapeCache = {}
  constructor(config: GraphConfig<Partial<RegPolygonShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          rx: 0,
          ry: 0,
          r: 0,
          side: 0,
        },
        config,
        ({ shape }) => {
          const { side } = shape
          const keys: (keyof RegPolygonShape)[] = ['rx', 'ry', 'r', 'side']
          if (keys.find(key => typeof shape[key] !== 'number'))
            throw new Error('CRender Graph RegPolygon: RegPolygon shape configuration is invalid!')
          if (side! < 3) throw new Error('CRender Graph RegPolygon: RegPolygon at least trigon!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      cache,
      render: { ctx },
    } = this
    const { rx, ry, r, side } = shape
    if (
      cache.points ||
      cache.rx !== rx ||
      cache.ry !== ry ||
      cache.r !== r ||
      cache.side !== side
    ) {
      const points = getRegularPolygonPoints(shape)
      Object.assign(cache, { points, rx, ry, r, side })
    }
    const { points } = cache!
    ctx.beginPath()
    drawPolylinePath(ctx, points!)
    ctx.closePath()
    ctx.stroke()
    ctx.fill()
  }
  hoverCheck(point: Point): boolean {
    const { points } = this.cache!
    return checkPointIsInPolygon(point, points!)
  }
  setGraphCenter(): void {
    const { shape, style } = this
    const { rx, ry } = shape
    style.graphCenter = [rx, ry]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const { shape, cache } = this
    const { rx, ry } = shape
    cache.rx! += movementX
    cache.ry! += movementY
    this.attr('shape', {
      rx: rx + movementX,
      ry: ry + movementY,
    })
    cache.points = cache.points!.map(([x, y]) => [x + movementX, y + movementY])
  }
}
export default RegPolygon
# 折线
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| points | Point[] |  [] |  构成折线的点 | 
| close | boolean |  false |  是否闭合折线 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  const top = h / 3
  const bottom = (h / 3) * 2
  const gap = w / 10
  const beginX = w / 2 - gap * 2
  const points = new Array(5).fill('').map((t, i) => [beginX + gap * i, i % 2 === 0 ? top : bottom])
  return {
    name: 'Polyline',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      points,
    },
    style: {
      stroke: '#9ce5f4',
      shadowBlur: 0,
      lineWidth: 10,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
    },
    onMouseEnter(e) {
      this.animation('style', { lineWidth: 20, shadowBlur: 20 })
    },
    onMouseOuter(e) {
      this.animation('style', { lineWidth: 10, shadowBlur: 0 })
    },
  }
}
点击以展开或折叠 Polyline 实现
 import { PolylineShape } from '../types/graphs/shape'
import { drawPolylinePath } from '../utils/canvas'
import { eliminateBlur, checkPointIsInPolygon, checkPointIsNearPolyline } from '../utils/graphs'
import Graph from '../core/graph.class'
import { GraphConfig, Point } from '../types/core/graph'
class Polyline extends Graph<PolylineShape> {
  name = 'polyline'
  constructor(config: GraphConfig<Partial<PolylineShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          points: [],
          close: false,
        },
        config,
        ({ shape: { points } }) => {
          if (!(points instanceof Array))
            throw new Error('CRender Graph Polyline: Polyline points should be an array!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      style: { lineWidth },
      render: { ctx },
    } = this
    const { points, close } = shape
    ctx.beginPath()
    drawPolylinePath(ctx, lineWidth === 1 ? eliminateBlur(points) : points)
    if (close) {
      ctx.closePath()
      ctx.fill()
      ctx.stroke()
    } else {
      ctx.stroke()
    }
  }
  hoverCheck(point: Point): boolean {
    const { shape, style } = this
    const { points, close } = shape
    const { lineWidth } = style
    if (close) {
      return checkPointIsInPolygon(point, points)
    } else {
      return checkPointIsNearPolyline(point, points, lineWidth)
    }
  }
  setGraphCenter(): void {
    const { shape, style } = this
    const { points } = shape
    style.graphCenter = points[0]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const {
      shape: { points },
    } = this
    const moveAfterPoints = points.map(([x, y]) => [x + movementX, y + movementY])
    this.attr('shape', {
      points: moveAfterPoints,
    })
  }
}
export default Polyline
# 折线(闭合)
点击以展开或折叠演示配置
 import { deepClone } from '../../../es/utils/common'
export default function (render) {
  const {
    area: [w, h],
  } = render
  const top = h / 3
  const bottom = (h / 3) * 2
  const gap = w / 10
  const beginX = w / 2 - gap * 2
  const points = new Array(5).fill('').map((t, i) => [beginX + gap * i, i % 2 === 0 ? top : bottom])
  points[2][1] += top * 1.3
  return {
    name: 'Polyline',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      points,
      close: true,
    },
    style: {
      fill: '#9ce5f4',
      shadowBlur: 0,
      lineWidth: 10,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
    },
    onMouseEnter(e) {
      this.animation('style', { shadowBlur: 20 }, true)
      const pointsCloned = deepClone(this.shape.points)
      pointsCloned[2][1] += top * 0.3
      this.animation('shape', { points: pointsCloned })
    },
    onMouseOuter(e) {
      this.animation('style', { shadowBlur: 0 }, true)
      const pointsCloned = deepClone(this.shape.points)
      pointsCloned[2][1] -= top * 0.3
      this.animation('shape', { points: pointsCloned })
    },
  }
}
# 光滑曲线
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| points | Point[] |  [] |  构成光滑曲线的点 | 
| close | boolean |  false |  是否闭合光滑曲线 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  const top = h / 3
  const bottom = (h / 3) * 2
  const gap = w / 10
  const beginX = w / 2 - gap * 2
  const points = new Array(5).fill('').map((t, i) => [beginX + gap * i, i % 2 === 0 ? top : bottom])
  return {
    name: 'Smoothline',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      points,
    },
    style: {
      stroke: '#9ce5f4',
      shadowBlur: 0,
      lineWidth: 10,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
    },
    onMouseEnter(e) {
      this.animation('style', { lineWidth: 20, shadowBlur: 20 })
    },
    onMouseOuter(e) {
      this.animation('style', { lineWidth: 10, shadowBlur: 0 })
    },
  }
}
点击以展开或折叠 Smoothline 实现
 import { Point, GraphConfig } from '../types/core/graph'
import { SmoothlineShape, SmoothlineShapeCache } from '../types/graphs/shape'
import { deepClone } from '../utils/common'
import { drawBezierCurvePath } from '../utils/canvas'
import { checkPointIsInPolygon, checkPointIsNearPolyline } from '../utils/graphs'
import Graph from '../core/graph.class'
import { polylineToBezierCurve, bezierCurveToPolyline } from '@jiaminghi/bezier-curve'
import { BezierCurveSegment, BezierCurve } from '@jiaminghi/bezier-curve/types/types'
class Smoothline extends Graph<SmoothlineShape> {
  name = 'smoothline'
  private cache: SmoothlineShapeCache = {}
  constructor(config: GraphConfig<Partial<SmoothlineShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          points: [],
          close: false,
        },
        config,
        ({ shape: { points } }) => {
          if (!(points instanceof Array))
            throw new Error('CRender Graph Smoothline: Smoothline points should be an array!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      cache,
      render: { ctx },
    } = this
    const { points, close } = shape
    if (!cache.points || cache.points.toString() !== points.toString()) {
      const bezierCurve = polylineToBezierCurve(points, close)
      const hoverPoints = bezierCurveToPolyline(bezierCurve)
      Object.assign(cache, {
        points: deepClone(points),
        bezierCurve,
        hoverPoints,
      })
    }
    const { bezierCurve } = cache
    ctx.beginPath()
    drawBezierCurvePath(ctx, bezierCurve!.slice(1) as Point[][], bezierCurve![0])
    if (close) {
      ctx.closePath()
      ctx.fill()
      ctx.stroke()
    } else {
      ctx.stroke()
    }
  }
  hoverCheck(point: Point): boolean {
    const { cache, shape, style } = this
    const { hoverPoints } = cache
    const { close } = shape
    const { lineWidth } = style
    if (close) {
      return checkPointIsInPolygon(point, hoverPoints!)
    } else {
      return checkPointIsNearPolyline(point, hoverPoints!, lineWidth)
    }
  }
  setGraphCenter(): void {
    const {
      shape: { points },
      style,
    } = this
    style.graphCenter = points[0]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const { shape, cache } = this
    const { points } = shape
    const moveAfterPoints = points.map<Point>(([x, y]) => [x + movementX, y + movementY])
    cache.points = moveAfterPoints
    const [fx, fy] = cache.bezierCurve![0]
    const curves = cache.bezierCurve!.slice(1)
    cache.bezierCurve = [
      [fx + movementX, fy + movementY],
      ...(curves as BezierCurveSegment[]).map(curve =>
        curve.map<Point>(([x, y]) => [x + movementX, y + movementY])
      ),
    ] as BezierCurve
    cache.hoverPoints = cache.hoverPoints!.map(([x, y]) => [x + movementX, y + movementY])
    this.attr('shape', {
      points: moveAfterPoints,
    })
  }
}
export default Smoothline
# 光滑曲线(闭合)
点击以展开或折叠演示配置
 import { getCircleRadianPoint } from '../../../es/utils/graphs'
function getPoints(radius, centerPoint, pointNum) {
  const PIDived = (Math.PI * 2) / pointNum
  const points = new Array(pointNum)
    .fill('')
    .map((foo, i) => getCircleRadianPoint(...centerPoint, radius, PIDived * i))
  return points
}
export default function (render) {
  const {
    area: [w, h],
  } = render
  const radius = h / 3
  const centerPoint = [w / 2, h / 2]
  return {
    name: 'Smoothline',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      points: getPoints(radius, centerPoint, 3),
      close: true,
    },
    style: {
      fill: '#9ce5f4',
      shadowBlur: 0,
      lineWidth: 10,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
      rotate: 0,
    },
    onMouseEnter(e) {
      this.animation('style', { lineWidth: 20, shadowBlur: 20, rotate: 120 })
    },
    onMouseOuter(e) {
      this.animation('style', { lineWidth: 10, shadowBlur: 0, rotate: 0 })
    },
    setGraphCenter(e) {
      const { style } = this
      if (e) {
        const { movementX, movementY } = e
        const [cx, cy] = style.graphCenter
        style.graphCenter = [cx + movementX, cy + movementY]
      } else {
        style.graphCenter = [...centerPoint]
      }
    },
  }
}
# 贝塞尔曲线
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| points | BezierCurve | [] |  [] |  构成贝塞尔曲线的点 | 
| close | boolean |  false |  是否闭合贝塞尔曲线 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  const offsetX = w / 2
  const offsetY = h / 2
  const points = [
    // 起始点
    [-100 + offsetX, -50 + offsetY],
    // N组贝塞尔曲线数据
    [
      // 贝塞尔曲线控制点1,控制点2,结束点
      [0 + offsetX, -50 + offsetY],
      [0 + offsetX, 50 + offsetY],
      [100 + offsetX, 50 + offsetY],
    ],
  ]
  return {
    name: 'BezierCurve',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      points,
    },
    style: {
      lineWidth: 10,
      stroke: '#9ce5f4',
      shadowBlur: 0,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
    },
    onMouseEnter(e) {
      this.animation('style', { lineWidth: 20, shadowBlur: 20 })
    },
    onMouseOuter(e) {
      this.animation('style', { lineWidth: 10, shadowBlur: 0 })
    },
  }
}
点击以展开或折叠 BezierCurve 实现
 import { Point, GraphConfig } from '../types/core/graph'
import { BezierCurveShape, BezierCurveShapeCache } from '../types/graphs/shape'
import { deepClone } from '../utils/common'
import { drawBezierCurvePath } from '../utils/canvas'
import { checkPointIsInPolygon, checkPointIsNearPolyline } from '../utils/graphs'
import Graph from '../core/graph.class'
import { bezierCurveToPolyline } from '@jiaminghi/bezier-curve'
import {
  BezierCurveSegment,
  BezierCurve as BezierCurveType,
} from '@jiaminghi/bezier-curve/types/types'
class BezierCurve extends Graph<BezierCurveShape> {
  name = 'bezierCurve'
  private cache: BezierCurveShapeCache = {}
  constructor(config: GraphConfig<Partial<BezierCurveShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          points: [],
          close: false,
        },
        config,
        ({ shape: { points } }) => {
          if (!(points instanceof Array))
            throw new Error('CRender Graph BezierCurve: BezierCurve points should be an array!')
        }
      )
    )
  }
  draw(): void {
    const { shape, cache, render } = this
    const { points, close } = shape
    const { ctx } = render
    if (!cache.points || cache.points.toString() !== points.toString()) {
      const hoverPoints = bezierCurveToPolyline(points as BezierCurveType, 20)
      Object.assign(cache, {
        points: deepClone(points),
        hoverPoints,
      })
    }
    ctx.beginPath()
    drawBezierCurvePath(ctx, points.slice(1) as Point[][], points[0])
    if (close) {
      ctx.closePath()
      ctx.fill()
      ctx.stroke()
    } else {
      ctx.stroke()
    }
  }
  hoverCheck(point: Point): boolean {
    const { cache, shape, style } = this
    const { hoverPoints } = cache
    const { close } = shape
    const { lineWidth } = style
    if (close) {
      return checkPointIsInPolygon(point, hoverPoints!)
    } else {
      return checkPointIsNearPolyline(point, hoverPoints!, lineWidth)
    }
  }
  setGraphCenter(): void {
    const { shape, style } = this
    const { points } = shape
    style.graphCenter = points[0]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const {
      shape: { points },
      cache,
    } = this
    const [fx, fy] = points[0] as Point
    const curves = points.slice(1)
    const bezierCurvePoints = [
      [fx + movementX, fy + movementY],
      ...(curves as BezierCurveSegment[]).map(curve =>
        curve.map(([x, y]) => [x + movementX, y + movementY])
      ),
    ] as BezierCurveType
    cache.points = bezierCurvePoints
    cache.hoverPoints = cache.hoverPoints!.map(([x, y]) => [x + movementX, y + movementY])
    this.attr('shape', {
      points: bezierCurvePoints,
    })
  }
}
export default BezierCurve
# 贝塞尔曲线(闭合)
点击以展开或折叠演示配置
 import { getCircleRadianPoint } from '../../../es/utils/graphs'
function getPetalPoints(insideRadius, outsideRadius, petalNum, petalCenter) {
  const PI2Dived = (Math.PI * 2) / (petalNum * 3)
  let points = new Array(petalNum * 3)
    .fill('')
    .map((_, i) =>
      getCircleRadianPoint(...petalCenter, i % 3 === 0 ? insideRadius : outsideRadius, PI2Dived * i)
    )
  const startPoint = points.shift()
  points.push(startPoint)
  points = new Array(petalNum).fill('').map(_ => points.splice(0, 3))
  points.unshift(startPoint)
  return points
}
export default function (render) {
  const {
    area: [w, h],
  } = render
  const petalCenter = [w / 2, h / 2]
  const [raidus1, raidus2, raidus3, raidus4] = [h / 6, h / 2.5, h / 3, h / 2]
  return {
    name: 'BezierCurve',
    animationCurve: 'easeOutBack',
    hover: true,
    drag: true,
    shape: {
      points: getPetalPoints(raidus1, raidus2, 6, petalCenter),
      close: true,
    },
    style: {
      fill: '#9ce5f4',
      shadowBlur: 0,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
    },
    onMouseEnter() {
      const {
        style: { graphCenter },
      } = this
      this.animation('style', { lineWidth: 20, shadowBlur: 20 }, true)
      this.animation('shape', { points: getPetalPoints(raidus3, raidus4, 6, graphCenter) })
    },
    onMouseOuter() {
      const {
        style: { graphCenter },
      } = this
      this.animation('style', { lineWidth: 10, shadowBlur: 0 }, true)
      this.animation('shape', { points: getPetalPoints(raidus1, raidus2, 6, graphCenter) })
    },
    setGraphCenter(e) {
      const { style } = this
      if (e) {
        const { movementX, movementY } = e
        const [cx, cy] = style.graphCenter
        style.graphCenter = [cx + movementX, cy + movementY]
      } else {
        style.graphCenter = [...petalCenter]
      }
    },
  }
}
# 文本
shape 属性表
| 属性名 | 类型 | 默认值 | 注解 | 
|---|---|---|---|
| content | string |  '' |  文本内容 | 
| position | [number, number] |  [0, 0] |  文本起始位置 | 
| maxWidth | number |  undefined |  文本最大宽度 | 
| rowGap | number |  0 |  行间距 | 
点击以展开或折叠演示配置
 export default function (render) {
  const {
    area: [w, h],
  } = render
  const centerPoint = [w / 2, h / 2]
  return {
    name: 'Text',
    animationCurve: 'easeOutBounce',
    hover: true,
    drag: true,
    shape: {
      content: 'CRender',
      position: centerPoint,
      maxWidth: 200,
    },
    style: {
      fill: '#9ce5f4',
      fontSize: 50,
      shadowBlur: 0,
      rotate: 0,
      shadowColor: '#66eece',
      hoverCursor: 'pointer',
      scale: [1, 1],
      rotate: 0,
    },
    onMouseEnter() {
      this.animation('style', { shadowBlur: 20, scale: [1.5, 1.5] })
    },
    onMouseOuter() {
      this.animation('style', { shadowBlur: 0, scale: [1, 1] })
    },
  }
}
点击以展开或折叠 Text 实现
 import { TextShape, TextShapeCache } from '../types/graphs/shape'
import { Point, GraphConfig } from '../types/core/graph'
import Graph from '../core/graph.class'
import { checkPointIsInRect } from '../utils/graphs'
class Text extends Graph<TextShape> {
  name = 'text'
  private cache: TextShapeCache = {}
  constructor(config: GraphConfig<Partial<TextShape>>) {
    super(
      Graph.mergeDefaultShape(
        {
          content: '',
          position: [0, 0],
          maxWidth: undefined,
          rowGap: 0,
        },
        config,
        ({ shape: { content, position, rowGap } }) => {
          if (typeof content !== 'string')
            throw new Error('CRender Graph Text: Text content should be a string!')
          if (!Array.isArray(position))
            throw new Error('CRender Graph Text: Text position should be an array!')
          if (typeof rowGap !== 'number')
            throw new Error('CRender Graph Text: Text rowGap should be a number!')
        }
      )
    )
  }
  draw(): void {
    const {
      shape,
      render: { ctx },
    } = this
    const { content, position, maxWidth, rowGap } = shape
    const { textBaseline, font } = ctx
    const contentArr = content.split('\n')
    const rowNum = contentArr.length
    const fontSize = parseInt(font.replace(/\D/g, ''))
    const lineHeight = fontSize + rowGap
    const allHeight = rowNum * lineHeight - rowGap
    let offset = 0
    const x = position[0]
    let y = position[1]
    if (textBaseline === 'middle') {
      offset = allHeight / 2
      y += fontSize / 2
    }
    if (textBaseline === 'bottom' || textBaseline === 'alphabetic') {
      offset = allHeight
      y += fontSize
    }
    const positions: Point[] = new Array(rowNum)
      .fill(0)
      .map((_, i) => [x, y + i * lineHeight - offset])
    ctx.beginPath()
    let realMaxWidth = 0
    contentArr.forEach((text, i) => {
      // calc text width and height for hover check
      const width = ctx.measureText(text).width
      if (width > realMaxWidth) realMaxWidth = width
      ctx.fillText(text, positions[i][0], positions[i][1], maxWidth)
      ctx.strokeText(text, positions[i][0], positions[i][1], maxWidth)
    })
    ctx.closePath()
    this.setCache(realMaxWidth, allHeight)
  }
  private setCache(width: number, height: number): void {
    const {
      cache,
      shape: {
        position: [x, y],
      },
      render: { ctx },
    } = this
    const { textAlign, textBaseline } = ctx
    cache.w = width
    cache.h = height
    cache.x = x
    cache.y = y
    if (textAlign === 'center') {
      cache.x = x - width / 2
    } else if (textAlign === 'end' || textAlign === 'right') {
      cache.x = x - width
    }
    if (textBaseline === 'middle') {
      cache.y = y - height / 2
    } else if (textBaseline === 'bottom' || textBaseline === 'alphabetic') {
      cache.y = y - height
    }
  }
  setGraphCenter(): void {
    const {
      shape: { position },
      style,
    } = this
    style.graphCenter = [...position] as [number, number]
  }
  move({ movementX, movementY }: MouseEvent): void {
    const {
      position: [x, y],
    } = this.shape
    this.attr('shape', {
      position: [x + movementX, y + movementY],
    })
  }
  hoverCheck(point: Point): boolean {
    const {
      cache: { x, y, w, h },
    } = this
    return checkPointIsInRect(point, { x: x!, y: y!, w: w!, h: h! })
  }
}
export default Text
TIP
文本中插入\n可以进行换行。
← Style