@antv/infographic 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/infographic.min.js +191 -191
- package/dist/infographic.min.js.map +1 -1
- package/esm/designs/items/BadgeCard.js +6 -1
- package/esm/designs/items/SimpleCircleNode.d.ts +8 -0
- package/esm/designs/items/SimpleCircleNode.js +14 -0
- package/esm/designs/items/index.d.ts +1 -0
- package/esm/designs/items/index.js +1 -0
- package/esm/designs/structures/hierarchy-mindmap.js +19 -5
- package/esm/designs/structures/hierarchy-tree.d.ts +2 -1
- package/esm/designs/structures/hierarchy-tree.js +23 -20
- package/esm/designs/structures/index.d.ts +1 -0
- package/esm/designs/structures/index.js +1 -0
- package/esm/designs/structures/relation-dagre-flow.d.ts +21 -0
- package/esm/designs/structures/relation-dagre-flow.js +497 -0
- package/esm/designs/utils/hierarchy-color.d.ts +1 -1
- package/esm/editor/plugins/edit-bar/edit-bar.js +27 -9
- package/esm/index.js +1 -1
- package/esm/jsx/global.d.ts +1 -0
- package/esm/jsx/types/element.d.ts +5 -1
- package/esm/jsx/utils/svg.js +2 -0
- package/esm/renderer/composites/icon.js +2 -0
- package/esm/renderer/composites/illus.d.ts +1 -1
- package/esm/renderer/composites/illus.js +9 -4
- package/esm/renderer/composites/text.js +4 -2
- package/esm/renderer/fonts/loader.js +3 -1
- package/esm/renderer/fonts/registry.js +1 -1
- package/esm/renderer/renderer.js +28 -25
- package/esm/resource/loader.js +3 -1
- package/esm/runtime/Infographic.js +1 -1
- package/esm/ssr/dom-shim.d.ts +4 -0
- package/esm/ssr/dom-shim.js +107 -0
- package/esm/ssr/index.d.ts +1 -0
- package/esm/ssr/index.js +1 -0
- package/esm/ssr/renderer.d.ts +2 -0
- package/esm/ssr/renderer.js +60 -0
- package/esm/syntax/index.js +57 -1
- package/esm/syntax/parser.js +44 -0
- package/esm/syntax/relations.d.ts +6 -0
- package/esm/syntax/relations.js +251 -0
- package/esm/syntax/schema.d.ts +1 -0
- package/esm/syntax/schema.js +12 -0
- package/esm/templates/built-in.js +2 -0
- package/esm/templates/relation-dagre-flow.d.ts +2 -0
- package/esm/templates/relation-dagre-flow.js +68 -0
- package/esm/types/data.d.ts +24 -3
- package/esm/utils/data.js +1 -1
- package/esm/utils/index.d.ts +1 -0
- package/esm/utils/index.js +1 -0
- package/esm/utils/is-browser.js +5 -9
- package/esm/utils/measure-text.d.ts +2 -2
- package/esm/utils/measure-text.js +4 -4
- package/esm/utils/recognizer.js +8 -5
- package/esm/utils/text.js +27 -19
- package/lib/designs/items/BadgeCard.js +6 -1
- package/lib/designs/items/SimpleCircleNode.d.ts +8 -0
- package/lib/designs/items/SimpleCircleNode.js +18 -0
- package/lib/designs/items/index.d.ts +1 -0
- package/lib/designs/items/index.js +1 -0
- package/lib/designs/structures/hierarchy-mindmap.js +19 -5
- package/lib/designs/structures/hierarchy-tree.d.ts +2 -1
- package/lib/designs/structures/hierarchy-tree.js +23 -20
- package/lib/designs/structures/index.d.ts +1 -0
- package/lib/designs/structures/index.js +1 -0
- package/lib/designs/structures/relation-dagre-flow.d.ts +21 -0
- package/lib/designs/structures/relation-dagre-flow.js +501 -0
- package/lib/designs/utils/hierarchy-color.d.ts +1 -1
- package/lib/editor/plugins/edit-bar/edit-bar.js +27 -9
- package/lib/jsx/global.d.ts +1 -0
- package/lib/jsx/types/element.d.ts +5 -1
- package/lib/jsx/utils/svg.js +2 -0
- package/lib/renderer/composites/icon.js +2 -0
- package/lib/renderer/composites/illus.d.ts +1 -1
- package/lib/renderer/composites/illus.js +8 -3
- package/lib/renderer/composites/text.js +4 -2
- package/lib/renderer/fonts/loader.js +2 -0
- package/lib/renderer/fonts/registry.js +6 -6
- package/lib/renderer/renderer.js +27 -24
- package/lib/resource/loader.js +3 -1
- package/lib/runtime/Infographic.js +1 -1
- package/lib/ssr/dom-shim.d.ts +4 -0
- package/lib/ssr/dom-shim.js +110 -0
- package/lib/ssr/index.d.ts +1 -0
- package/lib/ssr/index.js +5 -0
- package/lib/ssr/renderer.d.ts +2 -0
- package/lib/ssr/renderer.js +63 -0
- package/lib/syntax/index.js +57 -1
- package/lib/syntax/parser.js +44 -0
- package/lib/syntax/relations.d.ts +6 -0
- package/lib/syntax/relations.js +254 -0
- package/lib/syntax/schema.d.ts +1 -0
- package/lib/syntax/schema.js +13 -1
- package/lib/templates/built-in.js +2 -0
- package/lib/templates/relation-dagre-flow.d.ts +2 -0
- package/lib/templates/relation-dagre-flow.js +71 -0
- package/lib/types/data.d.ts +24 -3
- package/lib/utils/data.js +2 -5
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/is-browser.js +5 -9
- package/lib/utils/measure-text.d.ts +2 -2
- package/lib/utils/measure-text.js +4 -4
- package/lib/utils/recognizer.js +8 -5
- package/lib/utils/text.js +28 -23
- package/package.json +19 -7
- package/src/designs/items/BadgeCard.tsx +9 -2
- package/src/designs/items/SimpleCircleNode.tsx +46 -0
- package/src/designs/items/index.ts +1 -0
- package/src/designs/structures/hierarchy-mindmap.tsx +15 -2
- package/src/designs/structures/hierarchy-tree.tsx +33 -31
- package/src/designs/structures/index.ts +1 -0
- package/src/designs/structures/relation-dagre-flow.tsx +782 -0
- package/src/designs/utils/hierarchy-color.ts +6 -1
- package/src/editor/plugins/edit-bar/edit-bar.ts +41 -17
- package/src/index.ts +1 -1
- package/src/jsx/global.ts +1 -0
- package/src/jsx/types/element.ts +15 -6
- package/src/jsx/utils/svg.ts +2 -0
- package/src/renderer/composites/icon.ts +2 -0
- package/src/renderer/composites/illus.ts +16 -3
- package/src/renderer/composites/text.ts +7 -2
- package/src/renderer/fonts/loader.ts +7 -1
- package/src/renderer/fonts/registry.ts +1 -1
- package/src/renderer/renderer.ts +42 -24
- package/src/resource/loader.ts +3 -1
- package/src/runtime/Infographic.tsx +1 -1
- package/src/ssr/dom-shim.ts +120 -0
- package/src/ssr/index.ts +1 -0
- package/src/ssr/renderer.ts +72 -0
- package/src/syntax/index.ts +58 -1
- package/src/syntax/parser.ts +49 -0
- package/src/syntax/relations.ts +291 -0
- package/src/syntax/schema.ts +16 -0
- package/src/templates/built-in.ts +4 -2
- package/src/templates/relation-dagre-flow.ts +73 -0
- package/src/types/data.ts +26 -3
- package/src/utils/data.ts +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/is-browser.ts +3 -9
- package/src/utils/measure-text.ts +6 -7
- package/src/utils/recognizer.ts +9 -5
- package/src/utils/svg.ts +0 -1
- package/src/utils/text.ts +25 -19
package/src/syntax/index.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/src/syntax/parser.ts
CHANGED
|
@@ -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;)/g, '"')
|
|
32
|
+
.replace(/('|'|#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
|
+
}
|
package/src/syntax/schema.ts
CHANGED
|
@@ -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
|
|
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
package/src/utils/index.ts
CHANGED
package/src/utils/is-browser.ts
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
let
|
|
1
|
+
let IS_BROWSER: true | undefined;
|
|
2
2
|
|
|
3
3
|
export function isBrowser(): boolean {
|
|
4
|
-
if (
|
|
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
|
-
|
|
71
|
+
if (hasRealCanvas) IS_BROWSER = hasRealCanvas;
|
|
78
72
|
return hasRealCanvas;
|
|
79
73
|
}
|