@hpcc-js/graph 3.7.1 → 3.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +43 -43
  2. package/README.md +256 -256
  3. package/dist/assets/dagre-B-z4SP0u.js.map +1 -1
  4. package/dist/assets/graphviz-BK7FEJlA.js.map +1 -0
  5. package/dist/index.js +25 -25
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.umd.cjs +21 -21
  8. package/dist/index.umd.cjs.map +1 -1
  9. package/package.json +9 -9
  10. package/src/AdjacencyGraph.ts +224 -224
  11. package/src/Edge.css +22 -22
  12. package/src/Edge.ts +257 -257
  13. package/src/Graph.css +18 -18
  14. package/src/Graph.ts +1077 -1077
  15. package/src/GraphData.ts +187 -187
  16. package/src/GraphLayouts.ts +214 -214
  17. package/src/Sankey.css +44 -44
  18. package/src/Sankey.ts +304 -304
  19. package/src/Subgraph.css +9 -9
  20. package/src/Subgraph.ts +165 -165
  21. package/src/Vertex.css +2 -2
  22. package/src/Vertex.ts +282 -282
  23. package/src/__package__.ts +3 -3
  24. package/src/__tests__/data.ts +444 -444
  25. package/src/__tests__/index.ts +1 -1
  26. package/src/__tests__/test1.ts +18 -18
  27. package/src/__tests__/test2.ts +80 -80
  28. package/src/__tests__/test3.ts +46 -46
  29. package/src/__tests__/test4.ts +66 -66
  30. package/src/__tests__/test5.ts +85 -85
  31. package/src/common/graphT.css +38 -38
  32. package/src/common/graphT.ts +1363 -1363
  33. package/src/common/index.ts +3 -3
  34. package/src/common/layouts/circle.ts +37 -37
  35. package/src/common/layouts/dagre.ts +145 -145
  36. package/src/common/layouts/dagreWorker.ts +24 -24
  37. package/src/common/layouts/forceDirected.ts +117 -117
  38. package/src/common/layouts/forceDirectedWorker.ts +22 -22
  39. package/src/common/layouts/geoForceDirected.ts +112 -112
  40. package/src/common/layouts/graphviz.ts +137 -137
  41. package/src/common/layouts/graphvizWorker.ts +27 -27
  42. package/src/common/layouts/index.ts +7 -7
  43. package/src/common/layouts/layout.ts +147 -147
  44. package/src/common/layouts/null.ts +39 -39
  45. package/src/common/layouts/placeholders.ts +113 -113
  46. package/src/common/layouts/tree.ts +326 -326
  47. package/src/common/layouts/workers/dagre.ts +46 -46
  48. package/src/common/layouts/workers/dagreOptions.ts +35 -35
  49. package/src/common/layouts/workers/forceDirected.ts +38 -38
  50. package/src/common/layouts/workers/forceDirectedOptions.ts +30 -30
  51. package/src/common/layouts/workers/graphviz.ts +225 -225
  52. package/src/common/layouts/workers/graphvizOptions.ts +70 -70
  53. package/src/common/liteMap.ts +72 -72
  54. package/src/common/liteSVGZooom.ts +61 -61
  55. package/src/common/sankeyGraph.css +44 -44
  56. package/src/common/sankeyGraph.ts +345 -345
  57. package/src/html/annotation.ts +71 -71
  58. package/src/html/component.ts +18 -18
  59. package/src/html/edge.ts +15 -15
  60. package/src/html/graphHtml.ts +11 -11
  61. package/src/html/graphHtmlT.ts +117 -117
  62. package/src/html/icon.ts +64 -64
  63. package/src/html/image.ts +26 -26
  64. package/src/html/imageChar.ts +18 -18
  65. package/src/html/index.ts +8 -8
  66. package/src/html/intersection.ts +110 -110
  67. package/src/html/shape.ts +141 -141
  68. package/src/html/text.ts +59 -59
  69. package/src/html/textBox.ts +45 -45
  70. package/src/html/vertex.ts +67 -67
  71. package/src/index.ts +10 -10
  72. package/src/react/dataGraph.ts +345 -345
  73. package/src/react/graphReact.ts +177 -177
  74. package/src/react/graphReactT.ts +44 -44
  75. package/src/react/index.ts +4 -4
  76. package/src/react/subgraph.tsx +30 -30
  77. package/src/react/vertex.tsx +31 -31
  78. package/types/Edge.d.ts +1 -1
  79. package/types/Graph.d.ts +1 -1
  80. package/types/Sankey.d.ts +1 -1
  81. package/types/Subgraph.d.ts +1 -1
  82. package/types/Vertex.d.ts +1 -1
  83. package/dist/assets/graphviz-BvkMHneZ.js.map +0 -1
