@ts-graphviz/react 0.10.6 → 0.11.0
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/CHANGELOG.md +487 -0
- package/README.md +630 -67
- package/example/example.tsx +1 -1
- package/lib/react.d.ts +198 -96
- package/lib/react.js +558 -215
- package/package.json +15 -20
- package/tsconfig.test.json +11 -0
- package/lib/react.cjs +0 -457
- package/src/components/Digraph.spec.tsx +0 -18
- package/src/components/Digraph.tsx +0 -41
- package/src/components/Edge.tsx +0 -17
- package/src/components/Graph.spec.tsx +0 -18
- package/src/components/Graph.tsx +0 -36
- package/src/components/GraphPortal.tsx +0 -22
- package/src/components/Node.spec.tsx +0 -101
- package/src/components/Node.tsx +0 -21
- package/src/components/Subgraph.tsx +0 -30
- package/src/contexts/CurrentGraph.ts +0 -5
- package/src/contexts/GraphContainer.ts +0 -5
- package/src/contexts/GraphMap.ts +0 -5
- package/src/contexts/GraphvizContext.ts +0 -11
- package/src/hooks/__tests__/wrapper.tsx +0 -58
- package/src/hooks/useCurrentGraph.spec.ts +0 -56
- package/src/hooks/useCurrentGraph.ts +0 -18
- package/src/hooks/useDigraph.spec.ts +0 -15
- package/src/hooks/useDigraph.ts +0 -33
- package/src/hooks/useEdge.spec.ts +0 -46
- package/src/hooks/useEdge.ts +0 -35
- package/src/hooks/useGraph.spec.ts +0 -15
- package/src/hooks/useGraph.ts +0 -33
- package/src/hooks/useGraphAttributes.ts +0 -32
- package/src/hooks/useGraphContainer.spec.ts +0 -45
- package/src/hooks/useGraphContainer.ts +0 -10
- package/src/hooks/useGraphMap.ts +0 -7
- package/src/hooks/useGraphvizContext.spec.ts +0 -26
- package/src/hooks/useGraphvizContext.ts +0 -16
- package/src/hooks/useHasAttributes.ts +0 -16
- package/src/hooks/useHasComment.ts +0 -8
- package/src/hooks/useNode.spec.ts +0 -16
- package/src/hooks/useNode.ts +0 -29
- package/src/hooks/useRenderedID.spec.tsx +0 -28
- package/src/hooks/useRenderedID.ts +0 -6
- package/src/hooks/useSubgraph.spec.ts +0 -41
- package/src/hooks/useSubgraph.ts +0 -39
- package/src/react.ts +0 -16
- package/src/reconciler.ts +0 -272
- package/src/render.test.tsx +0 -71
- package/src/render.ts +0 -142
- package/src/renderHTMLLike.ts +0 -9
- package/src/renderId.ts +0 -9
- package/src/types.ts +0 -107
- package/tsconfig.json +0 -8
- package/typedoc.json +0 -5
- package/vite.config.ts +0 -31
package/README.md
CHANGED
|
@@ -1,8 +1,41 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ts-graphviz/ts-graphviz/actions/workflows/main.yaml)
|
|
4
|
+
[](https://github.com/ts-graphviz/ts-graphviz/actions/workflows/codeql-analysis.yml)
|
|
5
|
+
[](https://github.com/ts-graphviz/ts-graphviz/blob/main/LICENSE)
|
|
6
|
+
[](#contributors-)
|
|
7
|
+
|
|
8
|
+
[](https://www.bestpractices.dev/projects/8396)
|
|
9
|
+
[](https://scorecard.dev/viewer/?uri=github.com/ts-graphviz/ts-graphviz)
|
|
10
|
+
[](https://tidelift.com/subscription/pkg/npm-ts-graphviz?utm_source=npm-ts-graphviz&utm_medium=readme)
|
|
11
|
+
|
|
12
|
+
[](https://badge.fury.io/js/ts-graphviz)
|
|
13
|
+

|
|
14
|
+
[](https://github.com/denoland/deno)
|
|
15
|
+
[](https://npmtrends.com/ts-graphviz)
|
|
16
|
+
|
|
1
17
|
# @ts-graphviz/react
|
|
2
18
|
|
|
3
|
-
|
|
19
|
+
React Components and Renderer for ts-graphviz
|
|
20
|
+
🔗
|
|
21
|
+
|
|
22
|
+
[](https://github.com/ts-graphviz/ts-graphviz)
|
|
23
|
+
[](https://www.npmjs.com/package/ts-graphviz)
|
|
24
|
+
[](https://ts-graphviz.github.io/ts-graphviz/)
|
|
25
|
+
[](https://deepwiki.com/ts-graphviz/ts-graphviz)
|
|
26
|
+
|
|
27
|
+
[](https://github.com/sponsors/ts-graphviz)
|
|
28
|
+
[](https://opencollective.com/ts-graphviz)
|
|
29
|
+
|
|
30
|
+
[](https://biomejs.dev/)
|
|
31
|
+
[](https://vitest.dev/)
|
|
32
|
+
[](https://rollupjs.org/)
|
|
33
|
+
|
|
34
|
+
</div>
|
|
4
35
|
|
|
5
|
-
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
> It is part of the ts-graphviz library, which is split into modular packages to improve maintainability, flexibility, and ease of use.
|
|
6
39
|
|
|
7
40
|
## Installation
|
|
8
41
|
|
|
@@ -12,93 +45,623 @@ The module can then be installed using [npm](https://www.npmjs.com/):
|
|
|
12
45
|
|
|
13
46
|
```bash
|
|
14
47
|
# yarn
|
|
15
|
-
$ yarn add @ts-graphviz/react react
|
|
48
|
+
$ yarn add @ts-graphviz/react react@^19
|
|
16
49
|
# or npm
|
|
17
|
-
$ npm install -S @ts-graphviz/react react
|
|
50
|
+
$ npm install -S @ts-graphviz/react react@^19
|
|
51
|
+
# or pnpm
|
|
52
|
+
$ pnpm add @ts-graphviz/react react@^19
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> **Important**: Install React 19+ as a peerDependency. React 18 and earlier versions are not supported. Note that `react-dom` is no longer required as this package now uses a custom HTML rendering implementation.
|
|
56
|
+
|
|
57
|
+
## Key Features
|
|
58
|
+
|
|
59
|
+
## React Component
|
|
60
|
+
|
|
61
|
+
### Core Components
|
|
62
|
+
|
|
63
|
+
The package provides React components that map directly to Graphviz DOT language constructs:
|
|
64
|
+
|
|
65
|
+
#### Graph Containers
|
|
66
|
+
- **`<Digraph>`** - Creates a directed graph (arrows point from source to target)
|
|
67
|
+
- **`<Graph>`** - Creates an undirected graph (no arrow direction)
|
|
68
|
+
- **`<Subgraph>`** - Creates a subgraph cluster for grouping related nodes
|
|
69
|
+
|
|
70
|
+
#### Graph Elements
|
|
71
|
+
- **`<Node>`** - Creates a node/vertex with customizable attributes
|
|
72
|
+
- **`<Edge>`** - Creates an edge/connection between nodes with optional styling
|
|
73
|
+
|
|
74
|
+
### Component Props & Attributes
|
|
75
|
+
|
|
76
|
+
All components accept Graphviz DOT attributes as props with full TypeScript support:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// Node with styling attributes
|
|
80
|
+
<Node
|
|
81
|
+
id="server1"
|
|
82
|
+
label="Web Server"
|
|
83
|
+
shape="box"
|
|
84
|
+
color="blue"
|
|
85
|
+
style="filled"
|
|
86
|
+
fillcolor="lightblue"
|
|
87
|
+
/>
|
|
88
|
+
|
|
89
|
+
// Edge with custom styling
|
|
90
|
+
<Edge
|
|
91
|
+
targets={["server1", "database1"]}
|
|
92
|
+
label="API calls"
|
|
93
|
+
color="red"
|
|
94
|
+
style="dashed"
|
|
95
|
+
weight={2}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
// Digraph with global attributes
|
|
99
|
+
<Digraph
|
|
100
|
+
rankdir="LR"
|
|
101
|
+
bgcolor="white"
|
|
102
|
+
node={{ shape: "ellipse", color: "gray" }}
|
|
103
|
+
edge={{ color: "black", arrowhead: "vee" }}
|
|
104
|
+
>
|
|
105
|
+
{/* nodes and edges */}
|
|
106
|
+
</Digraph>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Nested Structure & Composition
|
|
110
|
+
|
|
111
|
+
Components can be freely nested to create complex graph structures:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// Define reusable components
|
|
115
|
+
const ServerNode = ({ id, name, type }) => (
|
|
116
|
+
<Node
|
|
117
|
+
id={id}
|
|
118
|
+
label={
|
|
119
|
+
<dot:table border="1" cellborder="0" cellspacing="0">
|
|
120
|
+
<dot:tr>
|
|
121
|
+
<dot:td bgcolor="lightgray">
|
|
122
|
+
<dot:b>{name}</dot:b>
|
|
123
|
+
</dot:td>
|
|
124
|
+
</dot:tr>
|
|
125
|
+
<dot:tr>
|
|
126
|
+
<dot:td>{type}</dot:td>
|
|
127
|
+
</dot:tr>
|
|
128
|
+
</dot:table>
|
|
129
|
+
}
|
|
130
|
+
shape="record"
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const ServiceCluster = ({ id, label, children }) => (
|
|
135
|
+
<Subgraph id={id} label={label} style="filled" fillcolor="lightblue">
|
|
136
|
+
{children}
|
|
137
|
+
</Subgraph>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Compose the architecture
|
|
141
|
+
const SystemArchitecture = () => (
|
|
142
|
+
<Digraph rankdir="TB">
|
|
143
|
+
<ServiceCluster id="cluster_frontend" label="Frontend Layer">
|
|
144
|
+
<ServerNode id="web1" name="Web Server" type="Nginx" />
|
|
145
|
+
<ServerNode id="app1" name="App Server" type="React" />
|
|
146
|
+
</ServiceCluster>
|
|
147
|
+
|
|
148
|
+
<ServiceCluster id="cluster_backend" label="Backend Layer">
|
|
149
|
+
<ServerNode id="api1" name="API Gateway" type="REST" />
|
|
150
|
+
<ServerNode id="auth1" name="Auth Service" type="OAuth" />
|
|
151
|
+
</ServiceCluster>
|
|
152
|
+
|
|
153
|
+
<ServiceCluster id="cluster_data" label="Data Layer">
|
|
154
|
+
<ServerNode id="db1" name="Database" type="PostgreSQL" />
|
|
155
|
+
<ServerNode id="cache1" name="Cache" type="Redis" />
|
|
156
|
+
</ServiceCluster>
|
|
157
|
+
|
|
158
|
+
<Edge targets={["web1", "app1"]} label="serves" />
|
|
159
|
+
<Edge targets={["app1", "api1"]} label="API calls" />
|
|
160
|
+
<Edge targets={["api1", "auth1"]} label="validates" />
|
|
161
|
+
<Edge targets={["api1", "db1"]} label="queries" />
|
|
162
|
+
<Edge targets={["api1", "cache1"]} label="caches" />
|
|
163
|
+
</Digraph>
|
|
164
|
+
);
|
|
18
165
|
```
|
|
19
166
|
|
|
20
|
-
|
|
167
|
+
### HTML-Like Labels with JSX
|
|
21
168
|
|
|
22
|
-
|
|
169
|
+
Create rich, formatted labels using Graphviz's HTML-like label syntax with natural JSX:
|
|
23
170
|
|
|
24
|
-
```
|
|
25
|
-
|
|
171
|
+
```tsx
|
|
172
|
+
// Reusable table component for data records
|
|
173
|
+
const DataRecord = ({ title, fields }) => (
|
|
174
|
+
<dot:table border="1" cellborder="0" cellspacing="0">
|
|
175
|
+
<dot:tr>
|
|
176
|
+
<dot:td bgcolor="lightblue" colspan="2">
|
|
177
|
+
<dot:b>{title}</dot:b>
|
|
178
|
+
</dot:td>
|
|
179
|
+
</dot:tr>
|
|
180
|
+
{fields.map(([key, value]) => (
|
|
181
|
+
<dot:tr key={key}>
|
|
182
|
+
<dot:td port={key}>{key}</dot:td>
|
|
183
|
+
<dot:td>{value}</dot:td>
|
|
184
|
+
</dot:tr>
|
|
185
|
+
))}
|
|
186
|
+
</dot:table>
|
|
187
|
+
);
|
|
26
188
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
189
|
+
// Status indicator component
|
|
190
|
+
const StatusIndicator = ({ status, message }) => (
|
|
191
|
+
<>
|
|
192
|
+
<dot:b>Status:</dot:b><dot:br/>
|
|
193
|
+
<dot:font color={status === 'active' ? 'green' : 'red'}>
|
|
194
|
+
<dot:b>{status.toUpperCase()}</dot:b>
|
|
195
|
+
</dot:font><dot:br/>
|
|
196
|
+
<dot:i>{message}</dot:i>
|
|
197
|
+
</>
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// Usage in graph
|
|
201
|
+
const DatabaseDiagram = () => (
|
|
202
|
+
<Digraph>
|
|
38
203
|
<Node
|
|
39
|
-
id="
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<dot:td port="m">middle</dot:td>
|
|
46
|
-
<dot:td port="r">right</dot:td>
|
|
47
|
-
</dot:tr>
|
|
48
|
-
</dot:table>
|
|
49
|
-
}
|
|
204
|
+
id="user-table"
|
|
205
|
+
label={<DataRecord
|
|
206
|
+
title="Users"
|
|
207
|
+
fields={[['id', '1001'], ['name', 'John Doe'], ['email', 'john@example.com']]}
|
|
208
|
+
/>}
|
|
209
|
+
shape="record"
|
|
50
210
|
/>
|
|
51
211
|
|
|
52
|
-
<
|
|
53
|
-
|
|
212
|
+
<Node
|
|
213
|
+
id="server-status"
|
|
214
|
+
label={<StatusIndicator status="active" message="All systems operational" />}
|
|
215
|
+
shape="box"
|
|
216
|
+
style="rounded,filled"
|
|
217
|
+
fillcolor="lightyellow"
|
|
218
|
+
/>
|
|
219
|
+
|
|
220
|
+
<Edge targets={["user-table", "server-status"]} label="monitors" />
|
|
221
|
+
</Digraph>
|
|
222
|
+
);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Available HTML-Like Elements
|
|
226
|
+
|
|
227
|
+
- `<dot:table>`, `<dot:tr>`, `<dot:td>` - Table structures
|
|
228
|
+
- `<dot:b>`, `<dot:i>`, `<dot:u>` - Text formatting (bold, italic, underline)
|
|
229
|
+
- `<dot:font>` - Font styling with color, face, point-size
|
|
230
|
+
- `<dot:br>` - Line breaks
|
|
231
|
+
- `<dot:hr>`, `<dot:vr>` - Horizontal/vertical rules
|
|
232
|
+
- `<dot:img>` - Images
|
|
233
|
+
- `<dot:s>`, `<dot:sub>`, `<dot:sup>`, `<dot:o>` - Advanced text formatting
|
|
234
|
+
|
|
235
|
+
### TypeScript Integration
|
|
236
|
+
|
|
237
|
+
Full type safety with IntelliSense support for all Graphviz attributes:
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import type { NodeProps, EdgeProps, DigraphProps } from '@ts-graphviz/react';
|
|
241
|
+
|
|
242
|
+
// Typed component props
|
|
243
|
+
const MyNode: React.FC<NodeProps> = (props) => (
|
|
244
|
+
<Node
|
|
245
|
+
shape="box" // ✅ TypeScript knows valid shapes
|
|
246
|
+
color="blue" // ✅ String colors supported
|
|
247
|
+
style="filled" // ✅ Valid style options
|
|
248
|
+
{...props}
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Edge with typed targets
|
|
253
|
+
<Edge
|
|
254
|
+
targets={["node1", "node2"]} // ✅ Tuple type enforced
|
|
255
|
+
arrowhead="diamond" // ✅ Valid arrowhead styles
|
|
256
|
+
/>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Enhanced Type Safety
|
|
260
|
+
|
|
261
|
+
The package provides sophisticated TypeScript support with automatic type inference and runtime type filtering:
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
// ✅ Automatic type inference - no casting needed
|
|
265
|
+
const root = createRoot();
|
|
266
|
+
await root.render(
|
|
267
|
+
<Digraph id="myGraph" rankdir="LR">
|
|
268
|
+
<Node id="A" shape="box" />
|
|
269
|
+
<Node id="B" shape="circle" />
|
|
270
|
+
<Edge targets={["A", "B"]} />
|
|
271
|
+
</Digraph>
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// ✅ Type-safe model access
|
|
275
|
+
const models = root.getTopLevelModels();
|
|
276
|
+
// models is automatically typed as DotObjectModel[]
|
|
277
|
+
|
|
278
|
+
// ✅ Runtime type filtering with built-in type guards
|
|
279
|
+
import { isNodeModel, isEdgeModel, isRootGraphModel } from '@ts-graphviz/common';
|
|
280
|
+
|
|
281
|
+
// Filter by model type with automatic type narrowing
|
|
282
|
+
const nodes = root.getTopLevelModels(isNodeModel);
|
|
283
|
+
nodes.forEach(node => console.log(node.id)); // TypeScript knows this is NodeModel
|
|
284
|
+
|
|
285
|
+
const edges = root.getTopLevelModels(isEdgeModel);
|
|
286
|
+
edges.forEach(edge => console.log(edge.targets)); // TypeScript knows this is EdgeModel
|
|
287
|
+
|
|
288
|
+
const graphs = root.getTopLevelModels(isRootGraphModel);
|
|
289
|
+
graphs.forEach(graph => console.log(graph.directed)); // TypeScript knows this is RootGraphModel
|
|
290
|
+
|
|
291
|
+
// ✅ Direct type casting (trusted user assertion)
|
|
292
|
+
// When you know the exact types, you can cast directly without runtime validation
|
|
293
|
+
const trustedNodes = root.getTopLevelModels<NodeModel>();
|
|
294
|
+
trustedNodes.forEach(node => console.log(node.id)); // TypeScript trusts your assertion
|
|
295
|
+
|
|
296
|
+
const trustedEdges = root.getTopLevelModels<EdgeModel>();
|
|
297
|
+
trustedEdges.forEach(edge => console.log(edge.targets)); // No runtime type checking
|
|
298
|
+
|
|
299
|
+
// ✅ Advanced model type checking
|
|
300
|
+
const allModels = root.getTopLevelModels();
|
|
301
|
+
for (const model of allModels) {
|
|
302
|
+
if (isNodeModel(model)) {
|
|
303
|
+
console.log(`Node: ${model.id}`);
|
|
304
|
+
} else if (isEdgeModel(model)) {
|
|
305
|
+
console.log(`Edge: ${model.targets.map(t => t.id).join(' -> ')}`);
|
|
306
|
+
} else if (isRootGraphModel(model)) {
|
|
307
|
+
console.log(`Graph: ${model.id} (directed: ${model.directed})`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### Container Mode Type Safety
|
|
313
|
+
|
|
314
|
+
When using container mode, you get access to all rendered models with full type safety:
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
import { digraph } from 'ts-graphviz';
|
|
318
|
+
import { isNodeModel, isEdgeModel, isSubgraphModel } from '@ts-graphviz/react';
|
|
319
|
+
|
|
320
|
+
const container = digraph('myContainer');
|
|
321
|
+
const root = createRoot({ container });
|
|
322
|
+
|
|
323
|
+
await root.render(
|
|
324
|
+
<>
|
|
325
|
+
<Node id="node1" />
|
|
326
|
+
<Node id="node2" />
|
|
327
|
+
<Edge targets={['node1', 'node2']} />
|
|
328
|
+
<Subgraph id="cluster1">
|
|
329
|
+
<Node id="node3" />
|
|
54
330
|
</Subgraph>
|
|
55
|
-
|
|
331
|
+
</>
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Container mode: access all non-container models with type safety
|
|
335
|
+
|
|
336
|
+
// Runtime type filtering (safe, validates at runtime)
|
|
337
|
+
const allNodes = root.getTopLevelModels(isNodeModel); // NodeModel[]
|
|
338
|
+
const allEdges = root.getTopLevelModels(isEdgeModel); // EdgeModel[]
|
|
339
|
+
const subgraphs = root.getTopLevelModels(isSubgraphModel); // SubgraphModel[]
|
|
340
|
+
|
|
341
|
+
// Direct type casting (user knows the types, no runtime validation)
|
|
342
|
+
const trustedNodes = root.getTopLevelModels<NodeModel>(); // All models cast as NodeModel[]
|
|
343
|
+
const trustedEdges = root.getTopLevelModels<EdgeModel>(); // All models cast as EdgeModel[]
|
|
344
|
+
|
|
345
|
+
// Type-safe operations
|
|
346
|
+
allNodes.forEach(node => {
|
|
347
|
+
node.attributes.set('color', 'blue'); // TypeScript knows node attributes
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
allEdges.forEach(edge => {
|
|
351
|
+
console.log(`Edge from ${edge.targets[0]} to ${edge.targets[1]}`);
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Core Functions
|
|
356
|
+
The package provides clean async rendering APIs:
|
|
357
|
+
|
|
358
|
+
- `createRoot()` - Creates a rendering root following React 19's createRoot pattern
|
|
359
|
+
- `renderToDot()` - Primary async function for converting React components to DOT language strings
|
|
360
|
+
- `renderHTMLLike()` - Renders HTML-like label structures for use in node or edge labels
|
|
361
|
+
|
|
362
|
+
All rendering functions are **async-only** and provide a clean, consistent API surface. The new `createRoot()` API follows React 19's modern patterns for better performance and error handling.
|
|
363
|
+
|
|
364
|
+
## Usage Examples
|
|
365
|
+
|
|
366
|
+
### Creating a Simple Graph
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
import { Digraph, Node, Edge, createRoot, renderToDot } from "@ts-graphviz/react";
|
|
370
|
+
|
|
371
|
+
// Define a reusable process component
|
|
372
|
+
const ProcessNode = ({ id, label, color = "lightblue" }) => (
|
|
373
|
+
<Node
|
|
374
|
+
id={id}
|
|
375
|
+
label={
|
|
376
|
+
<dot:table border="0" cellborder="1" cellspacing="0">
|
|
377
|
+
<dot:tr>
|
|
378
|
+
<dot:td bgcolor={color}>
|
|
379
|
+
<dot:b>{label}</dot:b>
|
|
380
|
+
</dot:td>
|
|
381
|
+
</dot:tr>
|
|
382
|
+
</dot:table>
|
|
383
|
+
}
|
|
384
|
+
shape="record"
|
|
385
|
+
/>
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// Create a workflow diagram
|
|
389
|
+
const WorkflowDiagram = () => (
|
|
390
|
+
<Digraph rankdir="LR">
|
|
391
|
+
<ProcessNode id="start" label="Start" color="lightgreen" />
|
|
392
|
+
<ProcessNode id="process" label="Process Data" />
|
|
393
|
+
<ProcessNode id="validate" label="Validate" />
|
|
394
|
+
<ProcessNode id="end" label="End" color="lightcoral" />
|
|
395
|
+
|
|
396
|
+
<Edge targets={["start", "process"]} />
|
|
397
|
+
<Edge targets={["process", "validate"]} />
|
|
398
|
+
<Edge targets={["validate", "end"]} />
|
|
399
|
+
</Digraph>
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// Create root and render to graph models
|
|
403
|
+
const root = createRoot();
|
|
404
|
+
await root.render(<WorkflowDiagram />);
|
|
405
|
+
const models = root.getTopLevelModels();
|
|
406
|
+
|
|
407
|
+
// Convert to DOT string
|
|
408
|
+
const dotString = await renderToDot(<WorkflowDiagram />);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Using HTML-Like Labels
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
import { Digraph, Node, Edge, renderToDot } from "@ts-graphviz/react";
|
|
415
|
+
|
|
416
|
+
// Reusable card component with HTML-like labels
|
|
417
|
+
const InfoCard = ({ id, title, items }) => (
|
|
418
|
+
<Node
|
|
419
|
+
id={id}
|
|
420
|
+
label={
|
|
421
|
+
<dot:table border="1" cellborder="0" cellspacing="0">
|
|
422
|
+
<dot:tr>
|
|
423
|
+
<dot:td bgcolor="navy">
|
|
424
|
+
<dot:font color="white">
|
|
425
|
+
<dot:b>{title}</dot:b>
|
|
426
|
+
</dot:font>
|
|
427
|
+
</dot:td>
|
|
428
|
+
</dot:tr>
|
|
429
|
+
{items.map((item, index) => (
|
|
430
|
+
<dot:tr key={index}>
|
|
431
|
+
<dot:td align="left">• {item}</dot:td>
|
|
432
|
+
</dot:tr>
|
|
433
|
+
))}
|
|
434
|
+
</dot:table>
|
|
435
|
+
}
|
|
436
|
+
shape="record"
|
|
437
|
+
/>
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
// Usage in graph
|
|
441
|
+
const ProjectDiagram = () => (
|
|
442
|
+
<Digraph>
|
|
443
|
+
<InfoCard
|
|
444
|
+
id="requirements"
|
|
445
|
+
title="Requirements"
|
|
446
|
+
items={["User login", "Data processing", "Reporting"]}
|
|
447
|
+
/>
|
|
448
|
+
<InfoCard
|
|
449
|
+
id="implementation"
|
|
450
|
+
title="Implementation"
|
|
451
|
+
items={["React frontend", "Node.js API", "PostgreSQL DB"]}
|
|
452
|
+
/>
|
|
453
|
+
|
|
454
|
+
<Edge targets={["requirements", "implementation"]} label="leads to" />
|
|
56
455
|
</Digraph>
|
|
57
456
|
);
|
|
58
457
|
|
|
59
|
-
const
|
|
458
|
+
const dotString = await renderToDot(<ProjectDiagram />);
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Using the renderHTMLLike Function
|
|
462
|
+
|
|
463
|
+
```ts
|
|
464
|
+
import { renderHTMLLike } from "@ts-graphviz/react";
|
|
465
|
+
|
|
466
|
+
const htmlLabel = renderHTMLLike(
|
|
467
|
+
<dot:table>
|
|
468
|
+
<dot:tr>
|
|
469
|
+
<dot:td>left</dot:td>
|
|
470
|
+
<dot:td>right</dot:td>
|
|
471
|
+
</dot:tr>
|
|
472
|
+
</dot:table>
|
|
473
|
+
);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Advanced Root Options
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
import { Digraph, Node, Edge, createRoot } from "@ts-graphviz/react";
|
|
480
|
+
|
|
481
|
+
// Basic usage
|
|
482
|
+
const root = createRoot();
|
|
483
|
+
await root.render(
|
|
484
|
+
<Digraph>
|
|
485
|
+
<Node id="A" />
|
|
486
|
+
<Node id="B" />
|
|
487
|
+
<Edge targets={["A", "B"]} />
|
|
488
|
+
</Digraph>
|
|
489
|
+
);
|
|
60
490
|
|
|
61
|
-
|
|
491
|
+
// Container mode - render into existing graph
|
|
492
|
+
import { digraph } from 'ts-graphviz';
|
|
493
|
+
const container = digraph('MyGraph');
|
|
494
|
+
const containerRoot = createRoot({ container });
|
|
495
|
+
await containerRoot.render(
|
|
496
|
+
<>
|
|
497
|
+
<Node id="A" />
|
|
498
|
+
<Node id="B" />
|
|
499
|
+
<Edge targets={["A", "B"]} />
|
|
500
|
+
</>
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
// Error handling options
|
|
504
|
+
const rootWithErrorHandling = createRoot({
|
|
505
|
+
onUncaughtError: (error, errorInfo) => {
|
|
506
|
+
console.error('Rendering error:', error);
|
|
507
|
+
console.log('Component stack:', errorInfo.componentStack);
|
|
508
|
+
},
|
|
509
|
+
onCaughtError: (error, errorInfo) => {
|
|
510
|
+
console.error('Caught error:', error);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
await rootWithErrorHandling.render(<MyComplexGraph />);
|
|
62
515
|
```
|
|
63
516
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
label = "This is label for nodeB.";
|
|
85
|
-
];
|
|
517
|
+
## Advanced Features
|
|
518
|
+
|
|
519
|
+
### Error Handling
|
|
520
|
+
|
|
521
|
+
The package provides robust error handling capabilities for rendering errors:
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
import { createRoot, renderToDot } from "@ts-graphviz/react";
|
|
525
|
+
|
|
526
|
+
// Error handling with createRoot
|
|
527
|
+
const root = createRoot({
|
|
528
|
+
onUncaughtError: (error, errorInfo) => {
|
|
529
|
+
console.error('Uncaught rendering error:', error.message);
|
|
530
|
+
console.log('Component stack:', errorInfo.componentStack);
|
|
531
|
+
// Send to error tracking service
|
|
532
|
+
errorTracker.captureException(error, { extra: errorInfo });
|
|
533
|
+
},
|
|
534
|
+
onCaughtError: (error, errorInfo) => {
|
|
535
|
+
console.error('Caught by error boundary:', error.message);
|
|
536
|
+
// Handle recoverable errors
|
|
86
537
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
await root.render(<MyGraph />);
|
|
541
|
+
|
|
542
|
+
// renderToDot also supports error handling
|
|
543
|
+
try {
|
|
544
|
+
const dotString = await renderToDot(<ComplexGraph />);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
if (error.message.includes('Multiple top-level graphs')) {
|
|
547
|
+
console.error('Invalid graph structure');
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Ref as Prop Support
|
|
553
|
+
|
|
554
|
+
The package supports using `ref` to access and manipulate graph models directly, allowing for dynamic updates and interactions:
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
import { useRef } from 'react';
|
|
558
|
+
import { Digraph, Graph, Node, Edge, createRoot } from "@ts-graphviz/react";
|
|
559
|
+
import type { NodeModel, EdgeModel, GraphBaseModel } from 'ts-graphviz';
|
|
560
|
+
|
|
561
|
+
function MyGraphComponent() {
|
|
562
|
+
const nodeRef = useRef<NodeModel>(null);
|
|
563
|
+
const edgeRef = useRef<EdgeModel>(null);
|
|
564
|
+
const digraphRef = useRef<GraphBaseModel>(null);
|
|
565
|
+
const graphRef = useRef<GraphBaseModel>(null);
|
|
566
|
+
|
|
567
|
+
const handleRender = async () => {
|
|
568
|
+
// Example with Digraph component
|
|
569
|
+
const digraphRoot = createRoot();
|
|
570
|
+
await digraphRoot.render(
|
|
571
|
+
<Digraph id="mygraph" ref={digraphRef}>
|
|
572
|
+
<Node id="A" ref={nodeRef} label="Node A" />
|
|
573
|
+
<Node id="B" label="Node B" />
|
|
574
|
+
<Edge targets={['A', 'B']} ref={edgeRef} label="A to B" />
|
|
575
|
+
</Digraph>
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
// Example with Graph component (undirected)
|
|
579
|
+
const graphRoot = createRoot();
|
|
580
|
+
await graphRoot.render(
|
|
581
|
+
<Graph id="undirected-graph" ref={graphRef}>
|
|
582
|
+
<Node id="X" label="Node X" />
|
|
583
|
+
<Node id="Y" label="Node Y" />
|
|
584
|
+
<Edge targets={['X', 'Y']} label="X -- Y" />
|
|
585
|
+
</Graph>
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
// Access and manipulate the models directly
|
|
589
|
+
if (nodeRef.current) {
|
|
590
|
+
nodeRef.current.attributes.set('color', 'red');
|
|
591
|
+
nodeRef.current.comment = 'Modified via ref';
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (edgeRef.current) {
|
|
595
|
+
edgeRef.current.attributes.set('style', 'dashed');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
console.log('Digraph nodes:', digraphRef.current?.nodes.length);
|
|
599
|
+
console.log('Digraph edges:', digraphRef.current?.edges.length);
|
|
600
|
+
console.log('Graph nodes:', graphRef.current?.nodes.length);
|
|
601
|
+
console.log('Graph edges:', graphRef.current?.edges.length);
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
return (
|
|
605
|
+
<button onClick={handleRender}>
|
|
606
|
+
Render Graph
|
|
607
|
+
</button>
|
|
608
|
+
);
|
|
91
609
|
}
|
|
92
610
|
```
|
|
93
611
|
|
|
94
|
-
|
|
612
|
+
## Contributors 👥
|
|
613
|
+
|
|
614
|
+
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
615
|
+
|
|
616
|
+
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
617
|
+
<!-- prettier-ignore-start -->
|
|
618
|
+
<!-- markdownlint-disable -->
|
|
619
|
+
<table>
|
|
620
|
+
<tbody>
|
|
621
|
+
<tr>
|
|
622
|
+
<td align="center" valign="top" width="14.28%"><a href="http://blog.kamiazya.tech/"><img src="https://avatars0.githubusercontent.com/u/35218186?v=4?s=100" width="100px;" alt="Yuki Yamazaki"/><br /><sub><b>Yuki Yamazaki</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=kamiazya" title="Code">💻</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=kamiazya" title="Tests">⚠️</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=kamiazya" title="Documentation">📖</a> <a href="#ideas-kamiazya" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
623
|
+
<td align="center" valign="top" width="14.28%"><a href="https://laysent.com"><img src="https://avatars2.githubusercontent.com/u/1191606?v=4?s=100" width="100px;" alt="LaySent"/><br /><sub><b>LaySent</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Alaysent" title="Bug reports">🐛</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=laysent" title="Tests">⚠️</a></td>
|
|
624
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/elasticdotventures"><img src="https://avatars0.githubusercontent.com/u/35611074?v=4?s=100" width="100px;" alt="elasticdotventures"/><br /><sub><b>elasticdotventures</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=elasticdotventures" title="Documentation">📖</a></td>
|
|
625
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ChristianMurphy"><img src="https://avatars.githubusercontent.com/u/3107513?v=4?s=100" width="100px;" alt="Christian Murphy"/><br /><sub><b>Christian Murphy</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=ChristianMurphy" title="Code">💻</a> <a href="#ideas-ChristianMurphy" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=ChristianMurphy" title="Documentation">📖</a></td>
|
|
626
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ArtemAdamenko"><img src="https://avatars.githubusercontent.com/u/2178516?v=4?s=100" width="100px;" alt="Artem"/><br /><sub><b>Artem</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3AArtemAdamenko" title="Bug reports">🐛</a></td>
|
|
627
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fredericohpandolfo"><img src="https://avatars.githubusercontent.com/u/24229136?v=4?s=100" width="100px;" alt="fredericohpandolfo"/><br /><sub><b>fredericohpandolfo</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Afredericohpandolfo" title="Bug reports">🐛</a></td>
|
|
628
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/diegoquinteiro"><img src="https://avatars.githubusercontent.com/u/1878108?v=4?s=100" width="100px;" alt="diegoquinteiro"/><br /><sub><b>diegoquinteiro</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Adiegoquinteiro" title="Bug reports">🐛</a></td>
|
|
629
|
+
</tr>
|
|
630
|
+
<tr>
|
|
631
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/robross0606"><img src="https://avatars.githubusercontent.com/u/2965467?v=4?s=100" width="100px;" alt="robross0606"/><br /><sub><b>robross0606</b></sub></a><br /><a href="#ideas-robross0606" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
632
|
+
<td align="center" valign="top" width="14.28%"><a href="https://blake-regalia.net"><img src="https://avatars.githubusercontent.com/u/1456400?v=4?s=100" width="100px;" alt="Blake Regalia"/><br /><sub><b>Blake Regalia</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Ablake-regalia" title="Bug reports">🐛</a></td>
|
|
633
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bigbug"><img src="https://avatars.githubusercontent.com/u/27259?v=4?s=100" width="100px;" alt="bigbug"/><br /><sub><b>bigbug</b></sub></a><br /><a href="#question-bigbug" title="Answering Questions">💬</a></td>
|
|
634
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/murawakimitsuhiro"><img src="https://avatars.githubusercontent.com/u/13833242?v=4?s=100" width="100px;" alt="mrwk"/><br /><sub><b>mrwk</b></sub></a><br /><a href="#question-murawakimitsuhiro" title="Answering Questions">💬</a></td>
|
|
635
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/svdvonde"><img src="https://avatars.githubusercontent.com/u/2751783?v=4?s=100" width="100px;" alt="svdvonde"/><br /><sub><b>svdvonde</b></sub></a><br /><a href="#question-svdvonde" title="Answering Questions">💬</a></td>
|
|
636
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/seethroughdev"><img src="https://avatars.githubusercontent.com/u/203779?v=4?s=100" width="100px;" alt="Adam"/><br /><sub><b>Adam</b></sub></a><br /><a href="#question-seethroughdev" title="Answering Questions">💬</a></td>
|
|
637
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/trevor-scheer"><img src="https://avatars.githubusercontent.com/u/29644393?v=4?s=100" width="100px;" alt="Trevor Scheer"/><br /><sub><b>Trevor Scheer</b></sub></a><br /><a href="#a11y-trevor-scheer" title="Accessibility">️️️️♿️</a></td>
|
|
638
|
+
</tr>
|
|
639
|
+
<tr>
|
|
640
|
+
<td align="center" valign="top" width="14.28%"><a href="https://pre.ms"><img src="https://avatars.githubusercontent.com/u/238277?v=4?s=100" width="100px;" alt="Prem Pillai"/><br /><sub><b>Prem Pillai</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Acloud-on-prem" title="Bug reports">🐛</a></td>
|
|
641
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nagasawaryoya"><img src="https://avatars.githubusercontent.com/u/53528726?v=4?s=100" width="100px;" alt="nagasawaryoya"/><br /><sub><b>nagasawaryoya</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=nagasawaryoya" title="Code">💻</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=nagasawaryoya" title="Tests">⚠️</a></td>
|
|
642
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tokidrill"><img src="https://avatars.githubusercontent.com/u/42460318?v=4?s=100" width="100px;" alt="YukiSasaki"/><br /><sub><b>YukiSasaki</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=tokidrill" title="Code">💻</a> <a href="https://github.com/ts-graphviz/ts-graphviz/commits?author=tokidrill" title="Tests">⚠️</a></td>
|
|
643
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Madd0g"><img src="https://avatars.githubusercontent.com/u/1171003?v=4?s=100" width="100px;" alt="Madd0g"/><br /><sub><b>Madd0g</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3AMadd0g" title="Bug reports">🐛</a></td>
|
|
644
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/j4k0xb"><img src="https://avatars.githubusercontent.com/u/55899582?v=4?s=100" width="100px;" alt="j4k0xb"/><br /><sub><b>j4k0xb</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Aj4k0xb" title="Bug reports">🐛</a></td>
|
|
645
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/haved"><img src="https://avatars.githubusercontent.com/u/3748845?v=4?s=100" width="100px;" alt="HKrogstie"/><br /><sub><b>HKrogstie</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Ahaved" title="Bug reports">🐛</a></td>
|
|
646
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/septatrix"><img src="https://avatars.githubusercontent.com/u/24257556?v=4?s=100" width="100px;" alt="Nils K"/><br /><sub><b>Nils K</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Aseptatrix" title="Bug reports">🐛</a></td>
|
|
647
|
+
</tr>
|
|
648
|
+
<tr>
|
|
649
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hao2013"><img src="https://avatars.githubusercontent.com/u/67059492?v=4?s=100" width="100px;" alt="hao2013"/><br /><sub><b>hao2013</b></sub></a><br /><a href="#maintenance-hao2013" title="Maintenance">🚧</a> <a href="https://github.com/ts-graphviz/ts-graphviz/pulls?q=is%3Apr+reviewed-by%3Ahao2013" title="Reviewed Pull Requests">👀</a></td>
|
|
650
|
+
<td align="center" valign="top" width="14.28%"><a href="http://www.walterra.dev"><img src="https://avatars.githubusercontent.com/u/230104?v=4?s=100" width="100px;" alt="Walter Rafelsberger"/><br /><sub><b>Walter Rafelsberger</b></sub></a><br /><a href="#question-walterra" title="Answering Questions">💬</a></td>
|
|
651
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/grsjst"><img src="https://avatars.githubusercontent.com/u/4739018?v=4?s=100" width="100px;" alt="grsjst"/><br /><sub><b>grsjst</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Agrsjst" title="Bug reports">🐛</a></td>
|
|
652
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/stephenirven"><img src="https://avatars.githubusercontent.com/u/4293560?v=4?s=100" width="100px;" alt="Steve"/><br /><sub><b>Steve</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Astephenirven" title="Bug reports">🐛</a></td>
|
|
653
|
+
</tr>
|
|
654
|
+
</tbody>
|
|
655
|
+
</table>
|
|
95
656
|
|
|
96
|
-
|
|
657
|
+
<!-- markdownlint-restore -->
|
|
658
|
+
<!-- prettier-ignore-end -->
|
|
97
659
|
|
|
98
|
-
|
|
660
|
+
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
99
661
|
|
|
100
|
-
|
|
662
|
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors)
|
|
663
|
+
specification. Contributions of any kind welcome!
|
|
101
664
|
|
|
102
|
-
## License
|
|
665
|
+
## License ⚖️
|
|
103
666
|
|
|
104
|
-
This software is released under the MIT License.
|
|
667
|
+
This software is released under the MIT License, see [LICENSE](https://github.com/ts-graphviz/ts-graphviz/blob/main/LICENSE).
|