From 44769c854c637cddba4ff6257e80c948478a22a9 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Mon, 14 Aug 2023 18:12:44 +0800 Subject: [PATCH] feat: symbol support more type and support svg format, closed #110 #313 --- common/config/rush/pnpm-lock.yaml | 15 ++++ docs/demos/package.json | 3 +- docs/demos/src/pages/symbol.ts | 84 +++++++++++++++---- packages/vrender/package.json | 3 +- .../src/graphic/builtin-symbol/arrow2-down.ts | 38 +++++++++ .../src/graphic/builtin-symbol/arrow2-left.ts | 2 +- .../graphic/builtin-symbol/arrow2-right.ts | 2 +- .../src/graphic/builtin-symbol/arrow2-up.ts | 38 +++++++++ .../src/graphic/builtin-symbol/close.ts | 43 ++++++++++ .../src/graphic/builtin-symbol/index.ts | 12 ++- .../src/graphic/builtin-symbol/line-h.ts | 40 +++++++++ .../src/graphic/builtin-symbol/line-v.ts | 40 +++++++++ .../src/graphic/builtin-symbol/utils.ts | 75 +++++++++++++++-- packages/vrender/src/graphic/constants.ts | 26 ++++++ packages/vrender/src/graphic/symbol.ts | 49 ++++++++++- .../vrender/src/interface/graphic/symbol.ts | 13 ++- .../canvas-picker/symbol-picker.ts | 6 +- .../vrender/src/picker/pick-interceptor.ts | 2 +- .../contributions/render/symbol-render.ts | 47 ++++++++++- 19 files changed, 500 insertions(+), 38 deletions(-) create mode 100644 packages/vrender/src/graphic/builtin-symbol/arrow2-down.ts create mode 100644 packages/vrender/src/graphic/builtin-symbol/arrow2-up.ts create mode 100644 packages/vrender/src/graphic/builtin-symbol/close.ts create mode 100644 packages/vrender/src/graphic/builtin-symbol/line-h.ts create mode 100644 packages/vrender/src/graphic/builtin-symbol/line-v.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index b7804c148..52bfb8494 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -18,6 +18,7 @@ importers: canvas: 2.11.2 d3-scale-chromatic: ^3.0.0 dat.gui: ^0.7.9 + fast-xml-parser: 4.2.7 lodash: 4.17.21 rollup-plugin-node-polyfills: 0.2.1 rollup-plugin-typescript2: ^0.33.0 @@ -27,6 +28,7 @@ importers: zrender: 5.4.0 dependencies: '@visactor/vrender': link:../../packages/vrender + fast-xml-parser: 4.2.7 devDependencies: '@antv/g': 5.18.12 '@esbuild-plugins/node-globals-polyfill': 0.1.1 @@ -138,6 +140,7 @@ importers: color-convert: 2.0.1 core-js: 3.31.1 eslint: ~8.18.0 + fast-xml-parser: 4.2.7 form-data: ~4.0.0 inversify: 6.0.1 jest: ^26.0.0 @@ -154,6 +157,7 @@ importers: '@visactor/vutils': 0.15.3 color-convert: 2.0.1 core-js: 3.31.1 + fast-xml-parser: 4.2.7 inversify: 6.0.1 devDependencies: '@internal/bundler': link:../../tools/bundler @@ -5329,6 +5333,13 @@ packages: /fast-levenshtein/2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + /fast-xml-parser/4.2.7: + resolution: {integrity: sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false + /fastq/1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: @@ -9937,6 +9948,10 @@ packages: acorn: 8.10.0 dev: true + /strnum/1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false + /sumchecker/3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} diff --git a/docs/demos/package.json b/docs/demos/package.json index b2c3251d4..65f6da86e 100644 --- a/docs/demos/package.json +++ b/docs/demos/package.json @@ -28,6 +28,7 @@ "@antv/g": "^5.7.4" }, "dependencies": { - "@visactor/vrender": "workspace:0.14.1" + "@visactor/vrender": "workspace:0.14.1", + "fast-xml-parser": "4.2.7" } } diff --git a/docs/demos/src/pages/symbol.ts b/docs/demos/src/pages/symbol.ts index afaba8111..be17a2ee8 100644 --- a/docs/demos/src/pages/symbol.ts +++ b/docs/demos/src/pages/symbol.ts @@ -1,42 +1,92 @@ import { createStage, createSymbol, container, IGraphic } from '@visactor/vrender'; import { roughModule } from '@visactor/vrender-kits'; import { addShapesToStage, colorPools } from '../utils'; +import { XMLParser, XMLValidator } from 'fast-xml-parser'; // container.load(roughModule); export const page = () => { - + console.time(); + const parser = new XMLParser({ ignoreAttributes: false }); + const isSvg = XMLValidator.validate( + ` + + + `, + { + allowBooleanAttributes: true + } + ); + console.log(isSvg); + const result = parser.parse( + ` + + ` + ); + console.timeEnd(); + console.log(result); const symbolList = [ - 'circle', 'cross', 'diamond', 'square', 'arrow', 'arrow2Left', 'arrow2Right', 'wedge', 'thinTriangle', 'triangle', 'triangleUp', 'triangleDown', 'triangleRight', 'triangleLeft', 'stroke', 'star', 'wye', 'rect', - 'M -2 2 L 4 -5 L 7 -6 L 6 -3 L -1 3 C 0 4 0 5 1 4 C 1 5 2 6 1 6 A 1.42 1.42 0 0 1 0 7 A 5 5 0 0 0 -2 4 Q -2.5 3.9 -2.5 4.5 T -4 5.8 T -4.8 5 T -3.5 3.5 T -3 3 A 5 5 90 0 0 -6 1 A 1.42 1.42 0 0 1 -5 0 C -5 -1 -4 0 -3 0 C -4 1 -3 1 -2 2 M 4 -5 L 4 -3 L 6 -3 L 5 -4 L 4 -5' - ] + 'circle', + 'cross', + 'diamond', + 'square', + 'arrow', + 'arrow2Left', + 'arrow2Right', + 'arrow2Up', + 'arrow2Down', + 'wedge', + 'thinTriangle', + 'triangle', + 'triangleUp', + 'triangleDown', + 'triangleRight', + 'triangleLeft', + 'stroke', + 'star', + 'wye', + 'rect', + 'lineH', + 'lineV', + 'close', + 'M -2 2 L 4 -5 L 7 -6 L 6 -3 L -1 3 C 0 4 0 5 1 4 C 1 5 2 6 1 6 A 1.42 1.42 0 0 1 0 7 A 5 5 0 0 0 -2 4 Q -2.5 3.9 -2.5 4.5 T -4 5.8 T -4.8 5 T -3.5 3.5 T -3 3 A 5 5 90 0 0 -6 1 A 1.42 1.42 0 0 1 -5 0 C -5 -1 -4 0 -3 0 C -4 1 -3 1 -2 2 M 4 -5 L 4 -3 L 6 -3 L 5 -4 L 4 -5', + `` + ]; const graphics: IGraphic[] = []; - + symbolList.forEach((st, i) => { const symbol = createSymbol({ symbolType: st, - x: (i * 100) % 500 + 100, - y: (Math.floor(i * 100 / 500) + 1) * 100, - size: 60, - background: - '', - texture: 'diamond', - texturePadding: 0, - textureSize: 3, - textureColor: 'red', + x: ((i * 100) % 500) + 100, + y: (Math.floor((i * 100) / 500) + 1) * 100, + stroke: 'black', + lineWidth: 3, + lineCap: 'round', + size: 60 + // background: + // '', + // texture: 'diamond', + // texturePadding: 0, + // textureSize: 3, + // textureColor: 'red' }); symbol.addEventListener('mouseenter', () => { symbol.setAttribute('fill', 'blue'); - }) + }); + symbol.setMode('3d'); graphics.push(symbol); - }) + }); const stage = createStage({ canvas: 'main', autoRender: true }); + stage.set3dOptions({ + enable: true, + alpha: 0.3 + }); graphics.forEach(g => { stage.defaultLayer.add(g); - }) + }); }; diff --git a/packages/vrender/package.json b/packages/vrender/package.json index 54f9b5095..73af7517c 100644 --- a/packages/vrender/package.json +++ b/packages/vrender/package.json @@ -27,7 +27,8 @@ "@visactor/vutils": "~0.15.3", "color-convert": "2.0.1", "inversify": "6.0.1", - "core-js": "3.31.1" + "core-js": "3.31.1", + "fast-xml-parser": "4.2.7" }, "devDependencies": { "@internal/bundler": "workspace:*", diff --git a/packages/vrender/src/graphic/builtin-symbol/arrow2-down.ts b/packages/vrender/src/graphic/builtin-symbol/arrow2-down.ts new file mode 100644 index 000000000..3fe68adbe --- /dev/null +++ b/packages/vrender/src/graphic/builtin-symbol/arrow2-down.ts @@ -0,0 +1,38 @@ +import type { IBounds } from '@visactor/vutils'; +import type { IContext2d, SymbolType, ISymbolClass } from '../../interface'; + +export function arrow2Down(ctx: IContext2d, r: number, transX: number, transY: number) { + const r2 = r * 2; + ctx.moveTo(transX - r2, transY - r); + ctx.lineTo(transX, transY + r); + ctx.lineTo(transX + r2, transY - r); + + return true; +} + +// 以中心为锚点,size为circle外接正方形的面积 +export class Arrow2DownSymbol implements ISymbolClass { + type: SymbolType = 'arrow2Down'; + /* eslint-disable max-len */ + pathStr: string = 'M -0.5 -0.25 L 0 0.25 l 0.5 -0.25'; + + draw(ctx: IContext2d, size: number, transX: number, transY: number) { + const r = size / 4; + return arrow2Down(ctx, r, transX, transY); + } + + drawOffset(ctx: IContext2d, size: number, transX: number, transY: number, offset: number) { + const r = size / 4 + offset; + return arrow2Down(ctx, r, transX, transY); + } + + bounds(size: number, bounds: IBounds) { + const r = size / 2; + bounds.x1 = -r; + bounds.x2 = r; + bounds.y1 = -r; + bounds.y2 = r; + } +} + +export default new Arrow2DownSymbol(); diff --git a/packages/vrender/src/graphic/builtin-symbol/arrow2-left.ts b/packages/vrender/src/graphic/builtin-symbol/arrow2-left.ts index f8980e4e0..140165e62 100644 --- a/packages/vrender/src/graphic/builtin-symbol/arrow2-left.ts +++ b/packages/vrender/src/graphic/builtin-symbol/arrow2-left.ts @@ -44,7 +44,7 @@ export function arrow2Left(ctx: IContext2d, r: number, transX: number, transY: n export class Arrow2LeftSymbol implements ISymbolClass { type: SymbolType = 'arrow2Left'; /* eslint-disable max-len */ - pathStr: string = 'M 0.25 -0.5 L -0.25 0 l 0.5 0.5'; + pathStr: string = 'M 0.25 -0.5 L -0.25 0 l 0.25 0.5'; draw(ctx: IContext2d, size: number, transX: number, transY: number) { const r = size / 4; diff --git a/packages/vrender/src/graphic/builtin-symbol/arrow2-right.ts b/packages/vrender/src/graphic/builtin-symbol/arrow2-right.ts index 7e86cc414..87f3267cb 100644 --- a/packages/vrender/src/graphic/builtin-symbol/arrow2-right.ts +++ b/packages/vrender/src/graphic/builtin-symbol/arrow2-right.ts @@ -44,7 +44,7 @@ export function arrow2Right(ctx: IContext2d, r: number, transX: number, transY: export class Arrow2RightSymbol implements ISymbolClass { type: SymbolType = 'arrow2Right'; /* eslint-disable max-len */ - pathStr: string = 'M -0.25 -0.5 l 0.5 0.5 l -0.5 0.5'; + pathStr: string = 'M -0.25 -0.5 l 0.25 0 l -0.25 0.5'; draw(ctx: IContext2d, size: number, transX: number, transY: number) { const r = size / 4; diff --git a/packages/vrender/src/graphic/builtin-symbol/arrow2-up.ts b/packages/vrender/src/graphic/builtin-symbol/arrow2-up.ts new file mode 100644 index 000000000..3237dad9d --- /dev/null +++ b/packages/vrender/src/graphic/builtin-symbol/arrow2-up.ts @@ -0,0 +1,38 @@ +import type { IBounds } from '@visactor/vutils'; +import type { IContext2d, SymbolType, ISymbolClass } from '../../interface'; + +export function arrow2Up(ctx: IContext2d, r: number, transX: number, transY: number) { + const r2 = r * 2; + ctx.moveTo(transX - r2, transY + r); + ctx.lineTo(transX, transY - r); + ctx.lineTo(transX + r2, transY + r); + + return true; +} + +// 以中心为锚点,size为circle外接正方形的面积 +export class Arrow2UpSymbol implements ISymbolClass { + type: SymbolType = 'arrow2Up'; + /* eslint-disable max-len */ + pathStr: string = 'M -0.5 0.25 L 0 -0.25 l 0.5 0.25'; + + draw(ctx: IContext2d, size: number, transX: number, transY: number) { + const r = size / 4; + return arrow2Up(ctx, r, transX, transY); + } + + drawOffset(ctx: IContext2d, size: number, transX: number, transY: number, offset: number) { + const r = size / 4 + offset; + return arrow2Up(ctx, r, transX, transY); + } + + bounds(size: number, bounds: IBounds) { + const r = size / 2; + bounds.x1 = -r; + bounds.x2 = r; + bounds.y1 = -r; + bounds.y2 = r; + } +} + +export default new Arrow2UpSymbol(); diff --git a/packages/vrender/src/graphic/builtin-symbol/close.ts b/packages/vrender/src/graphic/builtin-symbol/close.ts new file mode 100644 index 000000000..c2df6ef17 --- /dev/null +++ b/packages/vrender/src/graphic/builtin-symbol/close.ts @@ -0,0 +1,43 @@ +import type { IBounds } from '@visactor/vutils'; +import { tau } from '@visactor/vutils'; +import type { IContext2d, SymbolType, ISymbolClass, IPath2D } from '../../interface'; + +export function close(ctx: IContext2d, r: number, x: number, y: number, z?: number) { + ctx.moveTo(x - r, y - r); + ctx.lineTo(x + r, y + r); + + ctx.moveTo(x + r, y - r); + ctx.lineTo(x - r, y + r); + return true; +} + +// 以中心为锚点,size为circle外接正方形的面积 +export class CloseSymbol implements ISymbolClass { + type: SymbolType = 'close'; + pathStr: string = 'M-0.5,-0.5L0.5,0.5,M0.5,-0.5L-0.5,0.5'; + + draw(ctx: IContext2d, size: number, x: number, y: number, z?: number) { + const r = size / 2; + return close(ctx, r, x, y, z); + } + + drawOffset(ctx: IContext2d, size: number, x: number, y: number, offset: number, z?: number) { + const r = size / 2 + offset; + return close(ctx, r, x, y, z); + } + + drawToSvgPath(size: number, x: number, y: number, z?: number): string { + const r = size / 2; + return `M ${x - r}, ${y - r} L ${x + r},${y + r} M ${x + r}, ${y - r} L ${x - r},${y + r}`; + } + + bounds(size: number, bounds: IBounds) { + const r = size / 2; + bounds.x1 = -r; + bounds.x2 = r; + bounds.y1 = -r; + bounds.y2 = r; + } +} + +export default new CloseSymbol(); diff --git a/packages/vrender/src/graphic/builtin-symbol/index.ts b/packages/vrender/src/graphic/builtin-symbol/index.ts index 33ed50ef3..018aae8cd 100644 --- a/packages/vrender/src/graphic/builtin-symbol/index.ts +++ b/packages/vrender/src/graphic/builtin-symbol/index.ts @@ -15,6 +15,11 @@ import triangleDown from './triangle-down'; import thinTriangle from './thin-triangle'; import arrow2Left from './arrow2-left'; import arrow2Right from './arrow2-right'; +import arrow2Up from './arrow2-up'; +import arrow2Down from './arrow2-down'; +import lineV from './line-v'; +import lineH from './line-h'; +import close from './close'; import rect from './rect'; import type { ISymbolClass } from '../../interface'; @@ -36,7 +41,12 @@ export const builtinSymbols = [ triangleDown, arrow2Left, arrow2Right, - rect + arrow2Up, + arrow2Down, + rect, + lineV, + lineH, + close ]; export const builtinSymbolsMap: Record = {}; diff --git a/packages/vrender/src/graphic/builtin-symbol/line-h.ts b/packages/vrender/src/graphic/builtin-symbol/line-h.ts new file mode 100644 index 000000000..849e6584a --- /dev/null +++ b/packages/vrender/src/graphic/builtin-symbol/line-h.ts @@ -0,0 +1,40 @@ +import type { IBounds } from '@visactor/vutils'; +import { tau } from '@visactor/vutils'; +import type { IContext2d, SymbolType, ISymbolClass, IPath2D } from '../../interface'; + +export function lineH(ctx: IContext2d, r: number, x: number, y: number, z?: number) { + ctx.moveTo(x - r, y); + ctx.lineTo(x + r, y); + return true; +} + +// 以中心为锚点,size为circle外接正方形的面积 +export class LineHSymbol implements ISymbolClass { + type: SymbolType = 'lineH'; + pathStr: string = 'M-0.5,0L0.5,0'; + + draw(ctx: IContext2d, size: number, x: number, y: number, z?: number) { + const r = size / 2; + return lineH(ctx, r, x, y, z); + } + + drawOffset(ctx: IContext2d, size: number, x: number, y: number, offset: number, z?: number) { + const r = size / 2 + offset; + return lineH(ctx, r, x, y, z); + } + + drawToSvgPath(size: number, x: number, y: number, z?: number): string { + const r = size / 2; + return `M ${x - r}, ${y} L ${x + r},${y}`; + } + + bounds(size: number, bounds: IBounds) { + const r = size / 2; + bounds.x1 = -r; + bounds.x2 = r; + bounds.y1 = -r; + bounds.y2 = r; + } +} + +export default new LineHSymbol(); diff --git a/packages/vrender/src/graphic/builtin-symbol/line-v.ts b/packages/vrender/src/graphic/builtin-symbol/line-v.ts new file mode 100644 index 000000000..d051ea4e5 --- /dev/null +++ b/packages/vrender/src/graphic/builtin-symbol/line-v.ts @@ -0,0 +1,40 @@ +import type { IBounds } from '@visactor/vutils'; +import { tau } from '@visactor/vutils'; +import type { IContext2d, SymbolType, ISymbolClass, IPath2D } from '../../interface'; + +export function lineV(ctx: IContext2d, r: number, x: number, y: number, z?: number) { + ctx.moveTo(x, y - r); + ctx.lineTo(x, y + r); + return true; +} + +// 以中心为锚点,size为circle外接正方形的面积 +export class LineVSymbol implements ISymbolClass { + type: SymbolType = 'lineV'; + pathStr: string = 'M0,-0.5L0,0.5'; + + draw(ctx: IContext2d, size: number, x: number, y: number, z?: number) { + const r = size / 2; + return lineV(ctx, r, x, y, z); + } + + drawOffset(ctx: IContext2d, size: number, x: number, y: number, offset: number, z?: number) { + const r = size / 2 + offset; + return lineV(ctx, r, x, y, z); + } + + drawToSvgPath(size: number, x: number, y: number, z?: number): string { + const r = size / 2; + return `M ${x}, ${y - r} L ${x},${y + r}`; + } + + bounds(size: number, bounds: IBounds) { + const r = size / 2; + bounds.x1 = -r; + bounds.x2 = r; + bounds.y1 = -r; + bounds.y2 = r; + } +} + +export default new LineVSymbol(); diff --git a/packages/vrender/src/graphic/builtin-symbol/utils.ts b/packages/vrender/src/graphic/builtin-symbol/utils.ts index 108e783a8..c0f935749 100644 --- a/packages/vrender/src/graphic/builtin-symbol/utils.ts +++ b/packages/vrender/src/graphic/builtin-symbol/utils.ts @@ -1,28 +1,91 @@ -import type { IBounds } from '@visactor/vutils'; +import { isArray, type IBounds, AABBBounds } from '@visactor/vutils'; import { renderCommandList } from '../../common/render-command-list'; -import type { IContext2d, ICustomPath2D, ISymbolClass } from '../../interface'; +import type { IContext2d, ICustomPath2D, IGraphicAttribute, ISymbolClass } from '../../interface'; +const tempBounds = new AABBBounds(); export class CustomSymbolClass implements ISymbolClass { type: string; path: ICustomPath2D; pathStr: string = ''; + isSvg: boolean; + svgCache?: { path: ICustomPath2D; attribute: Partial }[]; - constructor(type: string, path: ICustomPath2D) { + constructor( + type: string, + path: ICustomPath2D | { path: ICustomPath2D; attribute: Partial }[], + isSvg: boolean = false + ) { this.type = type; - this.path = path; + if (isArray(path)) { + this.svgCache = path; + } else { + this.path = path; + } + this.isSvg = isSvg; } - drawOffset(ctx: IContext2d, size: number, x: number, y: number, offset: number) { + drawOffset( + ctx: IContext2d, + size: number, + x: number, + y: number, + offset: number, + z?: number, + cb?: (path: ICustomPath2D, attribute?: Record) => void + ) { + if (this.isSvg) { + if (!this.svgCache) { + return false; + } + this.svgCache.forEach(item => { + ctx.beginPath(); + renderCommandList(item.path.commandList, ctx, x, y, size, size); + cb && cb(item.path, item.attribute); + }); + return false; + } renderCommandList(this.path.commandList, ctx, x, y, size + offset, size + offset); return false; } - draw(ctx: IContext2d, size: number, x: number, y: number) { + draw( + ctx: IContext2d, + size: number, + x: number, + y: number, + z?: number, + cb?: (path: ICustomPath2D, attribute?: Record) => void + ) { + if (this.isSvg) { + if (!this.svgCache) { + return false; + } + this.svgCache.forEach(item => { + ctx.beginPath(); + renderCommandList(item.path.commandList, ctx, x, y, size, size); + cb && cb(item.path, item.attribute); + }); + return false; + } renderCommandList(this.path.commandList, ctx, x, y, size, size); return false; } bounds(size: number, bounds: IBounds) { + if (this.isSvg) { + if (!this.svgCache) { + return; + } + bounds.clear(); + this.svgCache.forEach(({ path }) => { + tempBounds.x1 = path.bounds.x1 * size; + tempBounds.y1 = path.bounds.y1 * size; + tempBounds.x2 = path.bounds.x2 * size; + tempBounds.y2 = path.bounds.y2 * size; + bounds.union(tempBounds); + }); + return; + } if (!this.path.bounds) { return; } diff --git a/packages/vrender/src/graphic/constants.ts b/packages/vrender/src/graphic/constants.ts index 1b9ccd2d9..3131b2755 100644 --- a/packages/vrender/src/graphic/constants.ts +++ b/packages/vrender/src/graphic/constants.ts @@ -19,3 +19,29 @@ export const TEXT_NUMBER_TYPE = genNumberType(); export const GraphicService = Symbol.for('GraphicService'); export const GraphicCreator = Symbol.for('GraphicCreator'); + +export const SVG_ATTRIBUTE_MAP = { + 'stroke-linecap': 'lineCap', + 'stroke-linejoin': 'lineJoin', + 'stroke-dasharray': 'lineDash', + 'stroke-dashoffset': 'lineDashOffset', + 'stroke-width': 'lineWidth', + 'fill-opacity': 'fillOpacity', + 'stroke-opacity': 'strokeOpacity' +}; + +export const SVG_ATTRIBUTE_MAP_KEYS = Object.keys(SVG_ATTRIBUTE_MAP); + +export const SVG_PARSE_ATTRIBUTE_MAP = { + '@_stroke-linecap': 'lineCap', + '@_stroke-linejoin': 'lineJoin', + '@_stroke-dasharray': 'lineDash', + '@_stroke-dashoffset': 'lineDashOffset', + '@_stroke-width': 'lineWidth', + '@_fill-opacity': 'fillOpacity', + '@_stroke-opacity': 'strokeOpacity', + '@_stroke': 'stroke', + '@_fill': 'fill' +}; + +export const SVG_PARSE_ATTRIBUTE_MAP_KEYS = Object.keys(SVG_PARSE_ATTRIBUTE_MAP); diff --git a/packages/vrender/src/graphic/symbol.ts b/packages/vrender/src/graphic/symbol.ts index 09fa1353e..00679d6ec 100644 --- a/packages/vrender/src/graphic/symbol.ts +++ b/packages/vrender/src/graphic/symbol.ts @@ -1,4 +1,5 @@ -import type { AABBBounds, OBBBounds } from '@visactor/vutils'; +import type { OBBBounds } from '@visactor/vutils'; +import { AABBBounds } from '@visactor/vutils'; import { isArray, max } from '@visactor/vutils'; import type { ISymbol, ISymbolClass, ISymbolGraphicAttribute } from '../interface'; import { builtinSymbolsMap, CustomSymbolClass } from './builtin-symbol'; @@ -7,7 +8,8 @@ import { parsePadding } from '../common/utils'; import { getTheme } from './theme'; import { application } from '../application'; import { CustomPath2D } from '../common/custom-path2d'; -import { SYMBOL_NUMBER_TYPE } from './constants'; +import { SVG_PARSE_ATTRIBUTE_MAP, SVG_PARSE_ATTRIBUTE_MAP_KEYS, SYMBOL_NUMBER_TYPE } from './constants'; +import { XMLValidator, XMLParser } from 'fast-xml-parser'; const SYMBOL_UPDATE_TAG_KEY = ['symbolType', 'size', ...GRAPHIC_UPDATE_TAG_KEY]; @@ -56,6 +58,49 @@ export class Symbol extends Graphic implements ISymbol this._parsedPath = path; return path; } + + // 判断是否是svg + const isSvg = XMLValidator.validate(symbolType, { + allowBooleanAttributes: true + }); + if (isSvg === true) { + const parser = new XMLParser({ ignoreAttributes: false }); + const { svg } = parser.parse(symbolType); + if (!svg) { + return null; + } + const path = isArray(svg.path) ? svg.path : [svg.path]; + const b = new AABBBounds(); + const cacheList: { path: CustomPath2D; attribute: Record }[] = []; + path.forEach((item: any) => { + const cache = new CustomPath2D().fromString(item['@_d']); + const attribute = { + fill: 'black' + }; + SVG_PARSE_ATTRIBUTE_MAP_KEYS.forEach(k => { + if (item[k]) { + attribute[SVG_PARSE_ATTRIBUTE_MAP[k]] = item[k]; + } + }); + // 查找 + cacheList.push({ + path: cache, + attribute + }); + b.union(cache.bounds); + }); + const width = b.width(); + const height = b.height(); + // 规范化到1 + const maxWH = max(width, height); + const scale = 1 / maxWH; + cacheList.forEach(cache => cache.path.transform(0, 0, scale, scale)); + + this._parsedPath = new CustomSymbolClass(symbolType, cacheList, true); + Symbol.userSymbolMap[symbolType] = this._parsedPath; + return this._parsedPath; + } + const cache = new CustomPath2D().fromString(symbolType); const width = cache.bounds.width(); const height = cache.bounds.height(); diff --git a/packages/vrender/src/interface/graphic/symbol.ts b/packages/vrender/src/interface/graphic/symbol.ts index 925b33427..7f3211c4a 100644 --- a/packages/vrender/src/interface/graphic/symbol.ts +++ b/packages/vrender/src/interface/graphic/symbol.ts @@ -38,16 +38,25 @@ export interface ISymbolClass { type: SymbolType | string; path?: ICustomPath2D; pathStr: string; + isSvg?: boolean; // 返回true表示内部已经调用closePath,返回false表示没有调用closePath,外部需要调用closePath - draw: (ctx: IPath2D, size: number | [number, number], x: number, y: number, z?: number) => boolean; + draw: ( + ctx: IPath2D, + size: number | [number, number], + x: number, + y: number, + z?: number, + cb?: (p: ICustomPath2D, a: any) => void + ) => boolean; drawOffset: ( ctx: IPath2D, size: number | [number, number], x: number, y: number, offset: number, - z?: number + z?: number, + cb?: (p: ICustomPath2D, a: any) => void ) => boolean; drawToSvgPath?: (size: number | [number, number], x: number, y: number, z?: number) => string; diff --git a/packages/vrender/src/picker/contributions/canvas-picker/symbol-picker.ts b/packages/vrender/src/picker/contributions/canvas-picker/symbol-picker.ts index 33b0a0ba8..bb4a8703d 100644 --- a/packages/vrender/src/picker/contributions/canvas-picker/symbol-picker.ts +++ b/packages/vrender/src/picker/contributions/canvas-picker/symbol-picker.ts @@ -16,6 +16,9 @@ import { mat4Allocate } from '../../../allocator/matrix-allocate'; import { getScaledStroke } from '../../../common/canvas-utils'; import { BasePicker } from './base-picker'; import { SYMBOL_NUMBER_TYPE } from '../../../graphic/constants'; +import { createRect } from '../../../graphic'; + +const rect = createRect({}); @injectable() export class DefaultCanvasSymbolPicker extends BasePicker implements IGraphicPicker { @@ -32,11 +35,12 @@ export class DefaultCanvasSymbolPicker extends BasePicker implements IG return false; } + const parsedPath = symbol.getParsedPath(); if (!pickContext.camera) { if (!symbol.AABBBounds.containsPoint(point)) { return false; } - if (symbol.attribute.pickMode === 'imprecise') { + if (parsedPath.isSvg || symbol.attribute.pickMode === 'imprecise') { return true; } } diff --git a/packages/vrender/src/picker/pick-interceptor.ts b/packages/vrender/src/picker/pick-interceptor.ts index 84b2c3da0..a373de877 100644 --- a/packages/vrender/src/picker/pick-interceptor.ts +++ b/packages/vrender/src/picker/pick-interceptor.ts @@ -158,11 +158,11 @@ export class Canvas3DPickItemInterceptor implements IPickItemInterceptorContribu } context.camera = null; - context.restore(); pickParams.in3dInterceptor = false; return result; } + context.restore(); return null; } diff --git a/packages/vrender/src/render/contributions/render/symbol-render.ts b/packages/vrender/src/render/contributions/render/symbol-render.ts index 47b5bcd6c..bcc7fff78 100644 --- a/packages/vrender/src/render/contributions/render/symbol-render.ts +++ b/packages/vrender/src/render/contributions/render/symbol-render.ts @@ -108,12 +108,50 @@ export class DefaultCanvasSymbolRender extends BaseRender implements IG const p = context.project(x, y, z); const camera = context.camera; context.camera = null; - if (parsedPath.draw(context, size, p.x, p.y) === false) { + if ( + parsedPath.draw(context, size, p.x, p.y, undefined, (p, a) => { + if (a.fill) { + if (fillCb) { + fillCb(context, symbol.attribute, symbolAttribute); + } else { + context.setCommonStyle(symbol, a, originX - x, originY - y); + context.fill(); + } + } + if (a.stroke) { + if (strokeCb) { + strokeCb(context, symbol.attribute, symbolAttribute); + } else { + context.setStrokeStyle(symbol, a, (originX - x) / scaleX, (originY - y) / scaleY); + context.stroke(); + } + } + }) === false + ) { context.closePath(); } context.camera = camera; } else { - if (parsedPath.draw(context, size, x, y, z) === false) { + if ( + parsedPath.draw(context, size, x, y, z, (p, a) => { + if (a.fill) { + if (fillCb) { + fillCb(context, symbol.attribute, symbolAttribute); + } else { + context.setCommonStyle(symbol, a, originX - x, originY - y); + context.fill(); + } + } + if (a.stroke) { + if (strokeCb) { + strokeCb(context, symbol.attribute, symbolAttribute); + } else { + context.setStrokeStyle(symbol, a, (originX - x) / scaleX, (originY - y) / scaleY); + context.stroke(); + } + } + }) === false + ) { context.closePath(); } } @@ -154,7 +192,8 @@ export class DefaultCanvasSymbolRender extends BaseRender implements IG // shadow context.setShadowStyle && context.setShadowStyle(symbol, symbol.attribute, symbolAttribute); - if (doFill) { + // svg就不用fill和stroke了 + if (doFill && !parsedPath.isSvg) { if (fillCb) { fillCb(context, symbol.attribute, symbolAttribute); } else if (fVisible) { @@ -162,7 +201,7 @@ export class DefaultCanvasSymbolRender extends BaseRender implements IG context.fill(); } } - if (doStroke) { + if (doStroke && !parsedPath.isSvg) { if (strokeCb) { strokeCb(context, symbol.attribute, symbolAttribute); } else if (sVisible) {