@@ -1,1363 +1,1363 @@
1
- import { d3Event, drag as d3Drag, Palette, select as d3Select, Selection, Spacer, SVGGlowFilter, SVGZoomWidget, Button, ToggleButton, Utility, Widget } from "@hpcc-js/common";
2
- import { Graph2 as GraphCollection, hashSum } from "@hpcc-js/util";
3
- import { HTMLTooltip } from "@hpcc-js/html";
4
- import { interpolateNumberArray as d3InterpolateNumberArray } from "d3-interpolate";
5
- import "d3-transition";
6
- import { interpolatePath as d3InterpolatePath } from "d3-interpolate-path";
7
- import { Circle, Dagre, ForceDirected, ForceDirectedAnimated, Graphviz, ILayout, Null } from "./layouts/index.ts";
8
- import { Options as FDOptions } from "./layouts/forceDirectedWorker.ts";
9
- import type { BaseProps, VertexBaseProps, EdgeBaseProps, GraphDataProps, HierarchyBase, SubgraphBaseProps } from "./layouts/placeholders.ts";
10
- import { EdgePlaceholder, SubgraphPlaceholder, VertexPlaceholder, isEdgePlaceholder } from "./layouts/placeholders.ts";
11
- import { Engine, graphviz as gvWorker } from "./layouts/graphvizWorker.ts";
12
- import { Tree, RadialTree, Dendrogram, RadialDendrogram } from "./layouts/tree.ts";
13
-
14
- import "./graphT.css";
15
-
16
- let scriptDir = (globalThis?.document?.currentScript as HTMLScriptElement)?.src ?? "./dummy.js";
17
- scriptDir = scriptDir.substring(0, scriptDir.replace(/[?#].*/, "").lastIndexOf("/") + 1);
18
-
19
- export {
20
- BaseProps,
21
- GraphDataProps,
22
- SubgraphBaseProps,
23
- VertexBaseProps,
24
- EdgeBaseProps,
25
- HierarchyBase
26
- };
27
-
28
- type GraphLayoutType = "Hierarchy" | "DOT" | "Tree" | "Dendrogram" | "RadialTree" | "RadialDendrogram" | "ForceDirected" | "ForceDirected2" | "ForceDirectedHybrid" | "Neato" | "FDP" | "Circle" | "TwoPI" | "Circo" | "None";
29
- const GraphLayoutTypeSet = ["Hierarchy", "DOT", "Tree", "Dendrogram", "RadialTree", "RadialDendrogram", "ForceDirected", "ForceDirected2", "ForceDirectedHybrid", "Neato", "FDP", "Circle", "TwoPI", "Circo", "None"];
30
-
31
- function dragStart<V extends VertexBaseProps>(n: VertexPlaceholder<V>) {
32
- n.fx = n.sx = n.x;
33
- n.fy = n.sy = n.y;
34
- }
35
- function dragTick<V extends VertexBaseProps>(n: VertexPlaceholder<V>, d: VertexPlaceholder<V>) {
36
- n.fx = n.sx + d.fx - d.sx;
37
- n.fy = n.sy + d.fy - d.sy;
38
- }
39
- function dragEnd<V extends VertexBaseProps>(n: VertexPlaceholder<V>) {
40
- n.x = n.fx;
41
- n.y = n.fy;
42
- n.fx = n.sx = undefined;
43
- n.fy = n.sy = undefined;
44
- }
45
-
46
- export type RendererT<T> = (props: T, element: SVGGElement) => unknown;
47
-
48
- export class GraphT<SG extends SubgraphBaseProps, V extends VertexBaseProps, E extends EdgeBaseProps<V>> extends SVGZoomWidget {
49
-
50
- protected _centroidFilter: SVGGlowFilter;
51
-
52
- private _toggleHierarchy = new ToggleButton().faChar("fa-sitemap").tooltip("Hierarchy").on("click", () => this.layoutClick("Hierarchy"));
53
- private _toggleForceDirected = new ToggleButton().faChar("fa-expand").tooltip("Force Directed").on("click", () => this.layoutClick("ForceDirected"));
54
- private _toggleForceDirected2 = new ToggleButton().faChar("fa-arrows").tooltip("Spring").on("click", () => this.layoutClick("ForceDirected2"));
55
- private _toggleCircle = new ToggleButton().faChar("fa-circle-o").tooltip("Circle").on("click", () => this.layoutClick("Circle"));
56
- private _toggleDot = new ToggleButton().faChar("fa-angle-double-down").tooltip("DOT").on("click", () => this.layoutClick("DOT"));
57
- private _toggleNeato = new ToggleButton().faChar("fa-sun-o").tooltip("Neato").on("click", () => this.layoutClick("Neato"));
58
- private _toggleFDP = new ToggleButton().faChar("fa-asterisk").tooltip("FDP").on("click", () => this.layoutClick("FDP"));
59
- private _toggleTwoPI = new ToggleButton().faChar("fa-bullseye").tooltip("TwoPI").on("click", () => this.layoutClick("TwoPI"));
60
- private _toggleCirco = new ToggleButton().faChar("fa-cogs").tooltip("Circo").on("click", () => this.layoutClick("Circo"));
61
- private _toggleT = new ToggleButton().faChar("fa-sitemap fa-rotate-270").tooltip("Tree").on("click", () => this.layoutClick("Tree"));
62
- private _toggleRT = new ToggleButton().faChar("fa-sun-o").tooltip("Radial Tree").on("click", () => this.layoutClick("RadialTree"));
63
- private _toggleD = new ToggleButton().faChar("fa-sitemap fa-rotate-270").tooltip("Dendrogram").on("click", () => this.layoutClick("Dendrogram"));
64
- private _toggleRD = new ToggleButton().faChar("fa-asterisk").tooltip("Radial Dendrogram").on("click", () => this.layoutClick("RadialDendrogram"));
65
-
66
- protected _graphData = new GraphCollection<VertexPlaceholder<V>, EdgePlaceholder<V, E>, SubgraphPlaceholder<SG>>()
67
- .idFunc(d => d.id)
68
- .sourceFunc(e => e.source.id)
69
- .targetFunc(e => e.target.id)
70
- .updateFunc((b: VertexPlaceholder<V> | EdgePlaceholder<V, E> | SubgraphPlaceholder<SG>, a: VertexPlaceholder<V> | EdgePlaceholder<V, E> | SubgraphPlaceholder<SG>) => {
71
- b.props = a.props;
72
- if (isEdgePlaceholder(a) && isEdgePlaceholder(b)) {
73
- b.source = a.source;
74
- b.target = a.target;
75
- }
76
- return b;
77
- })
78
- ;
79
-
80
- protected _prevDoClickTime: number = 0;
81
-
82
- protected _svgDefsAnn: any;
83
- protected _svgDefsCat: any;
84
- protected _subgraphG: Selection<SVGGElement, any, SVGGElement, any>;
85
- protected _edgeG: Selection<SVGGElement, any, SVGGElement, any>;
86
- protected _vertexG: Selection<SVGGElement, any, SVGGElement, any>;
87
-
88
- protected _tooltip: HTMLTooltip = new HTMLTooltip();
89
-
90
- protected _selection = new Utility.Selection(this);
91
- private _dragHandler = d3Drag<SVGGElement, VertexPlaceholder<V>, SVGGElement>();
92
-
93
- protected _catPalette = Palette.ordinal("hpcc10");
94
- _svgDefs: any;
95
-
96
- constructor(subgraphRenderer: RendererT<SG>, vertexRenderer: RendererT<V>, edgeRenderer: RendererT<E>) {
97
- super();
98
- this._subgraphRenderer = subgraphRenderer;
99
- this._vertexRenderer = vertexRenderer;
100
- this._edgeRenderer = edgeRenderer;
101
- const context = this;
102
- this._drawStartPos = "origin";
103
-
104
- const buttons: Widget[] = [
105
- this._toggleHierarchy,
106
- this._toggleForceDirected,
107
- this._toggleForceDirected2,
108
- this._toggleCircle,
109
- new Spacer(),
110
- this._toggleDot,
111
- this._toggleNeato,
112
- this._toggleFDP,
113
- this._toggleTwoPI,
114
- this._toggleCirco,
115
- new Spacer(),
116
- this._toggleT,
117
- this._toggleRT,
118
- this._toggleD,
119
- this._toggleRD,
120
- new Spacer()
121
- ];
122
- this._iconBar.buttons(buttons.concat(this._iconBar.buttons()));
123
-
124
- this.selectionGlowColor("navy");
125
-
126
- this._dragHandler
127
- .on("start", function (d) {
128
- if (context.allowDragging()) {
129
- d3Select(this).classed("grabbed", true);
130
- dragStart(d);
131
- Utility.safeRaise(this);
132
- context.moveVertexPlaceholder(d, false, true);
133
-
134
- const selection = context.selection();
135
- const isSelected = context.selected(d.props, selection as V[]);
136
- if (isSelected) {
137
- selection
138
- .filter(v => v.id !== d.props.id)
139
- .forEach(v => {
140
- const n = context._graphData.vertex(v.id);
141
- dragStart(n);
142
- });
143
- } else if (context.dragSingleNeighbors()) {
144
- context._graphData.singleNeighbors(d.id).forEach(n => {
145
- dragStart(n);
146
- });
147
- }
148
- }
149
- })
150
- .on("drag", function (d) {
151
- if (context.allowDragging()) {
152
- d.fx = d.sx + context.rproject(d3Event().x - d.sx);
153
- d.fy = d.sy + context.rproject(d3Event().y - d.sy);
154
- context._graphData.vertexEdges(d.id).forEach(e => delete e.points);
155
- context.moveVertexPlaceholder(d, false, true);
156
- const selection = context.selection();
157
- const isSelected = context.selected(d.props, selection as V[]);
158
-
159
- if (isSelected) {
160
- selection
161
- .filter(v => v.id !== d.props.id)
162
- .forEach(v => {
163
- const n = context._graphData.vertex(v.id);
164
- dragTick(n, d);
165
- context.moveVertexPlaceholder(n, false, true);
166
- });
167
- } else if (context.dragSingleNeighbors()) {
168
- context._graphData.singleNeighbors(d.id).forEach(n => {
169
- dragTick(n, d);
170
- context.moveVertexPlaceholder(n, false, true);
171
- });
172
- }
173
- }
174
- })
175
- .on("end", function (d) {
176
- let doClick = true;
177
- if (context.allowDragging()) {
178
- doClick = Math.abs(d.sx - d.fx) < 1 && Math.abs(d.sy - d.fy) < 1;
179
- dragEnd(d);
180
-
181
- const selection = context.selection();
182
- const isSelected = context.selected(d.props, selection as V[]);
183
- if (isSelected) {
184
- selection
185
- .filter(v => v.id !== d.props.id)
186
- .forEach(v => {
187
- const n = context._graphData.vertex(v.id);
188
- dragEnd(n);
189
- });
190
- } else if (context.dragSingleNeighbors()) {
191
- context._graphData.singleNeighbors(d.id).forEach(dragEnd);
192
- }
193
-
194
- d3Select(this).classed("grabbed", false);
195
- }
196
- if (doClick) {
197
- const event = d3Event();
198
- context._selection.click({
199
- _id: String(d.id),
200
- element: () => d.element as any
201
- }, event.sourceEvent);
202
- context.selectionChanged();
203
- const selected = d.element.classed("selected");
204
- const eventOrigin = context.resolveEventOrigin();
205
- if (event?.sourceEvent?.button === 2) {
206
- context.vertex_contextmenu(d.props.origData || d.props, "", selected, eventOrigin);
207
- } else {
208
- context.vertex_click(d.props.origData || d.props, "", selected, eventOrigin);
209
- }
210
- const doClickTime = Date.now();
211
- if (doClickTime - context._prevDoClickTime < context.doubleClickMaxDelay()) {
212
- context.vertex_dblclick(d.props.origData || d.props, "", selected, eventOrigin);
213
- }
214
- context._prevDoClickTime = doClickTime;
215
- }
216
- })
217
- .filter(() => true)
218
- ;
219
- }
220
-
221
- resolveEventOrigin(): { origin: string, data?: SG | V | E } {
222
- const d3evt = d3Event();
223
- const eventPath = d3evt?.sourceEvent?.path ?? d3evt?.sourceEvent?.composedPath() ?? d3evt?.path ?? d3evt?.composedPath();
224
- const element = eventPath?.find(n => n?.hasAttribute && n?.hasAttribute("data-click"));
225
- const origin = element ? element.getAttribute("data-click") : "";
226
- const dataStr = element ? element.getAttribute("data-click-data") : "";
227
- let data = undefined;
228
- if (dataStr) {
229
- try {
230
- data = JSON.parse(dataStr);
231
- } catch (e) {
232
- console.warn("Unexpected annotation data:", dataStr);
233
- }
234
- }
235
-
236
- return {
237
- origin,
238
- data
239
- };
240
- }
241
-
242
- iconBarButtons(): Widget[] {
243
- return this._iconBar.buttons();
244
- }
245
-
246
- private _origData: GraphDataProps<SG, V, E> = {
247
- subgraphs: [],
248
- vertices: [],
249
- edges: [],
250
- hierarchy: []
251
- };
252
- data(): GraphDataProps<SG, V, E>;
253
- data(_: GraphDataProps<SG, V, E>, merge?: boolean): this;
254
- data(_?: GraphDataProps<SG, V, E>, merge?: boolean): GraphDataProps<SG, V, E> | this {
255
- if (_ === void 0) return this._origData;
256
- this._origData = _;
257
-
258
- this._graphData.mergeSubgraphs((_.subgraphs || []).map(sg => ({
259
- id: sg.id,
260
- props: sg
261
- })));
262
-
263
- this._graphData.mergeVertices((_.vertices || []).map(v => ({
264
- id: v.id,
265
- centroid: v.centroid,
266
- props: v
267
- })));
268
-
269
- this._graphData.mergeEdges(
270
- (_.edges || [])
271
- .filter(e => {
272
- return this._graphData.vertexExists(e.source.id) && this._graphData.vertexExists(e.target.id);
273
- })
274
- .map(e => ({
275
- id: e.id,
276
- props: e,
277
- source: this._graphData.vertex(e.source.id),
278
- target: this._graphData.vertex(e.target.id)
279
- }))
280
- );
281
-
282
- this._graphData.clearParents();
283
- (_.hierarchy ? _.hierarchy : []).forEach(h => {
284
- if (this._graphData.subgraphExists(h.child.id)) {
285
- this._graphData.subgraphParent(h.child.id, h.parent.id);
286
- } else if (this._graphData.vertexExists(h.child.id)) {
287
- this._graphData.vertexParent(h.child.id, h.parent.id);
288
- }
289
- });
290
-
291
- return this;
292
- }
293
-
294
- selected(vertex: V, _?: Array<V>): boolean {
295
- return (_ || this.selection()).some(n => n.id === vertex.id);
296
- }
297
-
298
- selection(_: Array<V | SG | E>): this;
299
- selection(): Array<V | SG | E>;
300
- selection(_?: Array<V | SG | E>): Array<SG | V | E> | this {
301
- if (!arguments.length) return this._selection.get().map(item => this._graphData.item(item._id).props);
302
- this._selection.set(_.map(item => {
303
- const vp = this._graphData.item(item.id);
304
- return {
305
- _id: String(vp.id),
306
- element: () => vp.element as any
307
- };
308
- }));
309
- return this;
310
- }
311
-
312
- graphData(): GraphCollection<VertexPlaceholder<V>, EdgePlaceholder<V, E>> {
313
- return this._graphData;
314
- }
315
-
316
- resetLayout() {
317
- delete this._prevLayout;
318
- return this;
319
- }
320
-
321
- layoutRunning() {
322
- return this._layoutAlgo && this._layoutAlgo.running();
323
- }
324
-
325
- protected _layoutAlgo: ILayout = new Null(this);
326
- layoutAlgo(layout: ILayout) {
327
- if (this._layoutAlgo) {
328
- this._layoutAlgo.stop();
329
- }
330
- this._layoutAlgo = layout;
331
- return this._layoutAlgo.start().then(() => {
332
- this.updateIconBar();
333
- if (this.applyScaleOnLayout()) {
334
- // Wait for any transitions to finish ---
335
- setTimeout(() => {
336
- this.zoomToFit(this.transitionDuration());
337
- }, this.transitionDuration());
338
- }
339
- });
340
- }
341
-
342
- layoutClick(layout: GraphLayoutType) {
343
- if (this.layoutRunning()) {
344
- this._layoutAlgo.stop();
345
- this.updateIconBar();
346
- } else {
347
- delete this._prevLayout;
348
- this
349
- .layout(layout)
350
- .render()
351
- ;
352
- }
353
- }
354
-
355
- updateIconBarItem(tb: ToggleButton, tbLayout: GraphLayoutType) {
356
- const layout = this.layout();
357
- const running = this._layoutAlgo && this._layoutAlgo.running();
358
- tb.enabled(!running || layout === tbLayout).selected(running && layout === tbLayout).render();
359
- }
360
-
361
- updateIconBar() {
362
- super.updateIconBar();
363
- this.updateIconBarItem(this._toggleHierarchy, "Hierarchy");
364
- this.updateIconBarItem(this._toggleDot, "DOT");
365
- this.updateIconBarItem(this._toggleForceDirected, "ForceDirected");
366
- this.updateIconBarItem(this._toggleNeato, "Neato");
367
- this.updateIconBarItem(this._toggleFDP, "FDP");
368
- this.updateIconBarItem(this._toggleForceDirected2, "ForceDirected2");
369
- this.updateIconBarItem(this._toggleCircle, "Circle");
370
- this.updateIconBarItem(this._toggleTwoPI, "TwoPI");
371
- this.updateIconBarItem(this._toggleCirco, "Circo");
372
- this.updateIconBarItem(this._toggleT, "Tree");
373
- this.updateIconBarItem(this._toggleRT, "RadialTree");
374
- this.updateIconBarItem(this._toggleD, "Dendrogram");
375
- this.updateIconBarItem(this._toggleRD, "RadialDendrogram");
376
- }
377
-
378
- getNeighborMap(vertex: VertexPlaceholder<V>) {
379
- const vertices = {};
380
- const edges = {};
381
-
382
- if (vertex) {
383
- const nedges = this._graphData.vertexEdges(vertex.id);
384
- for (let i = 0; i < nedges.length; ++i) {
385
- const edge = this._graphData.edge(nedges[i].id);
386
- edges[edge.id] = edge;
387
- if (edge.source.id !== vertex.id) {
388
- vertices[edge.source.id] = edge.source;
389
- }
390
- if (edge.target.id !== vertex.id) {
391
- vertices[edge.target.id] = edge.target;
392
- }
393
- }
394
- }
395
-
396
- return {
397
- vertices,
398
- edges
399
- };
400
- }
401
-
402
- centerOnItem(id: string) {
403
- const item = this._graphData.item(id);
404
- let x;
405
- let y;
406
- if (this._graphData.isSubgraph(item) || this._graphData.isVertex(item)) {
407
- x = item.x;
408
- y = item.y;
409
- }
410
- if (this._graphData.isEdge(item)) {
411
- [x, y] = [0, 0]; // center(item.points);
412
- }
413
- if (x !== undefined && y !== undefined) {
414
- const bbox = item.element.node().getBBox();
415
- if (this._graphData.isVertex(item)) {
416
- const proj = this.projectPlacholder(item);
417
- x = proj.x;
418
- y = proj.y;
419
- } else if (this._graphData.isSubgraph(item)) {
420
- x = this.project(item.x);
421
- y = this.project(item.y);
422
- }
423
- const deltaX = bbox.x + bbox.width / 2;
424
- const deltaY = bbox.y + bbox.height / 2;
425
- const itemBBox = {
426
- x: x + deltaX - bbox.width / 2,
427
- y: y + deltaY - bbox.height / 2,
428
- width: bbox.width,
429
- height: bbox.height
430
- };
431
- this.centerOnBBox(itemBBox);
432
- }
433
- }
434
-
435
- hideVertex(id: string): this {
436
- const item = this._graphData.item(id);
437
- if (this._graphData.isVertex(item)) {
438
- item.props.hidden = true;
439
- }
440
- return this;
441
- }
442
-
443
- showVertex(id: string): this {
444
- const item = this._graphData.item(id);
445
- if (this._graphData.isVertex(item)) {
446
- item.props.hidden = false;
447
- }
448
- return this;
449
- }
450
-
451
- protected highlight = {
452
- zoom: 1.1,
453
- opacity: 0.33,
454
- edge: "1.25px"
455
- };
456
-
457
- highlightVerticies(vertexMap?: { [id: string]: boolean }) {
458
- const context = this;
459
- const vertexElements = this._vertexG.selectAll<SVGGElement, VertexPlaceholder<V>>(".graphVertex");
460
- const forceLabelOnHighlight = !context.showVertexLabels() && context.showVertexLabelsOnHighlight();
461
- vertexElements
462
- .classed("graphVertex-highlighted", d => !vertexMap || vertexMap[d.id])
463
- .style("filter", d => vertexMap && vertexMap[d.id] ? "url(#" + this.id() + "_glow)" : null)
464
- .each(function (d) {
465
- if (forceLabelOnHighlight) {
466
- const props = context.calcProps(
467
- {
468
- showLabel: !!(vertexMap && vertexMap[d.id]),
469
- ...context.vertexMapper(d.props, d.props.origData)
470
- }
471
- );
472
- d.renderResult = context._vertexRenderer(props, this);
473
- }
474
- })
475
- .transition().duration(this.transitionDuration())
476
- .on("end", function (d) {
477
- if (vertexMap && vertexMap[d.id]) {
478
- if (d.element.node() && d.element.node().parentNode) {
479
- d.element.node().parentNode.appendChild(d.element.node());
480
- }
481
- }
482
- })
483
- .style("opacity", function (d) {
484
- if (d.props.hidden) return 0;
485
- if (!vertexMap || vertexMap[d.id]) {
486
- return 1;
487
- }
488
- return context.highlight.opacity;
489
- })
490
- ;
491
- return this;
492
- }
493
-
494
- highlightEdges(edgeMap?: { [id: string]: boolean }) {
495
- const context = this;
496
- const edgeElements = this._edgeG.selectAll<SVGGElement, EdgePlaceholder<V, E>>(".graphEdge");
497
- edgeElements
498
- .classed("graphEdge-highlighted", d => !edgeMap || edgeMap[d.id])
499
- .style("stroke-width", function (o) {
500
- if (edgeMap && edgeMap[o.id]) {
501
- return context.highlight.edge;
502
- }
503
- return context.edgeStrokeWidth() + "px";
504
- }).transition().duration(this.transitionDuration())
505
- .style("opacity", function (o) {
506
- if (o.source.props.hidden || o.target.props.hidden) return 0;
507
- if (!edgeMap || edgeMap[o.id]) {
508
- return 1;
509
- }
510
- return context.highlight.opacity;
511
- })
512
- ;
513
- return this;
514
- }
515
-
516
- highlightVertex(_element, d?: VertexPlaceholder<V>) {
517
- if (this.highlightOnMouseOverVertex()) {
518
- if (d) {
519
- const highlight = this.getNeighborMap(d);
520
- highlight.vertices[d.id] = d;
521
- this.highlightVerticies(highlight.vertices);
522
- this.highlightEdges(highlight.edges);
523
- } else {
524
- this.highlightVerticies();
525
- this.highlightEdges();
526
- }
527
- }
528
- }
529
-
530
- highlightEdge(_element, d?: EdgePlaceholder<V, E>) {
531
- if (this.highlightOnMouseOverEdge()) {
532
- if (d) {
533
- const vertices = {};
534
- vertices[d.source.id] = d.source;
535
- vertices[d.target.id] = d.target;
536
- const edges = {};
537
- edges[d.id] = d;
538
- this.highlightVerticies(vertices);
539
- this.highlightEdges(edges);
540
- } else {
541
- this.highlightVerticies();
542
- this.highlightEdges();
543
- }
544
- }
545
- }
546
-
547
- moveSubgraphPlaceholder(sp: SubgraphPlaceholder<SG>, transition: boolean): this {
548
- const context = this;
549
- const x = this.project(sp.x);
550
- const y = this.project(sp.y);
551
- const width = this.project(sp.props.width, true);
552
- const height = this.project(sp.props.height, true);
553
- if (sp.element) {
554
- sp.element.transition().duration(transition ? this.transitionDuration() : 0)
555
- .attr("transform", `translate(${x} ${y})`)
556
- .each(function (d) {
557
- context._subgraphRenderer({ ...d.props, width, height }, this);
558
- })
559
- ;
560
- }
561
- return this;
562
- }
563
-
564
- moveEdgePlaceholder(ep: EdgePlaceholder<V, E>, transition: boolean): this {
565
- const edgeLayout = {
566
- ...this._layoutAlgo.edgePath(ep as any, this.edgeArcDepth()),
567
- };
568
- const context = this;
569
- if (this._edgeRenderer && ep.element) {
570
- const previousEdgeLayout = (ep as any).previousEdgeLayout ?? edgeLayout;
571
- (ep as any).previousEdgeLayout = edgeLayout;
572
- const pathInterpolator = d3InterpolatePath(previousEdgeLayout.path, edgeLayout.path);
573
- const labelPosInterpolator = d3InterpolateNumberArray(previousEdgeLayout.labelPos, edgeLayout.labelPos);
574
- ep.element.transition().duration(transition ? this.transitionDuration() : 0)
575
- .tween("some.path", function () {
576
- return function (t) {
577
- const updated = {
578
- path: pathInterpolator(t),
579
- labelPos: labelPosInterpolator(t),
580
- strokeWidth: ep.props.strokeWidth ?? context.edgeStrokeWidth(),
581
- color: ep.props.stroke ?? context.edgeColor()
582
- };
583
- ep.renderResult = context._edgeRenderer({ graphInstance: context, ...edgeLayout, ...ep.props, ...updated }, ep.element.node());
584
- };
585
- });
586
- }
587
- return this;
588
- }
589
-
590
- moveVertexPlaceholder(vp: VertexPlaceholder<V>, transition: boolean, moveNeighbours: boolean): this {
591
- const { x, y } = this.projectPlacholder(vp);
592
- if (vp.element) {
593
- (transition ? vp.element.transition().duration(this.transitionDuration()) as unknown as Selection<SVGPathElement, EdgePlaceholder<V, E>, SVGGElement, any> : vp.element)
594
- .attr("transform", `translate(${x} ${y})`)
595
- ;
596
- }
597
- if (moveNeighbours) {
598
- this._graphData.vertexEdges(vp.id).forEach(e => this.moveEdgePlaceholder(e, transition));
599
- }
600
- return this;
601
- }
602
-
603
- moveSubgraphs(transition: boolean): this {
604
- this._graphData.allSubgraphs().forEach(s => this.moveSubgraphPlaceholder(s, transition));
605
- return this;
606
- }
607
-
608
- moveEdges(transition: boolean): this {
609
- this._graphData.allEdges().forEach(e => this.moveEdgePlaceholder(e, transition));
610
- return this;
611
- }
612
-
613
- moveVertices(transition: boolean): this {
614
- this._graphData.allVertices().forEach(v => this.moveVertexPlaceholder(v, transition, false));
615
- return this;
616
- }
617
-
618
- project(pos: number, clip: boolean = false) {
619
- const rf = 10;
620
- pos = pos !== undefined ? pos : 0;
621
- let scale = this._transformScale;
622
- if (clip) {
623
- if (this._transformScale > this.maxScale() + (this._transformScale - this.maxScale()) / 2) {
624
- scale = this.maxScale() + (this._transformScale - this.maxScale()) / 2;
625
- } else if (this._transformScale < this.minScale() - (this._transformScale - this.minScale()) / 13) {
626
- scale = this.minScale() - (this._transformScale - this.minScale()) / 13;
627
- }
628
- }
629
- return Math.round(pos * scale * rf) / rf;
630
- }
631
-
632
- rproject(pos: number) {
633
- const rf = 10;
634
- pos = pos !== undefined ? pos : 0;
635
- return Math.round(pos / this._transformScale * rf) / rf;
636
- }
637
-
638
- projectPlacholder(vp: VertexPlaceholder<V>) {
639
- return {
640
- x: this.project(vp.fx !== undefined ? vp.fx : vp.x),
641
- y: this.project(vp.fy !== undefined ? vp.fy : vp.y),
642
- };
643
- }
644
-
645
- rprojectPlacholder(vp: VertexPlaceholder<V>) {
646
- return {
647
- x: this.rproject(vp.fx !== undefined ? vp.fx : vp.x),
648
- y: this.rproject(vp.fy !== undefined ? vp.fy : vp.y),
649
- };
650
- }
651
-
652
- categoryID(id: string | number, prefix: "cat" | "ann" = "cat"): string {
653
- return id === undefined || id === "" ? "" : `${prefix}${this.id()}_${id}`;
654
- }
655
-
656
- updateCategories() {
657
- }
658
-
659
- updateAnnotations() {
660
- }
661
-
662
- private _edgeRenderer: RendererT<E>;
663
- edgeRenderer(): RendererT<E>;
664
- edgeRenderer(_: RendererT<E>): this;
665
- edgeRenderer(_?: RendererT<E>): this | RendererT<E> {
666
- if (!arguments.length) return this._edgeRenderer;
667
- this._edgeRenderer = _;
668
- return this;
669
- }
670
-
671
- updateEdges(): this {
672
- const context = this;
673
- this._edgeG.selectAll(".graphEdge")
674
- .data(this._graphData.allEdges(), d => (d as EdgePlaceholder<V, E>).id)
675
- .join(
676
- enter => enter.append("g")
677
- .attr("class", "graphEdge")
678
- .on("click.selectionBag", function (d) {
679
- context._selection.click({
680
- _id: String(d.id),
681
- element: () => d.element as any
682
- }, d3Event());
683
- context.selectionChanged();
684
- })
685
- .on("click", function (this: SVGElement, d) {
686
- const selected = d.element.classed("selected");
687
- context.edge_click(d.props.origData || d.props, "", selected);
688
- })
689
- .on("mouseover", function (d) {
690
- Utility.safeRaise(this);
691
- context.edge_mouseover(d3Select(this), d);
692
- })
693
- .on("mouseout", function (d) {
694
- context.edge_mouseout(d3Select(this), d);
695
- })
696
- .each(function (d) {
697
- d.element = d3Select(this);
698
- if (!context._edgeRenderer) {
699
- d.elementPath = d.element.append("path");
700
- d.elementText = d.element.append("text")
701
- .attr("text-anchor", "middle")
702
- ;
703
- }
704
- })
705
- ,
706
- update => update
707
- .attr("opacity", d => d.source.props.hidden || d.target.props.hidden ? 0 : 1)
708
- .classed("hide-text", !context.showEdgeLabels())
709
- ,
710
- exit => exit
711
- .each(function (d) {
712
- delete d.element;
713
- })
714
- .remove()
715
- )
716
- ;
717
- return this;
718
- }
719
-
720
- private _vertexRenderer: RendererT<V>;
721
- vertexRenderer(): RendererT<V>;
722
- vertexRenderer(_: RendererT<V>): this;
723
- vertexRenderer(_?: RendererT<V>): this | RendererT<V> {
724
- if (!arguments.length) return this._vertexRenderer;
725
- this._vertexRenderer = _;
726
- return this;
727
- }
728
-
729
- vertexMapper(props: V, origRow: V): V {
730
- return {
731
- ...props,
732
- categoryID: this.categoryID(props.categoryID),
733
- annotationIDs: props.annotationIDs ? props.annotationIDs.map(a => this.categoryID(a, "ann")) : []
734
- };
735
- }
736
-
737
- updateVertices(): this {
738
- const context = this;
739
- this._vertexG.selectAll(".graphVertex")
740
- .data(this._graphData.allVertices(), d => (d as VertexPlaceholder<V>).id)
741
- .join(
742
- enter => enter.append("g")
743
- .attr("class", "graphVertex")
744
- .on("dblclick", function (this: SVGElement, d) {
745
- d3Event().stopPropagation();
746
- })
747
- .on("contextmenu", function (this: SVGElement, d) {
748
- d3Event().preventDefault();
749
- })
750
- .on("keydown", function (this: SVGElement, d) {
751
- if (!context.tabNavigation()) return;
752
- const event = d3Event() as KeyboardEvent;
753
- const isSpace = event.code === "Space" || event.key === " " || event.key === "Spacebar";
754
- const isEnter = event.key === "Enter";
755
- if (!(isSpace || isEnter)) return;
756
-
757
- event.preventDefault();
758
- const selectionItem = {
759
- _id: String(d.id),
760
- element: () => d.element as any
761
- };
762
- if (event.ctrlKey || event.metaKey) {
763
- if (context._selection.isSelected(selectionItem)) {
764
- context._selection.remove(selectionItem);
765
- } else {
766
- context._selection.append(selectionItem);
767
- }
768
- } else {
769
- context._selection.clear();
770
- context._selection.append(selectionItem);
771
- }
772
-
773
- context.selectionChanged();
774
- const selected = d.element.classed("selected");
775
- const eventOrigin = context.resolveEventOrigin();
776
- context.vertex_click(d.props.origData || d.props, "", selected, eventOrigin);
777
- })
778
- .on("mousein", function (d) {
779
- Utility.safeRaise(this);
780
- context.highlightVertex(d3Select(this), d);
781
- const selected = d.element.classed("selected");
782
- const eventOrigin = context.resolveEventOrigin();
783
- context.vertex_mousein(d.props.origData || d.props, "", selected, eventOrigin);
784
- })
785
- .on("mouseover", function (d) {
786
- Utility.safeRaise(this);
787
- context.highlightVertex(d3Select(this), d);
788
- const selected = d.element.classed("selected");
789
- if (d.props.tooltip) {
790
- context._tooltip
791
- .tooltipHTML(context.tooltipHTML.bind(context))
792
- .triggerElement(d.element)
793
- .tooltipWidth(context.tooltipWidth())
794
- .tooltipHeight(context.tooltipHeight())
795
- .enablePointerEvents(context.enableTooltipPointerEvents())
796
- .closeDelay(context.tooltipCloseDelay())
797
- .direction("n")
798
- .data(d)
799
- .visible(true)
800
- .render()
801
- ;
802
- }
803
- const eventOrigin = context.resolveEventOrigin();
804
- context.vertex_mouseover(d.props.origData || d.props, "", selected, eventOrigin);
805
- })
806
- .on("mouseout", function (d) {
807
- context.highlightVertex(null, null);
808
- const selected = d.element.classed("selected");
809
- const eventOrigin = context.resolveEventOrigin();
810
- context.vertex_mouseout(d.props.origData || d.props, "", selected, eventOrigin);
811
- if (d.props.tooltip) {
812
- context._tooltip.mouseout();
813
- }
814
- })
815
- .call((sel) => this._dragHandler(sel))
816
- .each(function (d) {
817
- d.element = d3Select(this);
818
- }),
819
- update => update,
820
- exit => exit
821
- .each(function (d) {
822
- delete d.element;
823
- })
824
- .remove()
825
- )
826
- .classed("centroid", d => d.props.centroid)
827
- .attr("opacity", d => d.props.hidden ? 0 : 1)
828
- .attr("filter", d => d.props.centroid ? "url(#" + this.id() + "_glow)" : null)
829
- .attr("tabindex", d => context.tabNavigation() && !d.props.hidden ? 0 : null)
830
- .attr("role", context.tabNavigation() ? "button" : null)
831
- .attr("aria-label", d => context.tabNavigation() ? context.vertexAriaLabel(d) : null)
832
- .attr("aria-hidden", d => d.props.hidden ? "true" : null)
833
- .each(function (this: any, d) {
834
- const props = context.calcProps(
835
- {
836
- showLabel: context.showVertexLabels(),
837
- ...context.vertexMapper(d.props, d.props.origData)
838
- }
839
- );
840
- d.renderResult = context._vertexRenderer(props, this);
841
- })
842
- ;
843
- return this;
844
- }
845
-
846
- calcProps(props: V): V {
847
- return { ...props };
848
- }
849
-
850
- protected vertexAriaLabel(d: VertexPlaceholder<V>): string {
851
- return d.props?.text || `Vertex ${d.id}`;
852
- }
853
-
854
- hasSubgraphs() {
855
- switch (this.layout()) {
856
- case "DOT":
857
- case "Hierarchy":
858
- return true;
859
- }
860
- return false;
861
- }
862
-
863
- private _subgraphRenderer: RendererT<SG>;
864
- subgraphRenderer(): RendererT<SG>;
865
- subgraphRenderer(_: RendererT<SG>): this;
866
- subgraphRenderer(_?: RendererT<SG>): this | RendererT<SG> {
867
- if (!arguments.length) return this._subgraphRenderer;
868
- this._subgraphRenderer = _;
869
- return this;
870
- }
871
-
872
- updateSubgraphs(): this {
873
- const context = this;
874
- this._subgraphG.selectAll(".subgraphPlaceholder")
875
- .data(this.hasSubgraphs() ? this._graphData.allSubgraphs() : [], d => (d as SubgraphPlaceholder<SG>).id)
876
- .join(
877
- enter => enter.append("g")
878
- .attr("class", "subgraphPlaceholder")
879
- .on("click.selectionBag", function (d) {
880
- context._selection.click({
881
- _id: String(d.id),
882
- element: () => d.element as any
883
- }, d3Event());
884
- context.selectionChanged();
885
- })
886
- .on("click", function (this: SVGElement, d) {
887
- const selected = d.element.classed("selected");
888
- context.subgraph_click(d.props.origData || d.props, "", selected);
889
- })
890
- .on("mouseover", function () {
891
- Utility.safeRaise(this);
892
- })
893
- .each(function (d) {
894
- d.element = d3Select(this);
895
- })
896
- ,
897
- update => update,
898
- exit => exit
899
- .each(function (d) {
900
- delete d.element;
901
- })
902
- .transition()
903
- .style("opacity", 0)
904
- .remove()
905
- )
906
- .each(function (d) {
907
- context.moveSubgraphPlaceholder(d, false);
908
- })
909
- ;
910
- return this;
911
- }
912
-
913
- enter(domNode, element) {
914
- super.enter(domNode, element);
915
-
916
- const svg = this.locateSVGNode(domNode);
917
- this._svgDefs = d3Select(svg).select<SVGDefsElement>("defs");
918
- this._centroidFilter = new SVGGlowFilter(this._svgDefs, this._id + "_glow");
919
- this._svgDefsCat = this._svgDefs.append("g");
920
- this._svgDefsAnn = this._svgDefs.append("g");
921
- this._subgraphG = this._renderElement.append("g");
922
- this._edgeG = this._renderElement.append("g");
923
- this._vertexG = this._renderElement.append("g");
924
-
925
- this._tooltip.target(domNode);
926
-
927
- this.on("startMarqueeSelection", () => {
928
- }).on("updateMarqueeSelection", rect => {
929
- const vertices: VertexPlaceholder<V>[] = this._graphData.allVertices().filter(v => v.x >= rect.x && v.x <= rect.x + rect.width && v.y >= rect.y && v.y <= rect.y + rect.height);
930
- this.selection(vertices.map(v => v.props));
931
- }).on("endMarqueeSelection", () => {
932
- this.selectionChanged();
933
- });
934
- }
935
-
936
- protected forceDirectedOptions(): FDOptions {
937
- return {
938
- alpha: this.forceDirectedAlpha(),
939
- alphaMin: this.forceDirectedAlphaMin(),
940
- alphaDecay: this.forceDirectedAlphaDecay(),
941
- velocityDecay: this.forceDirectedVelocityDecay(),
942
- repulsionStrength: this.forceDirectedRepulsionStrength(),
943
- iterations: this.forceDirectedIterations(),
944
- linkDistance: this.forceDirectedLinkDistance(),
945
- linkStrength: this.forceDirectedLinkStrength(),
946
- pinCentroid: this.forceDirectedPinCentroid(),
947
- forceStrength: this.forceDirectedForceStrength(),
948
- distanceMin: this.forceDirectedMinDistance(),
949
- distanceMax: this.forceDirectedMaxDistance(),
950
- };
951
- }
952
-
953
- private layoutOptions(layout: GraphLayoutType) {
954
- switch (layout) {
955
- case "ForceDirected":
956
- case "ForceDirected2":
957
- case "ForceDirectedHybrid":
958
- return this.forceDirectedOptions();
959
- case "Hierarchy":
960
- return {
961
- rankdir: this.hierarchyRankDirection(),
962
- nodesep: this.hierarchyNodeSeparation(),
963
- edgesep: this.hierarchyEdgeSeparation(),
964
- ranksep: this.hierarchyRankSeparation(),
965
- digraph: this.hierarchyDigraph()
966
- };
967
- case "Tree":
968
- case "Dendrogram":
969
- return { rankdir: this.treeRankDirection() };
970
- case "DOT":
971
- case "Neato":
972
- case "FDP":
973
- case "TwoPI":
974
- case "Circo":
975
- return new URL(this.wasmFolder() || scriptDir, document.baseURI).href;
976
- case "None":
977
- case "Circle":
978
- case "RadialTree":
979
- case "RadialDendrogram":
980
- default:
981
- return undefined;
982
- }
983
- }
984
-
985
- private _prevLayout: string;
986
- updateLayout() {
987
- const layout = this.layout();
988
- const options: any = this.layoutOptions(layout);
989
- const hash = hashSum([layout, options]);
990
- if (this._prevLayout !== hash) {
991
- this._prevLayout = hash;
992
- switch (layout) {
993
- case "None":
994
- this.layoutAlgo(new Null(this));
995
- break;
996
- case "Circle":
997
- this.layoutAlgo(new Circle(this));
998
- break;
999
- case "ForceDirected":
1000
- this.layoutAlgo(new ForceDirected(this, options));
1001
- break;
1002
- case "ForceDirected2":
1003
- this.layoutAlgo(new ForceDirectedAnimated(this, options));
1004
- break;
1005
- case "ForceDirectedHybrid":
1006
- this.layoutAlgo(new ForceDirected(this, options)).then(() => {
1007
- this.layoutAlgo(new ForceDirectedAnimated(this, options));
1008
- });
1009
- break;
1010
- case "Hierarchy":
1011
- this.layoutAlgo(new Dagre(this, options));
1012
- break;
1013
- case "DOT":
1014
- this.layoutAlgo(new Graphviz(this, "dot", options));
1015
- break;
1016
- case "Tree":
1017
- this.layoutAlgo(new Tree(this, options));
1018
- break;
1019
- case "RadialTree":
1020
- this.layoutAlgo(new RadialTree(this));
1021
- break;
1022
- case "Dendrogram":
1023
- this.layoutAlgo(new Dendrogram(this, options));
1024
- break;
1025
- case "RadialDendrogram":
1026
- this.layoutAlgo(new RadialDendrogram(this));
1027
- break;
1028
- case "Neato":
1029
- this.layoutAlgo(new Graphviz(this, "neato", options));
1030
- break;
1031
- case "FDP":
1032
- this.layoutAlgo(new Graphviz(this, "fdp", options));
1033
- break;
1034
- case "TwoPI":
1035
- this.layoutAlgo(new Graphviz(this, "twopi", options));
1036
- break;
1037
- case "Circo":
1038
- this.layoutAlgo(new Graphviz(this, "circo", options));
1039
- break;
1040
- }
1041
- }
1042
- }
1043
-
1044
- update(domNode, element) {
1045
- super.update(domNode, element);
1046
-
1047
- const hostDiv = this.parentRelativeDiv;
1048
- if (this.tabNavigation() && hostDiv) {
1049
- const vertexCount = this._graphData?.allVertices().length ?? 0;
1050
- const ariaLabel = vertexCount ? `Graph data (${vertexCount} vertices)` : "Graph data";
1051
- hostDiv
1052
- .attr("tabindex", "0")
1053
- .attr("role", "group")
1054
- .attr("aria-label", ariaLabel);
1055
- } else if (hostDiv) {
1056
- hostDiv
1057
- .attr("tabindex", null)
1058
- .attr("role", null)
1059
- .attr("aria-label", null);
1060
- }
1061
-
1062
- this._renderElement.classed("allowDragging", this.allowDragging());
1063
- if (this._centroidFilter) {
1064
- this._centroidFilter.update(this.selectionGlowColor());
1065
- }
1066
-
1067
- this.updateCategories();
1068
- this.updateAnnotations();
1069
-
1070
- this.updateSubgraphs();
1071
- this.updateVertices();
1072
- this.updateEdges();
1073
- this.moveEdges(false);
1074
-
1075
- this.updateLayout();
1076
-
1077
- this.updateIconBar();
1078
- }
1079
-
1080
- exit(domNode, element) {
1081
- super.exit(domNode, element);
1082
- this._tooltip.target(null);
1083
- }
1084
-
1085
- render(callback?: (w: Widget) => void): this {
1086
- this.progress("start");
1087
- super.render(w => {
1088
- this.progress("stop");
1089
- if (callback) {
1090
- callback(w);
1091
- }
1092
- });
1093
- return this;
1094
- }
1095
-
1096
- private _prevWidth;
1097
- private _prevHeight;
1098
- private _prevTransformScale;
1099
- private _transformScale = 1;
1100
- zoomed(transform) {
1101
- super.zoomed(transform);
1102
- const { width, height } = this.size();
1103
-
1104
- if (transform.k < this.minScale()) {
1105
- this._edgeG.attr("transform", `scale(${this.minScale() / transform.k})`);
1106
- this._subgraphG.attr("transform", `scale(${this.minScale() / transform.k})`);
1107
- this._vertexG.attr("transform", `scale(${this.minScale() / transform.k})`);
1108
- this._transformScale = transform.k / this.minScale();
1109
- } else if (transform.k > this.maxScale()) {
1110
- this._edgeG.attr("transform", `scale(${this.maxScale() / transform.k})`);
1111
- this._subgraphG.attr("transform", `scale(${this.maxScale() / transform.k})`);
1112
- this._vertexG.attr("transform", `scale(${this.maxScale() / transform.k})`);
1113
- this._transformScale = transform.k / this.maxScale();
1114
- } else {
1115
- this._transformScale = 1;
1116
- this._edgeG.attr("transform", null);
1117
- this._subgraphG.attr("transform", null);
1118
- this._vertexG.attr("transform", null);
1119
- }
1120
-
1121
- if (this._prevTransformScale !== this._transformScale ||
1122
- this._prevWidth !== width ||
1123
- this._prevHeight !== height) {
1124
- this._prevTransformScale = this._transformScale;
1125
- this._prevWidth = width;
1126
- this._prevHeight = height;
1127
- this
1128
- .moveSubgraphs(false)
1129
- .moveVertices(false)
1130
- .moveEdges(false)
1131
- ;
1132
- }
1133
- }
1134
-
1135
- // Events ---
1136
- centroids(): VertexPlaceholder<V>[] {
1137
- return this._graphData.allVertices().filter(vp => !!vp.props.centroid);
1138
- }
1139
-
1140
- selectionChanged() {
1141
- if (this.highlightSelectedPathToCentroid()) {
1142
- const highlightedVertices = {};
1143
- this.centroids().forEach(centroid => {
1144
- this.selection().forEach(selection => {
1145
- const { ids, len } = this._graphData.dijkstra(centroid.id, selection.id);
1146
- if (len) {
1147
- ids.forEach(id => {
1148
- highlightedVertices[id] = true;
1149
- });
1150
- }
1151
- });
1152
- });
1153
- this._edgeG.selectAll(".graphEdge")
1154
- // .classed("shortest-path", d => highlightedEdges[d.id()] === true)
1155
- ;
1156
- }
1157
- }
1158
-
1159
- tooltipHTML(data) {
1160
- return data.props.tooltip;
1161
- }
1162
-
1163
- subgraph_click(row, _col, sel) {
1164
- }
1165
-
1166
- vertex_click(row, _col, sel, data) {
1167
- }
1168
-
1169
- vertex_dblclick(row, _col, sel, data) {
1170
- }
1171
-
1172
- vertex_contextmenu(row, _col, sel, data) {
1173
- }
1174
-
1175
- vertex_mousein(row, _col, sel, data) {
1176
- }
1177
-
1178
- vertex_mouseover(row, _col, sel, data) {
1179
- }
1180
-
1181
- vertex_mouseout(row, _col, sel, data) {
1182
- }
1183
-
1184
- edge_click(row, _col, sel) {
1185
- }
1186
-
1187
- edge_mouseover(element, d) {
1188
- this.highlightEdge(element, d);
1189
- }
1190
-
1191
- edge_mouseout(_element, _d) {
1192
- this.highlightEdge(null, null);
1193
- }
1194
-
1195
- progress(what: "start" | "stop" | "layout-start" | "layout-tick" | "layout-stop") {
1196
- }
1197
- }
1198
- GraphT.prototype._class += " graph_GraphT";
1199
-
1200
- export interface GraphT<SG extends SubgraphBaseProps = any, V extends VertexBaseProps = any, E extends EdgeBaseProps<V> = any> {
1201
- allowDragging(): boolean;
1202
- allowDragging(_: boolean): this;
1203
- dragSingleNeighbors(): boolean;
1204
- dragSingleNeighbors(_: boolean): this;
1205
- layout(): GraphLayoutType;
1206
- layout(_: GraphLayoutType): this;
1207
- applyScaleOnLayout(): boolean;
1208
- applyScaleOnLayout(_: boolean): this;
1209
- highlightOnMouseOverVertex(): boolean;
1210
- highlightOnMouseOverVertex(_: boolean): this;
1211
- highlightOnMouseOverEdge(): boolean;
1212
- highlightOnMouseOverEdge(_: boolean): this;
1213
- transitionDuration(): number;
1214
- transitionDuration(_: number): this;
1215
-
1216
- highlightSelectedPathToCentroid(): boolean;
1217
- highlightSelectedPathToCentroid(_: boolean): this;
1218
- edgeArcDepth(): number;
1219
- edgeArcDepth(_: number): this;
1220
- minScale(): number;
1221
- minScale(_: number): this;
1222
- maxScale(): number;
1223
- maxScale(_: number): this;
1224
- showEdgeLabels(): boolean;
1225
- showEdgeLabels(_: boolean): this;
1226
- showEdgeLabelsOnHighlight(): boolean;
1227
- showEdgeLabelsOnHighlight(_: boolean): this;
1228
- showVertexLabels(): boolean;
1229
- showVertexLabels(_: boolean): this;
1230
- showVertexLabelsOnHighlight(): boolean;
1231
- showVertexLabelsOnHighlight(_: boolean): this;
1232
- tabNavigation(): boolean;
1233
- tabNavigation(_: boolean): this;
1234
-
1235
- hierarchyRankDirection(): "TB" | "BT" | "LR" | "RL";
1236
- hierarchyRankDirection(_: "TB" | "BT" | "LR" | "RL"): this;
1237
- hierarchyNodeSeparation(): number;
1238
- hierarchyNodeSeparation(_: number): this;
1239
- hierarchyEdgeSeparation(): number;
1240
- hierarchyEdgeSeparation(_: number): this;
1241
- hierarchyRankSeparation(): number;
1242
- hierarchyRankSeparation(_: number): this;
1243
- hierarchyDigraph(): boolean;
1244
- hierarchyDigraph(_: boolean): this;
1245
-
1246
- forceDirectedAlpha(): number;
1247
- forceDirectedAlpha(_: number): this;
1248
- forceDirectedAlphaMin(): number;
1249
- forceDirectedAlphaMin(_: number): this;
1250
- forceDirectedAlphaDecay(): number;
1251
- forceDirectedAlphaDecay(_: number): this;
1252
- forceDirectedRepulsionStrength(): number;
1253
- forceDirectedRepulsionStrength(_: number): this;
1254
- forceDirectedVelocityDecay(): number;
1255
- forceDirectedVelocityDecay(_: number): this;
1256
- forceDirectedIterations(): number;
1257
- forceDirectedIterations(_: number): this;
1258
- forceDirectedLinkDistance(): number;
1259
- forceDirectedLinkDistance(_: number): this;
1260
- forceDirectedLinkStrength(): number;
1261
- forceDirectedLinkStrength(_: number): this;
1262
- forceDirectedPinCentroid(): boolean;
1263
- forceDirectedPinCentroid(_: boolean): this;
1264
- forceDirectedForceStrength(): number;
1265
- forceDirectedForceStrength(_: number): this;
1266
- forceDirectedMinDistance(): number;
1267
- forceDirectedMinDistance(_: number): this;
1268
- forceDirectedMaxDistance(): number;
1269
- forceDirectedMaxDistance(_: number): this;
1270
-
1271
- treeRankDirection(): "TB" | "LR";
1272
- treeRankDirection(_: "TB" | "LR"): this;
1273
-
1274
- edgeColor(): string;
1275
- edgeColor(_: string): this;
1276
- edgeStrokeWidth(): number;
1277
- edgeStrokeWidth(_: number): this;
1278
- tooltipWidth(): number;
1279
- tooltipWidth(_: number): this;
1280
- tooltipHeight(): number;
1281
- tooltipHeight(_: number): this;
1282
- enableTooltipPointerEvents(): boolean;
1283
- enableTooltipPointerEvents(_: boolean): this;
1284
- tooltipCloseDelay(): number;
1285
- tooltipCloseDelay(_: number): this;
1286
- doubleClickMaxDelay(): number;
1287
- doubleClickMaxDelay(_: number): this;
1288
-
1289
- wasmFolder(): string;
1290
- wasmFolder(_: string): this;
1291
- }
1292
-
1293
- GraphT.prototype.publish("allowDragging", true, "boolean", "Allow Dragging of Vertices");
1294
- GraphT.prototype.publish("dragSingleNeighbors", true, "boolean", "Dragging a Vertex also moves its singleton neighbors");
1295
- GraphT.prototype.publish("layout", "ForceDirectedHybrid", "set", "Default Layout", GraphLayoutTypeSet);
1296
- GraphT.prototype.publish("scale", "100%", "set", "Zoom Level", ["all", "width", "selection", "100%", "90%", "75%", "50%", "25%", "10%"]);
1297
- GraphT.prototype.publish("applyScaleOnLayout", false, "boolean", "Shrink to fit on Layout");
1298
- GraphT.prototype.publish("highlightOnMouseOverVertex", true, "boolean", "Highlight Vertex on Mouse Over");
1299
- GraphT.prototype.publish("highlightOnMouseOverEdge", true, "boolean", "Highlight Edge on Mouse Over");
1300
- GraphT.prototype.publish("transitionDuration", 250, "number", "Transition Duration");
1301
- GraphT.prototype.publish("showEdges", true, "boolean", "Show Edges");
1302
- GraphT.prototype.publish("showEdgeLabels", true, "boolean", "Show Edge labels");
1303
- GraphT.prototype.publish("showEdgeLabelsOnHighlight", true, "boolean", "Show Edge labels when highlighted");
1304
- GraphT.prototype.publish("showVertexLabels", true, "boolean", "Show Vertex labels");
1305
- GraphT.prototype.publish("showVertexLabelsOnHighlight", true, "boolean", "Show Vertex labels when highlighted");
1306
- GraphT.prototype.publish("tabNavigation", false, "boolean", "Enable or disable tab navigation");
1307
- GraphT.prototype.publish("snapToGrid", 0, "number", "Snap to Grid");
1308
- GraphT.prototype.publish("selectionClearOnBackgroundClick", false, "boolean", "Clear selection on background click");
1309
- GraphT.prototype.publish("edgeArcDepth", 8, "number", "Edge Arc Depth");
1310
- GraphT.prototype.publish("edgeColor", null, "html-color", "Edge line stroke color", null, { optional: true });
1311
- GraphT.prototype.publish("edgeStrokeWidth", 1, "number", "Edge line stroke width (pixels)");
1312
- GraphT.prototype.publish("minScale", 0.6, "number", "Min scale size for text");
1313
- GraphT.prototype.publish("maxScale", 1.0, "number", "Max scale size for text");
1314
- GraphT.prototype.publish("tooltipWidth", 256, "number", "Tooltip width (pixels)");
1315
- GraphT.prototype.publish("tooltipHeight", 128, "number", "Tooltip width (pixels)");
1316
- GraphT.prototype.publish("enableTooltipPointerEvents", false, "boolean", "If true, tooltip will use the style: 'pointer-events: all'");
1317
- GraphT.prototype.publish("tooltipCloseDelay", 0, "number", "Number of milliseconds to wait before closing tooltip (cancelled on tooltip mouseover event)");
1318
- GraphT.prototype.publish("doubleClickMaxDelay", 300, "number", "Number of milliseconds to wait before a subsequent click is not considered a double click");
1319
- GraphT.prototype.publish("highlightSelectedPathToCentroid", true, "boolean", "Highlight path to Center Vertex (for selected vertices)");
1320
-
1321
- GraphT.prototype.publish("hierarchyRankDirection", "TB", "set", "Direction for Rank Nodes", ["TB", "BT", "LR", "RL"], { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1322
- GraphT.prototype.publish("hierarchyNodeSeparation", 50, "number", "Number of pixels that separate nodes horizontally in the layout", null, { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1323
- GraphT.prototype.publish("hierarchyEdgeSeparation", 10, "number", "Number of pixels that separate edges horizontally in the layout", null, { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1324
- GraphT.prototype.publish("hierarchyRankSeparation", 50, "number", "Number of pixels between each rank in the layout", null, { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1325
- GraphT.prototype.publish("hierarchyDigraph", true, "boolean", "Directional GraphBase", null, { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1326
-
1327
- GraphT.prototype.publish("forceDirectedAlpha", 1, "number", "Alpha", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1328
- GraphT.prototype.publish("forceDirectedAlphaMin", 0.001, "number", "Min Alpha", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1329
- GraphT.prototype.publish("forceDirectedAlphaDecay", 0.0228, "number", "Defaults to 1 - pow(alphaMin, 1 / 300)", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1330
- GraphT.prototype.publish("forceDirectedRepulsionStrength", -350, "number", "Charge strength ", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1331
- GraphT.prototype.publish("forceDirectedVelocityDecay", 0.4, "number", "Velocity Decay ", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1332
- GraphT.prototype.publish("forceDirectedIterations", 300, "number", "Iterations", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1333
- GraphT.prototype.publish("forceDirectedLinkDistance", 300, "number", "Target distance between linked nodes", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1334
- GraphT.prototype.publish("forceDirectedLinkStrength", 1, "number", "Strength (rigidity) of links", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1335
- GraphT.prototype.publish("forceDirectedPinCentroid", false, "boolean", "Pin centroid to center", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1336
- GraphT.prototype.publish("forceDirectedForceStrength", 0, "number", "Strength of center force", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1337
- GraphT.prototype.publish("forceDirectedMinDistance", 1, "number", "Min distance between nodes", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1338
- GraphT.prototype.publish("forceDirectedMaxDistance", Infinity, "number", "Max distance between nodes", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1339
-
1340
- GraphT.prototype.publish("treeRankDirection", "LR", "set", "Direction for Rank Nodes", ["TB", "LR"], { disable: (w: GraphT) => w.layout() !== "Tree" && w.layout() !== "Dendrogram" });
1341
-
1342
- GraphT.prototype.publish("wasmFolder", null, "string", "WASM Folder", null, { optional: true, disable: (w: GraphT) => ["DOT", "Neato", "FDP", "TwoPI", "Circo"].indexOf(w.layout()) < 0 });
1343
-
1344
- const _origScale = GraphT.prototype.scale;
1345
- // @ts-ignore ---
1346
- GraphT.prototype.scale = function (_?, transitionDuration?) {
1347
- const retVal = _origScale.apply(this, arguments as any);
1348
- if (arguments.length) {
1349
- this.zoomTo(_, transitionDuration);
1350
- }
1351
- return retVal;
1352
- };
1353
-
1354
- export function graphviz(dot: string, engine: Engine = "dot", _scriptDir: string = scriptDir) {
1355
-
1356
- return gvWorker({
1357
- items: [],
1358
- links: [],
1359
- raw: dot
1360
- }, {
1361
- engine: engine
1362
- });
1363
- }
1
+ import { d3Event, drag as d3Drag, Palette, select as d3Select, Selection, Spacer, SVGGlowFilter, SVGZoomWidget, Button, ToggleButton, Utility, Widget } from "@hpcc-js/common";
2
+ import { Graph2 as GraphCollection, hashSum } from "@hpcc-js/util";
3
+ import { HTMLTooltip } from "@hpcc-js/html";
4
+ import { interpolateNumberArray as d3InterpolateNumberArray } from "d3-interpolate";
5
+ import "d3-transition";
6
+ import { interpolatePath as d3InterpolatePath } from "d3-interpolate-path";
7
+ import { Circle, Dagre, ForceDirected, ForceDirectedAnimated, Graphviz, ILayout, Null } from "./layouts/index.ts";
8
+ import { Options as FDOptions } from "./layouts/forceDirectedWorker.ts";
9
+ import type { BaseProps, VertexBaseProps, EdgeBaseProps, GraphDataProps, HierarchyBase, SubgraphBaseProps } from "./layouts/placeholders.ts";
10
+ import { EdgePlaceholder, SubgraphPlaceholder, VertexPlaceholder, isEdgePlaceholder } from "./layouts/placeholders.ts";
11
+ import { Engine, graphviz as gvWorker } from "./layouts/graphvizWorker.ts";
12
+ import { Tree, RadialTree, Dendrogram, RadialDendrogram } from "./layouts/tree.ts";
13
+
14
+ import "./graphT.css";
15
+
16
+ let scriptDir = (globalThis?.document?.currentScript as HTMLScriptElement)?.src ?? "./dummy.js";
17
+ scriptDir = scriptDir.substring(0, scriptDir.replace(/[?#].*/, "").lastIndexOf("/") + 1);
18
+
19
+ export {
20
+ BaseProps,
21
+ GraphDataProps,
22
+ SubgraphBaseProps,
23
+ VertexBaseProps,
24
+ EdgeBaseProps,
25
+ HierarchyBase
26
+ };
27
+
28
+ type GraphLayoutType = "Hierarchy" | "DOT" | "Tree" | "Dendrogram" | "RadialTree" | "RadialDendrogram" | "ForceDirected" | "ForceDirected2" | "ForceDirectedHybrid" | "Neato" | "FDP" | "Circle" | "TwoPI" | "Circo" | "None";
29
+ const GraphLayoutTypeSet = ["Hierarchy", "DOT", "Tree", "Dendrogram", "RadialTree", "RadialDendrogram", "ForceDirected", "ForceDirected2", "ForceDirectedHybrid", "Neato", "FDP", "Circle", "TwoPI", "Circo", "None"];
30
+
31
+ function dragStart<V extends VertexBaseProps>(n: VertexPlaceholder<V>) {
32
+ n.fx = n.sx = n.x;
33
+ n.fy = n.sy = n.y;
34
+ }
35
+ function dragTick<V extends VertexBaseProps>(n: VertexPlaceholder<V>, d: VertexPlaceholder<V>) {
36
+ n.fx = n.sx + d.fx - d.sx;
37
+ n.fy = n.sy + d.fy - d.sy;
38
+ }
39
+ function dragEnd<V extends VertexBaseProps>(n: VertexPlaceholder<V>) {
40
+ n.x = n.fx;
41
+ n.y = n.fy;
42
+ n.fx = n.sx = undefined;
43
+ n.fy = n.sy = undefined;
44
+ }
45
+
46
+ export type RendererT<T> = (props: T, element: SVGGElement) => unknown;
47
+
48
+ export class GraphT<SG extends SubgraphBaseProps, V extends VertexBaseProps, E extends EdgeBaseProps<V>> extends SVGZoomWidget {
49
+
50
+ protected _centroidFilter: SVGGlowFilter;
51
+
52
+ private _toggleHierarchy = new ToggleButton().faChar("fa-sitemap").tooltip("Hierarchy").on("click", () => this.layoutClick("Hierarchy"));
53
+ private _toggleForceDirected = new ToggleButton().faChar("fa-expand").tooltip("Force Directed").on("click", () => this.layoutClick("ForceDirected"));
54
+ private _toggleForceDirected2 = new ToggleButton().faChar("fa-arrows").tooltip("Spring").on("click", () => this.layoutClick("ForceDirected2"));
55
+ private _toggleCircle = new ToggleButton().faChar("fa-circle-o").tooltip("Circle").on("click", () => this.layoutClick("Circle"));
56
+ private _toggleDot = new ToggleButton().faChar("fa-angle-double-down").tooltip("DOT").on("click", () => this.layoutClick("DOT"));
57
+ private _toggleNeato = new ToggleButton().faChar("fa-sun-o").tooltip("Neato").on("click", () => this.layoutClick("Neato"));
58
+ private _toggleFDP = new ToggleButton().faChar("fa-asterisk").tooltip("FDP").on("click", () => this.layoutClick("FDP"));
59
+ private _toggleTwoPI = new ToggleButton().faChar("fa-bullseye").tooltip("TwoPI").on("click", () => this.layoutClick("TwoPI"));
60
+ private _toggleCirco = new ToggleButton().faChar("fa-cogs").tooltip("Circo").on("click", () => this.layoutClick("Circo"));
61
+ private _toggleT = new ToggleButton().faChar("fa-sitemap fa-rotate-270").tooltip("Tree").on("click", () => this.layoutClick("Tree"));
62
+ private _toggleRT = new ToggleButton().faChar("fa-sun-o").tooltip("Radial Tree").on("click", () => this.layoutClick("RadialTree"));
63
+ private _toggleD = new ToggleButton().faChar("fa-sitemap fa-rotate-270").tooltip("Dendrogram").on("click", () => this.layoutClick("Dendrogram"));
64
+ private _toggleRD = new ToggleButton().faChar("fa-asterisk").tooltip("Radial Dendrogram").on("click", () => this.layoutClick("RadialDendrogram"));
65
+
66
+ protected _graphData = new GraphCollection<VertexPlaceholder<V>, EdgePlaceholder<V, E>, SubgraphPlaceholder<SG>>()
67
+ .idFunc(d => d.id)
68
+ .sourceFunc(e => e.source.id)
69
+ .targetFunc(e => e.target.id)
70
+ .updateFunc((b: VertexPlaceholder<V> | EdgePlaceholder<V, E> | SubgraphPlaceholder<SG>, a: VertexPlaceholder<V> | EdgePlaceholder<V, E> | SubgraphPlaceholder<SG>) => {
71
+ b.props = a.props;
72
+ if (isEdgePlaceholder(a) && isEdgePlaceholder(b)) {
73
+ b.source = a.source;
74
+ b.target = a.target;
75
+ }
76
+ return b;
77
+ })
78
+ ;
79
+
80
+ protected _prevDoClickTime: number = 0;
81
+
82
+ protected _svgDefsAnn: any;
83
+ protected _svgDefsCat: any;
84
+ protected _subgraphG: Selection<SVGGElement, any, SVGGElement, any>;
85
+ protected _edgeG: Selection<SVGGElement, any, SVGGElement, any>;
86
+ protected _vertexG: Selection<SVGGElement, any, SVGGElement, any>;
87
+
88
+ protected _tooltip: HTMLTooltip = new HTMLTooltip();
89
+
90
+ protected _selection = new Utility.Selection(this);
91
+ private _dragHandler = d3Drag<SVGGElement, VertexPlaceholder<V>, SVGGElement>();
92
+
93
+ protected _catPalette = Palette.ordinal("hpcc10");
94
+ _svgDefs: any;
95
+
96
+ constructor(subgraphRenderer: RendererT<SG>, vertexRenderer: RendererT<V>, edgeRenderer: RendererT<E>) {
97
+ super();
98
+ this._subgraphRenderer = subgraphRenderer;
99
+ this._vertexRenderer = vertexRenderer;
100
+ this._edgeRenderer = edgeRenderer;
101
+ const context = this;
102
+ this._drawStartPos = "origin";
103
+
104
+ const buttons: Widget[] = [
105
+ this._toggleHierarchy,
106
+ this._toggleForceDirected,
107
+ this._toggleForceDirected2,
108
+ this._toggleCircle,
109
+ new Spacer(),
110
+ this._toggleDot,
111
+ this._toggleNeato,
112
+ this._toggleFDP,
113
+ this._toggleTwoPI,
114
+ this._toggleCirco,
115
+ new Spacer(),
116
+ this._toggleT,
117
+ this._toggleRT,
118
+ this._toggleD,
119
+ this._toggleRD,
120
+ new Spacer()
121
+ ];
122
+ this._iconBar.buttons(buttons.concat(this._iconBar.buttons()));
123
+
124
+ this.selectionGlowColor("navy");
125
+
126
+ this._dragHandler
127
+ .on("start", function (d) {
128
+ if (context.allowDragging()) {
129
+ d3Select(this).classed("grabbed", true);
130
+ dragStart(d);
131
+ Utility.safeRaise(this);
132
+ context.moveVertexPlaceholder(d, false, true);
133
+
134
+ const selection = context.selection();
135
+ const isSelected = context.selected(d.props, selection as V[]);
136
+ if (isSelected) {
137
+ selection
138
+ .filter(v => v.id !== d.props.id)
139
+ .forEach(v => {
140
+ const n = context._graphData.vertex(v.id);
141
+ dragStart(n);
142
+ });
143
+ } else if (context.dragSingleNeighbors()) {
144
+ context._graphData.singleNeighbors(d.id).forEach(n => {
145
+ dragStart(n);
146
+ });
147
+ }
148
+ }
149
+ })
150
+ .on("drag", function (d) {
151
+ if (context.allowDragging()) {
152
+ d.fx = d.sx + context.rproject(d3Event().x - d.sx);
153
+ d.fy = d.sy + context.rproject(d3Event().y - d.sy);
154
+ context._graphData.vertexEdges(d.id).forEach(e => delete e.points);
155
+ context.moveVertexPlaceholder(d, false, true);
156
+ const selection = context.selection();
157
+ const isSelected = context.selected(d.props, selection as V[]);
158
+
159
+ if (isSelected) {
160
+ selection
161
+ .filter(v => v.id !== d.props.id)
162
+ .forEach(v => {
163
+ const n = context._graphData.vertex(v.id);
164
+ dragTick(n, d);
165
+ context.moveVertexPlaceholder(n, false, true);
166
+ });
167
+ } else if (context.dragSingleNeighbors()) {
168
+ context._graphData.singleNeighbors(d.id).forEach(n => {
169
+ dragTick(n, d);
170
+ context.moveVertexPlaceholder(n, false, true);
171
+ });
172
+ }
173
+ }
174
+ })
175
+ .on("end", function (d) {
176
+ let doClick = true;
177
+ if (context.allowDragging()) {
178
+ doClick = Math.abs(d.sx - d.fx) < 1 && Math.abs(d.sy - d.fy) < 1;
179
+ dragEnd(d);
180
+
181
+ const selection = context.selection();
182
+ const isSelected = context.selected(d.props, selection as V[]);
183
+ if (isSelected) {
184
+ selection
185
+ .filter(v => v.id !== d.props.id)
186
+ .forEach(v => {
187
+ const n = context._graphData.vertex(v.id);
188
+ dragEnd(n);
189
+ });
190
+ } else if (context.dragSingleNeighbors()) {
191
+ context._graphData.singleNeighbors(d.id).forEach(dragEnd);
192
+ }
193
+
194
+ d3Select(this).classed("grabbed", false);
195
+ }
196
+ if (doClick) {
197
+ const event = d3Event();
198
+ context._selection.click({
199
+ _id: String(d.id),
200
+ element: () => d.element as any
201
+ }, event.sourceEvent);
202
+ context.selectionChanged();
203
+ const selected = d.element.classed("selected");
204
+ const eventOrigin = context.resolveEventOrigin();
205
+ if (event?.sourceEvent?.button === 2) {
206
+ context.vertex_contextmenu(d.props.origData || d.props, "", selected, eventOrigin);
207
+ } else {
208
+ context.vertex_click(d.props.origData || d.props, "", selected, eventOrigin);
209
+ }
210
+ const doClickTime = Date.now();
211
+ if (doClickTime - context._prevDoClickTime < context.doubleClickMaxDelay()) {
212
+ context.vertex_dblclick(d.props.origData || d.props, "", selected, eventOrigin);
213
+ }
214
+ context._prevDoClickTime = doClickTime;
215
+ }
216
+ })
217
+ .filter(() => true)
218
+ ;
219
+ }
220
+
221
+ resolveEventOrigin(): { origin: string, data?: SG | V | E } {
222
+ const d3evt = d3Event();
223
+ const eventPath = d3evt?.sourceEvent?.path ?? d3evt?.sourceEvent?.composedPath() ?? d3evt?.path ?? d3evt?.composedPath();
224
+ const element = eventPath?.find(n => n?.hasAttribute && n?.hasAttribute("data-click"));
225
+ const origin = element ? element.getAttribute("data-click") : "";
226
+ const dataStr = element ? element.getAttribute("data-click-data") : "";
227
+ let data = undefined;
228
+ if (dataStr) {
229
+ try {
230
+ data = JSON.parse(dataStr);
231
+ } catch (e) {
232
+ console.warn("Unexpected annotation data:", dataStr);
233
+ }
234
+ }
235
+
236
+ return {
237
+ origin,
238
+ data
239
+ };
240
+ }
241
+
242
+ iconBarButtons(): Widget[] {
243
+ return this._iconBar.buttons();
244
+ }
245
+
246
+ private _origData: GraphDataProps<SG, V, E> = {
247
+ subgraphs: [],
248
+ vertices: [],
249
+ edges: [],
250
+ hierarchy: []
251
+ };
252
+ data(): GraphDataProps<SG, V, E>;
253
+ data(_: GraphDataProps<SG, V, E>, merge?: boolean): this;
254
+ data(_?: GraphDataProps<SG, V, E>, merge?: boolean): GraphDataProps<SG, V, E> | this {
255
+ if (_ === void 0) return this._origData;
256
+ this._origData = _;
257
+
258
+ this._graphData.mergeSubgraphs((_.subgraphs || []).map(sg => ({
259
+ id: sg.id,
260
+ props: sg
261
+ })));
262
+
263
+ this._graphData.mergeVertices((_.vertices || []).map(v => ({
264
+ id: v.id,
265
+ centroid: v.centroid,
266
+ props: v
267
+ })));
268
+
269
+ this._graphData.mergeEdges(
270
+ (_.edges || [])
271
+ .filter(e => {
272
+ return this._graphData.vertexExists(e.source.id) && this._graphData.vertexExists(e.target.id);
273
+ })
274
+ .map(e => ({
275
+ id: e.id,
276
+ props: e,
277
+ source: this._graphData.vertex(e.source.id),
278
+ target: this._graphData.vertex(e.target.id)
279
+ }))
280
+ );
281
+
282
+ this._graphData.clearParents();
283
+ (_.hierarchy ? _.hierarchy : []).forEach(h => {
284
+ if (this._graphData.subgraphExists(h.child.id)) {
285
+ this._graphData.subgraphParent(h.child.id, h.parent.id);
286
+ } else if (this._graphData.vertexExists(h.child.id)) {
287
+ this._graphData.vertexParent(h.child.id, h.parent.id);
288
+ }
289
+ });
290
+
291
+ return this;
292
+ }
293
+
294
+ selected(vertex: V, _?: Array<V>): boolean {
295
+ return (_ || this.selection()).some(n => n.id === vertex.id);
296
+ }
297
+
298
+ selection(_: Array<V | SG | E>): this;
299
+ selection(): Array<V | SG | E>;
300
+ selection(_?: Array<V | SG | E>): Array<SG | V | E> | this {
301
+ if (!arguments.length) return this._selection.get().map(item => this._graphData.item(item._id).props);
302
+ this._selection.set(_.map(item => {
303
+ const vp = this._graphData.item(item.id);
304
+ return {
305
+ _id: String(vp.id),
306
+ element: () => vp.element as any
307
+ };
308
+ }));
309
+ return this;
310
+ }
311
+
312
+ graphData(): GraphCollection<VertexPlaceholder<V>, EdgePlaceholder<V, E>> {
313
+ return this._graphData;
314
+ }
315
+
316
+ resetLayout() {
317
+ delete this._prevLayout;
318
+ return this;
319
+ }
320
+
321
+ layoutRunning() {
322
+ return this._layoutAlgo && this._layoutAlgo.running();
323
+ }
324
+
325
+ protected _layoutAlgo: ILayout = new Null(this);
326
+ layoutAlgo(layout: ILayout) {
327
+ if (this._layoutAlgo) {
328
+ this._layoutAlgo.stop();
329
+ }
330
+ this._layoutAlgo = layout;
331
+ return this._layoutAlgo.start().then(() => {
332
+ this.updateIconBar();
333
+ if (this.applyScaleOnLayout()) {
334
+ // Wait for any transitions to finish ---
335
+ setTimeout(() => {
336
+ this.zoomToFit(this.transitionDuration());
337
+ }, this.transitionDuration());
338
+ }
339
+ });
340
+ }
341
+
342
+ layoutClick(layout: GraphLayoutType) {
343
+ if (this.layoutRunning()) {
344
+ this._layoutAlgo.stop();
345
+ this.updateIconBar();
346
+ } else {
347
+ delete this._prevLayout;
348
+ this
349
+ .layout(layout)
350
+ .render()
351
+ ;
352
+ }
353
+ }
354
+
355
+ updateIconBarItem(tb: ToggleButton, tbLayout: GraphLayoutType) {
356
+ const layout = this.layout();
357
+ const running = this._layoutAlgo && this._layoutAlgo.running();
358
+ tb.enabled(!running || layout === tbLayout).selected(running && layout === tbLayout).render();
359
+ }
360
+
361
+ updateIconBar() {
362
+ super.updateIconBar();
363
+ this.updateIconBarItem(this._toggleHierarchy, "Hierarchy");
364
+ this.updateIconBarItem(this._toggleDot, "DOT");
365
+ this.updateIconBarItem(this._toggleForceDirected, "ForceDirected");
366
+ this.updateIconBarItem(this._toggleNeato, "Neato");
367
+ this.updateIconBarItem(this._toggleFDP, "FDP");
368
+ this.updateIconBarItem(this._toggleForceDirected2, "ForceDirected2");
369
+ this.updateIconBarItem(this._toggleCircle, "Circle");
370
+ this.updateIconBarItem(this._toggleTwoPI, "TwoPI");
371
+ this.updateIconBarItem(this._toggleCirco, "Circo");
372
+ this.updateIconBarItem(this._toggleT, "Tree");
373
+ this.updateIconBarItem(this._toggleRT, "RadialTree");
374
+ this.updateIconBarItem(this._toggleD, "Dendrogram");
375
+ this.updateIconBarItem(this._toggleRD, "RadialDendrogram");
376
+ }
377
+
378
+ getNeighborMap(vertex: VertexPlaceholder<V>) {
379
+ const vertices = {};
380
+ const edges = {};
381
+
382
+ if (vertex) {
383
+ const nedges = this._graphData.vertexEdges(vertex.id);
384
+ for (let i = 0; i < nedges.length; ++i) {
385
+ const edge = this._graphData.edge(nedges[i].id);
386
+ edges[edge.id] = edge;
387
+ if (edge.source.id !== vertex.id) {
388
+ vertices[edge.source.id] = edge.source;
389
+ }
390
+ if (edge.target.id !== vertex.id) {
391
+ vertices[edge.target.id] = edge.target;
392
+ }
393
+ }
394
+ }
395
+
396
+ return {
397
+ vertices,
398
+ edges
399
+ };
400
+ }
401
+
402
+ centerOnItem(id: string) {
403
+ const item = this._graphData.item(id);
404
+ let x;
405
+ let y;
406
+ if (this._graphData.isSubgraph(item) || this._graphData.isVertex(item)) {
407
+ x = item.x;
408
+ y = item.y;
409
+ }
410
+ if (this._graphData.isEdge(item)) {
411
+ [x, y] = [0, 0]; // center(item.points);
412
+ }
413
+ if (x !== undefined && y !== undefined) {
414
+ const bbox = item.element.node().getBBox();
415
+ if (this._graphData.isVertex(item)) {
416
+ const proj = this.projectPlacholder(item);
417
+ x = proj.x;
418
+ y = proj.y;
419
+ } else if (this._graphData.isSubgraph(item)) {
420
+ x = this.project(item.x);
421
+ y = this.project(item.y);
422
+ }
423
+ const deltaX = bbox.x + bbox.width / 2;
424
+ const deltaY = bbox.y + bbox.height / 2;
425
+ const itemBBox = {
426
+ x: x + deltaX - bbox.width / 2,
427
+ y: y + deltaY - bbox.height / 2,
428
+ width: bbox.width,
429
+ height: bbox.height
430
+ };
431
+ this.centerOnBBox(itemBBox);
432
+ }
433
+ }
434
+
435
+ hideVertex(id: string): this {
436
+ const item = this._graphData.item(id);
437
+ if (this._graphData.isVertex(item)) {
438
+ item.props.hidden = true;
439
+ }
440
+ return this;
441
+ }
442
+
443
+ showVertex(id: string): this {
444
+ const item = this._graphData.item(id);
445
+ if (this._graphData.isVertex(item)) {
446
+ item.props.hidden = false;
447
+ }
448
+ return this;
449
+ }
450
+
451
+ protected highlight = {
452
+ zoom: 1.1,
453
+ opacity: 0.33,
454
+ edge: "1.25px"
455
+ };
456
+
457
+ highlightVerticies(vertexMap?: { [id: string]: boolean }) {
458
+ const context = this;
459
+ const vertexElements = this._vertexG.selectAll<SVGGElement, VertexPlaceholder<V>>(".graphVertex");
460
+ const forceLabelOnHighlight = !context.showVertexLabels() && context.showVertexLabelsOnHighlight();
461
+ vertexElements
462
+ .classed("graphVertex-highlighted", d => !vertexMap || vertexMap[d.id])
463
+ .style("filter", d => vertexMap && vertexMap[d.id] ? "url(#" + this.id() + "_glow)" : null)
464
+ .each(function (d) {
465
+ if (forceLabelOnHighlight) {
466
+ const props = context.calcProps(
467
+ {
468
+ showLabel: !!(vertexMap && vertexMap[d.id]),
469
+ ...context.vertexMapper(d.props, d.props.origData)
470
+ }
471
+ );
472
+ d.renderResult = context._vertexRenderer(props, this);
473
+ }
474
+ })
475
+ .transition().duration(this.transitionDuration())
476
+ .on("end", function (d) {
477
+ if (vertexMap && vertexMap[d.id]) {
478
+ if (d.element.node() && d.element.node().parentNode) {
479
+ d.element.node().parentNode.appendChild(d.element.node());
480
+ }
481
+ }
482
+ })
483
+ .style("opacity", function (d) {
484
+ if (d.props.hidden) return 0;
485
+ if (!vertexMap || vertexMap[d.id]) {
486
+ return 1;
487
+ }
488
+ return context.highlight.opacity;
489
+ })
490
+ ;
491
+ return this;
492
+ }
493
+
494
+ highlightEdges(edgeMap?: { [id: string]: boolean }) {
495
+ const context = this;
496
+ const edgeElements = this._edgeG.selectAll<SVGGElement, EdgePlaceholder<V, E>>(".graphEdge");
497
+ edgeElements
498
+ .classed("graphEdge-highlighted", d => !edgeMap || edgeMap[d.id])
499
+ .style("stroke-width", function (o) {
500
+ if (edgeMap && edgeMap[o.id]) {
501
+ return context.highlight.edge;
502
+ }
503
+ return context.edgeStrokeWidth() + "px";
504
+ }).transition().duration(this.transitionDuration())
505
+ .style("opacity", function (o) {
506
+ if (o.source.props.hidden || o.target.props.hidden) return 0;
507
+ if (!edgeMap || edgeMap[o.id]) {
508
+ return 1;
509
+ }
510
+ return context.highlight.opacity;
511
+ })
512
+ ;
513
+ return this;
514
+ }
515
+
516
+ highlightVertex(_element, d?: VertexPlaceholder<V>) {
517
+ if (this.highlightOnMouseOverVertex()) {
518
+ if (d) {
519
+ const highlight = this.getNeighborMap(d);
520
+ highlight.vertices[d.id] = d;
521
+ this.highlightVerticies(highlight.vertices);
522
+ this.highlightEdges(highlight.edges);
523
+ } else {
524
+ this.highlightVerticies();
525
+ this.highlightEdges();
526
+ }
527
+ }
528
+ }
529
+
530
+ highlightEdge(_element, d?: EdgePlaceholder<V, E>) {
531
+ if (this.highlightOnMouseOverEdge()) {
532
+ if (d) {
533
+ const vertices = {};
534
+ vertices[d.source.id] = d.source;
535
+ vertices[d.target.id] = d.target;
536
+ const edges = {};
537
+ edges[d.id] = d;
538
+ this.highlightVerticies(vertices);
539
+ this.highlightEdges(edges);
540
+ } else {
541
+ this.highlightVerticies();
542
+ this.highlightEdges();
543
+ }
544
+ }
545
+ }
546
+
547
+ moveSubgraphPlaceholder(sp: SubgraphPlaceholder<SG>, transition: boolean): this {
548
+ const context = this;
549
+ const x = this.project(sp.x);
550
+ const y = this.project(sp.y);
551
+ const width = this.project(sp.props.width, true);
552
+ const height = this.project(sp.props.height, true);
553
+ if (sp.element) {
554
+ sp.element.transition().duration(transition ? this.transitionDuration() : 0)
555
+ .attr("transform", `translate(${x} ${y})`)
556
+ .each(function (d) {
557
+ context._subgraphRenderer({ ...d.props, width, height }, this);
558
+ })
559
+ ;
560
+ }
561
+ return this;
562
+ }
563
+
564
+ moveEdgePlaceholder(ep: EdgePlaceholder<V, E>, transition: boolean): this {
565
+ const edgeLayout = {
566
+ ...this._layoutAlgo.edgePath(ep as any, this.edgeArcDepth()),
567
+ };
568
+ const context = this;
569
+ if (this._edgeRenderer && ep.element) {
570
+ const previousEdgeLayout = (ep as any).previousEdgeLayout ?? edgeLayout;
571
+ (ep as any).previousEdgeLayout = edgeLayout;
572
+ const pathInterpolator = d3InterpolatePath(previousEdgeLayout.path, edgeLayout.path);
573
+ const labelPosInterpolator = d3InterpolateNumberArray(previousEdgeLayout.labelPos, edgeLayout.labelPos);
574
+ ep.element.transition().duration(transition ? this.transitionDuration() : 0)
575
+ .tween("some.path", function () {
576
+ return function (t) {
577
+ const updated = {
578
+ path: pathInterpolator(t),
579
+ labelPos: labelPosInterpolator(t),
580
+ strokeWidth: ep.props.strokeWidth ?? context.edgeStrokeWidth(),
581
+ color: ep.props.stroke ?? context.edgeColor()
582
+ };
583
+ ep.renderResult = context._edgeRenderer({ graphInstance: context, ...edgeLayout, ...ep.props, ...updated }, ep.element.node());
584
+ };
585
+ });
586
+ }
587
+ return this;
588
+ }
589
+
590
+ moveVertexPlaceholder(vp: VertexPlaceholder<V>, transition: boolean, moveNeighbours: boolean): this {
591
+ const { x, y } = this.projectPlacholder(vp);
592
+ if (vp.element) {
593
+ (transition ? vp.element.transition().duration(this.transitionDuration()) as unknown as Selection<SVGPathElement, EdgePlaceholder<V, E>, SVGGElement, any> : vp.element)
594
+ .attr("transform", `translate(${x} ${y})`)
595
+ ;
596
+ }
597
+ if (moveNeighbours) {
598
+ this._graphData.vertexEdges(vp.id).forEach(e => this.moveEdgePlaceholder(e, transition));
599
+ }
600
+ return this;
601
+ }
602
+
603
+ moveSubgraphs(transition: boolean): this {
604
+ this._graphData.allSubgraphs().forEach(s => this.moveSubgraphPlaceholder(s, transition));
605
+ return this;
606
+ }
607
+
608
+ moveEdges(transition: boolean): this {
609
+ this._graphData.allEdges().forEach(e => this.moveEdgePlaceholder(e, transition));
610
+ return this;
611
+ }
612
+
613
+ moveVertices(transition: boolean): this {
614
+ this._graphData.allVertices().forEach(v => this.moveVertexPlaceholder(v, transition, false));
615
+ return this;
616
+ }
617
+
618
+ project(pos: number, clip: boolean = false) {
619
+ const rf = 10;
620
+ pos = pos !== undefined ? pos : 0;
621
+ let scale = this._transformScale;
622
+ if (clip) {
623
+ if (this._transformScale > this.maxScale() + (this._transformScale - this.maxScale()) / 2) {
624
+ scale = this.maxScale() + (this._transformScale - this.maxScale()) / 2;
625
+ } else if (this._transformScale < this.minScale() - (this._transformScale - this.minScale()) / 13) {
626
+ scale = this.minScale() - (this._transformScale - this.minScale()) / 13;
627
+ }
628
+ }
629
+ return Math.round(pos * scale * rf) / rf;
630
+ }
631
+
632
+ rproject(pos: number) {
633
+ const rf = 10;
634
+ pos = pos !== undefined ? pos : 0;
635
+ return Math.round(pos / this._transformScale * rf) / rf;
636
+ }
637
+
638
+ projectPlacholder(vp: VertexPlaceholder<V>) {
639
+ return {
640
+ x: this.project(vp.fx !== undefined ? vp.fx : vp.x),
641
+ y: this.project(vp.fy !== undefined ? vp.fy : vp.y),
642
+ };
643
+ }
644
+
645
+ rprojectPlacholder(vp: VertexPlaceholder<V>) {
646
+ return {
647
+ x: this.rproject(vp.fx !== undefined ? vp.fx : vp.x),
648
+ y: this.rproject(vp.fy !== undefined ? vp.fy : vp.y),
649
+ };
650
+ }
651
+
652
+ categoryID(id: string | number, prefix: "cat" | "ann" = "cat"): string {
653
+ return id === undefined || id === "" ? "" : `${prefix}${this.id()}_${id}`;
654
+ }
655
+
656
+ updateCategories() {
657
+ }
658
+
659
+ updateAnnotations() {
660
+ }
661
+
662
+ private _edgeRenderer: RendererT<E>;
663
+ edgeRenderer(): RendererT<E>;
664
+ edgeRenderer(_: RendererT<E>): this;
665
+ edgeRenderer(_?: RendererT<E>): this | RendererT<E> {
666
+ if (!arguments.length) return this._edgeRenderer;
667
+ this._edgeRenderer = _;
668
+ return this;
669
+ }
670
+
671
+ updateEdges(): this {
672
+ const context = this;
673
+ this._edgeG.selectAll(".graphEdge")
674
+ .data(this._graphData.allEdges(), d => (d as EdgePlaceholder<V, E>).id)
675
+ .join(
676
+ enter => enter.append("g")
677
+ .attr("class", "graphEdge")
678
+ .on("click.selectionBag", function (d) {
679
+ context._selection.click({
680
+ _id: String(d.id),
681
+ element: () => d.element as any
682
+ }, d3Event());
683
+ context.selectionChanged();
684
+ })
685
+ .on("click", function (this: SVGElement, d) {
686
+ const selected = d.element.classed("selected");
687
+ context.edge_click(d.props.origData || d.props, "", selected);
688
+ })
689
+ .on("mouseover", function (d) {
690
+ Utility.safeRaise(this);
691
+ context.edge_mouseover(d3Select(this), d);
692
+ })
693
+ .on("mouseout", function (d) {
694
+ context.edge_mouseout(d3Select(this), d);
695
+ })
696
+ .each(function (d) {
697
+ d.element = d3Select(this);
698
+ if (!context._edgeRenderer) {
699
+ d.elementPath = d.element.append("path");
700
+ d.elementText = d.element.append("text")
701
+ .attr("text-anchor", "middle")
702
+ ;
703
+ }
704
+ })
705
+ ,
706
+ update => update
707
+ .attr("opacity", d => d.source.props.hidden || d.target.props.hidden ? 0 : 1)
708
+ .classed("hide-text", !context.showEdgeLabels())
709
+ ,
710
+ exit => exit
711
+ .each(function (d) {
712
+ delete d.element;
713
+ })
714
+ .remove()
715
+ )
716
+ ;
717
+ return this;
718
+ }
719
+
720
+ private _vertexRenderer: RendererT<V>;
721
+ vertexRenderer(): RendererT<V>;
722
+ vertexRenderer(_: RendererT<V>): this;
723
+ vertexRenderer(_?: RendererT<V>): this | RendererT<V> {
724
+ if (!arguments.length) return this._vertexRenderer;
725
+ this._vertexRenderer = _;
726
+ return this;
727
+ }
728
+
729
+ vertexMapper(props: V, origRow: V): V {
730
+ return {
731
+ ...props,
732
+ categoryID: this.categoryID(props.categoryID),
733
+ annotationIDs: props.annotationIDs ? props.annotationIDs.map(a => this.categoryID(a, "ann")) : []
734
+ };
735
+ }
736
+
737
+ updateVertices(): this {
738
+ const context = this;
739
+ this._vertexG.selectAll(".graphVertex")
740
+ .data(this._graphData.allVertices(), d => (d as VertexPlaceholder<V>).id)
741
+ .join(
742
+ enter => enter.append("g")
743
+ .attr("class", "graphVertex")
744
+ .on("dblclick", function (this: SVGElement, d) {
745
+ d3Event().stopPropagation();
746
+ })
747
+ .on("contextmenu", function (this: SVGElement, d) {
748
+ d3Event().preventDefault();
749
+ })
750
+ .on("keydown", function (this: SVGElement, d) {
751
+ if (!context.tabNavigation()) return;
752
+ const event = d3Event() as KeyboardEvent;
753
+ const isSpace = event.code === "Space" || event.key === " " || event.key === "Spacebar";
754
+ const isEnter = event.key === "Enter";
755
+ if (!(isSpace || isEnter)) return;
756
+
757
+ event.preventDefault();
758
+ const selectionItem = {
759
+ _id: String(d.id),
760
+ element: () => d.element as any
761
+ };
762
+ if (event.ctrlKey || event.metaKey) {
763
+ if (context._selection.isSelected(selectionItem)) {
764
+ context._selection.remove(selectionItem);
765
+ } else {
766
+ context._selection.append(selectionItem);
767
+ }
768
+ } else {
769
+ context._selection.clear();
770
+ context._selection.append(selectionItem);
771
+ }
772
+
773
+ context.selectionChanged();
774
+ const selected = d.element.classed("selected");
775
+ const eventOrigin = context.resolveEventOrigin();
776
+ context.vertex_click(d.props.origData || d.props, "", selected, eventOrigin);
777
+ })
778
+ .on("mousein", function (d) {
779
+ Utility.safeRaise(this);
780
+ context.highlightVertex(d3Select(this), d);
781
+ const selected = d.element.classed("selected");
782
+ const eventOrigin = context.resolveEventOrigin();
783
+ context.vertex_mousein(d.props.origData || d.props, "", selected, eventOrigin);
784
+ })
785
+ .on("mouseover", function (d) {
786
+ Utility.safeRaise(this);
787
+ context.highlightVertex(d3Select(this), d);
788
+ const selected = d.element.classed("selected");
789
+ if (d.props.tooltip) {
790
+ context._tooltip
791
+ .tooltipHTML(context.tooltipHTML.bind(context))
792
+ .triggerElement(d.element)
793
+ .tooltipWidth(context.tooltipWidth())
794
+ .tooltipHeight(context.tooltipHeight())
795
+ .enablePointerEvents(context.enableTooltipPointerEvents())
796
+ .closeDelay(context.tooltipCloseDelay())
797
+ .direction("n")
798
+ .data(d)
799
+ .visible(true)
800
+ .render()
801
+ ;
802
+ }
803
+ const eventOrigin = context.resolveEventOrigin();
804
+ context.vertex_mouseover(d.props.origData || d.props, "", selected, eventOrigin);
805
+ })
806
+ .on("mouseout", function (d) {
807
+ context.highlightVertex(null, null);
808
+ const selected = d.element.classed("selected");
809
+ const eventOrigin = context.resolveEventOrigin();
810
+ context.vertex_mouseout(d.props.origData || d.props, "", selected, eventOrigin);
811
+ if (d.props.tooltip) {
812
+ context._tooltip.mouseout();
813
+ }
814
+ })
815
+ .call((sel) => this._dragHandler(sel))
816
+ .each(function (d) {
817
+ d.element = d3Select(this);
818
+ }),
819
+ update => update,
820
+ exit => exit
821
+ .each(function (d) {
822
+ delete d.element;
823
+ })
824
+ .remove()
825
+ )
826
+ .classed("centroid", d => d.props.centroid)
827
+ .attr("opacity", d => d.props.hidden ? 0 : 1)
828
+ .attr("filter", d => d.props.centroid ? "url(#" + this.id() + "_glow)" : null)
829
+ .attr("tabindex", d => context.tabNavigation() && !d.props.hidden ? 0 : null)
830
+ .attr("role", context.tabNavigation() ? "button" : null)
831
+ .attr("aria-label", d => context.tabNavigation() ? context.vertexAriaLabel(d) : null)
832
+ .attr("aria-hidden", d => d.props.hidden ? "true" : null)
833
+ .each(function (this: any, d) {
834
+ const props = context.calcProps(
835
+ {
836
+ showLabel: context.showVertexLabels(),
837
+ ...context.vertexMapper(d.props, d.props.origData)
838
+ }
839
+ );
840
+ d.renderResult = context._vertexRenderer(props, this);
841
+ })
842
+ ;
843
+ return this;
844
+ }
845
+
846
+ calcProps(props: V): V {
847
+ return { ...props };
848
+ }
849
+
850
+ protected vertexAriaLabel(d: VertexPlaceholder<V>): string {
851
+ return d.props?.text || `Vertex ${d.id}`;
852
+ }
853
+
854
+ hasSubgraphs() {
855
+ switch (this.layout()) {
856
+ case "DOT":
857
+ case "Hierarchy":
858
+ return true;
859
+ }
860
+ return false;
861
+ }
862
+
863
+ private _subgraphRenderer: RendererT<SG>;
864
+ subgraphRenderer(): RendererT<SG>;
865
+ subgraphRenderer(_: RendererT<SG>): this;
866
+ subgraphRenderer(_?: RendererT<SG>): this | RendererT<SG> {
867
+ if (!arguments.length) return this._subgraphRenderer;
868
+ this._subgraphRenderer = _;
869
+ return this;
870
+ }
871
+
872
+ updateSubgraphs(): this {
873
+ const context = this;
874
+ this._subgraphG.selectAll(".subgraphPlaceholder")
875
+ .data(this.hasSubgraphs() ? this._graphData.allSubgraphs() : [], d => (d as SubgraphPlaceholder<SG>).id)
876
+ .join(
877
+ enter => enter.append("g")
878
+ .attr("class", "subgraphPlaceholder")
879
+ .on("click.selectionBag", function (d) {
880
+ context._selection.click({
881
+ _id: String(d.id),
882
+ element: () => d.element as any
883
+ }, d3Event());
884
+ context.selectionChanged();
885
+ })
886
+ .on("click", function (this: SVGElement, d) {
887
+ const selected = d.element.classed("selected");
888
+ context.subgraph_click(d.props.origData || d.props, "", selected);
889
+ })
890
+ .on("mouseover", function () {
891
+ Utility.safeRaise(this);
892
+ })
893
+ .each(function (d) {
894
+ d.element = d3Select(this);
895
+ })
896
+ ,
897
+ update => update,
898
+ exit => exit
899
+ .each(function (d) {
900
+ delete d.element;
901
+ })
902
+ .transition()
903
+ .style("opacity", 0)
904
+ .remove()
905
+ )
906
+ .each(function (d) {
907
+ context.moveSubgraphPlaceholder(d, false);
908
+ })
909
+ ;
910
+ return this;
911
+ }
912
+
913
+ enter(domNode, element) {
914
+ super.enter(domNode, element);
915
+
916
+ const svg = this.locateSVGNode(domNode);
917
+ this._svgDefs = d3Select(svg).select<SVGDefsElement>("defs");
918
+ this._centroidFilter = new SVGGlowFilter(this._svgDefs, this._id + "_glow");
919
+ this._svgDefsCat = this._svgDefs.append("g");
920
+ this._svgDefsAnn = this._svgDefs.append("g");
921
+ this._subgraphG = this._renderElement.append("g");
922
+ this._edgeG = this._renderElement.append("g");
923
+ this._vertexG = this._renderElement.append("g");
924
+
925
+ this._tooltip.target(domNode);
926
+
927
+ this.on("startMarqueeSelection", () => {
928
+ }).on("updateMarqueeSelection", rect => {
929
+ const vertices: VertexPlaceholder<V>[] = this._graphData.allVertices().filter(v => v.x >= rect.x && v.x <= rect.x + rect.width && v.y >= rect.y && v.y <= rect.y + rect.height);
930
+ this.selection(vertices.map(v => v.props));
931
+ }).on("endMarqueeSelection", () => {
932
+ this.selectionChanged();
933
+ });
934
+ }
935
+
936
+ protected forceDirectedOptions(): FDOptions {
937
+ return {
938
+ alpha: this.forceDirectedAlpha(),
939
+ alphaMin: this.forceDirectedAlphaMin(),
940
+ alphaDecay: this.forceDirectedAlphaDecay(),
941
+ velocityDecay: this.forceDirectedVelocityDecay(),
942
+ repulsionStrength: this.forceDirectedRepulsionStrength(),
943
+ iterations: this.forceDirectedIterations(),
944
+ linkDistance: this.forceDirectedLinkDistance(),
945
+ linkStrength: this.forceDirectedLinkStrength(),
946
+ pinCentroid: this.forceDirectedPinCentroid(),
947
+ forceStrength: this.forceDirectedForceStrength(),
948
+ distanceMin: this.forceDirectedMinDistance(),
949
+ distanceMax: this.forceDirectedMaxDistance(),
950
+ };
951
+ }
952
+
953
+ private layoutOptions(layout: GraphLayoutType) {
954
+ switch (layout) {
955
+ case "ForceDirected":
956
+ case "ForceDirected2":
957
+ case "ForceDirectedHybrid":
958
+ return this.forceDirectedOptions();
959
+ case "Hierarchy":
960
+ return {
961
+ rankdir: this.hierarchyRankDirection(),
962
+ nodesep: this.hierarchyNodeSeparation(),
963
+ edgesep: this.hierarchyEdgeSeparation(),
964
+ ranksep: this.hierarchyRankSeparation(),
965
+ digraph: this.hierarchyDigraph()
966
+ };
967
+ case "Tree":
968
+ case "Dendrogram":
969
+ return { rankdir: this.treeRankDirection() };
970
+ case "DOT":
971
+ case "Neato":
972
+ case "FDP":
973
+ case "TwoPI":
974
+ case "Circo":
975
+ return new URL(this.wasmFolder() || scriptDir, document.baseURI).href;
976
+ case "None":
977
+ case "Circle":
978
+ case "RadialTree":
979
+ case "RadialDendrogram":
980
+ default:
981
+ return undefined;
982
+ }
983
+ }
984
+
985
+ private _prevLayout: string;
986
+ updateLayout() {
987
+ const layout = this.layout();
988
+ const options: any = this.layoutOptions(layout);
989
+ const hash = hashSum([layout, options]);
990
+ if (this._prevLayout !== hash) {
991
+ this._prevLayout = hash;
992
+ switch (layout) {
993
+ case "None":
994
+ this.layoutAlgo(new Null(this));
995
+ break;
996
+ case "Circle":
997
+ this.layoutAlgo(new Circle(this));
998
+ break;
999
+ case "ForceDirected":
1000
+ this.layoutAlgo(new ForceDirected(this, options));
1001
+ break;
1002
+ case "ForceDirected2":
1003
+ this.layoutAlgo(new ForceDirectedAnimated(this, options));
1004
+ break;
1005
+ case "ForceDirectedHybrid":
1006
+ this.layoutAlgo(new ForceDirected(this, options)).then(() => {
1007
+ this.layoutAlgo(new ForceDirectedAnimated(this, options));
1008
+ });
1009
+ break;
1010
+ case "Hierarchy":
1011
+ this.layoutAlgo(new Dagre(this, options));
1012
+ break;
1013
+ case "DOT":
1014
+ this.layoutAlgo(new Graphviz(this, "dot", options));
1015
+ break;
1016
+ case "Tree":
1017
+ this.layoutAlgo(new Tree(this, options));
1018
+ break;
1019
+ case "RadialTree":
1020
+ this.layoutAlgo(new RadialTree(this));
1021
+ break;
1022
+ case "Dendrogram":
1023
+ this.layoutAlgo(new Dendrogram(this, options));
1024
+ break;
1025
+ case "RadialDendrogram":
1026
+ this.layoutAlgo(new RadialDendrogram(this));
1027
+ break;
1028
+ case "Neato":
1029
+ this.layoutAlgo(new Graphviz(this, "neato", options));
1030
+ break;
1031
+ case "FDP":
1032
+ this.layoutAlgo(new Graphviz(this, "fdp", options));
1033
+ break;
1034
+ case "TwoPI":
1035
+ this.layoutAlgo(new Graphviz(this, "twopi", options));
1036
+ break;
1037
+ case "Circo":
1038
+ this.layoutAlgo(new Graphviz(this, "circo", options));
1039
+ break;
1040
+ }
1041
+ }
1042
+ }
1043
+
1044
+ update(domNode, element) {
1045
+ super.update(domNode, element);
1046
+
1047
+ const hostDiv = this.parentRelativeDiv;
1048
+ if (this.tabNavigation() && hostDiv) {
1049
+ const vertexCount = this._graphData?.allVertices().length ?? 0;
1050
+ const ariaLabel = vertexCount ? `Graph data (${vertexCount} vertices)` : "Graph data";
1051
+ hostDiv
1052
+ .attr("tabindex", "0")
1053
+ .attr("role", "group")
1054
+ .attr("aria-label", ariaLabel);
1055
+ } else if (hostDiv) {
1056
+ hostDiv
1057
+ .attr("tabindex", null)
1058
+ .attr("role", null)
1059
+ .attr("aria-label", null);
1060
+ }
1061
+
1062
+ this._renderElement.classed("allowDragging", this.allowDragging());
1063
+ if (this._centroidFilter) {
1064
+ this._centroidFilter.update(this.selectionGlowColor());
1065
+ }
1066
+
1067
+ this.updateCategories();
1068
+ this.updateAnnotations();
1069
+
1070
+ this.updateSubgraphs();
1071
+ this.updateVertices();
1072
+ this.updateEdges();
1073
+ this.moveEdges(false);
1074
+
1075
+ this.updateLayout();
1076
+
1077
+ this.updateIconBar();
1078
+ }
1079
+
1080
+ exit(domNode, element) {
1081
+ super.exit(domNode, element);
1082
+ this._tooltip.target(null);
1083
+ }
1084
+
1085
+ render(callback?: (w: Widget) => void): this {
1086
+ this.progress("start");
1087
+ super.render(w => {
1088
+ this.progress("stop");
1089
+ if (callback) {
1090
+ callback(w);
1091
+ }
1092
+ });
1093
+ return this;
1094
+ }
1095
+
1096
+ private _prevWidth;
1097
+ private _prevHeight;
1098
+ private _prevTransformScale;
1099
+ private _transformScale = 1;
1100
+ zoomed(transform) {
1101
+ super.zoomed(transform);
1102
+ const { width, height } = this.size();
1103
+
1104
+ if (transform.k < this.minScale()) {
1105
+ this._edgeG.attr("transform", `scale(${this.minScale() / transform.k})`);
1106
+ this._subgraphG.attr("transform", `scale(${this.minScale() / transform.k})`);
1107
+ this._vertexG.attr("transform", `scale(${this.minScale() / transform.k})`);
1108
+ this._transformScale = transform.k / this.minScale();
1109
+ } else if (transform.k > this.maxScale()) {
1110
+ this._edgeG.attr("transform", `scale(${this.maxScale() / transform.k})`);
1111
+ this._subgraphG.attr("transform", `scale(${this.maxScale() / transform.k})`);
1112
+ this._vertexG.attr("transform", `scale(${this.maxScale() / transform.k})`);
1113
+ this._transformScale = transform.k / this.maxScale();
1114
+ } else {
1115
+ this._transformScale = 1;
1116
+ this._edgeG.attr("transform", null);
1117
+ this._subgraphG.attr("transform", null);
1118
+ this._vertexG.attr("transform", null);
1119
+ }
1120
+
1121
+ if (this._prevTransformScale !== this._transformScale ||
1122
+ this._prevWidth !== width ||
1123
+ this._prevHeight !== height) {
1124
+ this._prevTransformScale = this._transformScale;
1125
+ this._prevWidth = width;
1126
+ this._prevHeight = height;
1127
+ this
1128
+ .moveSubgraphs(false)
1129
+ .moveVertices(false)
1130
+ .moveEdges(false)
1131
+ ;
1132
+ }
1133
+ }
1134
+
1135
+ // Events ---
1136
+ centroids(): VertexPlaceholder<V>[] {
1137
+ return this._graphData.allVertices().filter(vp => !!vp.props.centroid);
1138
+ }
1139
+
1140
+ selectionChanged() {
1141
+ if (this.highlightSelectedPathToCentroid()) {
1142
+ const highlightedVertices = {};
1143
+ this.centroids().forEach(centroid => {
1144
+ this.selection().forEach(selection => {
1145
+ const { ids, len } = this._graphData.dijkstra(centroid.id, selection.id);
1146
+ if (len) {
1147
+ ids.forEach(id => {
1148
+ highlightedVertices[id] = true;
1149
+ });
1150
+ }
1151
+ });
1152
+ });
1153
+ this._edgeG.selectAll(".graphEdge")
1154
+ // .classed("shortest-path", d => highlightedEdges[d.id()] === true)
1155
+ ;
1156
+ }
1157
+ }
1158
+
1159
+ tooltipHTML(data) {
1160
+ return data.props.tooltip;
1161
+ }
1162
+
1163
+ subgraph_click(row, _col, sel) {
1164
+ }
1165
+
1166
+ vertex_click(row, _col, sel, data) {
1167
+ }
1168
+
1169
+ vertex_dblclick(row, _col, sel, data) {
1170
+ }
1171
+
1172
+ vertex_contextmenu(row, _col, sel, data) {
1173
+ }
1174
+
1175
+ vertex_mousein(row, _col, sel, data) {
1176
+ }
1177
+
1178
+ vertex_mouseover(row, _col, sel, data) {
1179
+ }
1180
+
1181
+ vertex_mouseout(row, _col, sel, data) {
1182
+ }
1183
+
1184
+ edge_click(row, _col, sel) {
1185
+ }
1186
+
1187
+ edge_mouseover(element, d) {
1188
+ this.highlightEdge(element, d);
1189
+ }
1190
+
1191
+ edge_mouseout(_element, _d) {
1192
+ this.highlightEdge(null, null);
1193
+ }
1194
+
1195
+ progress(what: "start" | "stop" | "layout-start" | "layout-tick" | "layout-stop") {
1196
+ }
1197
+ }
1198
+ GraphT.prototype._class += " graph_GraphT";
1199
+
1200
+ export interface GraphT<SG extends SubgraphBaseProps = any, V extends VertexBaseProps = any, E extends EdgeBaseProps<V> = any> {
1201
+ allowDragging(): boolean;
1202
+ allowDragging(_: boolean): this;
1203
+ dragSingleNeighbors(): boolean;
1204
+ dragSingleNeighbors(_: boolean): this;
1205
+ layout(): GraphLayoutType;
1206
+ layout(_: GraphLayoutType): this;
1207
+ applyScaleOnLayout(): boolean;
1208
+ applyScaleOnLayout(_: boolean): this;
1209
+ highlightOnMouseOverVertex(): boolean;
1210
+ highlightOnMouseOverVertex(_: boolean): this;
1211
+ highlightOnMouseOverEdge(): boolean;
1212
+ highlightOnMouseOverEdge(_: boolean): this;
1213
+ transitionDuration(): number;
1214
+ transitionDuration(_: number): this;
1215
+
1216
+ highlightSelectedPathToCentroid(): boolean;
1217
+ highlightSelectedPathToCentroid(_: boolean): this;
1218
+ edgeArcDepth(): number;
1219
+ edgeArcDepth(_: number): this;
1220
+ minScale(): number;
1221
+ minScale(_: number): this;
1222
+ maxScale(): number;
1223
+ maxScale(_: number): this;
1224
+ showEdgeLabels(): boolean;
1225
+ showEdgeLabels(_: boolean): this;
1226
+ showEdgeLabelsOnHighlight(): boolean;
1227
+ showEdgeLabelsOnHighlight(_: boolean): this;
1228
+ showVertexLabels(): boolean;
1229
+ showVertexLabels(_: boolean): this;
1230
+ showVertexLabelsOnHighlight(): boolean;
1231
+ showVertexLabelsOnHighlight(_: boolean): this;
1232
+ tabNavigation(): boolean;
1233
+ tabNavigation(_: boolean): this;
1234
+
1235
+ hierarchyRankDirection(): "TB" | "BT" | "LR" | "RL";
1236
+ hierarchyRankDirection(_: "TB" | "BT" | "LR" | "RL"): this;
1237
+ hierarchyNodeSeparation(): number;
1238
+ hierarchyNodeSeparation(_: number): this;
1239
+ hierarchyEdgeSeparation(): number;
1240
+ hierarchyEdgeSeparation(_: number): this;
1241
+ hierarchyRankSeparation(): number;
1242
+ hierarchyRankSeparation(_: number): this;
1243
+ hierarchyDigraph(): boolean;
1244
+ hierarchyDigraph(_: boolean): this;
1245
+
1246
+ forceDirectedAlpha(): number;
1247
+ forceDirectedAlpha(_: number): this;
1248
+ forceDirectedAlphaMin(): number;
1249
+ forceDirectedAlphaMin(_: number): this;
1250
+ forceDirectedAlphaDecay(): number;
1251
+ forceDirectedAlphaDecay(_: number): this;
1252
+ forceDirectedRepulsionStrength(): number;
1253
+ forceDirectedRepulsionStrength(_: number): this;
1254
+ forceDirectedVelocityDecay(): number;
1255
+ forceDirectedVelocityDecay(_: number): this;
1256
+ forceDirectedIterations(): number;
1257
+ forceDirectedIterations(_: number): this;
1258
+ forceDirectedLinkDistance(): number;
1259
+ forceDirectedLinkDistance(_: number): this;
1260
+ forceDirectedLinkStrength(): number;
1261
+ forceDirectedLinkStrength(_: number): this;
1262
+ forceDirectedPinCentroid(): boolean;
1263
+ forceDirectedPinCentroid(_: boolean): this;
1264
+ forceDirectedForceStrength(): number;
1265
+ forceDirectedForceStrength(_: number): this;
1266
+ forceDirectedMinDistance(): number;
1267
+ forceDirectedMinDistance(_: number): this;
1268
+ forceDirectedMaxDistance(): number;
1269
+ forceDirectedMaxDistance(_: number): this;
1270
+
1271
+ treeRankDirection(): "TB" | "LR";
1272
+ treeRankDirection(_: "TB" | "LR"): this;
1273
+
1274
+ edgeColor(): string;
1275
+ edgeColor(_: string): this;
1276
+ edgeStrokeWidth(): number;
1277
+ edgeStrokeWidth(_: number): this;
1278
+ tooltipWidth(): number;
1279
+ tooltipWidth(_: number): this;
1280
+ tooltipHeight(): number;
1281
+ tooltipHeight(_: number): this;
1282
+ enableTooltipPointerEvents(): boolean;
1283
+ enableTooltipPointerEvents(_: boolean): this;
1284
+ tooltipCloseDelay(): number;
1285
+ tooltipCloseDelay(_: number): this;
1286
+ doubleClickMaxDelay(): number;
1287
+ doubleClickMaxDelay(_: number): this;
1288
+
1289
+ wasmFolder(): string;
1290
+ wasmFolder(_: string): this;
1291
+ }
1292
+
1293
+ GraphT.prototype.publish("allowDragging", true, "boolean", "Allow Dragging of Vertices");
1294
+ GraphT.prototype.publish("dragSingleNeighbors", true, "boolean", "Dragging a Vertex also moves its singleton neighbors");
1295
+ GraphT.prototype.publish("layout", "ForceDirectedHybrid", "set", "Default Layout", GraphLayoutTypeSet);
1296
+ GraphT.prototype.publish("scale", "100%", "set", "Zoom Level", ["all", "width", "selection", "100%", "90%", "75%", "50%", "25%", "10%"]);
1297
+ GraphT.prototype.publish("applyScaleOnLayout", false, "boolean", "Shrink to fit on Layout");
1298
+ GraphT.prototype.publish("highlightOnMouseOverVertex", true, "boolean", "Highlight Vertex on Mouse Over");
1299
+ GraphT.prototype.publish("highlightOnMouseOverEdge", true, "boolean", "Highlight Edge on Mouse Over");
1300
+ GraphT.prototype.publish("transitionDuration", 250, "number", "Transition Duration");
1301
+ GraphT.prototype.publish("showEdges", true, "boolean", "Show Edges");
1302
+ GraphT.prototype.publish("showEdgeLabels", true, "boolean", "Show Edge labels");
1303
+ GraphT.prototype.publish("showEdgeLabelsOnHighlight", true, "boolean", "Show Edge labels when highlighted");
1304
+ GraphT.prototype.publish("showVertexLabels", true, "boolean", "Show Vertex labels");
1305
+ GraphT.prototype.publish("showVertexLabelsOnHighlight", true, "boolean", "Show Vertex labels when highlighted");
1306
+ GraphT.prototype.publish("tabNavigation", false, "boolean", "Enable or disable tab navigation");
1307
+ GraphT.prototype.publish("snapToGrid", 0, "number", "Snap to Grid");
1308
+ GraphT.prototype.publish("selectionClearOnBackgroundClick", false, "boolean", "Clear selection on background click");
1309
+ GraphT.prototype.publish("edgeArcDepth", 8, "number", "Edge Arc Depth");
1310
+ GraphT.prototype.publish("edgeColor", null, "html-color", "Edge line stroke color", null, { optional: true });
1311
+ GraphT.prototype.publish("edgeStrokeWidth", 1, "number", "Edge line stroke width (pixels)");
1312
+ GraphT.prototype.publish("minScale", 0.6, "number", "Min scale size for text");
1313
+ GraphT.prototype.publish("maxScale", 1.0, "number", "Max scale size for text");
1314
+ GraphT.prototype.publish("tooltipWidth", 256, "number", "Tooltip width (pixels)");
1315
+ GraphT.prototype.publish("tooltipHeight", 128, "number", "Tooltip width (pixels)");
1316
+ GraphT.prototype.publish("enableTooltipPointerEvents", false, "boolean", "If true, tooltip will use the style: 'pointer-events: all'");
1317
+ GraphT.prototype.publish("tooltipCloseDelay", 0, "number", "Number of milliseconds to wait before closing tooltip (cancelled on tooltip mouseover event)");
1318
+ GraphT.prototype.publish("doubleClickMaxDelay", 300, "number", "Number of milliseconds to wait before a subsequent click is not considered a double click");
1319
+ GraphT.prototype.publish("highlightSelectedPathToCentroid", true, "boolean", "Highlight path to Center Vertex (for selected vertices)");
1320
+
1321
+ GraphT.prototype.publish("hierarchyRankDirection", "TB", "set", "Direction for Rank Nodes", ["TB", "BT", "LR", "RL"], { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1322
+ GraphT.prototype.publish("hierarchyNodeSeparation", 50, "number", "Number of pixels that separate nodes horizontally in the layout", null, { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1323
+ GraphT.prototype.publish("hierarchyEdgeSeparation", 10, "number", "Number of pixels that separate edges horizontally in the layout", null, { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1324
+ GraphT.prototype.publish("hierarchyRankSeparation", 50, "number", "Number of pixels between each rank in the layout", null, { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1325
+ GraphT.prototype.publish("hierarchyDigraph", true, "boolean", "Directional GraphBase", null, { disable: (w: GraphT) => w.layout() !== "Hierarchy" });
1326
+
1327
+ GraphT.prototype.publish("forceDirectedAlpha", 1, "number", "Alpha", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1328
+ GraphT.prototype.publish("forceDirectedAlphaMin", 0.001, "number", "Min Alpha", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1329
+ GraphT.prototype.publish("forceDirectedAlphaDecay", 0.0228, "number", "Defaults to 1 - pow(alphaMin, 1 / 300)", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1330
+ GraphT.prototype.publish("forceDirectedRepulsionStrength", -350, "number", "Charge strength ", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1331
+ GraphT.prototype.publish("forceDirectedVelocityDecay", 0.4, "number", "Velocity Decay ", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1332
+ GraphT.prototype.publish("forceDirectedIterations", 300, "number", "Iterations", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1333
+ GraphT.prototype.publish("forceDirectedLinkDistance", 300, "number", "Target distance between linked nodes", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1334
+ GraphT.prototype.publish("forceDirectedLinkStrength", 1, "number", "Strength (rigidity) of links", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1335
+ GraphT.prototype.publish("forceDirectedPinCentroid", false, "boolean", "Pin centroid to center", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1336
+ GraphT.prototype.publish("forceDirectedForceStrength", 0, "number", "Strength of center force", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1337
+ GraphT.prototype.publish("forceDirectedMinDistance", 1, "number", "Min distance between nodes", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1338
+ GraphT.prototype.publish("forceDirectedMaxDistance", Infinity, "number", "Max distance between nodes", null, { disable: (w: GraphT) => w.layout().indexOf("ForceDirected") !== 0 });
1339
+
1340
+ GraphT.prototype.publish("treeRankDirection", "LR", "set", "Direction for Rank Nodes", ["TB", "LR"], { disable: (w: GraphT) => w.layout() !== "Tree" && w.layout() !== "Dendrogram" });
1341
+
1342
+ GraphT.prototype.publish("wasmFolder", null, "string", "WASM Folder", null, { optional: true, disable: (w: GraphT) => ["DOT", "Neato", "FDP", "TwoPI", "Circo"].indexOf(w.layout()) < 0 });
1343
+
1344
+ const _origScale = GraphT.prototype.scale;
1345
+ // @ts-ignore ---
1346
+ GraphT.prototype.scale = function (_?, transitionDuration?) {
1347
+ const retVal = _origScale.apply(this, arguments as any);
1348
+ if (arguments.length) {
1349
+ this.zoomTo(_, transitionDuration);
1350
+ }
1351
+ return retVal;
1352
+ };
1353
+
1354
+ export function graphviz(dot: string, engine: Engine = "dot", _scriptDir: string = scriptDir) {
1355
+
1356
+ return gvWorker({
1357
+ items: [],
1358
+ links: [],
1359
+ raw: dot
1360
+ }, {
1361
+ engine: engine
1362
+ });
1363
+ }