@antv/infographic 0.2.6 → 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 (164) 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/HorizontalIconArrow.js +2 -2
  5. package/esm/designs/items/SimpleCircleNode.d.ts +8 -0
  6. package/esm/designs/items/SimpleCircleNode.js +14 -0
  7. package/esm/designs/items/index.d.ts +1 -0
  8. package/esm/designs/items/index.js +1 -0
  9. package/esm/designs/structures/hierarchy-mindmap.js +19 -5
  10. package/esm/designs/structures/hierarchy-tree.d.ts +2 -1
  11. package/esm/designs/structures/hierarchy-tree.js +23 -20
  12. package/esm/designs/structures/index.d.ts +2 -0
  13. package/esm/designs/structures/index.js +2 -0
  14. package/esm/designs/structures/relation-dagre-flow.d.ts +21 -0
  15. package/esm/designs/structures/relation-dagre-flow.js +497 -0
  16. package/esm/designs/structures/sequence-funnel.d.ts +10 -0
  17. package/esm/designs/structures/sequence-funnel.js +110 -0
  18. package/esm/designs/utils/hierarchy-color.d.ts +1 -1
  19. package/esm/editor/plugins/edit-bar/edit-bar.js +27 -9
  20. package/esm/index.js +1 -1
  21. package/esm/jsx/global.d.ts +1 -0
  22. package/esm/jsx/types/element.d.ts +5 -1
  23. package/esm/jsx/utils/svg.js +2 -0
  24. package/esm/options/types.d.ts +2 -0
  25. package/esm/renderer/composites/background.d.ts +2 -1
  26. package/esm/renderer/composites/background.js +6 -4
  27. package/esm/renderer/composites/icon.js +2 -0
  28. package/esm/renderer/composites/illus.d.ts +1 -1
  29. package/esm/renderer/composites/illus.js +9 -4
  30. package/esm/renderer/composites/text.js +4 -2
  31. package/esm/renderer/fonts/loader.js +5 -3
  32. package/esm/renderer/fonts/registry.js +1 -1
  33. package/esm/renderer/renderer.js +34 -22
  34. package/esm/resource/loader.js +3 -1
  35. package/esm/resource/loaders/svg.js +6 -4
  36. package/esm/runtime/Infographic.js +1 -1
  37. package/esm/ssr/dom-shim.d.ts +4 -0
  38. package/esm/ssr/dom-shim.js +107 -0
  39. package/esm/ssr/index.d.ts +1 -0
  40. package/esm/ssr/index.js +1 -0
  41. package/esm/ssr/renderer.d.ts +2 -0
  42. package/esm/ssr/renderer.js +60 -0
  43. package/esm/syntax/index.js +57 -1
  44. package/esm/syntax/parser.js +44 -0
  45. package/esm/syntax/relations.d.ts +6 -0
  46. package/esm/syntax/relations.js +251 -0
  47. package/esm/syntax/schema.d.ts +1 -0
  48. package/esm/syntax/schema.js +12 -0
  49. package/esm/templates/built-in.js +12 -0
  50. package/esm/templates/relation-dagre-flow.d.ts +2 -0
  51. package/esm/templates/relation-dagre-flow.js +68 -0
  52. package/esm/types/data.d.ts +24 -3
  53. package/esm/utils/data.js +1 -1
  54. package/esm/utils/index.d.ts +1 -0
  55. package/esm/utils/index.js +1 -0
  56. package/esm/utils/is-browser.js +5 -9
  57. package/esm/utils/measure-text.d.ts +2 -2
  58. package/esm/utils/measure-text.js +4 -4
  59. package/esm/utils/padding.js +19 -14
  60. package/esm/utils/recognizer.js +8 -5
  61. package/esm/utils/text.js +27 -19
  62. package/lib/designs/items/BadgeCard.js +6 -1
  63. package/lib/designs/items/HorizontalIconArrow.js +1 -1
  64. package/lib/designs/items/SimpleCircleNode.d.ts +8 -0
  65. package/lib/designs/items/SimpleCircleNode.js +18 -0
  66. package/lib/designs/items/index.d.ts +1 -0
  67. package/lib/designs/items/index.js +1 -0
  68. package/lib/designs/structures/hierarchy-mindmap.js +19 -5
  69. package/lib/designs/structures/hierarchy-tree.d.ts +2 -1
  70. package/lib/designs/structures/hierarchy-tree.js +23 -20
  71. package/lib/designs/structures/index.d.ts +2 -0
  72. package/lib/designs/structures/index.js +2 -0
  73. package/lib/designs/structures/relation-dagre-flow.d.ts +21 -0
  74. package/lib/designs/structures/relation-dagre-flow.js +501 -0
  75. package/lib/designs/structures/sequence-funnel.d.ts +10 -0
  76. package/lib/designs/structures/sequence-funnel.js +150 -0
  77. package/lib/designs/utils/hierarchy-color.d.ts +1 -1
  78. package/lib/editor/plugins/edit-bar/edit-bar.js +27 -9
  79. package/lib/jsx/global.d.ts +1 -0
  80. package/lib/jsx/types/element.d.ts +5 -1
  81. package/lib/jsx/utils/svg.js +2 -0
  82. package/lib/options/types.d.ts +2 -0
  83. package/lib/renderer/composites/background.d.ts +2 -1
  84. package/lib/renderer/composites/background.js +6 -4
  85. package/lib/renderer/composites/icon.js +2 -0
  86. package/lib/renderer/composites/illus.d.ts +1 -1
  87. package/lib/renderer/composites/illus.js +8 -3
  88. package/lib/renderer/composites/text.js +4 -2
  89. package/lib/renderer/fonts/loader.js +4 -2
  90. package/lib/renderer/fonts/registry.js +6 -6
  91. package/lib/renderer/renderer.js +33 -21
  92. package/lib/resource/loader.js +3 -1
  93. package/lib/resource/loaders/svg.js +6 -4
  94. package/lib/runtime/Infographic.js +1 -1
  95. package/lib/ssr/dom-shim.d.ts +4 -0
  96. package/lib/ssr/dom-shim.js +110 -0
  97. package/lib/ssr/index.d.ts +1 -0
  98. package/lib/ssr/index.js +5 -0
  99. package/lib/ssr/renderer.d.ts +2 -0
  100. package/lib/ssr/renderer.js +63 -0
  101. package/lib/syntax/index.js +57 -1
  102. package/lib/syntax/parser.js +44 -0
  103. package/lib/syntax/relations.d.ts +6 -0
  104. package/lib/syntax/relations.js +254 -0
  105. package/lib/syntax/schema.d.ts +1 -0
  106. package/lib/syntax/schema.js +13 -1
  107. package/lib/templates/built-in.js +12 -0
  108. package/lib/templates/relation-dagre-flow.d.ts +2 -0
  109. package/lib/templates/relation-dagre-flow.js +71 -0
  110. package/lib/types/data.d.ts +24 -3
  111. package/lib/utils/data.js +2 -5
  112. package/lib/utils/index.d.ts +1 -0
  113. package/lib/utils/index.js +1 -0
  114. package/lib/utils/is-browser.js +5 -9
  115. package/lib/utils/measure-text.d.ts +2 -2
  116. package/lib/utils/measure-text.js +4 -4
  117. package/lib/utils/padding.js +19 -14
  118. package/lib/utils/recognizer.js +8 -5
  119. package/lib/utils/text.js +28 -23
  120. package/package.json +20 -8
  121. package/src/designs/items/BadgeCard.tsx +9 -2
  122. package/src/designs/items/HorizontalIconArrow.tsx +10 -5
  123. package/src/designs/items/SimpleCircleNode.tsx +46 -0
  124. package/src/designs/items/index.ts +1 -0
  125. package/src/designs/structures/hierarchy-mindmap.tsx +15 -2
  126. package/src/designs/structures/hierarchy-tree.tsx +33 -31
  127. package/src/designs/structures/index.ts +2 -0
  128. package/src/designs/structures/relation-dagre-flow.tsx +782 -0
  129. package/src/designs/structures/sequence-funnel.tsx +260 -0
  130. package/src/designs/utils/hierarchy-color.ts +6 -1
  131. package/src/editor/plugins/edit-bar/edit-bar.ts +41 -17
  132. package/src/index.ts +1 -1
  133. package/src/jsx/global.ts +1 -0
  134. package/src/jsx/types/element.ts +15 -6
  135. package/src/jsx/utils/svg.ts +2 -0
  136. package/src/options/types.ts +2 -0
  137. package/src/renderer/composites/background.ts +8 -5
  138. package/src/renderer/composites/icon.ts +2 -0
  139. package/src/renderer/composites/illus.ts +16 -3
  140. package/src/renderer/composites/text.ts +7 -2
  141. package/src/renderer/fonts/loader.ts +9 -3
  142. package/src/renderer/fonts/registry.ts +1 -1
  143. package/src/renderer/renderer.ts +49 -22
  144. package/src/resource/loader.ts +3 -1
  145. package/src/resource/loaders/svg.ts +8 -4
  146. package/src/runtime/Infographic.tsx +1 -1
  147. package/src/ssr/dom-shim.ts +120 -0
  148. package/src/ssr/index.ts +1 -0
  149. package/src/ssr/renderer.ts +72 -0
  150. package/src/syntax/index.ts +58 -1
  151. package/src/syntax/parser.ts +49 -0
  152. package/src/syntax/relations.ts +291 -0
  153. package/src/syntax/schema.ts +16 -0
  154. package/src/templates/built-in.ts +12 -0
  155. package/src/templates/relation-dagre-flow.ts +73 -0
  156. package/src/types/data.ts +26 -3
  157. package/src/utils/data.ts +1 -1
  158. package/src/utils/index.ts +1 -0
  159. package/src/utils/is-browser.ts +3 -9
  160. package/src/utils/measure-text.ts +6 -7
  161. package/src/utils/padding.ts +18 -14
  162. package/src/utils/recognizer.ts +9 -5
  163. package/src/utils/svg.ts +0 -1
  164. package/src/utils/text.ts +25 -19
