@antv/infographic 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/infographic.min.js +191 -191
- package/dist/infographic.min.js.map +1 -1
- package/esm/designs/items/BadgeCard.js +6 -1
- package/esm/designs/items/SimpleCircleNode.d.ts +8 -0
- package/esm/designs/items/SimpleCircleNode.js +14 -0
- package/esm/designs/items/index.d.ts +1 -0
- package/esm/designs/items/index.js +1 -0
- package/esm/designs/structures/hierarchy-mindmap.js +19 -5
- package/esm/designs/structures/hierarchy-tree.d.ts +2 -1
- package/esm/designs/structures/hierarchy-tree.js +23 -20
- package/esm/designs/structures/index.d.ts +1 -0
- package/esm/designs/structures/index.js +1 -0
- package/esm/designs/structures/relation-dagre-flow.d.ts +21 -0
- package/esm/designs/structures/relation-dagre-flow.js +497 -0
- package/esm/designs/utils/hierarchy-color.d.ts +1 -1
- package/esm/editor/plugins/edit-bar/edit-bar.js +27 -9
- package/esm/index.js +1 -1
- package/esm/jsx/global.d.ts +1 -0
- package/esm/jsx/types/element.d.ts +5 -1
- package/esm/jsx/utils/svg.js +2 -0
- package/esm/renderer/composites/icon.js +2 -0
- package/esm/renderer/composites/illus.d.ts +1 -1
- package/esm/renderer/composites/illus.js +9 -4
- package/esm/renderer/composites/text.js +4 -2
- package/esm/renderer/fonts/loader.js +3 -1
- package/esm/renderer/fonts/registry.js +1 -1
- package/esm/renderer/renderer.js +28 -25
- package/esm/resource/loader.js +3 -1
- package/esm/runtime/Infographic.js +1 -1
- package/esm/ssr/dom-shim.d.ts +4 -0
- package/esm/ssr/dom-shim.js +107 -0
- package/esm/ssr/index.d.ts +1 -0
- package/esm/ssr/index.js +1 -0
- package/esm/ssr/renderer.d.ts +2 -0
- package/esm/ssr/renderer.js +60 -0
- package/esm/syntax/index.js +57 -1
- package/esm/syntax/parser.js +44 -0
- package/esm/syntax/relations.d.ts +6 -0
- package/esm/syntax/relations.js +251 -0
- package/esm/syntax/schema.d.ts +1 -0
- package/esm/syntax/schema.js +12 -0
- package/esm/templates/built-in.js +2 -0
- package/esm/templates/relation-dagre-flow.d.ts +2 -0
- package/esm/templates/relation-dagre-flow.js +68 -0
- package/esm/types/data.d.ts +24 -3
- package/esm/utils/data.js +1 -1
- package/esm/utils/index.d.ts +1 -0
- package/esm/utils/index.js +1 -0
- package/esm/utils/is-browser.js +5 -9
- package/esm/utils/measure-text.d.ts +2 -2
- package/esm/utils/measure-text.js +4 -4
- package/esm/utils/recognizer.js +8 -5
- package/esm/utils/text.js +27 -19
- package/lib/designs/items/BadgeCard.js +6 -1
- package/lib/designs/items/SimpleCircleNode.d.ts +8 -0
- package/lib/designs/items/SimpleCircleNode.js +18 -0
- package/lib/designs/items/index.d.ts +1 -0
- package/lib/designs/items/index.js +1 -0
- package/lib/designs/structures/hierarchy-mindmap.js +19 -5
- package/lib/designs/structures/hierarchy-tree.d.ts +2 -1
- package/lib/designs/structures/hierarchy-tree.js +23 -20
- package/lib/designs/structures/index.d.ts +1 -0
- package/lib/designs/structures/index.js +1 -0
- package/lib/designs/structures/relation-dagre-flow.d.ts +21 -0
- package/lib/designs/structures/relation-dagre-flow.js +501 -0
- package/lib/designs/utils/hierarchy-color.d.ts +1 -1
- package/lib/editor/plugins/edit-bar/edit-bar.js +27 -9
- package/lib/jsx/global.d.ts +1 -0
- package/lib/jsx/types/element.d.ts +5 -1
- package/lib/jsx/utils/svg.js +2 -0
- package/lib/renderer/composites/icon.js +2 -0
- package/lib/renderer/composites/illus.d.ts +1 -1
- package/lib/renderer/composites/illus.js +8 -3
- package/lib/renderer/composites/text.js +4 -2
- package/lib/renderer/fonts/loader.js +2 -0
- package/lib/renderer/fonts/registry.js +6 -6
- package/lib/renderer/renderer.js +27 -24
- package/lib/resource/loader.js +3 -1
- package/lib/runtime/Infographic.js +1 -1
- package/lib/ssr/dom-shim.d.ts +4 -0
- package/lib/ssr/dom-shim.js +110 -0
- package/lib/ssr/index.d.ts +1 -0
- package/lib/ssr/index.js +5 -0
- package/lib/ssr/renderer.d.ts +2 -0
- package/lib/ssr/renderer.js +63 -0
- package/lib/syntax/index.js +57 -1
- package/lib/syntax/parser.js +44 -0
- package/lib/syntax/relations.d.ts +6 -0
- package/lib/syntax/relations.js +254 -0
- package/lib/syntax/schema.d.ts +1 -0
- package/lib/syntax/schema.js +13 -1
- package/lib/templates/built-in.js +2 -0
- package/lib/templates/relation-dagre-flow.d.ts +2 -0
- package/lib/templates/relation-dagre-flow.js +71 -0
- package/lib/types/data.d.ts +24 -3
- package/lib/utils/data.js +2 -5
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/is-browser.js +5 -9
- package/lib/utils/measure-text.d.ts +2 -2
- package/lib/utils/measure-text.js +4 -4
- package/lib/utils/recognizer.js +8 -5
- package/lib/utils/text.js +28 -23
- package/package.json +19 -7
- package/src/designs/items/BadgeCard.tsx +9 -2
- package/src/designs/items/SimpleCircleNode.tsx +46 -0
- package/src/designs/items/index.ts +1 -0
- package/src/designs/structures/hierarchy-mindmap.tsx +15 -2
- package/src/designs/structures/hierarchy-tree.tsx +33 -31
- package/src/designs/structures/index.ts +1 -0
- package/src/designs/structures/relation-dagre-flow.tsx +782 -0
- package/src/designs/utils/hierarchy-color.ts +6 -1
- package/src/editor/plugins/edit-bar/edit-bar.ts +41 -17
- package/src/index.ts +1 -1
- package/src/jsx/global.ts +1 -0
- package/src/jsx/types/element.ts +15 -6
- package/src/jsx/utils/svg.ts +2 -0
- package/src/renderer/composites/icon.ts +2 -0
- package/src/renderer/composites/illus.ts +16 -3
- package/src/renderer/composites/text.ts +7 -2
- package/src/renderer/fonts/loader.ts +7 -1
- package/src/renderer/fonts/registry.ts +1 -1
- package/src/renderer/renderer.ts +42 -24
- package/src/resource/loader.ts +3 -1
- package/src/runtime/Infographic.tsx +1 -1
- package/src/ssr/dom-shim.ts +120 -0
- package/src/ssr/index.ts +1 -0
- package/src/ssr/renderer.ts +72 -0
- package/src/syntax/index.ts +58 -1
- package/src/syntax/parser.ts +49 -0
- package/src/syntax/relations.ts +291 -0
- package/src/syntax/schema.ts +16 -0
- package/src/templates/built-in.ts +4 -2
- package/src/templates/relation-dagre-flow.ts +73 -0
- package/src/types/data.ts +26 -3
- package/src/utils/data.ts +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/is-browser.ts +3 -9
- package/src/utils/measure-text.ts +6 -7
- package/src/utils/recognizer.ts +9 -5
- package/src/utils/svg.ts +0 -1
- package/src/utils/text.ts +25 -19
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@antv/infographic/jsx-runtime";
|
|
2
|
+
import { DagreLayout } from '@antv/layout';
|
|
3
|
+
import { Defs, getElementBounds, Group, Path, Polygon, Text } from '../../jsx';
|
|
4
|
+
import { BtnAdd, BtnsGroup, ItemsGroup } from '../components';
|
|
5
|
+
import { FlexLayout } from '../layouts';
|
|
6
|
+
import { getColorPrimary, getPaletteColor, getThemeColors } from '../utils';
|
|
7
|
+
import { registerStructure } from './registry';
|
|
8
|
+
const DEFAULT_NODE_SEP = 50;
|
|
9
|
+
const DEFAULT_RANK_SEP = 70;
|
|
10
|
+
const DEFAULT_EDGE_SEP = 10;
|
|
11
|
+
const DEFAULT_EDGE_WIDTH = 2;
|
|
12
|
+
const DEFAULT_PADDING = 30;
|
|
13
|
+
const checkUndirectedCycle = (nodeIds, edges) => {
|
|
14
|
+
const adj = new Map();
|
|
15
|
+
nodeIds.forEach((id) => adj.set(id, []));
|
|
16
|
+
for (const edge of edges) {
|
|
17
|
+
if (edge.source === edge.target)
|
|
18
|
+
return true;
|
|
19
|
+
adj.get(edge.source)?.push({ target: edge.target, edgeId: edge.id });
|
|
20
|
+
adj.get(edge.target)?.push({ target: edge.source, edgeId: edge.id });
|
|
21
|
+
}
|
|
22
|
+
const visited = new Set();
|
|
23
|
+
const dfs = (u, parentEdgeId) => {
|
|
24
|
+
visited.add(u);
|
|
25
|
+
const neighbors = adj.get(u) || [];
|
|
26
|
+
for (const { target: v, edgeId } of neighbors) {
|
|
27
|
+
if (edgeId === parentEdgeId)
|
|
28
|
+
continue;
|
|
29
|
+
if (visited.has(v))
|
|
30
|
+
return true;
|
|
31
|
+
if (dfs(v, edgeId))
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
for (const node of nodeIds) {
|
|
37
|
+
if (!visited.has(node)) {
|
|
38
|
+
if (dfs(node, null))
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
};
|
|
44
|
+
export const RelationDagreFlow = (props) => {
|
|
45
|
+
const { Title, Item, data, rankdir = 'TB', nodesep = DEFAULT_NODE_SEP, ranksep = DEFAULT_RANK_SEP, edgesep = DEFAULT_EDGE_SEP, edgeWidth = DEFAULT_EDGE_WIDTH, showConnections = true, edgeColorMode = 'gradient', edgeStyle = 'solid', edgeDashPattern = '5,5', edgeCornerRadius = 12, edgeRouting = 'orth', showArrow = true, arrowType = 'triangle', padding = DEFAULT_PADDING, edgeAnimation = 'none', edgeAnimationSpeed = 1, options, } = props;
|
|
46
|
+
const { title, desc, items = [] } = data;
|
|
47
|
+
const titleContent = Title ? _jsx(Title, { title: title, desc: desc }) : null;
|
|
48
|
+
if (!Item || items.length === 0) {
|
|
49
|
+
return (_jsxs(FlexLayout, { id: "infographic-container", flexDirection: "column", justifyContent: "center", alignItems: "center", children: [titleContent, _jsx(Group, { children: _jsx(BtnAdd, { indexes: [0], x: 0, y: 0 }) })] }));
|
|
50
|
+
}
|
|
51
|
+
const nodeMetaMap = new Map();
|
|
52
|
+
const nodeSizeMap = new Map();
|
|
53
|
+
const nodeColorMap = new Map();
|
|
54
|
+
const nodeIdsByIndex = new Map();
|
|
55
|
+
const nodeIdSet = new Set();
|
|
56
|
+
const colorGroupIndexMap = new Map();
|
|
57
|
+
let nextColorGroupIndex = 0;
|
|
58
|
+
const nodes = items.map((item, index) => {
|
|
59
|
+
const datum = item;
|
|
60
|
+
const id = String(datum.id ?? index);
|
|
61
|
+
const indexes = [index];
|
|
62
|
+
let primary;
|
|
63
|
+
const groupKey = String(datum.group ?? '');
|
|
64
|
+
if (groupKey) {
|
|
65
|
+
let groupIndex = colorGroupIndexMap.get(groupKey);
|
|
66
|
+
if (groupIndex == null) {
|
|
67
|
+
groupIndex = nextColorGroupIndex;
|
|
68
|
+
colorGroupIndexMap.set(groupKey, groupIndex);
|
|
69
|
+
nextColorGroupIndex += 1;
|
|
70
|
+
}
|
|
71
|
+
primary = getPaletteColor(options, [groupIndex]);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
primary = getPaletteColor(options, indexes);
|
|
75
|
+
}
|
|
76
|
+
const themeColors = primary
|
|
77
|
+
? getThemeColors({ colorPrimary: primary }, options)
|
|
78
|
+
: undefined;
|
|
79
|
+
if (primary) {
|
|
80
|
+
nodeColorMap.set(id, primary);
|
|
81
|
+
}
|
|
82
|
+
const bounds = getElementBounds(_jsx(Item, { indexes: indexes, data: data, datum: datum, positionH: "center", positionV: "middle", themeColors: themeColors }));
|
|
83
|
+
nodeSizeMap.set(id, bounds);
|
|
84
|
+
nodeMetaMap.set(id, { id, indexes, datum, themeColors });
|
|
85
|
+
nodeIdsByIndex.set(index, id);
|
|
86
|
+
nodeIdSet.add(id);
|
|
87
|
+
return { id, parentId: datum.parentId };
|
|
88
|
+
});
|
|
89
|
+
const relations = data.relations ?? [];
|
|
90
|
+
const resolveNodeId = (value) => {
|
|
91
|
+
if (value == null)
|
|
92
|
+
return null;
|
|
93
|
+
const direct = String(value);
|
|
94
|
+
if (nodeIdSet.has(direct))
|
|
95
|
+
return direct;
|
|
96
|
+
const asIndex = Number(value);
|
|
97
|
+
if (!Number.isNaN(asIndex)) {
|
|
98
|
+
const mapped = nodeIdsByIndex.get(asIndex);
|
|
99
|
+
if (mapped)
|
|
100
|
+
return mapped;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
};
|
|
104
|
+
const edges = relations
|
|
105
|
+
.map((relation, index) => {
|
|
106
|
+
const source = resolveNodeId(relation.from);
|
|
107
|
+
const target = resolveNodeId(relation.to);
|
|
108
|
+
if (!source || !target)
|
|
109
|
+
return null;
|
|
110
|
+
return {
|
|
111
|
+
id: relation.id ? String(relation.id) : `edge-${index}`,
|
|
112
|
+
source,
|
|
113
|
+
target,
|
|
114
|
+
relation,
|
|
115
|
+
};
|
|
116
|
+
})
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
const hasCycle = checkUndirectedCycle(Array.from(nodeIdSet), edges);
|
|
119
|
+
const finalEdgeRouting = hasCycle ? 'dagre' : edgeRouting;
|
|
120
|
+
const layout = new DagreLayout({
|
|
121
|
+
rankdir,
|
|
122
|
+
nodesep,
|
|
123
|
+
ranksep,
|
|
124
|
+
edgesep,
|
|
125
|
+
controlPoints: true,
|
|
126
|
+
nodeSize: (node) => {
|
|
127
|
+
const id = String(node.id ?? '');
|
|
128
|
+
const bounds = nodeSizeMap.get(id);
|
|
129
|
+
return bounds ? [bounds.width, bounds.height] : [0, 0];
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
layout.execute({ nodes, edges });
|
|
133
|
+
const nodeLayouts = [];
|
|
134
|
+
layout.forEachNode((node) => {
|
|
135
|
+
const id = String(node.id);
|
|
136
|
+
const meta = nodeMetaMap.get(id);
|
|
137
|
+
if (!meta)
|
|
138
|
+
return;
|
|
139
|
+
const bounds = nodeSizeMap.get(id);
|
|
140
|
+
const width = bounds?.width ?? 0;
|
|
141
|
+
const height = bounds?.height ?? 0;
|
|
142
|
+
const x = (node.x ?? 0) - width / 2;
|
|
143
|
+
const y = (node.y ?? 0) - height / 2;
|
|
144
|
+
nodeLayouts.push({
|
|
145
|
+
...meta,
|
|
146
|
+
x,
|
|
147
|
+
y,
|
|
148
|
+
width,
|
|
149
|
+
height,
|
|
150
|
+
centerX: x + width / 2,
|
|
151
|
+
centerY: y + height / 2,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
if (nodeLayouts.length === 0) {
|
|
155
|
+
return (_jsxs(FlexLayout, { id: "infographic-container", flexDirection: "column", justifyContent: "center", alignItems: "center", children: [titleContent, _jsx(Group, { children: _jsx(BtnAdd, { indexes: [0], x: 0, y: 0 }) })] }));
|
|
156
|
+
}
|
|
157
|
+
const minX = Math.min(...nodeLayouts.map((node) => node.x));
|
|
158
|
+
const minY = Math.min(...nodeLayouts.map((node) => node.y));
|
|
159
|
+
const offsetX = padding - minX;
|
|
160
|
+
const offsetY = padding - minY;
|
|
161
|
+
const nodeLayoutById = new Map();
|
|
162
|
+
const itemElements = [];
|
|
163
|
+
nodeLayouts.forEach((node) => {
|
|
164
|
+
const displayX = node.x + offsetX;
|
|
165
|
+
const displayY = node.y + offsetY;
|
|
166
|
+
const positionH = rankdir === 'LR' ? 'normal' : rankdir === 'RL' ? 'flipped' : 'center';
|
|
167
|
+
const positionV = rankdir === 'TB' ? 'normal' : rankdir === 'BT' ? 'flipped' : 'middle';
|
|
168
|
+
itemElements.push(_jsx(Item, { indexes: node.indexes, datum: node.datum, data: data, x: displayX, y: displayY, positionH: positionH, positionV: positionV, themeColors: node.themeColors }));
|
|
169
|
+
nodeLayoutById.set(node.id, {
|
|
170
|
+
...node,
|
|
171
|
+
x: displayX,
|
|
172
|
+
y: displayY,
|
|
173
|
+
centerX: displayX + node.width / 2,
|
|
174
|
+
centerY: displayY + node.height / 2,
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
const defsElements = [];
|
|
178
|
+
const decorElements = [];
|
|
179
|
+
if (showConnections) {
|
|
180
|
+
const defaultStroke = getColorPrimary(options);
|
|
181
|
+
const themeColors = getThemeColors(options.themeConfig, options);
|
|
182
|
+
const labelBackground = themeColors?.colorBg ?? '#ffffff';
|
|
183
|
+
const labelTextColor = themeColors?.colorText ?? defaultStroke;
|
|
184
|
+
const arrowSize = Math.max(10, edgeWidth * 4);
|
|
185
|
+
const isVertical = rankdir === 'TB' || rankdir === 'BT';
|
|
186
|
+
const enableAnimation = edgeAnimation === 'ant-line';
|
|
187
|
+
const animationDashArray = enableAnimation ? edgeDashPattern : '';
|
|
188
|
+
const staticDashArray = !enableAnimation && edgeStyle === 'dashed' ? edgeDashPattern : '';
|
|
189
|
+
const actualDashArray = enableAnimation
|
|
190
|
+
? animationDashArray
|
|
191
|
+
: staticDashArray;
|
|
192
|
+
const dashPatternLength = enableAnimation
|
|
193
|
+
? animationDashArray
|
|
194
|
+
.split(',')
|
|
195
|
+
.reduce((sum, val) => sum + parseFloat(val.trim() || '0'), 0)
|
|
196
|
+
: 0;
|
|
197
|
+
const animationDuration = enableAnimation && dashPatternLength > 0
|
|
198
|
+
? `${dashPatternLength / (edgeAnimationSpeed * 10)}s`
|
|
199
|
+
: '1s';
|
|
200
|
+
const straightCornerRadius = edgeCornerRadius;
|
|
201
|
+
const createStraightPath = (points, dx, dy) => points
|
|
202
|
+
.map(([x, y], index) => {
|
|
203
|
+
const prefix = index === 0 ? 'M' : 'L';
|
|
204
|
+
return `${prefix} ${x + dx} ${y + dy}`;
|
|
205
|
+
})
|
|
206
|
+
.join(' ');
|
|
207
|
+
const createRoundedPath = (points, radius, dx, dy) => {
|
|
208
|
+
if (points.length < 2)
|
|
209
|
+
return '';
|
|
210
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
211
|
+
const toPoint = ([x, y]) => ({
|
|
212
|
+
x: x + dx,
|
|
213
|
+
y: y + dy,
|
|
214
|
+
});
|
|
215
|
+
const output = [];
|
|
216
|
+
const first = toPoint(points[0]);
|
|
217
|
+
output.push(`M ${first.x} ${first.y}`);
|
|
218
|
+
if (points.length === 2) {
|
|
219
|
+
const last = toPoint(points[1]);
|
|
220
|
+
output.push(`L ${last.x} ${last.y}`);
|
|
221
|
+
return output.join(' ');
|
|
222
|
+
}
|
|
223
|
+
for (let i = 1; i < points.length - 1; i += 1) {
|
|
224
|
+
const prev = points[i - 1];
|
|
225
|
+
const curr = points[i];
|
|
226
|
+
const next = points[i + 1];
|
|
227
|
+
const v0x = curr[0] - prev[0];
|
|
228
|
+
const v0y = curr[1] - prev[1];
|
|
229
|
+
const v1x = next[0] - curr[0];
|
|
230
|
+
const v1y = next[1] - curr[1];
|
|
231
|
+
const d0 = Math.hypot(v0x, v0y);
|
|
232
|
+
const d1 = Math.hypot(v1x, v1y);
|
|
233
|
+
if (d0 === 0 || d1 === 0) {
|
|
234
|
+
const currPoint = toPoint(curr);
|
|
235
|
+
output.push(`L ${currPoint.x} ${currPoint.y}`);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const r = clamp(radius, 0, Math.min(d0, d1) / 2);
|
|
239
|
+
if (r === 0) {
|
|
240
|
+
const currPoint = toPoint(curr);
|
|
241
|
+
output.push(`L ${currPoint.x} ${currPoint.y}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const u0x = v0x / d0;
|
|
245
|
+
const u0y = v0y / d0;
|
|
246
|
+
const u1x = v1x / d1;
|
|
247
|
+
const u1y = v1y / d1;
|
|
248
|
+
const start = toPoint([curr[0] - u0x * r, curr[1] - u0y * r]);
|
|
249
|
+
const end = toPoint([curr[0] + u1x * r, curr[1] + u1y * r]);
|
|
250
|
+
output.push(`L ${start.x} ${start.y}`);
|
|
251
|
+
const currPoint = toPoint(curr);
|
|
252
|
+
output.push(`Q ${currPoint.x} ${currPoint.y} ${end.x} ${end.y}`);
|
|
253
|
+
}
|
|
254
|
+
const last = toPoint(points[points.length - 1]);
|
|
255
|
+
output.push(`L ${last.x} ${last.y}`);
|
|
256
|
+
return output.join(' ');
|
|
257
|
+
};
|
|
258
|
+
const createArrowElements = (x, y, angle, type, fillColor) => {
|
|
259
|
+
const ux = Math.cos(angle);
|
|
260
|
+
const uy = Math.sin(angle);
|
|
261
|
+
const px = -uy;
|
|
262
|
+
const py = ux;
|
|
263
|
+
const length = arrowSize;
|
|
264
|
+
const halfWidth = arrowSize * 0.55;
|
|
265
|
+
if (type === 'arrow') {
|
|
266
|
+
const leftX = x - ux * length + px * halfWidth;
|
|
267
|
+
const leftY = y - uy * length + py * halfWidth;
|
|
268
|
+
const rightX = x - ux * length - px * halfWidth;
|
|
269
|
+
const rightY = y - uy * length - py * halfWidth;
|
|
270
|
+
return [
|
|
271
|
+
_jsx(Path, { d: `M ${leftX} ${leftY} L ${x} ${y} L ${rightX} ${rightY}`, stroke: fillColor, strokeWidth: Math.max(1.5, edgeWidth), strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }),
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
if (type === 'diamond') {
|
|
275
|
+
const diamondLength = length * 1.25;
|
|
276
|
+
const diamondWidth = halfWidth * 0.75;
|
|
277
|
+
const midX = x - ux * diamondLength * 0.5;
|
|
278
|
+
const midY = y - uy * diamondLength * 0.5;
|
|
279
|
+
const diamondPoints = [
|
|
280
|
+
{ x, y },
|
|
281
|
+
{ x: midX + px * diamondWidth, y: midY + py * diamondWidth },
|
|
282
|
+
{ x: x - ux * diamondLength, y: y - uy * diamondLength },
|
|
283
|
+
{ x: midX - px * diamondWidth, y: midY - py * diamondWidth },
|
|
284
|
+
];
|
|
285
|
+
return [
|
|
286
|
+
_jsx(Polygon, { points: diamondPoints, fill: fillColor, stroke: fillColor, strokeWidth: Math.max(1, edgeWidth * 0.8) }),
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
const trianglePoints = [
|
|
290
|
+
{ x, y },
|
|
291
|
+
{
|
|
292
|
+
x: x - ux * length + px * halfWidth,
|
|
293
|
+
y: y - uy * length + py * halfWidth,
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
x: x - ux * length - px * halfWidth,
|
|
297
|
+
y: y - uy * length - py * halfWidth,
|
|
298
|
+
},
|
|
299
|
+
];
|
|
300
|
+
return [
|
|
301
|
+
_jsx(Polygon, { points: trianglePoints, fill: fillColor, stroke: fillColor, strokeWidth: Math.max(1, edgeWidth * 0.8) }),
|
|
302
|
+
];
|
|
303
|
+
};
|
|
304
|
+
const getMidPoint = (points) => {
|
|
305
|
+
if (points.length === 0)
|
|
306
|
+
return null;
|
|
307
|
+
if (points.length === 1)
|
|
308
|
+
return points[0];
|
|
309
|
+
let total = 0;
|
|
310
|
+
const segments = [];
|
|
311
|
+
for (let i = 0; i < points.length - 1; i += 1) {
|
|
312
|
+
const start = points[i];
|
|
313
|
+
const end = points[i + 1];
|
|
314
|
+
const length = Math.hypot(end[0] - start[0], end[1] - start[1]);
|
|
315
|
+
segments.push({ length, start, end });
|
|
316
|
+
total += length;
|
|
317
|
+
}
|
|
318
|
+
if (total === 0)
|
|
319
|
+
return points[0];
|
|
320
|
+
let target = total / 2;
|
|
321
|
+
for (let i = 0; i < segments.length; i += 1) {
|
|
322
|
+
const segment = segments[i];
|
|
323
|
+
if (target <= segment.length || i === segments.length - 1) {
|
|
324
|
+
const ratio = segment.length === 0
|
|
325
|
+
? 0
|
|
326
|
+
: Math.max(0, Math.min(1, target / segment.length));
|
|
327
|
+
return [
|
|
328
|
+
segment.start[0] + (segment.end[0] - segment.start[0]) * ratio,
|
|
329
|
+
segment.start[1] + (segment.end[1] - segment.start[1]) * ratio,
|
|
330
|
+
];
|
|
331
|
+
}
|
|
332
|
+
target -= segment.length;
|
|
333
|
+
}
|
|
334
|
+
return points[Math.floor(points.length / 2)];
|
|
335
|
+
};
|
|
336
|
+
const getOrthEdgeEndpoints = (sourceId, targetId) => {
|
|
337
|
+
const source = nodeLayoutById.get(sourceId);
|
|
338
|
+
const target = nodeLayoutById.get(targetId);
|
|
339
|
+
if (!source || !target)
|
|
340
|
+
return null;
|
|
341
|
+
if (rankdir === 'TB') {
|
|
342
|
+
return {
|
|
343
|
+
start: [source.centerX, source.y + source.height],
|
|
344
|
+
end: [target.centerX, target.y],
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (rankdir === 'BT') {
|
|
348
|
+
return {
|
|
349
|
+
start: [source.centerX, source.y],
|
|
350
|
+
end: [target.centerX, target.y + target.height],
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
if (rankdir === 'LR') {
|
|
354
|
+
return {
|
|
355
|
+
start: [source.x + source.width, source.centerY],
|
|
356
|
+
end: [target.x, target.centerY],
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
start: [source.x, source.centerY],
|
|
361
|
+
end: [target.x + target.width, target.centerY],
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
const getOrthEdgePoints = (sourceId, targetId) => {
|
|
365
|
+
const endpoints = getOrthEdgeEndpoints(sourceId, targetId);
|
|
366
|
+
if (!endpoints)
|
|
367
|
+
return null;
|
|
368
|
+
const { start, end } = endpoints;
|
|
369
|
+
if (isVertical) {
|
|
370
|
+
const midY = start[1] + (end[1] - start[1]) / 2;
|
|
371
|
+
return {
|
|
372
|
+
start,
|
|
373
|
+
end,
|
|
374
|
+
points: [start, [start[0], midY], [end[0], midY], end],
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const midX = start[0] + (end[0] - start[0]) / 2;
|
|
378
|
+
return {
|
|
379
|
+
start,
|
|
380
|
+
end,
|
|
381
|
+
points: [start, [midX, start[1]], [midX, end[1]], end],
|
|
382
|
+
};
|
|
383
|
+
};
|
|
384
|
+
layout.forEachEdge((edge) => {
|
|
385
|
+
const normalizePoints = (rawPoints) => {
|
|
386
|
+
if (!Array.isArray(rawPoints))
|
|
387
|
+
return [];
|
|
388
|
+
return rawPoints
|
|
389
|
+
.map((point) => {
|
|
390
|
+
if (!point)
|
|
391
|
+
return null;
|
|
392
|
+
if (Array.isArray(point) && point.length >= 2) {
|
|
393
|
+
return [Number(point[0]), Number(point[1])];
|
|
394
|
+
}
|
|
395
|
+
return null;
|
|
396
|
+
})
|
|
397
|
+
.filter((point) => !!point && Number.isFinite(point[0]) && Number.isFinite(point[1]));
|
|
398
|
+
};
|
|
399
|
+
const fallbackPoints = () => {
|
|
400
|
+
const source = nodeLayoutById.get(String(edge.source));
|
|
401
|
+
const target = nodeLayoutById.get(String(edge.target));
|
|
402
|
+
if (!source || !target)
|
|
403
|
+
return [];
|
|
404
|
+
return [
|
|
405
|
+
[source.centerX - offsetX, source.centerY - offsetY],
|
|
406
|
+
[target.centerX - offsetX, target.centerY - offsetY],
|
|
407
|
+
];
|
|
408
|
+
};
|
|
409
|
+
const useOrthRouting = finalEdgeRouting === 'orth';
|
|
410
|
+
const orthEdge = useOrthRouting
|
|
411
|
+
? getOrthEdgePoints(String(edge.source), String(edge.target))
|
|
412
|
+
: null;
|
|
413
|
+
const normalized = useOrthRouting ? [] : normalizePoints(edge.points);
|
|
414
|
+
const points = useOrthRouting
|
|
415
|
+
? (orthEdge?.points ?? [])
|
|
416
|
+
: normalized.length
|
|
417
|
+
? normalized
|
|
418
|
+
: fallbackPoints();
|
|
419
|
+
if (!points.length)
|
|
420
|
+
return;
|
|
421
|
+
const pointsOffsetX = useOrthRouting ? 0 : offsetX;
|
|
422
|
+
const pointsOffsetY = useOrthRouting ? 0 : offsetY;
|
|
423
|
+
const startPoint = useOrthRouting
|
|
424
|
+
? (orthEdge?.start ?? points[0])
|
|
425
|
+
: points[0];
|
|
426
|
+
const endPoint = useOrthRouting
|
|
427
|
+
? (orthEdge?.end ?? points[points.length - 1])
|
|
428
|
+
: points[points.length - 1];
|
|
429
|
+
const relation = edge
|
|
430
|
+
._original?.relation;
|
|
431
|
+
const sourceColor = nodeColorMap.get(String(edge.source)) ?? defaultStroke;
|
|
432
|
+
const targetColor = nodeColorMap.get(String(edge.target)) ?? defaultStroke;
|
|
433
|
+
const gradientKey = `edge-gradient-${String(sourceColor)}-${String(targetColor)}`.replace(/[^a-zA-Z0-9_-]/g, '');
|
|
434
|
+
const edgeStroke = edgeColorMode === 'gradient' ? `url(#${gradientKey})` : defaultStroke;
|
|
435
|
+
let pathD = '';
|
|
436
|
+
if (straightCornerRadius > 0) {
|
|
437
|
+
pathD = createRoundedPath(points, straightCornerRadius, pointsOffsetX, pointsOffsetY);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
pathD = createStraightPath(points, pointsOffsetX, pointsOffsetY);
|
|
441
|
+
}
|
|
442
|
+
if (!pathD)
|
|
443
|
+
return;
|
|
444
|
+
const pathElement = (_jsx(Path, { d: pathD, stroke: edgeStroke, strokeWidth: edgeWidth, strokeDasharray: actualDashArray, fill: "none", "data-element-type": "shape", children: enableAnimation && (_jsx("animate", { attributeName: "stroke-dashoffset", from: String(dashPatternLength), to: "0", dur: animationDuration, repeatCount: "indefinite" })) }));
|
|
445
|
+
decorElements.push(pathElement);
|
|
446
|
+
if (edgeColorMode === 'gradient') {
|
|
447
|
+
const start = startPoint;
|
|
448
|
+
const end = endPoint;
|
|
449
|
+
defsElements.push(_jsxs("linearGradient", { id: gradientKey, gradientUnits: "userSpaceOnUse", x1: start[0] + pointsOffsetX, y1: start[1] + pointsOffsetY, x2: end[0] + pointsOffsetX, y2: end[1] + pointsOffsetY, children: [_jsx("stop", { offset: "0%", stopColor: sourceColor }), _jsx("stop", { offset: "100%", stopColor: targetColor })] }));
|
|
450
|
+
}
|
|
451
|
+
if (relation?.label) {
|
|
452
|
+
let labelPoint = null;
|
|
453
|
+
const midPoint = getMidPoint(points);
|
|
454
|
+
if (midPoint) {
|
|
455
|
+
labelPoint = [
|
|
456
|
+
midPoint[0] + pointsOffsetX,
|
|
457
|
+
midPoint[1] + pointsOffsetY,
|
|
458
|
+
];
|
|
459
|
+
}
|
|
460
|
+
if (labelPoint) {
|
|
461
|
+
const labelText = String(relation.label);
|
|
462
|
+
const labelBounds = getElementBounds(_jsx(Text, { fontSize: 14, fontWeight: "normal", children: labelText }));
|
|
463
|
+
const labelX = labelPoint[0] - labelBounds.width / 2;
|
|
464
|
+
const labelY = labelPoint[1] - labelBounds.height / 2;
|
|
465
|
+
decorElements.push(_jsx(Text, { x: labelX, y: labelY, width: labelBounds.width, height: labelBounds.height, fontSize: 14, fontWeight: "normal", alignHorizontal: "center", alignVertical: "middle", fill: labelTextColor, backgroundColor: labelBackground, children: labelText }));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const effectiveShowArrow = relation?.showArrow ?? showArrow;
|
|
469
|
+
const direction = relation?.direction ?? 'forward';
|
|
470
|
+
const edgeArrowType = relation?.arrowType ?? arrowType;
|
|
471
|
+
const lastIndex = points.length - 1;
|
|
472
|
+
if (effectiveShowArrow && points.length > 1) {
|
|
473
|
+
if (direction === 'forward' || direction === 'both') {
|
|
474
|
+
const head = points[lastIndex];
|
|
475
|
+
const tail = points[lastIndex - 1];
|
|
476
|
+
const angle = Math.atan2(head[1] - tail[1], head[0] - tail[0]);
|
|
477
|
+
const arrowFill = edgeColorMode === 'gradient' ? targetColor : defaultStroke;
|
|
478
|
+
const arrowElements = createArrowElements(head[0] + pointsOffsetX, head[1] + pointsOffsetY, angle, edgeArrowType, arrowFill);
|
|
479
|
+
decorElements.push(...arrowElements);
|
|
480
|
+
}
|
|
481
|
+
if (direction === 'both') {
|
|
482
|
+
const head = points[0];
|
|
483
|
+
const tail = points[1];
|
|
484
|
+
const angle = Math.atan2(head[1] - tail[1], head[0] - tail[0]);
|
|
485
|
+
const arrowFill = edgeColorMode === 'gradient' ? sourceColor : defaultStroke;
|
|
486
|
+
const arrowElements = createArrowElements(head[0] + pointsOffsetX, head[1] + pointsOffsetY, angle, edgeArrowType, arrowFill);
|
|
487
|
+
decorElements.push(...arrowElements);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
return (_jsxs(FlexLayout, { id: "infographic-container", flexDirection: "column", justifyContent: "center", alignItems: "center", children: [titleContent, _jsxs(Group, { children: [_jsx(Defs, { children: defsElements }), _jsx(Group, { width: 0, height: 0, children: decorElements }), _jsx(ItemsGroup, { children: itemElements }), _jsx(BtnsGroup, {})] })] }));
|
|
493
|
+
};
|
|
494
|
+
registerStructure('relation-dagre-flow', {
|
|
495
|
+
component: RelationDagreFlow,
|
|
496
|
+
composites: ['title', 'item'],
|
|
497
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { COMPONENT_ROLE } from '../../../constants';
|
|
2
2
|
import { getCombinedBounds } from '../../../jsx';
|
|
3
3
|
import { getCommonAttrs, getIconAttrs, getTextElementProps, isEditableText, isGeometryElement, isIconElement, setElementRole, } from '../../../utils';
|
|
4
|
-
import { getElementViewportBounds, getScreenCTM } from '../../utils';
|
|
5
4
|
import { Plugin } from '../base';
|
|
6
5
|
import { ElementAlign, FontAlign, FontColor, FontFamily, FontSize, IconColor, } from './edit-items';
|
|
7
6
|
export class EditBar extends Plugin {
|
|
@@ -171,24 +170,43 @@ export class EditBar extends Plugin {
|
|
|
171
170
|
placeEditBar(container, selection) {
|
|
172
171
|
if (selection.length === 0)
|
|
173
172
|
return;
|
|
174
|
-
const
|
|
175
|
-
const combinedBounds = getCombinedBounds(selection.map((element) => getElementViewportBounds(svg, element)));
|
|
173
|
+
const combinedBounds = getCombinedBounds(selection.map((element) => element.getBoundingClientRect()));
|
|
176
174
|
const offsetParent = container.offsetParent ??
|
|
177
175
|
document.documentElement;
|
|
178
|
-
const parentRect = offsetParent.getBoundingClientRect();
|
|
179
176
|
const viewportHeight = document.documentElement.clientHeight;
|
|
177
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
180
178
|
const containerRect = container.getBoundingClientRect();
|
|
181
179
|
const offset = 8;
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
const anchorTop = {
|
|
181
|
+
x: combinedBounds.x + combinedBounds.width / 2,
|
|
182
|
+
y: combinedBounds.y,
|
|
183
|
+
};
|
|
184
|
+
const anchorBottom = {
|
|
185
|
+
x: anchorTop.x,
|
|
186
|
+
y: combinedBounds.y + combinedBounds.height,
|
|
187
|
+
};
|
|
185
188
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
186
|
-
let left = anchorTop.x - parentRect.left - containerRect.width / 2;
|
|
187
|
-
left = clamp(left, 0, Math.max(parentRect.width - containerRect.width, 0));
|
|
188
189
|
// Use viewport space, not container space, to decide whether we have enough room above.
|
|
189
190
|
const spaceAbove = anchorTop.y - offset;
|
|
190
191
|
const spaceBelow = viewportHeight - anchorBottom.y - offset;
|
|
191
192
|
const shouldPlaceAbove = spaceAbove >= containerRect.height || spaceAbove >= spaceBelow;
|
|
193
|
+
if (offsetParent === document.body ||
|
|
194
|
+
offsetParent === document.documentElement) {
|
|
195
|
+
const scrollX = window.scrollX || document.documentElement.scrollLeft;
|
|
196
|
+
const scrollY = window.scrollY || document.documentElement.scrollTop;
|
|
197
|
+
let left = scrollX + anchorTop.x - containerRect.width / 2;
|
|
198
|
+
left = clamp(left, scrollX, scrollX + Math.max(viewportWidth - containerRect.width, 0));
|
|
199
|
+
let top = shouldPlaceAbove
|
|
200
|
+
? scrollY + anchorTop.y - containerRect.height - offset
|
|
201
|
+
: scrollY + anchorBottom.y + offset;
|
|
202
|
+
top = clamp(top, scrollY, scrollY + Math.max(viewportHeight - containerRect.height, 0));
|
|
203
|
+
container.style.left = `${left}px`;
|
|
204
|
+
container.style.top = `${top}px`;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const parentRect = offsetParent.getBoundingClientRect();
|
|
208
|
+
let left = anchorTop.x - parentRect.left - containerRect.width / 2;
|
|
209
|
+
left = clamp(left, 0, Math.max(parentRect.width - containerRect.width, 0));
|
|
192
210
|
let top = shouldPlaceAbove
|
|
193
211
|
? anchorTop.y - parentRect.top - containerRect.height - offset
|
|
194
212
|
: anchorBottom.y - parentRect.top + offset;
|
package/esm/index.js
CHANGED
package/esm/jsx/global.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ declare global {
|
|
|
65
65
|
symbol: SVGAttributes<SVGSymbolElement>;
|
|
66
66
|
text: SVGAttributes<SVGTextElement>;
|
|
67
67
|
textPath: SVGAttributes<SVGTextPathElement>;
|
|
68
|
+
title: SVGAttributes<SVGTitleElement>;
|
|
68
69
|
tspan: SVGAttributes<SVGTSpanElement>;
|
|
69
70
|
use: SVGAttributes<SVGUseElement>;
|
|
70
71
|
view: SVGAttributes<SVGViewElement>;
|
|
@@ -21,8 +21,10 @@ export interface GroupProps extends BaseGeometryProps {
|
|
|
21
21
|
children?: JSXNode;
|
|
22
22
|
}
|
|
23
23
|
export interface RectProps extends BaseGeometryProps {
|
|
24
|
+
children?: JSXNode;
|
|
24
25
|
}
|
|
25
26
|
export interface EllipseProps extends BaseGeometryProps {
|
|
27
|
+
children?: JSXNode;
|
|
26
28
|
}
|
|
27
29
|
export interface TextProps extends BaseGeometryProps {
|
|
28
30
|
lineHeight?: number;
|
|
@@ -32,10 +34,12 @@ export interface TextProps extends BaseGeometryProps {
|
|
|
32
34
|
backgroundColor?: string;
|
|
33
35
|
backgroundOpacity?: number;
|
|
34
36
|
backgroundRadius?: number;
|
|
35
|
-
children?: string | number;
|
|
37
|
+
children?: string | number | JSXNode;
|
|
36
38
|
}
|
|
37
39
|
export interface PathProps extends BaseGeometryProps {
|
|
40
|
+
children?: JSXNode;
|
|
38
41
|
}
|
|
39
42
|
export interface PolygonProps extends Omit<BaseGeometryProps, 'points'> {
|
|
40
43
|
points?: Point[];
|
|
44
|
+
children?: JSXNode;
|
|
41
45
|
}
|
package/esm/jsx/utils/svg.js
CHANGED
|
@@ -6,8 +6,10 @@ export function renderItemIcon(svg, node, datum, options) {
|
|
|
6
6
|
if (!value)
|
|
7
7
|
return null;
|
|
8
8
|
const { themeConfig } = options;
|
|
9
|
+
const dataAttrs = datum.attributes?.icon;
|
|
9
10
|
const attrs = {
|
|
10
11
|
...themeConfig.item?.icon,
|
|
12
|
+
...dataAttrs,
|
|
11
13
|
};
|
|
12
14
|
const parsedAttrs = parseDynamicAttributes(node, attrs);
|
|
13
15
|
return createIcon(svg, node, value, parsedAttrs, datum);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type ResourceConfig } from '../../resource';
|
|
2
2
|
import type { IllusElement, ItemDatum } from '../../types';
|
|
3
|
-
export declare function renderIllus(svg: SVGSVGElement, node: SVGElement, value: string | ResourceConfig | undefined, datum?: ItemDatum): IllusElement | null;
|
|
3
|
+
export declare function renderIllus(svg: SVGSVGElement, node: SVGElement, value: string | ResourceConfig | undefined, datum?: ItemDatum, attrs?: Record<string, any>): IllusElement | null;
|
|
4
4
|
export declare function renderItemIllus(svg: SVGSVGElement, node: SVGElement, datum: ItemDatum): SVGGElement | null;
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
import { getResourceHref, getResourceId, loadResource, parseResourceConfig, } from '../../resource';
|
|
2
|
-
import { createElement, getAttributes, getOrCreateDefs, removeAttributes, uuid, } from '../../utils';
|
|
3
|
-
export function renderIllus(svg, node, value, datum) {
|
|
2
|
+
import { createElement, getAttributes, getOrCreateDefs, removeAttributes, setAttributes, uuid, } from '../../utils';
|
|
3
|
+
export function renderIllus(svg, node, value, datum, attrs = {}) {
|
|
4
4
|
if (!value)
|
|
5
5
|
return null;
|
|
6
6
|
const config = parseResourceConfig(value);
|
|
7
7
|
if (!config)
|
|
8
8
|
return null;
|
|
9
9
|
const id = getResourceId(config);
|
|
10
|
+
if (attrs && Object.keys(attrs).length > 0) {
|
|
11
|
+
setAttributes(node, attrs);
|
|
12
|
+
}
|
|
10
13
|
const clipPathId = createClipPath(svg, node, id);
|
|
11
14
|
loadResource(svg, 'illus', config, datum);
|
|
12
15
|
const { data, color } = config;
|
|
13
16
|
return createIllusElement(id, {
|
|
14
17
|
...parseIllusBounds(node),
|
|
15
|
-
'clip-path': `url(#${clipPathId})`,
|
|
16
18
|
...(color ? { color } : {}),
|
|
19
|
+
...attrs,
|
|
20
|
+
'clip-path': `url(#${clipPathId})`,
|
|
17
21
|
}, data);
|
|
18
22
|
}
|
|
19
23
|
export function renderItemIllus(svg, node, datum) {
|
|
20
24
|
const value = datum.illus;
|
|
21
|
-
|
|
25
|
+
const attrs = datum.attributes?.illus;
|
|
26
|
+
return renderIllus(svg, node, value, datum, attrs);
|
|
22
27
|
}
|
|
23
28
|
function createClipPath(svg, node, id) {
|
|
24
29
|
const clipPathId = `clip-${id}-${uuid()}`;
|