@particle-academy/fancy-echarts 2.0.2 → 3.0.1
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/README.md +189 -0
- package/dist/diagram.serializers-DSYRR3LI.js +277 -0
- package/dist/diagram.serializers-DSYRR3LI.js.map +1 -0
- package/dist/index.cjs +2339 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +247 -2
- package/dist/index.d.ts +247 -2
- package/dist/index.js +2042 -3
- package/dist/index.js.map +1 -1
- package/docs/Diagram.md +220 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,194 @@ option={{
|
|
|
73
73
|
|
|
74
74
|
This is consumer responsibility — the wrapper does not introspect `option` to identify HTML-bearing fields.
|
|
75
75
|
|
|
76
|
+
## Recipes: ECharts × react-fancy
|
|
77
|
+
|
|
78
|
+
`fancy-echarts` is a thin wrapper — every interaction surface is reachable through `onEvents`, `useECharts().instance`, and `tooltip.formatter`. These recipes cover the patterns that hold up across charts.
|
|
79
|
+
|
|
80
|
+
### Wrap any chart with a Popover, ContextMenu, and Action button
|
|
81
|
+
|
|
82
|
+
The pattern that scales: a single `ChartFrame` wrapper that gives every chart an info popover next to the title, action buttons in the header, and a right-click context menu on the body. Build it once, reuse for every chart type:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { Card, Popover, Action, Badge, ContextMenu, Icon, useToast } from "@particle-academy/react-fancy";
|
|
86
|
+
|
|
87
|
+
function ChartFrame({ title, info, actions = [], onExport, extraMenu, children }) {
|
|
88
|
+
const { toast } = useToast();
|
|
89
|
+
return (
|
|
90
|
+
<Card>
|
|
91
|
+
<Card.Header>
|
|
92
|
+
<div className="flex items-center justify-between">
|
|
93
|
+
<div className="flex items-center gap-2">
|
|
94
|
+
<h3 className="font-semibold">{title}</h3>
|
|
95
|
+
<Popover hover placement="right">
|
|
96
|
+
<Popover.Trigger>
|
|
97
|
+
<button><Icon name="info" size="sm" /></button>
|
|
98
|
+
</Popover.Trigger>
|
|
99
|
+
<Popover.Content>
|
|
100
|
+
<p className="w-64 text-sm text-zinc-500">{info}</p>
|
|
101
|
+
</Popover.Content>
|
|
102
|
+
</Popover>
|
|
103
|
+
</div>
|
|
104
|
+
<div className="flex gap-2">
|
|
105
|
+
{actions.map((a) => <Action key={a.label} size="sm" onClick={a.onClick}>{a.label}</Action>)}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</Card.Header>
|
|
109
|
+
<Card.Body>
|
|
110
|
+
<ContextMenu>
|
|
111
|
+
<ContextMenu.Trigger><div>{children}</div></ContextMenu.Trigger>
|
|
112
|
+
<ContextMenu.Content>
|
|
113
|
+
<ContextMenu.Item onClick={onExport}>Export CSV</ContextMenu.Item>
|
|
114
|
+
<ContextMenu.Item onClick={() => toast({ title: "Exported PNG" })}>Export PNG</ContextMenu.Item>
|
|
115
|
+
{extraMenu && <ContextMenu.Separator />}
|
|
116
|
+
{extraMenu}
|
|
117
|
+
</ContextMenu.Content>
|
|
118
|
+
</ContextMenu>
|
|
119
|
+
</Card.Body>
|
|
120
|
+
</Card>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
<ChartFrame title="Revenue" info="..." actions={[{ label: "Forecast", onClick: ... }]}>
|
|
125
|
+
<EChart option={lineOption} style={{ height: 320 }} />
|
|
126
|
+
</ChartFrame>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Three reasons this works: `<ContextMenu.Trigger>` wraps the chart canvas without injecting DOM into ECharts, `Popover hover` is independent of any chart event, and the chart inside is unaware of the frame.
|
|
130
|
+
|
|
131
|
+
### Right-click a data point → open a Modal drill-down
|
|
132
|
+
|
|
133
|
+
`onEvents.contextmenu` fires per-datum with the same `params` shape as `click`. Call `params.event.event.preventDefault()` to suppress the browser menu, then drive your own state.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<EChart
|
|
137
|
+
option={barOption}
|
|
138
|
+
onEvents={{
|
|
139
|
+
contextmenu: (params) => {
|
|
140
|
+
params.event.event.preventDefault();
|
|
141
|
+
const region = regions.find((r) => r.name === params.name);
|
|
142
|
+
setDrill(region); // opens a <Modal>
|
|
143
|
+
},
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
If you also wrap the chart in `<ContextMenu>` (chart-level actions), this still works — the per-bar `contextmenu` event fires *and* the wrapper menu opens. Suppress one or the other based on which event the cursor was over.
|
|
149
|
+
|
|
150
|
+
### Click-to-toast and hover-popover on a slice
|
|
151
|
+
|
|
152
|
+
`onEvents.click` for actions, native ECharts `tooltip` for hover details — they don't conflict.
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<EChart
|
|
156
|
+
option={{
|
|
157
|
+
tooltip: { trigger: "item", formatter: "{b}<br/>${c}k ({d}%)" },
|
|
158
|
+
series: [{ type: "pie", data: categories }],
|
|
159
|
+
}}
|
|
160
|
+
onEvents={{
|
|
161
|
+
click: (p) => toast({ title: `${p.name}: ${p.percent}%`, variant: "info" }),
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Rich HTML tooltips with sanitized data
|
|
167
|
+
|
|
168
|
+
`tooltip.formatter` accepts a function returning an HTML string. Compose colored arrows, badges, and metadata — but always escape user input.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
tooltip: {
|
|
172
|
+
trigger: "axis",
|
|
173
|
+
formatter: (params) => {
|
|
174
|
+
const p = params[0];
|
|
175
|
+
const region = regions.find((r) => r.name === p.name);
|
|
176
|
+
const arrow = region.growth >= 0 ? "▲" : "▼";
|
|
177
|
+
const color = region.growth >= 15 ? "#10b981" : "#3b82f6";
|
|
178
|
+
return `<div style="font-weight:600">${escape(p.name)}</div>
|
|
179
|
+
<div>Revenue: <b>$${p.value.toLocaleString()}k</b></div>
|
|
180
|
+
<div style="color:${color}">${arrow} ${region.growth}% YoY</div>`;
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
For interactive content inside a tooltip (buttons that fire React state), use a `Popover` keyed off `onEvents.mouseover` instead — ECharts tooltips are detached HTML and lose React handlers.
|
|
186
|
+
|
|
187
|
+
### Per-datum styling from an array of objects
|
|
188
|
+
|
|
189
|
+
Pass `data` as `{ value, itemStyle }[]` to color each bar/slice individually based on a property of the underlying record:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
series: [{
|
|
193
|
+
type: "bar",
|
|
194
|
+
data: regions.map((r) => ({
|
|
195
|
+
value: r.value,
|
|
196
|
+
itemStyle: {
|
|
197
|
+
color: r.growth >= 15 ? "#10b981" : "#3b82f6",
|
|
198
|
+
borderRadius: [0, 6, 6, 0],
|
|
199
|
+
},
|
|
200
|
+
})),
|
|
201
|
+
}]
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Wrap the page in `<Toast.Provider>` for toast feedback
|
|
205
|
+
|
|
206
|
+
`useToast()` only works inside a provider. Wrap the demo's outermost element so chart-event toasts have somewhere to render:
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
export function Showcase() {
|
|
210
|
+
return (
|
|
211
|
+
<Toast.Provider position="bottom-right">
|
|
212
|
+
<ShowcaseInner />
|
|
213
|
+
</Toast.Provider>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Keep the chart `option` memoized
|
|
219
|
+
|
|
220
|
+
Re-creating the option object on every render forces ECharts to diff and reapply. `useMemo` keeps the reference stable so the chart only updates when its inputs actually change:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
const lineOption = useMemo(() => ({ /* ... */ }), [revenue, expenses]);
|
|
224
|
+
<EChart option={lineOption} />
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Theme-toggling is a prop swap
|
|
228
|
+
|
|
229
|
+
Pass `theme="dark-preset"` (after `registerBuiltinThemes()`) or `theme="light"` and re-render — the wrapper rebuilds the chart with the new theme automatically. No `dispose()` calls needed.
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
const [theme, setTheme] = useState<"light" | "dark-preset">("light");
|
|
233
|
+
|
|
234
|
+
<Action onClick={() => setTheme((t) => t === "light" ? "dark-preset" : "light")}>
|
|
235
|
+
Toggle theme
|
|
236
|
+
</Action>
|
|
237
|
+
<EChart theme={theme} option={option} />
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Pitfalls
|
|
241
|
+
|
|
242
|
+
- **Badge takes `color`, not `variant`.** Valid colors: `zinc | red | blue | green | amber | violet | rose`. The `variant` prop selects the *style* (`soft | solid | outline`), not the semantic intent. Don't confuse this with `Toast.toast({ variant: "success" })`, which uses semantic names.
|
|
243
|
+
- **`<ContextMenu.Trigger>` needs a single DOM child.** Wrap `<EChart>` in a plain `<div>` if you have additional siblings (or none — Trigger forwards refs through the wrapper).
|
|
244
|
+
- **`params.event` vs `params.event.event`.** ECharts wraps the native event. Call `params.event.event.preventDefault()` to stop the browser context menu, not `params.event.preventDefault()`.
|
|
245
|
+
- **Don't render React inside `tooltip.formatter`.** Returning an HTML string is fine; expecting React state or handlers to attach to that HTML is not. For interactive tooltips, use `Popover` driven by `onEvents.mouseover` / `mouseout` instead.
|
|
246
|
+
|
|
247
|
+
## Diagrams
|
|
248
|
+
|
|
249
|
+
Beyond charts, fancy-echarts ships four schema-driven diagram components for data-modeling, process flows, mindmapping, and hierarchies. They share one routing/marker engine — same import surface, same theming.
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
import { DataDiagram, Flowchart, Mindmap, OrgChart } from "@particle-academy/fancy-echarts";
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
| Component | Use case | Default routing |
|
|
256
|
+
|-----------|----------|-----------------|
|
|
257
|
+
| `<DataDiagram>` | ERD / UML class diagrams with fields, primary/foreign keys, exports | manhattan |
|
|
258
|
+
| `<Flowchart>` | Boxes + typed arrows, no fields | manhattan |
|
|
259
|
+
| `<Mindmap>` | Radial single-root tree with bezier connectors | bezier |
|
|
260
|
+
| `<OrgChart>` | Top-down hierarchy, tidy-tree layout, inheritance markers | manhattan |
|
|
261
|
+
|
|
262
|
+
See [docs/Diagram.md](docs/Diagram.md) for schemas, props, and layout details.
|
|
263
|
+
|
|
76
264
|
## Documentation
|
|
77
265
|
|
|
78
266
|
Full component documentation is available in the [docs/](docs/) folder:
|
|
@@ -82,6 +270,7 @@ Full component documentation is available in the [docs/](docs/) folder:
|
|
|
82
270
|
| [EChart](docs/EChart.md) | Base chart component + all 20 series sub-components |
|
|
83
271
|
| [EChart3D](docs/EChart3D.md) | 3D charts (Bar, Scatter, Line, Surface, Globe) |
|
|
84
272
|
| [EChartGraphic](docs/EChartGraphic.md) | Custom drawing with the graphic API |
|
|
273
|
+
| [Diagram](docs/Diagram.md) | Diagram engine + DataDiagram, Flowchart, Mindmap, OrgChart presets |
|
|
85
274
|
| [useECharts](docs/useECharts.md) | Core hook for custom integrations |
|
|
86
275
|
| [Registration](docs/registration.md) | Tree shaking and selective chart registration |
|
|
87
276
|
| [Themes](docs/themes.md) | Built-in themes and custom theme creation |
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// src/components/Diagram/diagram.serializers.ts
|
|
2
|
+
function serializeToERD(schema) {
|
|
3
|
+
const lines = [];
|
|
4
|
+
for (const entity of schema.entities) {
|
|
5
|
+
lines.push(`[${entity.name}]`);
|
|
6
|
+
if (entity.fields) {
|
|
7
|
+
for (const field of entity.fields) {
|
|
8
|
+
const parts = [` ${field.name}`];
|
|
9
|
+
if (field.type) parts.push(field.type);
|
|
10
|
+
if (field.primary) parts.push("PK");
|
|
11
|
+
if (field.foreign) parts.push("FK");
|
|
12
|
+
if (field.nullable) parts.push("?");
|
|
13
|
+
lines.push(parts.join(" "));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
lines.push("");
|
|
17
|
+
}
|
|
18
|
+
for (const rel of schema.relations) {
|
|
19
|
+
const fromEntity = schema.entities.find((e) => e.id === rel.from);
|
|
20
|
+
const toEntity = schema.entities.find((e) => e.id === rel.to);
|
|
21
|
+
if (!fromEntity || !toEntity) continue;
|
|
22
|
+
const marker = getERDMarker(rel.type);
|
|
23
|
+
const parts = [fromEntity.name, marker, toEntity.name];
|
|
24
|
+
if (rel.label) parts.push(`: ${rel.label}`);
|
|
25
|
+
lines.push(parts.join(" "));
|
|
26
|
+
}
|
|
27
|
+
return lines.join("\n").trim();
|
|
28
|
+
}
|
|
29
|
+
function getERDMarker(type) {
|
|
30
|
+
switch (type) {
|
|
31
|
+
case "one-to-one":
|
|
32
|
+
return "1--1";
|
|
33
|
+
case "one-to-many":
|
|
34
|
+
return "1--*";
|
|
35
|
+
case "many-to-many":
|
|
36
|
+
return "*--*";
|
|
37
|
+
default:
|
|
38
|
+
return "--";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function serializeToUML(schema) {
|
|
42
|
+
const lines = ["@startuml"];
|
|
43
|
+
for (const entity of schema.entities) {
|
|
44
|
+
lines.push(`class ${entity.name} {`);
|
|
45
|
+
if (entity.fields) {
|
|
46
|
+
for (const field of entity.fields) {
|
|
47
|
+
const typeStr = field.type ?? "any";
|
|
48
|
+
const nullable = field.nullable ? "?" : "";
|
|
49
|
+
const stereotype = field.primary ? " <<PK>>" : field.foreign ? " <<FK>>" : "";
|
|
50
|
+
lines.push(` ${field.name} : ${typeStr}${nullable}${stereotype}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
lines.push("}");
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
for (const rel of schema.relations) {
|
|
57
|
+
const fromEntity = schema.entities.find((e) => e.id === rel.from);
|
|
58
|
+
const toEntity = schema.entities.find((e) => e.id === rel.to);
|
|
59
|
+
if (!fromEntity || !toEntity) continue;
|
|
60
|
+
const arrow = getUMLArrow(rel.type);
|
|
61
|
+
const label = rel.label ? ` : ${rel.label}` : "";
|
|
62
|
+
lines.push(`${fromEntity.name} ${arrow} ${toEntity.name}${label}`);
|
|
63
|
+
}
|
|
64
|
+
lines.push("@enduml");
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
function getUMLArrow(type) {
|
|
68
|
+
switch (type) {
|
|
69
|
+
case "one-to-one":
|
|
70
|
+
return '"1" -- "1"';
|
|
71
|
+
case "one-to-many":
|
|
72
|
+
return '"1" -- "*"';
|
|
73
|
+
case "many-to-many":
|
|
74
|
+
return '"*" -- "*"';
|
|
75
|
+
default:
|
|
76
|
+
return "--";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function serializeToDFD(schema) {
|
|
80
|
+
const lines = [];
|
|
81
|
+
for (const entity of schema.entities) {
|
|
82
|
+
lines.push(`entity ${entity.name}`);
|
|
83
|
+
}
|
|
84
|
+
lines.push("");
|
|
85
|
+
for (const rel of schema.relations) {
|
|
86
|
+
const fromEntity = schema.entities.find((e) => e.id === rel.from);
|
|
87
|
+
const toEntity = schema.entities.find((e) => e.id === rel.to);
|
|
88
|
+
if (!fromEntity || !toEntity) continue;
|
|
89
|
+
const label = rel.label ? ` "${rel.label}"` : "";
|
|
90
|
+
lines.push(`${fromEntity.name} -> ${toEntity.name}${label}`);
|
|
91
|
+
}
|
|
92
|
+
return lines.join("\n").trim();
|
|
93
|
+
}
|
|
94
|
+
function deserializeSchema(input, format) {
|
|
95
|
+
switch (format) {
|
|
96
|
+
case "erd":
|
|
97
|
+
return deserializeERD(input);
|
|
98
|
+
case "uml":
|
|
99
|
+
return deserializeUML(input);
|
|
100
|
+
case "dfd":
|
|
101
|
+
return deserializeDFD(input);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function deserializeERD(input) {
|
|
105
|
+
const entities = [];
|
|
106
|
+
const relations = [];
|
|
107
|
+
const lines = input.split("\n");
|
|
108
|
+
let currentEntity = null;
|
|
109
|
+
for (const rawLine of lines) {
|
|
110
|
+
const line = rawLine.trim();
|
|
111
|
+
const entityMatch = line.match(/^\[(.+)\]$/);
|
|
112
|
+
if (entityMatch) {
|
|
113
|
+
currentEntity = {
|
|
114
|
+
id: entityMatch[1].toLowerCase().replace(/\s+/g, "_"),
|
|
115
|
+
name: entityMatch[1],
|
|
116
|
+
fields: []
|
|
117
|
+
};
|
|
118
|
+
entities.push(currentEntity);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (currentEntity && rawLine.startsWith(" ") && line.length > 0) {
|
|
122
|
+
const parts = line.split(/\s+/);
|
|
123
|
+
const field = { name: parts[0] };
|
|
124
|
+
if (parts.length > 1 && !["PK", "FK", "?"].includes(parts[1])) {
|
|
125
|
+
field.type = parts[1];
|
|
126
|
+
}
|
|
127
|
+
if (parts.includes("PK")) field.primary = true;
|
|
128
|
+
if (parts.includes("FK")) field.foreign = true;
|
|
129
|
+
if (parts.includes("?")) field.nullable = true;
|
|
130
|
+
currentEntity.fields.push(field);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const relMatch = line.match(
|
|
134
|
+
/^(\S+)\s+(1--1|1--\*|\*--\*)\s+(\S+)(?:\s*:\s*(.+))?$/
|
|
135
|
+
);
|
|
136
|
+
if (relMatch) {
|
|
137
|
+
currentEntity = null;
|
|
138
|
+
const fromName = relMatch[1];
|
|
139
|
+
const marker = relMatch[2];
|
|
140
|
+
const toName = relMatch[3];
|
|
141
|
+
const label = relMatch[4];
|
|
142
|
+
const fromEntity = entities.find((e) => e.name === fromName);
|
|
143
|
+
const toEntity = entities.find((e) => e.name === toName);
|
|
144
|
+
if (fromEntity && toEntity) {
|
|
145
|
+
relations.push({
|
|
146
|
+
id: `${fromEntity.id ?? fromEntity.name}_${toEntity.id ?? toEntity.name}`,
|
|
147
|
+
from: fromEntity.id ?? fromEntity.name,
|
|
148
|
+
to: toEntity.id ?? toEntity.name,
|
|
149
|
+
type: parseERDMarker(marker),
|
|
150
|
+
label
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (line === "") {
|
|
156
|
+
currentEntity = null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { entities, relations };
|
|
160
|
+
}
|
|
161
|
+
function parseERDMarker(marker) {
|
|
162
|
+
switch (marker) {
|
|
163
|
+
case "1--1":
|
|
164
|
+
return "one-to-one";
|
|
165
|
+
case "1--*":
|
|
166
|
+
return "one-to-many";
|
|
167
|
+
case "*--*":
|
|
168
|
+
return "many-to-many";
|
|
169
|
+
default:
|
|
170
|
+
return "one-to-many";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function deserializeUML(input) {
|
|
174
|
+
const entities = [];
|
|
175
|
+
const relations = [];
|
|
176
|
+
const lines = input.split("\n");
|
|
177
|
+
let currentEntity = null;
|
|
178
|
+
for (const rawLine of lines) {
|
|
179
|
+
const line = rawLine.trim();
|
|
180
|
+
if (line === "@startuml" || line === "@enduml" || line === "") continue;
|
|
181
|
+
const classMatch = line.match(/^class\s+(\S+)\s*\{$/);
|
|
182
|
+
if (classMatch) {
|
|
183
|
+
currentEntity = {
|
|
184
|
+
id: classMatch[1].toLowerCase().replace(/\s+/g, "_"),
|
|
185
|
+
name: classMatch[1],
|
|
186
|
+
fields: []
|
|
187
|
+
};
|
|
188
|
+
entities.push(currentEntity);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (line === "}") {
|
|
192
|
+
currentEntity = null;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (currentEntity) {
|
|
196
|
+
const fieldMatch = line.match(
|
|
197
|
+
/^(\S+)\s*:\s*(\S+?)(\?)?(?:\s*<<(PK|FK)>>)?$/
|
|
198
|
+
);
|
|
199
|
+
if (fieldMatch) {
|
|
200
|
+
const field = {
|
|
201
|
+
name: fieldMatch[1],
|
|
202
|
+
type: fieldMatch[2]
|
|
203
|
+
};
|
|
204
|
+
if (fieldMatch[3]) field.nullable = true;
|
|
205
|
+
if (fieldMatch[4] === "PK") field.primary = true;
|
|
206
|
+
if (fieldMatch[4] === "FK") field.foreign = true;
|
|
207
|
+
currentEntity.fields.push(field);
|
|
208
|
+
}
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const relMatch = line.match(
|
|
212
|
+
/^(\S+)\s+"([1*])"\s+--\s+"([1*])"\s+(\S+)(?:\s*:\s*(.+))?$/
|
|
213
|
+
);
|
|
214
|
+
if (relMatch) {
|
|
215
|
+
const fromName = relMatch[1];
|
|
216
|
+
const fromCard = relMatch[2];
|
|
217
|
+
const toCard = relMatch[3];
|
|
218
|
+
const toName = relMatch[4];
|
|
219
|
+
const label = relMatch[5];
|
|
220
|
+
const fromEntity = entities.find((e) => e.name === fromName);
|
|
221
|
+
const toEntity = entities.find((e) => e.name === toName);
|
|
222
|
+
if (fromEntity && toEntity) {
|
|
223
|
+
const type = fromCard === "1" && toCard === "1" ? "one-to-one" : fromCard === "1" && toCard === "*" ? "one-to-many" : "many-to-many";
|
|
224
|
+
relations.push({
|
|
225
|
+
id: `${fromEntity.id ?? fromEntity.name}_${toEntity.id ?? toEntity.name}`,
|
|
226
|
+
from: fromEntity.id ?? fromEntity.name,
|
|
227
|
+
to: toEntity.id ?? toEntity.name,
|
|
228
|
+
type,
|
|
229
|
+
label
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return { entities, relations };
|
|
235
|
+
}
|
|
236
|
+
function deserializeDFD(input) {
|
|
237
|
+
const entities = [];
|
|
238
|
+
const relations = [];
|
|
239
|
+
const lines = input.split("\n");
|
|
240
|
+
for (const rawLine of lines) {
|
|
241
|
+
const line = rawLine.trim();
|
|
242
|
+
if (line === "") continue;
|
|
243
|
+
const entityMatch = line.match(/^entity\s+(\S+)$/);
|
|
244
|
+
if (entityMatch) {
|
|
245
|
+
entities.push({
|
|
246
|
+
id: entityMatch[1].toLowerCase().replace(/\s+/g, "_"),
|
|
247
|
+
name: entityMatch[1],
|
|
248
|
+
fields: []
|
|
249
|
+
});
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const flowMatch = line.match(
|
|
253
|
+
/^(\S+)\s+->\s+(\S+)(?:\s+"(.+)")?$/
|
|
254
|
+
);
|
|
255
|
+
if (flowMatch) {
|
|
256
|
+
const fromName = flowMatch[1];
|
|
257
|
+
const toName = flowMatch[2];
|
|
258
|
+
const label = flowMatch[3];
|
|
259
|
+
const fromEntity = entities.find((e) => e.name === fromName);
|
|
260
|
+
const toEntity = entities.find((e) => e.name === toName);
|
|
261
|
+
if (fromEntity && toEntity) {
|
|
262
|
+
relations.push({
|
|
263
|
+
id: `${fromEntity.id ?? fromEntity.name}_${toEntity.id ?? toEntity.name}`,
|
|
264
|
+
from: fromEntity.id ?? fromEntity.name,
|
|
265
|
+
to: toEntity.id ?? toEntity.name,
|
|
266
|
+
type: "one-to-many",
|
|
267
|
+
label
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { entities, relations };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export { deserializeSchema, serializeToDFD, serializeToERD, serializeToUML };
|
|
276
|
+
//# sourceMappingURL=diagram.serializers-DSYRR3LI.js.map
|
|
277
|
+
//# sourceMappingURL=diagram.serializers-DSYRR3LI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Diagram/diagram.serializers.ts"],"names":[],"mappings":"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","file":"diagram.serializers-DSYRR3LI.js","sourcesContent":["import type {\n DiagramSchema,\n DiagramEntityData,\n DiagramRelationData,\n DiagramFieldData,\n ExportFormat,\n RelationType,\n} from \"./Diagram.types\";\n\n/**\n * Serialize a diagram schema to PlantUML-style ERD text.\n *\n * Example output:\n * ```\n * [Users]\n * id int PK\n * email varchar\n *\n * [Posts]\n * id int PK\n * user_id int FK\n *\n * Users 1--* Posts\n * ```\n */\nexport function serializeToERD(schema: DiagramSchema): string {\n const lines: string[] = [];\n\n for (const entity of schema.entities) {\n lines.push(`[${entity.name}]`);\n if (entity.fields) {\n for (const field of entity.fields) {\n const parts = [` ${field.name}`];\n if (field.type) parts.push(field.type);\n if (field.primary) parts.push(\"PK\");\n if (field.foreign) parts.push(\"FK\");\n if (field.nullable) parts.push(\"?\");\n lines.push(parts.join(\" \"));\n }\n }\n lines.push(\"\");\n }\n\n for (const rel of schema.relations) {\n const fromEntity = schema.entities.find((e) => e.id === rel.from);\n const toEntity = schema.entities.find((e) => e.id === rel.to);\n if (!fromEntity || !toEntity) continue;\n\n const marker = getERDMarker(rel.type);\n const parts = [fromEntity.name, marker, toEntity.name];\n if (rel.label) parts.push(`: ${rel.label}`);\n lines.push(parts.join(\" \"));\n }\n\n return lines.join(\"\\n\").trim();\n}\n\nfunction getERDMarker(type: RelationType): string {\n switch (type) {\n case \"one-to-one\":\n return \"1--1\";\n case \"one-to-many\":\n return \"1--*\";\n case \"many-to-many\":\n return \"*--*\";\n default:\n return \"--\";\n }\n}\n\n/**\n * Serialize a diagram schema to PlantUML class diagram text.\n */\nexport function serializeToUML(schema: DiagramSchema): string {\n const lines: string[] = [\"@startuml\"];\n\n for (const entity of schema.entities) {\n lines.push(`class ${entity.name} {`);\n if (entity.fields) {\n for (const field of entity.fields) {\n const typeStr = field.type ?? \"any\";\n const nullable = field.nullable ? \"?\" : \"\";\n const stereotype = field.primary\n ? \" <<PK>>\"\n : field.foreign\n ? \" <<FK>>\"\n : \"\";\n lines.push(` ${field.name} : ${typeStr}${nullable}${stereotype}`);\n }\n }\n lines.push(\"}\");\n lines.push(\"\");\n }\n\n for (const rel of schema.relations) {\n const fromEntity = schema.entities.find((e) => e.id === rel.from);\n const toEntity = schema.entities.find((e) => e.id === rel.to);\n if (!fromEntity || !toEntity) continue;\n\n const arrow = getUMLArrow(rel.type);\n const label = rel.label ? ` : ${rel.label}` : \"\";\n lines.push(`${fromEntity.name} ${arrow} ${toEntity.name}${label}`);\n }\n\n lines.push(\"@enduml\");\n return lines.join(\"\\n\");\n}\n\nfunction getUMLArrow(type: RelationType): string {\n switch (type) {\n case \"one-to-one\":\n return '\"1\" -- \"1\"';\n case \"one-to-many\":\n return '\"1\" -- \"*\"';\n case \"many-to-many\":\n return '\"*\" -- \"*\"';\n default:\n return \"--\";\n }\n}\n\n/**\n * Serialize a diagram schema to a simple DFD text format.\n */\nexport function serializeToDFD(schema: DiagramSchema): string {\n const lines: string[] = [];\n\n for (const entity of schema.entities) {\n lines.push(`entity ${entity.name}`);\n }\n\n lines.push(\"\");\n\n for (const rel of schema.relations) {\n const fromEntity = schema.entities.find((e) => e.id === rel.from);\n const toEntity = schema.entities.find((e) => e.id === rel.to);\n if (!fromEntity || !toEntity) continue;\n\n const label = rel.label ? ` \"${rel.label}\"` : \"\";\n lines.push(`${fromEntity.name} -> ${toEntity.name}${label}`);\n }\n\n return lines.join(\"\\n\").trim();\n}\n\n/**\n * Parse a serialized schema string back into a DiagramSchema.\n */\nexport function deserializeSchema(\n input: string,\n format: ExportFormat,\n): DiagramSchema {\n switch (format) {\n case \"erd\":\n return deserializeERD(input);\n case \"uml\":\n return deserializeUML(input);\n case \"dfd\":\n return deserializeDFD(input);\n }\n}\n\nfunction deserializeERD(input: string): DiagramSchema {\n const entities: DiagramEntityData[] = [];\n const relations: DiagramRelationData[] = [];\n const lines = input.split(\"\\n\");\n\n let currentEntity: DiagramEntityData | null = null;\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n\n // Entity header: [EntityName]\n const entityMatch = line.match(/^\\[(.+)\\]$/);\n if (entityMatch) {\n currentEntity = {\n id: entityMatch[1].toLowerCase().replace(/\\s+/g, \"_\"),\n name: entityMatch[1],\n fields: [],\n };\n entities.push(currentEntity);\n continue;\n }\n\n // Field line (indented): name type PK FK ?\n if (currentEntity && rawLine.startsWith(\" \") && line.length > 0) {\n const parts = line.split(/\\s+/);\n const field: DiagramFieldData = { name: parts[0] };\n if (parts.length > 1 && ![\"PK\", \"FK\", \"?\"].includes(parts[1])) {\n field.type = parts[1];\n }\n if (parts.includes(\"PK\")) field.primary = true;\n if (parts.includes(\"FK\")) field.foreign = true;\n if (parts.includes(\"?\")) field.nullable = true;\n currentEntity.fields!.push(field);\n continue;\n }\n\n // Relation: EntityA 1--* EntityB : label\n const relMatch = line.match(\n /^(\\S+)\\s+(1--1|1--\\*|\\*--\\*)\\s+(\\S+)(?:\\s*:\\s*(.+))?$/,\n );\n if (relMatch) {\n currentEntity = null;\n const fromName = relMatch[1];\n const marker = relMatch[2];\n const toName = relMatch[3];\n const label = relMatch[4];\n\n const fromEntity = entities.find((e) => e.name === fromName);\n const toEntity = entities.find((e) => e.name === toName);\n if (fromEntity && toEntity) {\n relations.push({\n id: `${fromEntity.id ?? fromEntity.name}_${toEntity.id ?? toEntity.name}`,\n from: fromEntity.id ?? fromEntity.name,\n to: toEntity.id ?? toEntity.name,\n type: parseERDMarker(marker),\n label,\n });\n }\n continue;\n }\n\n // Empty line resets current entity context\n if (line === \"\") {\n currentEntity = null;\n }\n }\n\n return { entities, relations };\n}\n\nfunction parseERDMarker(marker: string): RelationType {\n switch (marker) {\n case \"1--1\":\n return \"one-to-one\";\n case \"1--*\":\n return \"one-to-many\";\n case \"*--*\":\n return \"many-to-many\";\n default:\n return \"one-to-many\";\n }\n}\n\nfunction deserializeUML(input: string): DiagramSchema {\n const entities: DiagramEntityData[] = [];\n const relations: DiagramRelationData[] = [];\n const lines = input.split(\"\\n\");\n\n let currentEntity: DiagramEntityData | null = null;\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n\n if (line === \"@startuml\" || line === \"@enduml\" || line === \"\") continue;\n\n // Class header: class EntityName {\n const classMatch = line.match(/^class\\s+(\\S+)\\s*\\{$/);\n if (classMatch) {\n currentEntity = {\n id: classMatch[1].toLowerCase().replace(/\\s+/g, \"_\"),\n name: classMatch[1],\n fields: [],\n };\n entities.push(currentEntity);\n continue;\n }\n\n // Closing brace\n if (line === \"}\") {\n currentEntity = null;\n continue;\n }\n\n // Field: name : type<<stereotype>>\n if (currentEntity) {\n const fieldMatch = line.match(\n /^(\\S+)\\s*:\\s*(\\S+?)(\\?)?(?:\\s*<<(PK|FK)>>)?$/,\n );\n if (fieldMatch) {\n const field: DiagramFieldData = {\n name: fieldMatch[1],\n type: fieldMatch[2],\n };\n if (fieldMatch[3]) field.nullable = true;\n if (fieldMatch[4] === \"PK\") field.primary = true;\n if (fieldMatch[4] === \"FK\") field.foreign = true;\n currentEntity.fields!.push(field);\n }\n continue;\n }\n\n // Relation: EntityA \"1\" -- \"*\" EntityB : label\n const relMatch = line.match(\n /^(\\S+)\\s+\"([1*])\"\\s+--\\s+\"([1*])\"\\s+(\\S+)(?:\\s*:\\s*(.+))?$/,\n );\n if (relMatch) {\n const fromName = relMatch[1];\n const fromCard = relMatch[2];\n const toCard = relMatch[3];\n const toName = relMatch[4];\n const label = relMatch[5];\n\n const fromEntity = entities.find((e) => e.name === fromName);\n const toEntity = entities.find((e) => e.name === toName);\n if (fromEntity && toEntity) {\n const type: RelationType =\n fromCard === \"1\" && toCard === \"1\"\n ? \"one-to-one\"\n : fromCard === \"1\" && toCard === \"*\"\n ? \"one-to-many\"\n : \"many-to-many\";\n relations.push({\n id: `${fromEntity.id ?? fromEntity.name}_${toEntity.id ?? toEntity.name}`,\n from: fromEntity.id ?? fromEntity.name,\n to: toEntity.id ?? toEntity.name,\n type,\n label,\n });\n }\n }\n }\n\n return { entities, relations };\n}\n\nfunction deserializeDFD(input: string): DiagramSchema {\n const entities: DiagramEntityData[] = [];\n const relations: DiagramRelationData[] = [];\n const lines = input.split(\"\\n\");\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (line === \"\") continue;\n\n // Entity: entity EntityName\n const entityMatch = line.match(/^entity\\s+(\\S+)$/);\n if (entityMatch) {\n entities.push({\n id: entityMatch[1].toLowerCase().replace(/\\s+/g, \"_\"),\n name: entityMatch[1],\n fields: [],\n });\n continue;\n }\n\n // Flow: EntityA -> EntityB \"label\"\n const flowMatch = line.match(\n /^(\\S+)\\s+->\\s+(\\S+)(?:\\s+\"(.+)\")?$/,\n );\n if (flowMatch) {\n const fromName = flowMatch[1];\n const toName = flowMatch[2];\n const label = flowMatch[3];\n\n const fromEntity = entities.find((e) => e.name === fromName);\n const toEntity = entities.find((e) => e.name === toName);\n if (fromEntity && toEntity) {\n relations.push({\n id: `${fromEntity.id ?? fromEntity.name}_${toEntity.id ?? toEntity.name}`,\n from: fromEntity.id ?? fromEntity.name,\n to: toEntity.id ?? toEntity.name,\n type: \"one-to-many\",\n label,\n });\n }\n }\n }\n\n return { entities, relations };\n}\n"]}
|