@@ -0,0 +1,291 @@
1
+ import type { ItemDatum, RelationDatum } from '../types';
2
+ import { mapWithSchema } from './mapper';
3
+ import { RelationSchema } from './schema';
4
+ import type { SyntaxError, SyntaxNode } from './types';
5
+
6
+ const RELATION_TOKEN = /[<>=o.x-]{2,}/;
7
+ const ARROW_TOKEN = /[<>=o.x-]{2,}/g;
8
+
9
+ interface ParsedNode {
10
+ id: string;
11
+ label?: string;
12
+ }
13
+
14
+ interface ParsedEdge {
15
+ label?: string;
16
+ direction: 'forward' | 'both' | 'none';
17
+ reverse: boolean;
18
+ nextIndex: number;
19
+ }
20
+
21
+ function normalizeLabel(text: string) {
22
+ let label = text.trim();
23
+ if (!label) return '';
24
+ const first = label[0];
25
+ const last = label[label.length - 1];
26
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
27
+ label = label.slice(1, -1);
28
+ }
29
+ label = label
30
+ .replace(/\\(["'])/g, '$1')
31
+ .replace(/(&quot;|&#quot;|#quot;)/g, '"')
32
+ .replace(/(&apos;|&#39;|#apos;)/g, "'");
33
+ return label.trim();
34
+ }
35
+
36
+ function stripEdgeLabelPrefix(text: string) {
37
+ const trimmed = text
38
+ .trim()
39
+ .replace(/^[-=.ox]+/, '')
40
+ .trim();
41
+ return normalizeLabel(trimmed);
42
+ }
43
+
44
+ function skipSpaces(text: string, index: number) {
45
+ let cursor = index;
46
+ while (cursor < text.length && /\s/.test(text[cursor])) {
47
+ cursor += 1;
48
+ }
49
+ return cursor;
50
+ }
51
+
52
+ function readNode(text: string, startIndex: number) {
53
+ let index = skipSpaces(text, startIndex);
54
+ if (index >= text.length) return null;
55
+ const idStart = index;
56
+ while (index < text.length) {
57
+ const char = text[index];
58
+ if (
59
+ /\s/.test(char) ||
60
+ char === '[' ||
61
+ char === '(' ||
62
+ char === '@' ||
63
+ char === '|' ||
64
+ char === '<' ||
65
+ char === '>'
66
+ ) {
67
+ break;
68
+ }
69
+ if (
70
+ char === '-' &&
71
+ (text[index + 1] === '-' ||
72
+ text[index + 1] === '>' ||
73
+ text[index + 1] === '<')
74
+ ) {
75
+ break;
76
+ }
77
+ index += 1;
78
+ }
79
+ if (index === idStart) return null;
80
+ const id = text.slice(idStart, index).trim();
81
+ if (!id) return null;
82
+
83
+ if (text.startsWith('@{', index)) {
84
+ const braceEnd = text.indexOf('}', index + 2);
85
+ if (braceEnd !== -1) {
86
+ index = braceEnd + 1;
87
+ }
88
+ }
89
+
90
+ index = skipSpaces(text, index);
91
+ let label: string | undefined;
92
+ if (text[index] === '[') {
93
+ const end = text.indexOf(']', index + 1);
94
+ if (end !== -1) {
95
+ label = normalizeLabel(text.slice(index + 1, end));
96
+ index = end + 1;
97
+ }
98
+ } else if (text[index] === '(') {
99
+ const end = text.indexOf(')', index + 1);
100
+ if (end !== -1) {
101
+ let content = text.slice(index + 1, end).trim();
102
+ if (content.startsWith('[') && content.endsWith(']')) {
103
+ content = content.slice(1, -1).trim();
104
+ }
105
+ label = normalizeLabel(content);
106
+ index = end + 1;
107
+ }
108
+ }
109
+
110
+ return {
111
+ node: { id, label } as ParsedNode,
112
+ nextIndex: index,
113
+ };
114
+ }
115
+
116
+ function readEdge(text: string, startIndex: number): ParsedEdge | null {
117
+ ARROW_TOKEN.lastIndex = startIndex;
118
+ const match = ARROW_TOKEN.exec(text);
119
+ if (!match) return null;
120
+ const arrowToken = match[0];
121
+ const arrowStart = match.index;
122
+ const arrowEnd = arrowStart + arrowToken.length;
123
+ const labelPrefix = stripEdgeLabelPrefix(text.slice(startIndex, arrowStart));
124
+ let label = labelPrefix || undefined;
125
+ let directionToken = arrowToken;
126
+ let index = arrowEnd;
127
+
128
+ index = skipSpaces(text, index);
129
+ if (text[index] === '|') {
130
+ const pipeEnd = text.indexOf('|', index + 1);
131
+ if (pipeEnd !== -1) {
132
+ const pipeLabel = normalizeLabel(text.slice(index + 1, pipeEnd));
133
+ label = pipeLabel || label;
134
+ index = pipeEnd + 1;
135
+ const afterLabel = skipSpaces(text, index);
136
+ ARROW_TOKEN.lastIndex = afterLabel;
137
+ const tail = ARROW_TOKEN.exec(text);
138
+ if (tail && tail.index === afterLabel) {
139
+ directionToken += tail[0];
140
+ index = tail.index + tail[0].length;
141
+ } else {
142
+ index = afterLabel;
143
+ }
144
+ }
145
+ }
146
+
147
+ const hasLeft = directionToken.includes('<');
148
+ const hasRight = directionToken.includes('>');
149
+ const markerMatch = directionToken.match(/[xo]/gi) || [];
150
+ const markerLeft = /^[xo]/i.test(directionToken);
151
+ const markerRight = /[xo]$/i.test(directionToken);
152
+ let direction: ParsedEdge['direction'] = 'none';
153
+ let reverse = false;
154
+ if (
155
+ (hasLeft && hasRight) ||
156
+ (hasLeft && markerRight) ||
157
+ (hasRight && markerLeft) ||
158
+ (markerLeft && markerRight)
159
+ ) {
160
+ direction = 'both';
161
+ } else if (hasLeft || hasRight || markerMatch.length > 0) {
162
+ direction = 'forward';
163
+ reverse = hasLeft && !hasRight;
164
+ }
165
+
166
+ return { label, direction, reverse, nextIndex: index };
167
+ }
168
+
169
+ function parseRelationLine(text: string) {
170
+ const relations: RelationDatum[] = [];
171
+ const nodes: ParsedNode[] = [];
172
+ const nodeMap = new Map<string, ParsedNode>();
173
+ let index = 0;
174
+ const first = readNode(text, index);
175
+ if (!first) return { relations, nodes };
176
+ let current = first.node;
177
+ if (!nodeMap.has(current.id)) {
178
+ nodeMap.set(current.id, current);
179
+ nodes.push(current);
180
+ }
181
+ index = first.nextIndex;
182
+
183
+ while (index < text.length) {
184
+ const edge = readEdge(text, index);
185
+ if (!edge) break;
186
+ index = edge.nextIndex;
187
+ const nextNode = readNode(text, index);
188
+ if (!nextNode) break;
189
+ index = nextNode.nextIndex;
190
+
191
+ let from = current.id;
192
+ let to = nextNode.node.id;
193
+ const direction = edge.direction;
194
+ if (edge.reverse) {
195
+ from = nextNode.node.id;
196
+ to = current.id;
197
+ }
198
+
199
+ const relation: RelationDatum = {
200
+ from,
201
+ to,
202
+ };
203
+ if (edge.label) relation.label = edge.label;
204
+ if (direction === 'both') relation.direction = 'both';
205
+ if (direction === 'none') relation.direction = 'none';
206
+ relations.push(relation);
207
+
208
+ if (!nodeMap.has(current.id)) {
209
+ nodeMap.set(current.id, current);
210
+ nodes.push(current);
211
+ }
212
+ if (!nodeMap.has(nextNode.node.id)) {
213
+ nodeMap.set(nextNode.node.id, nextNode.node);
214
+ nodes.push(nextNode.node);
215
+ }
216
+ current = nextNode.node;
217
+ }
218
+
219
+ return { relations, nodes };
220
+ }
221
+
222
+ function ensureItemLabel(
223
+ items: Map<string, ItemDatum>,
224
+ list: ItemDatum[],
225
+ id: string,
226
+ label?: string,
227
+ ) {
228
+ if (!id) return;
229
+ const existing = items.get(id);
230
+ if (existing) {
231
+ if (!existing.label && label) existing.label = label;
232
+ return;
233
+ }
234
+ const item: ItemDatum = { id, label: label || id };
235
+ items.set(id, item);
236
+ list.push(item);
237
+ }
238
+
239
+ function parseExplicitRelation(
240
+ node: SyntaxNode,
241
+ path: string,
242
+ errors: SyntaxError[],
243
+ ) {
244
+ const value = mapWithSchema(node, RelationSchema, path, errors);
245
+ if (!value || typeof value !== 'object') return null;
246
+ if (typeof value.from !== 'string' || typeof value.to !== 'string') {
247
+ return null;
248
+ }
249
+ return value as RelationDatum;
250
+ }
251
+
252
+ export function parseRelationsNode(
253
+ node: SyntaxNode,
254
+ errors: SyntaxError[],
255
+ path: string,
256
+ ) {
257
+ const relations: RelationDatum[] = [];
258
+ const items: ItemDatum[] = [];
259
+ const itemMap = new Map<string, ItemDatum>();
260
+
261
+ const parseLine = (line: string) => {
262
+ if (!RELATION_TOKEN.test(line)) return;
263
+ const parsed = parseRelationLine(line);
264
+ parsed.nodes.forEach((nodeItem) => {
265
+ ensureItemLabel(itemMap, items, nodeItem.id, nodeItem.label);
266
+ });
267
+ relations.push(...parsed.relations);
268
+ };
269
+
270
+ if (node.kind === 'array') {
271
+ node.items.forEach((item, index) => {
272
+ if (
273
+ item.kind === 'object' &&
274
+ item.value &&
275
+ Object.keys(item.entries).length === 0 &&
276
+ RELATION_TOKEN.test(item.value)
277
+ ) {
278
+ parseLine(item.value);
279
+ return;
280
+ }
281
+ const relation = parseExplicitRelation(item, `${path}[${index}]`, errors);
282
+ if (relation) {
283
+ relations.push(relation);
284
+ }
285
+ });
286
+ } else if (node.kind === 'object' && node.value) {
287
+ parseLine(node.value);
288
+ }
289
+
290
+ return { relations, items };
291
+ }
@@ -38,13 +38,28 @@ const shapeStyleSchema = object(nullableColorFields, { allowUnknown: true });
38
38
 
39
39
  const itemDatumSchema: ObjectSchema = object({}, { allowUnknown: true });
40
40
  itemDatumSchema.fields = {
41
+ id: string(),
41
42
  label: string(),
42
43
  value: union(number(), string()),
43
44
  desc: string(),
44
45
  icon: string(),
46
+ group: string(),
45
47
  children: array(itemDatumSchema),
46
48
  };
47
49
 
50
+ export const RelationSchema: ObjectSchema = object(
51
+ {
52
+ id: string(),
53
+ from: string(),
54
+ to: string(),
55
+ label: string(),
56
+ direction: enumOf(['forward', 'both', 'none']),
57
+ showArrow: enumOf(['true', 'false']),
58
+ arrowType: enumOf(['arrow', 'triangle', 'diamond']),
59
+ },
60
+ { allowUnknown: true },
61
+ );
62
+
48
63
  export const ThemeSchema = object(
49
64
  {
50
65
  type: string(),
@@ -101,6 +116,7 @@ export const DataSchema = object({
101
116
  title: string(),
102
117
  desc: string(),
103
118
  items: array(itemDatumSchema),
119
+ relations: array(RelationSchema),
104
120
  });
105
121
 
106
122
  export const TemplateSchema = object(
@@ -3,6 +3,7 @@ import { hierarchyStructureTemplates } from './hierarchy-structure';
3
3
  import { hierarchyTreeTemplates } from './hierarchy-tree';
4
4
  import { listZigzagTemplates } from './list-zigzag';
5
5
  import { registerTemplate } from './registry';
6
+ import { relationDagreFlowTemplates } from './relation-dagre-flow';
6
7
  import { sequenceStairsTemplates } from './sequence-stairs';
7
8
  import type { TemplateOptions } from './types';
8
9
  import { wordCloudTemplate } from './word-cloud';
@@ -310,6 +311,16 @@ const BUILT_IN_TEMPLATES: Record<string, TemplateOptions> = {
310
311
  colorPrimary: '#1677ff',
311
312
  },
312
313
  },
314
+ 'sequence-funnel-simple': {
315
+ design: {
316
+ title: 'default',
317
+ structure: { type: 'sequence-funnel' },
318
+ items: [{ type: 'simple', showIcon: false, usePaletteColor: true }],
319
+ },
320
+ themeConfig: {
321
+ palette: '#1677ff',
322
+ },
323
+ },
313
324
  'list-row-horizontal-icon-line': {
314
325
  design: {
315
326
  title: 'default',
@@ -810,6 +821,7 @@ const BUILT_IN_TEMPLATES: Record<string, TemplateOptions> = {
810
821
  ...sequenceStairsTemplates,
811
822
  ...wordCloudTemplate,
812
823
  ...listZigzagTemplates,
824
+ ...relationDagreFlowTemplates,
813
825
  ...hierarchyStructureTemplates,
814
826
  };
815
827
 
@@ -0,0 +1,73 @@
1
+ import type { TemplateOptions } from './types';
2
+
3
+ const items = {
4
+ 'simple-circle-node': {
5
+ type: 'simple-circle-node',
6
+ },
7
+ 'badge-card': {
8
+ type: 'badge-card',
9
+ },
10
+ capsule: {
11
+ type: 'capsule-item',
12
+ },
13
+ 'compact-card': {
14
+ type: 'compact-card',
15
+ },
16
+ } as const;
17
+
18
+ const baseStructureAttrs = {
19
+ type: 'relation-dagre-flow',
20
+ edgeColorMode: 'gradient',
21
+ showArrow: true,
22
+ arrowType: 'triangle',
23
+ colorMode: 'node',
24
+ } as const;
25
+
26
+ const structures = {
27
+ tb: {
28
+ ...baseStructureAttrs,
29
+ rankdir: 'TB',
30
+ edgeRouting: 'orth',
31
+ },
32
+ lr: {
33
+ ...baseStructureAttrs,
34
+ rankdir: 'LR',
35
+ edgeRouting: 'orth',
36
+ },
37
+ 'tb-animated': {
38
+ ...baseStructureAttrs,
39
+ rankdir: 'TB',
40
+ edgeCornerRadius: 12,
41
+ edgeRouting: 'orth',
42
+ edgeAnimation: 'ant-line',
43
+ edgeDashPattern: '8,4',
44
+ edgeAnimationSpeed: 1,
45
+ },
46
+ 'lr-animated': {
47
+ ...baseStructureAttrs,
48
+ rankdir: 'LR',
49
+ edgeCornerRadius: 12,
50
+ edgeRouting: 'orth',
51
+ edgeAnimation: 'ant-line',
52
+ edgeDashPattern: '8,4',
53
+ edgeAnimationSpeed: 1,
54
+ },
55
+ };
56
+
57
+ export const relationDagreFlowTemplates: Record<string, TemplateOptions> = {};
58
+
59
+ const omit = ['lr-capsule', 'tb-capsule'];
60
+ Object.entries(structures).forEach(([strKey, strAttrs]) => {
61
+ Object.entries(items).forEach(([itemKey, itemAttrs]) => {
62
+ const appendix = `${strKey}-${itemKey}`;
63
+ if (omit.includes(appendix)) return;
64
+ const templateKey = `relation-dagre-flow-${appendix}`;
65
+ relationDagreFlowTemplates[templateKey] = {
66
+ design: {
67
+ title: 'default',
68
+ structure: strAttrs,
69
+ item: itemAttrs,
70
+ },
71
+ };
72
+ });
73
+ });
package/src/types/data.ts CHANGED
@@ -1,20 +1,43 @@
1
1
  import type { ResourceConfig } from '../resource';
2
2
 
3
- export interface ItemDatum {
3
+ export interface BaseDatum {
4
+ id?: string;
4
5
  icon?: string | ResourceConfig;
5
6
  label?: string;
6
7
  desc?: string;
7
8
  value?: number;
8
- illus?: string | ResourceConfig;
9
- children?: ItemDatum[];
10
9
  attributes?: Record<string, object>;
11
10
  [key: string]: any;
12
11
  }
13
12
 
13
+ export interface ItemDatum extends BaseDatum {
14
+ illus?: string | ResourceConfig;
15
+ /** for hierarchical structure */
16
+ children?: ItemDatum[];
17
+ /** 图布局中用于分组,相同的 group 会使用同样的颜色 */
18
+ group?: string;
19
+ }
20
+
21
+ export interface RelationDatum extends BaseDatum {
22
+ id?: string;
23
+ from: string;
24
+ to: string;
25
+ /**
26
+ * 表示连线的方向,默认 'forward'
27
+ * - 'forward':单向,从 from 指向 to
28
+ * - 'both':双向
29
+ * - 'none':无方向
30
+ */
31
+ direction?: 'forward' | 'both' | 'none';
32
+ showArrow?: boolean;
33
+ arrowType?: 'arrow' | 'triangle' | 'diamond';
34
+ }
35
+
14
36
  export interface Data {
15
37
  title?: string;
16
38
  desc?: string;
17
39
  items: ItemDatum[];
40
+ relations?: RelationDatum[];
18
41
  illus?: Record<string, string | ResourceConfig>;
19
42
  attributes?: Record<string, object>;
20
43
  [key: string]: any;
package/src/utils/data.ts CHANGED
@@ -1,4 +1,4 @@
1
- import get from 'lodash-es/get';
1
+ import { get } from 'lodash-es';
2
2
  import type { Data, ItemDatum } from '../types';
3
3
 
4
4
  /**
@@ -8,6 +8,7 @@ export * from './font';
8
8
  export * from './get-types';
9
9
  export * from './hash';
10
10
  export * from './icon';
11
+ export * from './is-node';
11
12
  export * from './item';
12
13
  export * from './join';
13
14
  export * from './measure-text';
@@ -1,18 +1,14 @@
1
- let _isBrowser: boolean | undefined;
1
+ let IS_BROWSER: true | undefined;
2
2
 
3
3
  export function isBrowser(): boolean {
4
- if (_isBrowser !== undefined) {
5
- return _isBrowser;
6
- }
4
+ if (IS_BROWSER) return true;
7
5
 
8
6
  if (typeof window === 'undefined' || typeof document === 'undefined') {
9
- _isBrowser = false;
10
7
  return false;
11
8
  }
12
9
 
13
10
  const body = document.body;
14
11
  if (!body) {
15
- _isBrowser = false;
16
12
  return false;
17
13
  }
18
14
 
@@ -47,7 +43,6 @@ export function isBrowser(): boolean {
47
43
  }
48
44
 
49
45
  if (!hasRealLayout) {
50
- _isBrowser = false;
51
46
  return false;
52
47
  }
53
48
 
@@ -59,7 +54,6 @@ export function isBrowser(): boolean {
59
54
 
60
55
  const ctx = canvas.getContext('2d');
61
56
  if (!ctx) {
62
- _isBrowser = false;
63
57
  return false;
64
58
  }
65
59
 
@@ -74,6 +68,6 @@ export function isBrowser(): boolean {
74
68
  hasRealCanvas = false;
75
69
  }
76
70
 
77
- _isBrowser = hasRealCanvas;
71
+ if (hasRealCanvas) IS_BROWSER = hasRealCanvas;
78
72
  return hasRealCanvas;
79
73
  }
@@ -1,10 +1,9 @@
1
1
  import { measureText as measure, registerFont } from 'measury';
2
2
  import AlibabaPuHuiTi from 'measury/fonts/AlibabaPuHuiTi-Regular';
3
- import { TextProps } from '../jsx';
3
+ import { JSXNode, TextProps } from '../jsx';
4
4
  import { DEFAULT_FONT } from '../renderer';
5
5
  import { encodeFontFamily } from './font';
6
6
  import { isBrowser } from './is-browser';
7
- import { isNode } from './is-node';
8
7
 
9
8
  let FONT_EXTEND_FACTOR = 1.01;
10
9
 
@@ -12,9 +11,7 @@ export const setFontExtendFactor = (factor: number) => {
12
11
  FONT_EXTEND_FACTOR = factor;
13
12
  };
14
13
 
15
- if (isNode) {
16
- registerFont(AlibabaPuHuiTi);
17
- }
14
+ registerFont(AlibabaPuHuiTi);
18
15
 
19
16
  let canvasContext: CanvasRenderingContext2D | null = null;
20
17
  let measureSpan: HTMLSpanElement | null = null;
@@ -105,13 +102,15 @@ function measureTextInBrowser(
105
102
  }
106
103
 
107
104
  export function measureText(
108
- text: string | number | undefined = '',
105
+ text: JSXNode = '',
109
106
  attrs: TextProps,
110
107
  ): { width: number; height: number } {
111
108
  if (attrs.width && attrs.height) {
112
109
  return { width: attrs.width, height: attrs.height };
113
110
  }
114
-
111
+ if (typeof text !== 'string' && typeof text !== 'number') {
112
+ return { width: 0, height: 0 };
113
+ }
115
114
  const {
116
115
  fontFamily = DEFAULT_FONT,
117
116
  fontSize = 14,
@@ -30,24 +30,28 @@ export function setSVGPadding(svg: SVGSVGElement, padding: ParsedPadding) {
30
30
  if (document.contains(svg)) {
31
31
  setSVGPaddingInBrowser(svg, padding);
32
32
  } else {
33
- const observer = new MutationObserver((mutations) => {
34
- mutations.forEach((mutation) => {
35
- mutation.addedNodes.forEach((node) => {
36
- if (node === svg || node.contains(svg)) {
37
- waitForLayout(svg, () => {
38
- setSVGPaddingInBrowser(svg, padding);
39
- });
33
+ try {
34
+ const observer = new MutationObserver((mutations) => {
35
+ mutations.forEach((mutation) => {
36
+ mutation.addedNodes.forEach((node) => {
37
+ if (node === svg || node.contains(svg)) {
38
+ waitForLayout(svg, () => {
39
+ setSVGPaddingInBrowser(svg, padding);
40
+ });
40
41
 
41
- observer.disconnect();
42
- }
42
+ observer.disconnect();
43
+ }
44
+ });
43
45
  });
44
46
  });
45
- });
46
47
 
47
- observer.observe(document, {
48
- childList: true,
49
- subtree: true,
50
- });
48
+ observer.observe(document, {
49
+ childList: true,
50
+ subtree: true,
51
+ });
52
+ } catch {
53
+ setSVGPaddingInNode(svg, padding);
54
+ }
51
55
  }
52
56
  }
53
57
  }
@@ -6,8 +6,12 @@ import { getTextEntity } from './text';
6
6
  const is = (element: SVGElement, role: string) => {
7
7
  return element?.dataset?.elementType === role;
8
8
  };
9
+ const isTagName = (element: HTMLElement | SVGElement, tagName: string) => {
10
+ return element.tagName.toLowerCase() === tagName.toLowerCase();
11
+ };
12
+
9
13
  export const isSVG = (element: any): element is SVGSVGElement =>
10
- element instanceof SVGElement && element.tagName === 'svg';
14
+ element instanceof SVGElement && isTagName(element, 'svg');
11
15
  export const isTitle = (element: SVGElement) => is(element, 'title');
12
16
  export const isDesc = (element: SVGElement) => is(element, 'desc');
13
17
  export const isShape = (element: SVGElement) => is(element, 'shape');
@@ -15,9 +19,9 @@ export const isShapesGroup = (element: SVGElement) =>
15
19
  is(element, 'shapes-group');
16
20
  export const isIllus = (element: SVGElement) => is(element, 'illus');
17
21
  export const isText = (element: SVGElement): element is SVGTextElement =>
18
- element instanceof SVGElement && element.tagName === 'text';
22
+ element instanceof SVGElement && isTagName(element, 'text');
19
23
  export const isGroup = (element: SVGElement): element is SVGGElement =>
20
- element instanceof SVGElement && element.tagName === 'g';
24
+ element instanceof SVGElement && isTagName(element, 'g');
21
25
  export const isItemIcon = (element: SVGElement) => is(element, 'item-icon');
22
26
  export const isItemIconGroup = (element: SVGElement) =>
23
27
  is(element, 'item-icon-group');
@@ -37,11 +41,11 @@ export const isRoughVolume = (element: SVGElement) =>
37
41
  export function isForeignObjectElement(
38
42
  element: any,
39
43
  ): element is SVGForeignObjectElement {
40
- return element.tagName === 'foreignObject';
44
+ return isTagName(element, 'foreignObject');
41
45
  }
42
46
 
43
47
  export function isTextEntity(element: any): element is HTMLSpanElement {
44
- return element.tagName === 'SPAN';
48
+ return isTagName(element, 'SPAN');
45
49
  }
46
50
 
47
51
  export function isEditableText(node: SVGElement): node is TextElement {
package/src/utils/svg.ts CHANGED
@@ -82,7 +82,6 @@ export function getOrCreateDefs(
82
82
  const defs = svg.querySelector<SVGDefsElement>(selector);
83
83
 
84
84
  if (defs) return defs;
85
-
86
85
  const newDefs = createElement<SVGDefsElement>('defs');
87
86
  if (defsId) newDefs.id = defsId;
88
87
  svg.prepend(newDefs);