@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.
Files changed (142) hide show
  1. package/dist/infographic.min.js +191 -191
  2. package/dist/infographic.min.js.map +1 -1
  3. package/esm/designs/items/BadgeCard.js +6 -1
  4. package/esm/designs/items/SimpleCircleNode.d.ts +8 -0
  5. package/esm/designs/items/SimpleCircleNode.js +14 -0
  6. package/esm/designs/items/index.d.ts +1 -0
  7. package/esm/designs/items/index.js +1 -0
  8. package/esm/designs/structures/hierarchy-mindmap.js +19 -5
  9. package/esm/designs/structures/hierarchy-tree.d.ts +2 -1
  10. package/esm/designs/structures/hierarchy-tree.js +23 -20
  11. package/esm/designs/structures/index.d.ts +1 -0
  12. package/esm/designs/structures/index.js +1 -0
  13. package/esm/designs/structures/relation-dagre-flow.d.ts +21 -0
  14. package/esm/designs/structures/relation-dagre-flow.js +497 -0
  15. package/esm/designs/utils/hierarchy-color.d.ts +1 -1
  16. package/esm/editor/plugins/edit-bar/edit-bar.js +27 -9
  17. package/esm/index.js +1 -1
  18. package/esm/jsx/global.d.ts +1 -0
  19. package/esm/jsx/types/element.d.ts +5 -1
  20. package/esm/jsx/utils/svg.js +2 -0
  21. package/esm/renderer/composites/icon.js +2 -0
  22. package/esm/renderer/composites/illus.d.ts +1 -1
  23. package/esm/renderer/composites/illus.js +9 -4
  24. package/esm/renderer/composites/text.js +4 -2
  25. package/esm/renderer/fonts/loader.js +3 -1
  26. package/esm/renderer/fonts/registry.js +1 -1
  27. package/esm/renderer/renderer.js +28 -25
  28. package/esm/resource/loader.js +3 -1
  29. package/esm/runtime/Infographic.js +1 -1
  30. package/esm/ssr/dom-shim.d.ts +4 -0
  31. package/esm/ssr/dom-shim.js +107 -0
  32. package/esm/ssr/index.d.ts +1 -0
  33. package/esm/ssr/index.js +1 -0
  34. package/esm/ssr/renderer.d.ts +2 -0
  35. package/esm/ssr/renderer.js +60 -0
  36. package/esm/syntax/index.js +57 -1
  37. package/esm/syntax/parser.js +44 -0
  38. package/esm/syntax/relations.d.ts +6 -0
  39. package/esm/syntax/relations.js +251 -0
  40. package/esm/syntax/schema.d.ts +1 -0
  41. package/esm/syntax/schema.js +12 -0
  42. package/esm/templates/built-in.js +2 -0
  43. package/esm/templates/relation-dagre-flow.d.ts +2 -0
  44. package/esm/templates/relation-dagre-flow.js +68 -0
  45. package/esm/types/data.d.ts +24 -3
  46. package/esm/utils/data.js +1 -1
  47. package/esm/utils/index.d.ts +1 -0
  48. package/esm/utils/index.js +1 -0
  49. package/esm/utils/is-browser.js +5 -9
  50. package/esm/utils/measure-text.d.ts +2 -2
  51. package/esm/utils/measure-text.js +4 -4
  52. package/esm/utils/recognizer.js +8 -5
  53. package/esm/utils/text.js +27 -19
  54. package/lib/designs/items/BadgeCard.js +6 -1
  55. package/lib/designs/items/SimpleCircleNode.d.ts +8 -0
  56. package/lib/designs/items/SimpleCircleNode.js +18 -0
  57. package/lib/designs/items/index.d.ts +1 -0
  58. package/lib/designs/items/index.js +1 -0
  59. package/lib/designs/structures/hierarchy-mindmap.js +19 -5
  60. package/lib/designs/structures/hierarchy-tree.d.ts +2 -1
  61. package/lib/designs/structures/hierarchy-tree.js +23 -20
  62. package/lib/designs/structures/index.d.ts +1 -0
  63. package/lib/designs/structures/index.js +1 -0
  64. package/lib/designs/structures/relation-dagre-flow.d.ts +21 -0
  65. package/lib/designs/structures/relation-dagre-flow.js +501 -0
  66. package/lib/designs/utils/hierarchy-color.d.ts +1 -1
  67. package/lib/editor/plugins/edit-bar/edit-bar.js +27 -9
  68. package/lib/jsx/global.d.ts +1 -0
  69. package/lib/jsx/types/element.d.ts +5 -1
  70. package/lib/jsx/utils/svg.js +2 -0
  71. package/lib/renderer/composites/icon.js +2 -0
  72. package/lib/renderer/composites/illus.d.ts +1 -1
  73. package/lib/renderer/composites/illus.js +8 -3
  74. package/lib/renderer/composites/text.js +4 -2
  75. package/lib/renderer/fonts/loader.js +2 -0
  76. package/lib/renderer/fonts/registry.js +6 -6
  77. package/lib/renderer/renderer.js +27 -24
  78. package/lib/resource/loader.js +3 -1
  79. package/lib/runtime/Infographic.js +1 -1
  80. package/lib/ssr/dom-shim.d.ts +4 -0
  81. package/lib/ssr/dom-shim.js +110 -0
  82. package/lib/ssr/index.d.ts +1 -0
  83. package/lib/ssr/index.js +5 -0
  84. package/lib/ssr/renderer.d.ts +2 -0
  85. package/lib/ssr/renderer.js +63 -0
  86. package/lib/syntax/index.js +57 -1
  87. package/lib/syntax/parser.js +44 -0
  88. package/lib/syntax/relations.d.ts +6 -0
  89. package/lib/syntax/relations.js +254 -0
  90. package/lib/syntax/schema.d.ts +1 -0
  91. package/lib/syntax/schema.js +13 -1
  92. package/lib/templates/built-in.js +2 -0
  93. package/lib/templates/relation-dagre-flow.d.ts +2 -0
  94. package/lib/templates/relation-dagre-flow.js +71 -0
  95. package/lib/types/data.d.ts +24 -3
  96. package/lib/utils/data.js +2 -5
  97. package/lib/utils/index.d.ts +1 -0
  98. package/lib/utils/index.js +1 -0
  99. package/lib/utils/is-browser.js +5 -9
  100. package/lib/utils/measure-text.d.ts +2 -2
  101. package/lib/utils/measure-text.js +4 -4
  102. package/lib/utils/recognizer.js +8 -5
  103. package/lib/utils/text.js +28 -23
  104. package/package.json +19 -7
  105. package/src/designs/items/BadgeCard.tsx +9 -2
  106. package/src/designs/items/SimpleCircleNode.tsx +46 -0
  107. package/src/designs/items/index.ts +1 -0
  108. package/src/designs/structures/hierarchy-mindmap.tsx +15 -2
  109. package/src/designs/structures/hierarchy-tree.tsx +33 -31
  110. package/src/designs/structures/index.ts +1 -0
  111. package/src/designs/structures/relation-dagre-flow.tsx +782 -0
  112. package/src/designs/utils/hierarchy-color.ts +6 -1
  113. package/src/editor/plugins/edit-bar/edit-bar.ts +41 -17
  114. package/src/index.ts +1 -1
  115. package/src/jsx/global.ts +1 -0
  116. package/src/jsx/types/element.ts +15 -6
  117. package/src/jsx/utils/svg.ts +2 -0
  118. package/src/renderer/composites/icon.ts +2 -0
  119. package/src/renderer/composites/illus.ts +16 -3
  120. package/src/renderer/composites/text.ts +7 -2
  121. package/src/renderer/fonts/loader.ts +7 -1
  122. package/src/renderer/fonts/registry.ts +1 -1
  123. package/src/renderer/renderer.ts +42 -24
  124. package/src/resource/loader.ts +3 -1
  125. package/src/runtime/Infographic.tsx +1 -1
  126. package/src/ssr/dom-shim.ts +120 -0
  127. package/src/ssr/index.ts +1 -0
  128. package/src/ssr/renderer.ts +72 -0
  129. package/src/syntax/index.ts +58 -1
  130. package/src/syntax/parser.ts +49 -0
  131. package/src/syntax/relations.ts +291 -0
  132. package/src/syntax/schema.ts +16 -0
  133. package/src/templates/built-in.ts +4 -2
  134. package/src/templates/relation-dagre-flow.ts +73 -0
  135. package/src/types/data.ts +26 -3
  136. package/src/utils/data.ts +1 -1
  137. package/src/utils/index.ts +1 -0
  138. package/src/utils/is-browser.ts +3 -9
  139. package/src/utils/measure-text.ts +6 -7
  140. package/src/utils/recognizer.ts +9 -5
  141. package/src/utils/svg.ts +0 -1
  142. 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,7 @@
