@hpcc-js/wasm-graphviz 1.24.0 → 1.25.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hpcc-js/wasm-graphviz",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "hpcc-js - WASM Graphviz",
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "clean": "rimraf coverage dist dist-test types .nyc_output",
21
- "gen-types": "tsgo --project tsconfig.json --emitDeclarationOnly",
21
+ "gen-types": "tsc --project tsconfig.json --emitDeclarationOnly",
22
22
  "gen-types-watch": "npm run gen-types -- --watch",
23
23
  "bundle": "node esbuild.js",
24
24
  "bundle-dev": "npm run bundle -- --development",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "dependencies": {},
39
39
  "devDependencies": {
40
- "@hpcc-js/esbuild-plugins": "1.9.3",
40
+ "@hpcc-js/esbuild-plugins": "1.9.4",
41
41
  "@hpcc-js/wasm-util": "1.0.0"
42
42
  },
43
43
  "keywords": [
@@ -56,5 +56,5 @@
56
56
  },
57
57
  "homepage": "https://hpcc-systems.github.io/hpcc-js-wasm/",
58
58
  "license": "Apache-2.0",
59
- "gitHead": "32faad64c64599080e938fe69207fa2080cd1de6"
59
+ "gitHead": "e9302b649306927c2844eda31d3ee58b17b8eb8a"
60
60
  }
package/src/graphviz.ts CHANGED
@@ -8,8 +8,24 @@ import type {
8
8
  GraphDotAttr, GraphAttrs as GraphAttrs
9
9
  } from "./types.ts";
10
10
 
11
+ type AttrValues<T> = Partial<{ [K in keyof T]: NonNullable<T[K]> }>;
12
+
13
+ function applyAttrs<T extends object>(
14
+ attrs: AttrValues<T> | undefined,
15
+ apply: (attr: string, value: string | number | boolean) => void
16
+ ): void {
17
+ if (!attrs) {
18
+ return;
19
+ }
20
+ for (const [attr, value] of Object.entries(attrs)) {
21
+ if (value !== undefined) {
22
+ apply(attr, value as string | number | boolean);
23
+ }
24
+ }
25
+ }
26
+
11
27
  /**
12
- * Various graphic and data formats for end user, web, documents and other applications. See [Output Formats](https://graphviz.gitlab.io/docs/outputs/) for more information.
28
+ * Various graphic and data formats for end user, web, documents and other applications. See [Output Formats](https://graphviz.org/docs/outputs/) for more information.
13
29
  */
14
30
  export type Format = "svg" | "svg_inline" | "dot" | "json" | "dot_json" | "xdot_json" | "plain" | "plain-ext" | "canon";
15
31
 
@@ -43,7 +59,7 @@ function parseEdges(json: string): EdgeInfo[] {
43
59
  }
44
60
 
45
61
  /**
46
- * Various algorithms for projecting abstract graphs into a space for visualization. See [Layout Engines](https://graphviz.gitlab.io/docs/layouts/) for more details.
62
+ * Various algorithms for projecting abstract graphs into a space for visualization. See [Layout Engines](https://graphviz.org/docs/layouts/) for more details.
47
63
  */
48
64
  export type Engine = "circo" | "dot" | "fdp" | "sfdp" | "neato" | "osage" | "patchwork" | "twopi" | "nop" | "nop2";
49
65
 
@@ -117,6 +133,35 @@ let g_graphviz: Promise<Graphviz> | undefined;
117
133
  */
118
134
  export type GraphType = "directed" | "undirected" | "strict directed" | "strict undirected";
119
135
 
136
+ export interface GraphInit {
137
+ name?: string;
138
+ type?: GraphType;
139
+ attrs?: AttrValues<GraphAttrs>;
140
+ htmlAttrs?: AttrValues<GraphAttrs>;
141
+ }
142
+
143
+ export interface NodeInit {
144
+ name: string;
145
+ attrs?: AttrValues<NodeAttrs>;
146
+ htmlAttrs?: AttrValues<NodeAttrs>;
147
+ }
148
+
149
+ export interface EdgeInit {
150
+ tail: string;
151
+ head: string;
152
+ key?: string;
153
+ attrs?: AttrValues<EdgeAttrs>;
154
+ htmlAttrs?: AttrValues<EdgeAttrs>;
155
+ }
156
+
157
+ export interface SubgraphInit {
158
+ name: string;
159
+ attrs?: AttrValues<ClusterAttrs>;
160
+ htmlAttrs?: AttrValues<ClusterAttrs>;
161
+ }
162
+
163
+ export type ClusterInit = SubgraphInit;
164
+
120
165
  /**
121
166
  * A subgraph (or cluster) inside a {@link Graph}.
122
167
  *
@@ -148,8 +193,15 @@ export class Subgraph {
148
193
  /**
149
194
  * Create (or find) a node and add it to this subgraph.
150
195
  */
