# 基础图形
插件内置了丰富的基础图形,如下是相关实例,你还可以尝试拖拽他们。
点击以展开或折叠内置图形 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