1
1
  /**
2
2
  * 层级结构着色模式类型
3
3
  */
4
- export type HierarchyColorMode = 'level' | 'branch' | 'node' | 'node-flat';
4
+ export type HierarchyColorMode = 'level' | 'branch' | 'node' | 'node-flat' | 'group';
5
5
  /**
6
6
  * 层级节点信息接口
7
7
  */
@@ -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 svg = this.editor.getDocument();
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 matrix = getScreenCTM(svg);
183
- const anchorTop = new DOMPoint(combinedBounds.x + combinedBounds.width / 2, combinedBounds.y).matrixTransform(matrix);
184
- const anchorBottom = new DOMPoint(combinedBounds.x + combinedBounds.width / 2, combinedBounds.y + combinedBounds.height).matrixTransform(matrix);
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
@@ -1,4 +1,4 @@
1
- import pkg from '../package.json';
1
+ import pkg from '../package.json' with { type: 'json' };
2
2
  export const VERSION = pkg.version;
3
3
  export * from './designs';
4
4
  export { getItemProps, getThemeColors } from './designs/utils';
@@ -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
  }
@@ -36,6 +36,8 @@ const SPECIFIC_ATTRS_MAP = {
36
36
  textLength: 'textLength',
37
37
  lengthAdjust: 'lengthAdjust',
38
38
  // Animation
39
+ attributeName: 'attributeName',
40
+ attributeType: 'attributeType',
39
41
  repeatCount: 'repeatCount',
40
42
  repeatDur: 'repeatDur',
41
43
  calcMode: 'calcMode',
@@ -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
- return renderIllus(svg, node, value, datum);
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()}`;