151
- addNode(name: string): this {
196
+ addNode(name: string): this;
197
+ addNode(init: NodeInit): this;
198
+ addNode(nameOrInit: string | NodeInit): this {
199
+ const init = typeof nameOrInit === "string" ? { name: nameOrInit } : nameOrInit;
200
+ const { name, attrs, htmlAttrs } = init;
201
+
152
202
  this._sg.addNode(name);
203
+ applyAttrs<NodeAttrs>(attrs, (attr, value) => this.setNodeAttr(name, attr, value));
204
+ applyAttrs<NodeAttrs>(htmlAttrs, (attr, value) => this.setNodeHtmlAttr(name, attr, value));
153
205
  return this;
154
206
  }
155
207
 
@@ -157,8 +209,23 @@ export class Subgraph {
157
209
  * Create an edge inside this subgraph. Both endpoints are created
158
210
  * automatically if they do not already exist.
159
211
  */
160
- addEdge(tail: string, head: string, key: string = ""): this {
161
- this._sg.addEdge(tail, head, key);
212
+ addEdge(tail: string, head: string, key?: string): this;
213
+ addEdge(init: EdgeInit): this;
214
+ addEdge(tailOrInit: string | EdgeInit, head?: string, key: string = ""): this {
215
+ const init = typeof tailOrInit === "string"
216
+ ? { tail: tailOrInit, head: head!, key }
217
+ : tailOrInit;
218
+ const { tail, head: resolvedHead, key: resolvedKey = "", attrs, htmlAttrs } = init;
219
+
220
+ this._sg.addEdge(tail, resolvedHead, resolvedKey);
221
+ applyAttrs<EdgeAttrs>(attrs, (attr, value) => this.setEdgeAttr(tail, resolvedHead, resolvedKey, attr, value));
222
+ applyAttrs<EdgeAttrs>(htmlAttrs, (attr, value) => this.setEdgeHtmlAttr(tail, resolvedHead, resolvedKey, attr, value));
223
+ return this;
224
+ }
225
+
226
+ applyInit(init: SubgraphInit): this {
227
+ applyAttrs<ClusterAttrs>(init.attrs, (attr, value) => this.setAttr(attr, value));
228
+ applyAttrs<ClusterAttrs>(init.htmlAttrs, (attr, value) => this.setHtmlAttr(attr, value));
162
229
  return this;
163
230
  }
164
231
 
@@ -183,16 +250,50 @@ export class Subgraph {
183
250
 
184
251
  /**
185
252
  * Set an attribute on the subgraph itself (e.g. `"label"`, `"style"`,
186
- * `"color"`, `"bgcolor"`).
253
+ * `"color"`, `"bgcolor"`). See the official Graphviz
254
+ * [attribute reference](https://graphviz.org/docs/attrs/) for supported
255
+ * attributes and values.
187
256
  *
188
257
  * When `attr` is a known cluster attribute the value type is inferred
189
258
  * automatically. A generic `string | number | boolean` fallback is
190
- * provided for custom or less-common attributes.
191
- */
192
- setAttr<K extends ClusterDotAttr>(attr: K, value: NonNullable<ClusterAttrs[K]>): this;
193
- setAttr(attr: string, value: string | number | boolean): this;
194
- setAttr(attr: string, value: unknown): this {
195
- this._sg.setAttr(attr, String(value));
259
+ * provided for custom or less-common attributes. Omit `value` to reset
260
+ * the attribute to Graphviz's default empty value. `defaultValue` is
261
+ * passed to Graphviz as the default for this attribute if it has not
262
+ * already been declared.
263
+ */
264
+ setAttr<K extends ClusterDotAttr>(attr: K, value?: NonNullable<ClusterAttrs[K]>, defaultValue?: string | number | boolean): this;
265
+ setAttr(attr: string, value?: string | number | boolean, defaultValue?: string | number | boolean): this;
266
+ setAttr(attr: string, value: unknown = "", defaultValue?: unknown): this {
267
+ if (defaultValue === undefined) {
268
+ this._sg.setAttr(attr, String(value));
269
+ } else {
270
+ this._sg.setAttr(attr, String(value), String(defaultValue));
271
+ }
272
+ return this;
273
+ }
274
+
275
+ setHtmlAttr<K extends ClusterDotAttr>(attr: K, value: NonNullable<ClusterAttrs[K]>, defaultValue?: string | number | boolean): this;
276
+ setHtmlAttr(attr: string, value: string | number | boolean, defaultValue?: string | number | boolean): this;
277
+ setHtmlAttr(attr: string, value: unknown, defaultValue?: unknown): this {
278
+ if (defaultValue === undefined) {
279
+ this._sg.setHtmlAttr(attr, String(value));
280
+ } else {
281
+ this._sg.setHtmlAttr(attr, String(value), String(defaultValue));
282
+ }
283
+ return this;
284
+ }
285
+
286
+ setDefaultAttr<K extends ClusterDotAttr>(attr: K, value: NonNullable<ClusterAttrs[K]>): this;
287
+ setDefaultAttr(attr: string, value: string | number | boolean): this;
288
+ setDefaultAttr(attr: string, value: unknown): this {
289
+ this._sg.setDefaultAttr(attr, String(value));
290
+ return this;
291
+ }
292
+
293
+ setDefaultHtmlAttr<K extends ClusterDotAttr>(attr: K, value: NonNullable<ClusterAttrs[K]>): this;
294
+ setDefaultHtmlAttr(attr: string, value: string | number | boolean): this;
295
+ setDefaultHtmlAttr(attr: string, value: unknown): this {
296
+ this._sg.setDefaultHtmlAttr(attr, String(value));
196
297
  return this;
197
298
  }
198
299
 
@@ -209,12 +310,43 @@ export class Subgraph {
209
310
  *
210
311
  * When `attr` is a known node attribute the value type is inferred
211
312
  * automatically. A generic `string | number | boolean` fallback is
212
- * provided for custom attributes.
213
- */
214
- setNodeAttr<K extends NodeDotAttr>(node: string, attr: K, value: NonNullable<NodeAttrs[K]>): this;
215
- setNodeAttr(node: string, attr: string, value: string | number | boolean): this;
216
- setNodeAttr(node: string, attr: string, value: unknown): this {
217
- this._sg.setNodeAttr(node, attr, String(value));
313
+ * provided for custom attributes. Omit `value` to reset the attribute to
314
+ * Graphviz's default empty value. `defaultValue` is passed to Graphviz as
315
+ * the default for this attribute if it has not already been declared.
316
+ */
317
+ setNodeAttr<K extends NodeDotAttr>(node: string, attr: K, value?: NonNullable<NodeAttrs[K]>, defaultValue?: string | number | boolean): this;
318
+ setNodeAttr(node: string, attr: string, value?: string | number | boolean, defaultValue?: string | number | boolean): this;
319
+ setNodeAttr(node: string, attr: string, value: unknown = "", defaultValue?: unknown): this {
320
+ if (defaultValue === undefined) {
321
+ this._sg.setNodeAttr(node, attr, String(value));
322
+ } else {
323
+ this._sg.setNodeAttr(node, attr, String(value), String(defaultValue));
324
+ }
325
+ return this;
326
+ }
327
+
328
+ setNodeHtmlAttr<K extends NodeDotAttr>(node: string, attr: K, value: NonNullable<NodeAttrs[K]>, defaultValue?: string | number | boolean): this;
329
+ setNodeHtmlAttr(node: string, attr: string, value: string | number | boolean, defaultValue?: string | number | boolean): this;
330
+ setNodeHtmlAttr(node: string, attr: string, value: unknown, defaultValue?: unknown): this {
331
+ if (defaultValue === undefined) {
332
+ this._sg.setNodeHtmlAttr(node, attr, String(value));
333
+ } else {
334
+ this._sg.setNodeHtmlAttr(node, attr, String(value), String(defaultValue));
335
+ }
336
+ return this;
337
+ }
338
+
339
+ setDefaultNodeAttr<K extends NodeDotAttr>(attr: K, value: NonNullable<NodeAttrs[K]>): this;
340
+ setDefaultNodeAttr(attr: string, value: string | number | boolean): this;
341
+ setDefaultNodeAttr(attr: string, value: unknown): this {
342
+ this._sg.setDefaultNodeAttr(attr, String(value));
343
+ return this;
344
+ }
345
+
346
+ setDefaultNodeHtmlAttr<K extends NodeDotAttr>(attr: K, value: NonNullable<NodeAttrs[K]>): this;
347
+ setDefaultNodeHtmlAttr(attr: string, value: string | number | boolean): this;
348
+ setDefaultNodeHtmlAttr(attr: string, value: unknown): this {
349
+ this._sg.setDefaultNodeHtmlAttr(attr, String(value));
218
350
  return this;
219
351
  }
220
352
 
@@ -232,12 +364,43 @@ export class Subgraph {
232
364
  *
233
365
  * When `attr` is a known edge attribute the value type is inferred
234
366
  * automatically. A generic `string | number | boolean` fallback is
235
- * provided for custom attributes.
236
- */
237
- setEdgeAttr<K extends EdgeDotAttr>(tail: string, head: string, key: string, attr: K, value: NonNullable<EdgeAttrs[K]>): this;
238
- setEdgeAttr(tail: string, head: string, key: string, attr: string, value: string | number | boolean): this;
239
- setEdgeAttr(tail: string, head: string, key: string, attr: string, value: unknown): this {
240
- this._sg.setEdgeAttr(tail, head, key, attr, String(value));
367
+ * provided for custom attributes. Omit `value` to reset the attribute to
368
+ * Graphviz's default empty value. `defaultValue` is passed to Graphviz as
369
+ * the default for this attribute if it has not already been declared.
370
+ */
371
+ setEdgeAttr<K extends EdgeDotAttr>(tail: string, head: string, key: string, attr: K, value?: NonNullable<EdgeAttrs[K]>, defaultValue?: string | number | boolean): this;
372
+ setEdgeAttr(tail: string, head: string, key: string, attr: string, value?: string | number | boolean, defaultValue?: string | number | boolean): this;
373
+ setEdgeAttr(tail: string, head: string, key: string, attr: string, value: unknown = "", defaultValue?: unknown): this {
374
+ if (defaultValue === undefined) {
375
+ this._sg.setEdgeAttr(tail, head, key, attr, String(value));
376
+ } else {
377
+ this._sg.setEdgeAttr(tail, head, key, attr, String(value), String(defaultValue));
378
+ }
379
+ return this;
380
+ }
381
+
382
+ setEdgeHtmlAttr<K extends EdgeDotAttr>(tail: string, head: string, key: string, attr: K, value: NonNullable<EdgeAttrs[K]>, defaultValue?: string | number | boolean): this;
383
+ setEdgeHtmlAttr(tail: string, head: string, key: string, attr: string, value: string | number | boolean, defaultValue?: string | number | boolean): this;
384
+ setEdgeHtmlAttr(tail: string, head: string, key: string, attr: string, value: unknown, defaultValue?: unknown): this {
385
+ if (defaultValue === undefined) {
386
+ this._sg.setEdgeHtmlAttr(tail, head, key, attr, String(value));
387
+ } else {
388
+ this._sg.setEdgeHtmlAttr(tail, head, key, attr, String(value), String(defaultValue));
389
+ }
390
+ return this;
391
+ }
392
+
393
+ setDefaultEdgeAttr<K extends EdgeDotAttr>(attr: K, value: NonNullable<EdgeAttrs[K]>): this;
394
+ setDefaultEdgeAttr(attr: string, value: string | number | boolean): this;
395
+ setDefaultEdgeAttr(attr: string, value: unknown): this {
396
+ this._sg.setDefaultEdgeAttr(attr, String(value));
397
+ return this;
398
+ }
399
+
400
+ setDefaultEdgeHtmlAttr<K extends EdgeDotAttr>(attr: K, value: NonNullable<EdgeAttrs[K]>): this;
401
+ setDefaultEdgeHtmlAttr(attr: string, value: string | number | boolean): this;
402
+ setDefaultEdgeHtmlAttr(attr: string, value: unknown): this {
403
+ this._sg.setDefaultEdgeHtmlAttr(attr, String(value));
241
404
  return this;
242
405
  }
243
406
 
@@ -275,6 +438,11 @@ export class Subgraph {
275
438
  /** Returns the number of edges in this subgraph. */
276
439
  edgeCount(): number { return this._sg.edgeCount(); }
277
440
 
441
+ /** Returns the degree of a named node in this subgraph. */
442
+ nodeDegree(node: string, inDegree: number = 1, outDegree: number = 1): number {
443
+ return this._sg.nodeDegree(node, inDegree, outDegree);
444
+ }
445
+
278
446
  // ---- Attribute reading ----------------------------------------------
279
447
 
280
448
  /**
@@ -396,8 +564,15 @@ export class Graph {
396
564
  * Create a node. If a node with this name already exists it is returned
397
565
  * unchanged.
398
566
  */
399
- addNode(name: string): this {
567
+ addNode(name: string): this;
568
+ addNode(init: NodeInit): this;
569
+ addNode(nameOrInit: string | NodeInit): this {
570
+ const init = typeof nameOrInit === "string" ? { name: nameOrInit } : nameOrInit;
571
+ const { name, attrs, htmlAttrs } = init;
572
+
400
573
  this._graph.addNode(name);
574
+ applyAttrs<NodeAttrs>(attrs, (attr, value) => this.setNodeAttr(name, attr, value));
575
+ applyAttrs<NodeAttrs>(htmlAttrs, (attr, value) => this.setNodeHtmlAttr(name, attr, value));
401
576
  return this;
402
577
  }
403
578
 
@@ -407,22 +582,82 @@ export class Graph {
407
582
  * parallel edges between the same pair of nodes; omit (or pass `""`) for
408
583
  * an anonymous edge.
409
584
  */
410
- addEdge(tail: string, head: string, key: string = ""): this {
411
- this._graph.addEdge(tail, head, key);
585
+ addEdge(tail: string, head: string, key?: string): this;
586
+ addEdge(init: EdgeInit): this;
587
+ addEdge(tailOrInit: string | EdgeInit, head?: string, key: string = ""): this {
588
+ const init = typeof tailOrInit === "string"
589
+ ? { tail: tailOrInit, head: head!, key }
590
+ : tailOrInit;
591
+ const { tail, head: resolvedHead, key: resolvedKey = "", attrs, htmlAttrs } = init;
592
+
593
+ this._graph.addEdge(tail, resolvedHead, resolvedKey);
594
+ applyAttrs<EdgeAttrs>(attrs, (attr, value) => this.setEdgeAttr(tail, resolvedHead, resolvedKey, attr, value));
595
+ applyAttrs<EdgeAttrs>(htmlAttrs, (attr, value) => this.setEdgeHtmlAttr(tail, resolvedHead, resolvedKey, attr, value));
596
+ return this;
597
+ }
598
+
599
+ applyInit(init: GraphInit): this {
600
+ applyAttrs<GraphAttrs>(init.attrs, (attr, value) => this.setGraphAttr(attr, value));
601
+ applyAttrs<GraphAttrs>(init.htmlAttrs, (attr, value) => this.setGraphHtmlAttr(attr, value));
602
+ return this;
603
+ }
604
+
605
+ /**
606
+ * Replace this graph with one parsed from DOT source using Graphviz cgraph
607
+ * reading support.
608
+ */
609
+ read(dotSource: string): this {
610
+ if (!this._graph.read(dotSource)) {
611
+ throw new Error("Invalid DOT source");
612
+ }
412
613
  return this;
413
614
  }
414
615
 
415
616
  /**
416
617
  * Set a graph-level attribute (e.g. `"rankdir"`, `"label"`, `"bgcolor"`).
618
+ * See the official Graphviz [attribute reference](https://graphviz.org/docs/attrs/)
619
+ * for supported attributes and values.
417
620
  *
418
621
  * When `attr` is a known graph attribute the value type is inferred
419
622
  * automatically. A generic `string | number | boolean` fallback is
420
- * provided for custom or less-common attributes.
421
- */
422
- setGraphAttr<K extends GraphDotAttr>(attr: K, value: NonNullable<GraphAttrs[K]>): this;
423
- setGraphAttr(attr: string, value: string | number | boolean): this;
424
- setGraphAttr(attr: string, value: unknown): this {
425
- this._graph.setGraphAttr(attr, String(value));
623
+ * provided for custom or less-common attributes. Omit `value` to reset
624
+ * the attribute to Graphviz's default empty value. `defaultValue` is
625
+ * passed to Graphviz as the default for this attribute if it has not
626
+ * already been declared.
627
+ */
628
+ setGraphAttr<K extends GraphDotAttr>(attr: K, value?: NonNullable<GraphAttrs[K]>, defaultValue?: string | number | boolean): this;
629
+ setGraphAttr(attr: string, value?: string | number | boolean, defaultValue?: string | number | boolean): this;
630
+ setGraphAttr(attr: string, value: unknown = "", defaultValue?: unknown): this {
631
+ if (defaultValue === undefined) {
632
+ this._graph.setGraphAttr(attr, String(value));
633
+ } else {
634
+ this._graph.setGraphAttr(attr, String(value), String(defaultValue));
635
+ }
636
+ return this;
637
+ }
638
+
639
+ setGraphHtmlAttr<K extends GraphDotAttr>(attr: K, value: NonNullable<GraphAttrs[K]>, defaultValue?: string | number | boolean): this;
640
+ setGraphHtmlAttr(attr: string, value: string | number | boolean, defaultValue?: string | number | boolean): this;
641
+ setGraphHtmlAttr(attr: string, value: unknown, defaultValue?: unknown): this {
642
+ if (defaultValue === undefined) {
643
+ this._graph.setGraphHtmlAttr(attr, String(value));
644
+ } else {
645
+ this._graph.setGraphHtmlAttr(attr, String(value), String(defaultValue));
646
+ }
647
+ return this;
648
+ }
649
+
650
+ setDefaultGraphAttr<K extends GraphDotAttr>(attr: K, value: NonNullable<GraphAttrs[K]>): this;
651
+ setDefaultGraphAttr(attr: string, value: string | number | boolean): this;
652
+ setDefaultGraphAttr(attr: string, value: unknown): this {
653
+ this._graph.setDefaultGraphAttr(attr, String(value));
654
+ return this;
655
+ }
656
+
657
+ setDefaultGraphHtmlAttr<K extends GraphDotAttr>(attr: K, value: NonNullable<GraphAttrs[K]>): this;
658
+ setDefaultGraphHtmlAttr(attr: string, value: string | number | boolean): this;
659
+ setDefaultGraphHtmlAttr(attr: string, value: unknown): this {
660
+ this._graph.setDefaultGraphHtmlAttr(attr, String(value));
426
661
  return this;
427
662
  }
428
663
 
@@ -432,12 +667,43 @@ export class Graph {
432
667
  *
433
668
  * When `attr` is a known node attribute the value type is inferred
434
669
  * automatically. A generic `string | number | boolean` fallback is
435
- * provided for custom attributes.
436
- */
437
- setNodeAttr<K extends NodeDotAttr>(node: string, attr: K, value: NonNullable<NodeAttrs[K]>): this;
438
- setNodeAttr(node: string, attr: string, value: string | number | boolean): this;
439
- setNodeAttr(node: string, attr: string, value: unknown): this {
440
- this._graph.setNodeAttr(node, attr, String(value));
670
+ * provided for custom attributes. Omit `value` to reset the attribute to
671
+ * Graphviz's default empty value. `defaultValue` is passed to Graphviz as
672
+ * the default for this attribute if it has not already been declared.
673
+ */
674
+ setNodeAttr<K extends NodeDotAttr>(node: string, attr: K, value?: NonNullable<NodeAttrs[K]>, defaultValue?: string | number | boolean): this;
675
+ setNodeAttr(node: string, attr: string, value?: string | number | boolean, defaultValue?: string | number | boolean): this;
676
+ setNodeAttr(node: string, attr: string, value: unknown = "", defaultValue?: unknown): this {
677
+ if (defaultValue === undefined) {
678
+ this._graph.setNodeAttr(node, attr, String(value));
679
+ } else {
680
+ this._graph.setNodeAttr(node, attr, String(value), String(defaultValue));
681
+ }
682
+ return this;
683
+ }
684
+
685
+ setNodeHtmlAttr<K extends NodeDotAttr>(node: string, attr: K, value: NonNullable<NodeAttrs[K]>, defaultValue?: string | number | boolean): this;
686
+ setNodeHtmlAttr(node: string, attr: string, value: string | number | boolean, defaultValue?: string | number | boolean): this;
687
+ setNodeHtmlAttr(node: string, attr: string, value: unknown, defaultValue?: unknown): this {
688
+ if (defaultValue === undefined) {
689
+ this._graph.setNodeHtmlAttr(node, attr, String(value));
690
+ } else {
691
+ this._graph.setNodeHtmlAttr(node, attr, String(value), String(defaultValue));
692
+ }
693
+ return this;
694
+ }
695
+
696
+ setDefaultNodeAttr<K extends NodeDotAttr>(attr: K, value: NonNullable<NodeAttrs[K]>): this;
697
+ setDefaultNodeAttr(attr: string, value: string | number | boolean): this;
698
+ setDefaultNodeAttr(attr: string, value: unknown): this {
699
+ this._graph.setDefaultNodeAttr(attr, String(value));
700
+ return this;
701
+ }
702
+
703
+ setDefaultNodeHtmlAttr<K extends NodeDotAttr>(attr: K, value: NonNullable<NodeAttrs[K]>): this;
704
+ setDefaultNodeHtmlAttr(attr: string, value: string | number | boolean): this;
705
+ setDefaultNodeHtmlAttr(attr: string, value: unknown): this {
706
+ this._graph.setDefaultNodeHtmlAttr(attr, String(value));
441
707
  return this;
442
708
  }
443
709
 
@@ -448,12 +714,43 @@ export class Graph {
448
714
  *
449
715
  * When `attr` is a known edge attribute the value type is inferred
450
716
  * automatically. A generic `string | number | boolean` fallback is
451
- * provided for custom attributes.
452
- */
453
- setEdgeAttr<K extends EdgeDotAttr>(tail: string, head: string, key: string, attr: K, value: NonNullable<EdgeAttrs[K]>): this;
454
- setEdgeAttr(tail: string, head: string, key: string, attr: string, value: string | number | boolean): this;
455
- setEdgeAttr(tail: string, head: string, key: string, attr: string, value: unknown): this {
456
- this._graph.setEdgeAttr(tail, head, key, attr, String(value));
717
+ * provided for custom attributes. Omit `value` to reset the attribute to
718
+ * Graphviz's default empty value. `defaultValue` is passed to Graphviz as
719
+ * the default for this attribute if it has not already been declared.
720
+ */
721
+ setEdgeAttr<K extends EdgeDotAttr>(tail: string, head: string, key: string, attr: K, value?: NonNullable<EdgeAttrs[K]>, defaultValue?: string | number | boolean): this;
722
+ setEdgeAttr(tail: string, head: string, key: string, attr: string, value?: string | number | boolean, defaultValue?: string | number | boolean): this;
723
+ setEdgeAttr(tail: string, head: string, key: string, attr: string, value: unknown = "", defaultValue?: unknown): this {
724
+ if (defaultValue === undefined) {
725
+ this._graph.setEdgeAttr(tail, head, key, attr, String(value));
726
+ } else {
727
+ this._graph.setEdgeAttr(tail, head, key, attr, String(value), String(defaultValue));
728
+ }
729
+ return this;
730
+ }
731
+
732
+ setEdgeHtmlAttr<K extends EdgeDotAttr>(tail: string, head: string, key: string, attr: K, value: NonNullable<EdgeAttrs[K]>, defaultValue?: string | number | boolean): this;
733
+ setEdgeHtmlAttr(tail: string, head: string, key: string, attr: string, value: string | number | boolean, defaultValue?: string | number | boolean): this;
734
+ setEdgeHtmlAttr(tail: string, head: string, key: string, attr: string, value: unknown, defaultValue?: unknown): this {
735
+ if (defaultValue === undefined) {
736
+ this._graph.setEdgeHtmlAttr(tail, head, key, attr, String(value));
737
+ } else {
738
+ this._graph.setEdgeHtmlAttr(tail, head, key, attr, String(value), String(defaultValue));
739
+ }
740
+ return this;
741
+ }
742
+
743
+ setDefaultEdgeAttr<K extends EdgeDotAttr>(attr: K, value: NonNullable<EdgeAttrs[K]>): this;
744
+ setDefaultEdgeAttr(attr: string, value: string | number | boolean): this;
745
+ setDefaultEdgeAttr(attr: string, value: unknown): this {
746
+ this._graph.setDefaultEdgeAttr(attr, String(value));
747
+ return this;
748
+ }
749
+
750
+ setDefaultEdgeHtmlAttr<K extends EdgeDotAttr>(attr: K, value: NonNullable<EdgeAttrs[K]>): this;
751
+ setDefaultEdgeHtmlAttr(attr: string, value: string | number | boolean): this;
752
+ setDefaultEdgeHtmlAttr(attr: string, value: unknown): this {
753
+ this._graph.setDefaultEdgeHtmlAttr(attr, String(value));
457
754
  return this;
458
755
  }
459
756
 
@@ -547,6 +844,11 @@ export class Graph {
547
844
  /** Returns the number of direct subgraphs of this graph. */
548
845
  subgraphCount(): number { return this._graph.subgraphCount(); }
549
846
 
847
+ /** Returns the degree of a named node in the graph. */
848
+ nodeDegree(node: string, inDegree: number = 1, outDegree: number = 1): number {
849
+ return this._graph.nodeDegree(node, inDegree, outDegree);
850
+ }
851
+
550
852
  // ---- Attribute reading ----------------------------------------------
551
853
 
552
854
  /**
@@ -636,10 +938,13 @@ export class Graph {
636
938
  * cluster.setAttr("label", "My Cluster").addEdge("a", "b");
637
939
  * ```
638
940
  */
639
- addSubgraph(name: string): Subgraph {
941
+ addSubgraph(name: string): Subgraph;
942
+ addSubgraph(init: SubgraphInit): Subgraph;
943
+ addSubgraph(nameOrInit: string | SubgraphInit): Subgraph {
944
+ const init = typeof nameOrInit === "string" ? { name: nameOrInit } : nameOrInit;
640
945
  // addSubgraph only returns null when the internal graph pointer is null,
641
946
  // which cannot happen while this Graph instance is alive.
642
- return new Subgraph(this._graph.addSubgraph(name)!);
947
+ return new Subgraph(this._graph.addSubgraph(init.name)!).applyInit(init);
643
948
  }
644
949
 
645
950
  /**
@@ -697,11 +1002,18 @@ export class Graph {
697
1002
  return retVal;
698
1003
  }
699
1004
 
1005
+ /**
1006
+ * Serialise the graph to a DOT-language string.
1007
+ */
1008
+ write(): string {
1009
+ return this._graph.write();
1010
+ }
1011
+
700
1012
  /**
701
1013
  * Serialise the graph to a DOT-language string.
702
1014
  */
703
1015
  toDot(): string {
704
- return this._graph.toDot();
1016
+ return this.write();
705
1017
  }
706
1018
 
707
1019
  /**
@@ -1067,9 +1379,32 @@ export class Graphviz {
1067
1379
  * const svg = graphviz.dot(graph.toDot());
1068
1380
  * ```
1069
1381
  */
1070
- createGraph(name: string = "G", type: GraphType = "directed"): Graph {
1071
- const directed = !type.includes("undirected") ? 1 : 0;
1072
- const strict = type.startsWith("strict") ? 1 : 0;
1073
- return new Graph(new this._module.CGraph(name, directed, strict), this._module);
1382
+ createGraph(name?: string, type?: GraphType): Graph;
1383
+ createGraph(init: GraphInit): Graph;
1384
+ createGraph(nameOrInit: string | GraphInit = "G", type: GraphType = "directed"): Graph {
1385
+ const init = typeof nameOrInit === "string"
1386
+ ? { name: nameOrInit, type }
1387
+ : nameOrInit;
1388
+ const resolvedName = init.name ?? "G";
1389
+ const resolvedType = init.type ?? "directed";
1390
+ const directed = !resolvedType.includes("undirected") ? 1 : 0;
1391
+ const strict = resolvedType.startsWith("strict") ? 1 : 0;
1392
+ return new Graph(new this._module.CGraph(resolvedName, directed, strict), this._module).applyInit(init);
1393
+ }
1394
+
1395
+ /**
1396
+ * Parse DOT source into a mutable {@link Graph}. The returned graph can
1397
+ * be queried, modified, rendered directly, or serialised back to DOT with
1398
+ * {@link Graph.toDot}.
1399
+ */
1400
+ read(dotSource: string): Graph {
1401
+ const graph = this.createGraph();
1402
+ try {
1403
+ graph.read(dotSource);
1404
+ return graph;
1405
+ } catch (e) {
1406
+ graph.delete();
1407
+ throw e;
1408
+ }
1074
1409
  }
1075
1410
  }
package/src/types.ts CHANGED
@@ -81,6 +81,7 @@ export interface NodeAttrs {
81
81
  group?: string;
82
82
  height?: number;
83
83
  href?: string;
84
+ id?: string;
84
85
  image?: string;
85
86
  imagepos?: string;
86
87
  imagescale?: boolean | string;
@@ -146,6 +147,7 @@ export interface EdgeAttrs {
146
147
  headtooltip?: string;
147
148
  headURL?: string;
148
149
  href?: string;
150
+ id?: string;
149
151
  label?: string;
150
152
  labelangle?: number;
151
153
  labeldistance?: number;
@@ -204,6 +206,7 @@ export interface ClusterAttrs {
204
206
  fontsize?: number;
205
207
  gradientangle?: number;
206
208
  href?: string;
209
+ id?: string;
207
210
  K?: number;
208
211
  label?: string;
209
212
  labeljust?: string;
@@ -374,6 +377,7 @@ export const NODE_DOT_ATTRS: readonly NodeDotAttr[] = attrsOf<NodeDotAttr>({
374
377
  group: true,
375
378
  height: true,
376
379
  href: true,
380
+ id: true,
377
381
  image: true,
378
382
  imagepos: true,
379
383
  imagescale: true,
@@ -434,6 +438,7 @@ export const EDGE_DOT_ATTRS: readonly EdgeDotAttr[] = attrsOf<EdgeDotAttr>({
434
438
  headtooltip: true,
435
439
  headURL: true,
436
440
  href: true,
441
+ id: true,
437
442
  label: true,
438
443
  labelangle: true,
439
444
  labeldistance: true,
@@ -486,6 +491,7 @@ export const CLUSTER_DOT_ATTRS: readonly ClusterDotAttr[] = attrsOf<ClusterDotAt
486
491
  fontsize: true,
487
492
  gradientangle: true,
488
493
  href: true,
494
+ id: true,
489
495
  K: true,
490
496
  label: true,
491
497
  labeljust: true,