@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
@@ -1,6 +1,8 @@
1
1
  import type { InfographicOptions } from '../options';
2
+ import type { ItemDatum } from '../types';
2
3
  import { mapWithSchema } from './mapper';
3
4
  import { parseSyntaxToAst } from './parser';
5
+ import { parseRelationsNode } from './relations';
4
6
  import {
5
7
  DataSchema,
6
8
  DesignSchema,
@@ -10,6 +12,24 @@ import {
10
12
  } from './schema';
11
13
  import type { SyntaxNode, SyntaxParseResult } from './types';
12
14
 
15
+ function normalizeItems(items: ItemDatum[]) {
16
+ const seen = new Set<string>();
17
+ const normalized: ItemDatum[] = [];
18
+ for (let index = items.length - 1; index >= 0; index -= 1) {
19
+ const item = items[index];
20
+ const id = item.id ?? item.label;
21
+ if (!id) {
22
+ normalized.push(item);
23
+ continue;
24
+ }
25
+ if (seen.has(id)) continue;
26
+ seen.add(id);
27
+ if (!item.id) item.id = id;
28
+ normalized.push(item);
29
+ }
30
+ return normalized.reverse();
31
+ }
32
+
13
33
  function resolveTemplate(
14
34
  node: SyntaxNode | undefined,
15
35
  errors: SyntaxParseResult['errors'],
@@ -75,8 +95,45 @@ export function parseSyntax(input: string): SyntaxParseResult {
75
95
 
76
96
  const dataNode = mergedEntries.data as SyntaxNode | undefined;
77
97
  if (dataNode) {
78
- const data = mapWithSchema(dataNode, DataSchema, 'data', errors);
98
+ let relationsNode: SyntaxNode | undefined;
99
+ let dataNodeForMapping = dataNode;
100
+ if (dataNode.kind === 'object') {
101
+ const { relations, ...rest } = dataNode.entries;
102
+ relationsNode = relations;
103
+ dataNodeForMapping = { ...dataNode, entries: rest };
104
+ }
105
+ const data = mapWithSchema(dataNodeForMapping, DataSchema, 'data', errors);
79
106
  if (data) options.data = data;
107
+ if (relationsNode) {
108
+ const parsed = parseRelationsNode(
109
+ relationsNode,
110
+ errors,
111
+ 'data.relations',
112
+ );
113
+ if (parsed.relations.length > 0 || parsed.items.length > 0) {
114
+ const current = (options.data ?? {}) as Record<string, any>;
115
+ const existingItems = Array.isArray(current.items)
116
+ ? (current.items as ItemDatum[])
117
+ : [];
118
+ const normalizedItems = normalizeItems(existingItems);
119
+ const itemMap = new Map<string, ItemDatum>();
120
+ normalizedItems.forEach((item) => {
121
+ if (item.id) itemMap.set(item.id, item);
122
+ });
123
+ parsed.items.forEach((item) => {
124
+ const existing = itemMap.get(item.id as string);
125
+ if (existing) {
126
+ if (!existing.label && item.label) existing.label = item.label;
127
+ } else {
128
+ normalizedItems.push(item);
129
+ itemMap.set(item.id as string, item);
130
+ }
131
+ });
132
+ current.items = normalizedItems;
133
+ current.relations = parsed.relations;
134
+ options.data = current as any;
135
+ }
136
+ }
80
137
  }
81
138
 
82
139
  const themeNode = mergedEntries.theme as SyntaxNode | undefined;
@@ -40,6 +40,10 @@ function stripComments(content: string) {
40
40
  return content.trimEnd();
41
41
  }
42
42
 
43
+ function looksLikeRelationExpression(text: string) {
44
+ return /[<>=o.x-]{2,}/.test(text);
45
+ }
46
+
43
47
  function parseKeyValue(raw: string) {
44
48
  const text = raw.trim();
45
49
  if (!text) return null;
@@ -130,6 +134,51 @@ export function parseSyntaxToAst(input: string): ParseResult {
130
134
  return;
131
135
  }
132
136
 
137
+ if (
138
+ parentFrame.key === 'relations' &&
139
+ !trimmed.startsWith('-') &&
140
+ looksLikeRelationExpression(trimmed)
141
+ ) {
142
+ if (parentNode.kind !== 'array') {
143
+ if (
144
+ parentNode.kind === 'object' &&
145
+ Object.keys(parentNode.entries).length === 0 &&
146
+ parentNode.value === undefined &&
147
+ parentFrame.parent &&
148
+ parentFrame.key
149
+ ) {
150
+ const arrayNode = createArrayNode(parentNode.line);
151
+ if (parentFrame.parent.kind === 'object') {
152
+ parentFrame.parent.entries[parentFrame.key] = arrayNode;
153
+ } else if (parentFrame.parent.kind === 'array') {
154
+ const indexInParent = parentFrame.parent.items.indexOf(parentNode);
155
+ if (indexInParent >= 0)
156
+ parentFrame.parent.items[indexInParent] = arrayNode;
157
+ }
158
+ parentFrame.node = arrayNode;
159
+ parentNode = arrayNode;
160
+ } else {
161
+ errors.push({
162
+ path: '',
163
+ line: lineNumber,
164
+ code: 'bad_list',
165
+ message: 'List item is not under an array container.',
166
+ raw: trimmed,
167
+ });
168
+ return;
169
+ }
170
+ }
171
+
172
+ const itemNode = createObjectNode(lineNumber, trimmed);
173
+ parentNode.items.push(itemNode);
174
+ stack.push({
175
+ indent,
176
+ node: itemNode,
177
+ parent: parentNode,
178
+ });
179
+ return;
180
+ }
181
+
133
182
  const parsed = parseKeyValue(trimmed);
134
183
  if (!parsed) {
135
184
  errors.push({
@@ -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';
@@ -313,8 +314,8 @@ const BUILT_IN_TEMPLATES: Record<string, TemplateOptions> = {
313
314
  'sequence-funnel-simple': {
314
315
  design: {
315
316
  title: 'default',
316
- structure: { type: 'sequence-funnel'},
317
- items: [{ type: 'simple', showIcon: false, usePaletteColor: true}],
317
+ structure: { type: 'sequence-funnel' },
318
+ items: [{ type: 'simple', showIcon: false, usePaletteColor: true }],
318
319
  },
319
320
  themeConfig: {
320
321
  palette: '#1677ff',
@@ -820,6 +821,7 @@ const BUILT_IN_TEMPLATES: Record<string, TemplateOptions> = {
820
821
  ...sequenceStairsTemplates,
821
822
  ...wordCloudTemplate,
822
823
  ...listZigzagTemplates,
824
+ ...relationDagreFlowTemplates,
823
825
  ...hierarchyStructureTemplates,
824
826
  };
825
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
  }