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) {