@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
package/src/Graph.ts CHANGED
@@ -1,1077 +1,1077 @@
1
- import { IGraph, ITooltip } from "@hpcc-js/api";
2
- import { d3Event, drag as d3Drag, ISize, Platform, select as d3Select, Spacer, SVGGlowFilter, SVGZoomWidget, ToggleButton, Utility, Widget } from "@hpcc-js/common";
3
-
4
- import "d3-transition";
5
- import { Edge } from "./Edge.ts";
6
- import { GraphData } from "./GraphData.ts";
7
- import * as GraphLayouts from "./GraphLayouts.ts";
8
- import { Subgraph } from "./Subgraph.ts";
9
- import { Vertex } from "./Vertex.ts";
10
-
11
- import "../src/Graph.css";
12
-
13
- export interface Lineage {
14
- parent: Widget;
15
- child: Widget;
16
- }
17
- export interface IGraphData {
18
- subgraphs?: Widget[];
19
- vertices: Widget[];
20
- edges: Edge[];
21
- hierarchy?: Lineage[];
22
- }
23
- export type GraphLayoutType = "Hierarchy" | "DOT" | "ForceDirected" | "ForceDirected2" | "Neato" | "FDP" | "Circle" | "TwoPI" | "Circo" | "None";
24
-
25
- export class Graph extends SVGZoomWidget {
26
- static Subgraph = Subgraph;
27
- static Vertex = Vertex;
28
- static Edge = Edge;
29
-
30
- private _toggleHierarchy = new ToggleButton().faChar("fa-sitemap").tooltip("Hierarchy").on("click", () => this.layoutClick("Hierarchy"));
31
- private _toggleForceDirected = new ToggleButton().faChar("fa-expand").tooltip("Force Directed").on("click", () => this.layoutClick("ForceDirected"));
32
- private _toggleForceDirected2 = new ToggleButton().faChar("fa-arrows").tooltip("Spring").on("click", () => this.layoutClick("ForceDirected2"));
33
- private _toggleCircle = new ToggleButton().faChar("fa-circle-o").tooltip("Circle").on("click", () => this.layoutClick("Circle"));
34
-
35
- private _graphData: GraphData;
36
- protected highlight;
37
- protected _selection;
38
- protected _dragging;
39
- protected forceLayout;
40
- protected _d3Drag;
41
- protected defs;
42
- protected _centroidFilter: SVGGlowFilter;
43
- protected svgFragment;
44
- protected svg;
45
- protected svgC;
46
- protected svgE;
47
- protected svgV;
48
-
49
- constructor() {
50
- super();
51
- IGraph.call(this);
52
- ITooltip.call(this);
53
- this.tooltipHTML(function (d: any) {
54
- let content;
55
- if (d instanceof Subgraph) {
56
- content = d.title().replace(/\n/g, "<br>");
57
- } else if (d instanceof Vertex || d instanceof Edge) {
58
- content = d.text().replace(/\n/g, "<br>");
59
- }
60
- if (content) {
61
- return `<p style="text-align:center">${content}</p>`;
62
- }
63
- return null;
64
- });
65
-
66
- this._drawStartPos = "origin";
67
-
68
- const buttons: Widget[] = [
69
- this._toggleHierarchy,
70
- this._toggleForceDirected,
71
- this._toggleForceDirected2,
72
- this._toggleCircle,
73
- new Spacer()];
74
- this._iconBar.buttons(buttons.concat(this._iconBar.buttons()));
75
-
76
- this._graphData = new GraphData();
77
- this.highlight = {
78
- zoom: 1.1,
79
- opacity: 0.33,
80
- edge: "1.25px"
81
- };
82
- this._selection = new Utility.Selection(this);
83
- this.zoomToFitLimit(1);
84
- }
85
-
86
- iconBarButtons(): Widget[] {
87
- return this._iconBar.buttons();
88
- }
89
-
90
- layoutClick(layout: GraphLayoutType) {
91
- this.layout(layout);
92
- if (layout !== "ForceDirected2") this.applyScaleOnLayout(true);
93
- this
94
- .layout(layout)
95
- .render(w => {
96
- this.applyScaleOnLayout(false);
97
- });
98
- }
99
-
100
- // Properties ---
101
- getOffsetPos() {
102
- return { x: 0, y: 0 };
103
- }
104
-
105
- size(): ISize;
106
- size(_): this;
107
- size(_?): ISize | this {
108
- const retVal = super.size.apply(this, arguments as any);
109
- return retVal;
110
- }
111
-
112
- clear() {
113
- this.data({ subgraphs: [], vertices: [], edges: [], hierarchy: [] }, false);
114
- }
115
-
116
- _dataHash = 0;
117
- data(): IGraphData;
118
- data(_: IGraphData, merge?: boolean): this;
119
- data(_?: IGraphData, merge?: boolean): IGraphData | this {
120
- const retVal = super.data.apply(this, arguments as any);
121
- if (arguments.length) {
122
- if (!merge) {
123
- this._graphData = new GraphData();
124
- this._renderCount = 0;
125
- }
126
- const data = this._graphData.setData(_.subgraphs || [], _.vertices || [], _.edges || [], _.hierarchy || [], merge || false);
127
- if (data.addedVertices.length) {
128
- this._dataHash++;
129
- }
130
-
131
- const context = this;
132
- data.addedVertices.forEach(function (item) {
133
- item._graphID = context._id;
134
- });
135
- data.addedEdges.forEach(function (item) {
136
- item._graphID = context._id;
137
- });
138
-
139
- // Recalculate edge arcs ---
140
- const dupMap = {};
141
- this._graphData.edges().forEach(function (item) {
142
- if (!dupMap[item._sourceVertex._id]) {
143
- dupMap[item._sourceVertex._id] = {};
144
- }
145
- if (!dupMap[item._sourceVertex._id][item._targetVertex._id]) {
146
- dupMap[item._sourceVertex._id][item._targetVertex._id] = 0;
147
- }
148
- const dupEdgeCount = ++dupMap[item._sourceVertex._id][item._targetVertex._id];
149
- item.arcDepth(16 * dupEdgeCount);
150
- });
151
- }
152
- return retVal;
153
- }
154
-
155
- graphData(): any {
156
- return this._graphData;
157
- }
158
-
159
- selection(_: Widget[]): this;
160
- selection(): Widget[];
161
- selection(_?: Widget[]): Widget[] | this {
162
- if (!arguments.length) return this._selection.get();
163
- this._selection.set(_);
164
- return this;
165
- }
166
-
167
- private _linkcolor: string;
168
- linkcolor_default(): string;
169
- linkcolor_default(_: string): this;
170
- linkcolor_default(_?: string): string | this {
171
- if (!arguments.length) return this._linkcolor;
172
- this._linkcolor = _;
173
- return this;
174
- }
175
-
176
- private _linktooltip: string;
177
- linktooltip_default(): string;
178
- linktooltip_default(_: string): this;
179
- linktooltip_default(_?: string): string | this {
180
- if (!arguments.length) return this._linktooltip;
181
- this._linktooltip = _;
182
- return this;
183
- }
184
-
185
- // Drag ---
186
- private _neighborOffsets: Array<{ neighbor: Vertex, offsetX: number, offsetY: number }> = [];
187
- dragstart(d) {
188
- if (this.allowDragging()) {
189
- const event = d3Event();
190
- event.sourceEvent.stopPropagation();
191
-
192
- d.__drag_dx = event.x - d.x();
193
- d.__drag_dy = event.y - d.y();
194
- this._dragging = true;
195
- if (this.forceLayout) {
196
- if (!event.active) this.forceLayout.force.alphaTarget(0.3).restart();
197
- const forceNode = this.forceLayout.vertexMap[d.id()];
198
- forceNode.fixed = true;
199
- forceNode.fx = forceNode.x;
200
- forceNode.fy = forceNode.y;
201
- }
202
-
203
- this._neighborOffsets = [];
204
- if (this.dragSingleNeighbors()) {
205
- this._neighborOffsets = this._graphData.singleNeighbors(d.id()).map((neighbor: any) => {
206
- d3Select(neighbor.target()).raise();
207
- return {
208
- neighbor,
209
- offsetX: d.x() - neighbor.x(),
210
- offsetY: d.y() - neighbor.y()
211
- };
212
- });
213
- }
214
-
215
- // Safe Raise - does not interfere with current click event ---
216
- const target = d.target();
217
- let nextSibling = target.nextSibling;
218
- while (nextSibling) {
219
- target.parentNode.insertBefore(nextSibling, target);
220
- nextSibling = target.nextSibling;
221
- }
222
-
223
- if (Platform.svgMarkerGlitch) {
224
- this._graphData.nodeEdges(d.id()).forEach(glEdge => {
225
- const edge = this._graphData.edge(glEdge);
226
- this._pushMarkers(edge.element());
227
- });
228
- }
229
- }
230
- }
231
-
232
- dragging(d) {
233
- if (this.allowDragging()) {
234
- const event = d3Event();
235
- event.sourceEvent.stopPropagation();
236
- d.move({ x: event.x - d.__drag_dx, y: event.y - d.__drag_dy });
237
- if (this.forceLayout) {
238
- const forceNode = this.forceLayout.vertexMap[d.id()];
239
- forceNode.fixed = true;
240
- forceNode.fx = event.x - d.__drag_dx;
241
- forceNode.fy = event.y - d.__drag_dy;
242
- }
243
-
244
- // Drag singleton child nodes
245
- this._neighborOffsets.forEach(neighborOffset => {
246
- const neighborX = event.x - d.__drag_dx - neighborOffset.offsetX;
247
- const neighborY = event.y - d.__drag_dy - neighborOffset.offsetY;
248
- if (this.forceLayout) {
249
- const forceNode = this.forceLayout.vertexMap[neighborOffset.neighbor.id()];
250
- forceNode.fixed = true;
251
- forceNode.fx = neighborX;
252
- forceNode.fy = neighborY;
253
- }
254
- neighborOffset.neighbor.move({ x: neighborX, y: neighborY });
255
- });
256
-
257
- this.refreshIncidentEdges(d, true);
258
- }
259
- }
260
-
261
- dragend(d) {
262
- if (this.allowDragging()) {
263
- d3Event().sourceEvent.stopPropagation();
264
- this._dragging = false;
265
- if (this.snapToGrid()) {
266
- const snapLoc = d.calcSnap(this.snapToGrid());
267
- d.move(snapLoc[0]);
268
- this.refreshIncidentEdges(d, true);
269
- }
270
- if (this.forceLayout) {
271
- const forceNode = this.forceLayout.vertexMap[d.id()];
272
- forceNode.fixed = false;
273
- forceNode.fx = null;
274
- forceNode.fy = null;
275
-
276
- this._neighborOffsets.forEach(neighborOffset => {
277
- const forceNode = this.forceLayout.vertexMap[neighborOffset.neighbor.id()];
278
- forceNode.fixed = false;
279
- forceNode.fx = null;
280
- forceNode.fy = null;
281
- });
282
- }
283
- this._neighborOffsets = [];
284
-
285
- if (Platform.svgMarkerGlitch) {
286
- this._graphData.nodeEdges(d.id()).forEach(function (this, id) {
287
- const edge = this._graphData.edge(id);
288
- this._popMarkers(edge.element());
289
- });
290
- }
291
- }
292
- }
293
-
294
- enter(domNode, element) {
295
- super.enter(domNode, element);
296
-
297
- this._zoomGrab.on("click.clear", () => {
298
- if (this.selectionClearOnBackgroundClick()) {
299
- this._selection.clear();
300
- }
301
- });
302
-
303
- this._d3Drag = d3Drag()
304
- // .origin(function (d) {
305
- // return d.pos();
306
- // })
307
- .on("start", d => this.dragstart(d))
308
- .on("end", d => this.dragend(d))
309
- .on("drag", d => this.dragging(d))
310
- ;
311
- // SVG ---
312
- this.defs = this._renderElement.append("defs");
313
- this.addMarkers();
314
- this._centroidFilter = new SVGGlowFilter(this.defs, this._id + "_glow");
315
-
316
- // element.call(this.zoom);
317
- this.svg = this._renderElement.append("svg:g");
318
- // this._svgBrush = this.svg.append("g").attr("class", "selectionBrush").call(this.brush);
319
- // this._svgBrush.select(".background").style("cursor", null);
320
- // context._svgBrush.call(context.brush.clear());
321
- this.svgC = this.svg.append("g").attr("id", this._id + "C");
322
- this.svgE = this.svg.append("g").attr("id", this._id + "E");
323
- this.svgV = this.svg.append("g").attr("id", this._id + "V");
324
- }
325
-
326
- getBounds(items, layoutEngine?) {
327
- const vBounds = [[null, null], [null, null]];
328
- items.forEach(function (item) {
329
- const pos = layoutEngine ? layoutEngine.nodePos(item._id) : { x: item.x(), y: item.y(), width: item.width(), height: item.height() };
330
- const leftX = pos.x - pos.width / 2;
331
- const rightX = pos.x + pos.width / 2;
332
- const topY = pos.y - pos.height / 2;
333
- const bottomY = pos.y + pos.height / 2;
334
- if (vBounds[0][0] === null || vBounds[0][0] > leftX) {
335
- vBounds[0][0] = leftX;
336
- }
337
- if (vBounds[0][1] === null || vBounds[0][1] > topY) {
338
- vBounds[0][1] = topY;
339
- }
340
- if (vBounds[1][0] === null || vBounds[1][0] < rightX) {
341
- vBounds[1][0] = rightX;
342
- }
343
- if (vBounds[1][1] === null || vBounds[1][1] < bottomY) {
344
- vBounds[1][1] = bottomY;
345
- }
346
- });
347
- return vBounds;
348
- }
349
-
350
- getVertexBounds(layoutEngine) {
351
- return this.getBounds(this._graphData.nodes(), layoutEngine);
352
- }
353
-
354
- getSelectionBounds(layoutEngine) {
355
- return this.getBounds(this._selection.get(), layoutEngine);
356
- }
357
-
358
- centerOnItem(item: Widget) {
359
- const bbox = item.getBBox(true);
360
- const deltaX = bbox.x + bbox.width / 2;
361
- const deltaY = bbox.y + bbox.height / 2;
362
- const itemBBox = {
363
- x: item.x() + deltaX - bbox.width / 2,
364
- y: item.y() + deltaY - bbox.height / 2,
365
- width: bbox.width,
366
- height: bbox.height
367
- };
368
- this.centerOnBBox(itemBBox);
369
- }
370
-
371
- zoomToItem(item: Widget) {
372
- const bbox = item.getBBox(true);
373
- const deltaX = bbox.x + bbox.width / 2;
374
- const deltaY = bbox.y + bbox.height / 2;
375
- const itemBBox = {
376
- x: item.x() + deltaX - bbox.width / 2,
377
- y: item.y() + deltaY - bbox.height / 2,
378
- width: bbox.width,
379
- height: bbox.height
380
- };
381
- this.zoomToBBox(itemBBox);
382
- }
383
-
384
- // Render ---
385
- updateVertices(rootElement, rootSuffix, data) {
386
- const width = this.width();
387
- const height = this.height();
388
- const context = this;
389
-
390
- const vertexElements = rootElement.selectAll("#" + this._id + rootSuffix + " > .graphVertex").data(data, function (d) { return d.id(); });
391
- vertexElements.enter().append("g")
392
- .attr("class", "graphVertex")
393
- .style("opacity", 1e-6)
394
- // TODO: Events need to be optional ---
395
- .on("click.selectionBag", function (d) {
396
- context._selection.click(d, d3Event());
397
- context.selectionChanged();
398
- })
399
- .on("click", function (this: SVGElement, d) {
400
- const vertexElement = d3Select(this).select(".graph_Vertex");
401
- let selected = false;
402
- if (!vertexElement.empty()) {
403
- selected = vertexElement.classed("selected");
404
- }
405
- context.vertex_click(context.rowToObj(d.data()), "", selected, {
406
- vertex: d
407
- });
408
- })
409
- .on("dblclick", function (this: SVGElement, d) {
410
- d3Event().stopPropagation();
411
- const vertexElement = d3Select(this).select(".graph_Vertex");
412
- let selected = false;
413
- if (!vertexElement.empty()) {
414
- selected = vertexElement.classed("selected");
415
- }
416
- context.vertex_dblclick(context.rowToObj(d.data()), "", selected, {
417
- vertex: d
418
- });
419
- })
420
- .on("contextmenu", function (this: SVGElement, d) {
421
- const vertexElement = d3Select(this).select(".graph_Vertex");
422
- let selected = false;
423
- if (!vertexElement.empty()) {
424
- selected = vertexElement.classed("selected");
425
- }
426
- context.vertex_contextmenu(context.rowToObj(d.data()), "", selected, {
427
- vertex: d
428
- });
429
- })
430
- .on("mouseout.tooltip", this.tooltip.hide)
431
- .on("mousemove.tooltip", this.tooltip.show)
432
- .on("mouseover", function (this: SVGElement, d) {
433
- if (context._dragging)
434
- return;
435
- context.vertex_mouseover(d3Select(this), d);
436
- })
437
- .on("mouseout", function (this: SVGElement, d) {
438
- if (context._dragging)
439
- return;
440
- context.vertex_mouseout(d3Select(this), d);
441
- })
442
- .each(createV)
443
- .transition()
444
- .duration(750)
445
- .style("opacity", 1)
446
- ;
447
- function createV(this: SVGElement, d) {
448
- d3Select(this).style("cursor", context.allowDragging() ? "move" : "pointer");
449
- d
450
- .target(this)
451
- .pos({ x: d.x() || width / 2, y: d.y() || height / 2 })
452
- .animationFrameRender()
453
- ;
454
- if (context.allowDragging()) {
455
- d3Select(this)
456
- .call(context._d3Drag)
457
- ;
458
- }
459
- if (d.dispatch) {
460
- d.dispatch.on("sizestart", function (d2) {
461
- d2.allowResize(context.allowDragging());
462
- if (context.allowDragging()) {
463
- context._dragging = true;
464
- }
465
- });
466
- d.dispatch.on("size", function (d2) {
467
- context.refreshIncidentEdges(d2, false);
468
- });
469
- d.dispatch.on("sizeend", function (d2) {
470
- context._dragging = false;
471
- if (context.snapToGrid()) {
472
- const snapLoc = d2.calcSnap(context.snapToGrid());
473
- d2
474
- .pos(snapLoc[0])
475
- .size(snapLoc[1])
476
- .render()
477
- ;
478
- context.refreshIncidentEdges(d2, false);
479
- }
480
- });
481
- }
482
- }
483
-
484
- vertexElements
485
- .each(updateV)
486
- ;
487
- function updateV(d) {
488
- d
489
- .animationFrameRender()
490
- ;
491
- }
492
-
493
- vertexElements.exit()
494
- .each(function (d) {
495
- d.target(null);
496
- })
497
- .remove()
498
- ;
499
-
500
- vertexElements.order();
501
- }
502
-
503
- update(domNode, element) {
504
- super.update(domNode, element);
505
- this.tooltip.hide();
506
- this._centroidFilter.update(this.centroidColor());
507
-
508
- // IconBar ---
509
- const layout = this.layout();
510
- this._toggleHierarchy.selected(layout === "Hierarchy").render();
511
- this._toggleForceDirected.selected(layout === "ForceDirected").render();
512
- this._toggleForceDirected2.selected(layout === "ForceDirected2").render();
513
- this._toggleCircle.selected(layout === "Circle").render();
514
-
515
- // Create ---
516
- const context = this;
517
-
518
- this.updateVertices(this.svgC, "C", this._graphData.nodes().filter(v => this.layout() === "Hierarchy" ? (v instanceof Subgraph) : false));
519
- this.updateVertices(this.svgV, "V", this._graphData.nodes().filter(v => !(v instanceof Subgraph)));
520
-
521
- const edgeElements = this.svgE.selectAll("#" + this._id + "E > .graphEdge").data(this.showEdges() ? this._graphData.edges() : [], function (d) { return d.id(); });
522
- edgeElements.enter().append("g")
523
- .attr("class", "graphEdge")
524
- .style("opacity", 1e-6)
525
- .on("click.selectionBag", function (d) {
526
- context._selection.click(d, d3Event());
527
- })
528
- .on("click", function (this: SVGElement, d) {
529
- const edgeElement = d3Select(this).select(".graph_Edge");
530
- let selected = false;
531
- if (!edgeElement.empty()) {
532
- selected = edgeElement.classed("selected");
533
- }
534
- context.edge_click(context.rowToObj(d.data()), "", selected, {
535
- edge: d
536
- });
537
- })
538
- .on("dblclick", function (this: SVGElement, d) {
539
- const edgeElement = d3Select(this).select(".graph_Edge");
540
- let selected = false;
541
- if (!edgeElement.empty()) {
542
- selected = edgeElement.classed("selected");
543
- }
544
- context.edge_dblclick(context.rowToObj(d.data()), "", selected, {
545
- edge: d
546
- });
547
- })
548
- .on("mouseout.tooltip", this.tooltip.hide)
549
- .on("mousemove.tooltip", this.tooltip.show)
550
- .on("mouseover", function (this: SVGElement, d) {
551
- if (context._dragging)
552
- return;
553
- context.edge_mouseover(d3Select(this), d);
554
- })
555
- .on("mouseout", function (this: SVGElement, d) {
556
- if (context._dragging)
557
- return;
558
- context.edge_mouseout(d3Select(this), d);
559
- })
560
- .each(createE)
561
- .transition()
562
- .duration(750)
563
- .style("opacity", 1)
564
- ;
565
- function createE(this: SVGElement, d) {
566
- d
567
- .target(this)
568
- .animationFrameRender()
569
- ;
570
- }
571
-
572
- edgeElements
573
- .each(updateE)
574
- ;
575
- function updateE(d) {
576
- d
577
- .animationFrameRender()
578
- ;
579
- }
580
-
581
- edgeElements.exit()
582
- .each(function (d) {
583
- d.target(null);
584
- })
585
- .remove()
586
- ;
587
-
588
- if (!this._renderCount) {
589
- this._renderCount++;
590
- this.layout(this.layout());
591
- }
592
- }
593
-
594
- exit(domNode, element) {
595
- this._graphData.nodes().forEach(v => v.target(null));
596
- this._graphData.edges().forEach(e => e.target(null));
597
- super.exit(domNode, element);
598
- }
599
-
600
- static profileID = 0;
601
- render(callback?: (w: Widget) => void): this {
602
- this.progress("start");
603
- super.render(w => {
604
- this.doLayout().then(() => {
605
- this.progress("end");
606
- if (callback) {
607
- callback(w);
608
- }
609
- });
610
- });
611
- return this;
612
- }
613
-
614
- // Methods ---
615
- _prevLayout;
616
- _prevDataHash;
617
- doLayout(transitionDuration = 0): Promise<void> {
618
- return new Promise((resolve, reject) => {
619
- requestAnimationFrame(() => {
620
- if (this._prevLayout !== this.layout() || this._prevDataHash !== this._dataHash) {
621
- this._prevLayout = this.layout();
622
- this._prevDataHash = this._dataHash;
623
- this._doLayout(transitionDuration).then(() => {
624
- resolve();
625
- });
626
- } else {
627
- resolve();
628
- }
629
- });
630
- });
631
- }
632
-
633
- _doLayout(transitionDuration = 0): Promise<void> {
634
- return new Promise((resolve, reject) => {
635
- this.progress("layout-start");
636
- if (this.forceLayout) {
637
- this.forceLayout.force.stop();
638
- this.forceLayout = null;
639
- }
640
-
641
- const context = this;
642
- const layoutEngine = this.getLayoutEngine() as GraphLayouts.ForceDirected;
643
- if (this.layout() === "ForceDirected2") {
644
- this.forceLayout = layoutEngine;
645
- this.forceLayout.force
646
- .on("tick", function (this: SVGElement) {
647
- context.progress("layout-tick");
648
- layoutEngine.vertices.forEach(function (item) {
649
- if (item.fixed) {
650
- // item.x = item.px;
651
- // item.y = item.py;
652
- } else {
653
- // item.px = item.x;
654
- // item.py = item.y;
655
-
656
- // Might have been cleared ---
657
- const vertex = context._graphData.node(item.id);
658
- if (vertex) {
659
- vertex
660
- .move({ x: item.x, y: item.y })
661
- ;
662
- }
663
- }
664
- });
665
- context._graphData.edges().forEach((item: any) => {
666
- item
667
- .points([], false, false)
668
- ;
669
- });
670
- if (context.applyScaleOnLayout()) {
671
- // const vBounds = context.getVertexBounds(layoutEngine);
672
- // context.shrinkToFit(vBounds);
673
- }
674
- })
675
- .on("end", function (this: SVGElement) {
676
- context.progress("layout-end");
677
- })
678
- ;
679
- this.forceLayout.force.restart();
680
- resolve();
681
- } else if (layoutEngine) {
682
- this.forceLayout = null;
683
- context._dragging = true;
684
- context._graphData.nodes().forEach(function (item) {
685
- const pos = layoutEngine.nodePos(item.id());
686
- if (item instanceof Graph.Subgraph) {
687
- item
688
- .pos({ x: pos.x, y: pos.y })
689
- .size({ width: pos.width, height: pos.height })
690
- .animationFrameRender()
691
- ;
692
- } else {
693
- item.move({ x: pos.x, y: pos.y }, 0);
694
- }
695
- });
696
- context._graphData.edges().forEach((item: any) => {
697
- const points = layoutEngine.edgePoints(item);
698
- item.points(points, transitionDuration);
699
- });
700
- if (context.applyScaleOnLayout()) {
701
- requestAnimationFrame(() => {
702
- context.zoomToFit();
703
- resolve();
704
- });
705
- }
706
- this._fixIEMarkers();
707
- setTimeout(function () {
708
- context._dragging = false;
709
- }, transitionDuration ? transitionDuration + 50 : 50); // Prevents highlighting during morph ---
710
- this.progress("layout-end");
711
- if (!context.applyScaleOnLayout()) {
712
- resolve();
713
- }
714
- }
715
- });
716
- }
717
-
718
- getLayoutEngine() {
719
- switch (this.layout()) {
720
- case "Circle":
721
- return new GraphLayouts.Circle(this._graphData, this._size.width, this._size.height);
722
- case "ForceDirected":
723
- return new GraphLayouts.ForceDirected(this._graphData, this._size.width, this._size.height, {
724
- oneShot: true,
725
- linkDistance: this.forceDirectedLinkDistance(),
726
- linkStrength: this.forceDirectedLinkStrength(),
727
- friction: this.forceDirectedFriction(),
728
- charge: this.forceDirectedCharge(),
729
- chargeDistance: this.forceDirectedChargeDistance(),
730
- theta: this.forceDirectedTheta(),
731
- gravity: this.forceDirectedGravity()
732
- });
733
- case "ForceDirected2":
734
- return new GraphLayouts.ForceDirected(this._graphData, this._size.width, this._size.height, {
735
- linkDistance: this.forceDirectedLinkDistance(),
736
- linkStrength: this.forceDirectedLinkStrength(),
737
- friction: this.forceDirectedFriction(),
738
- charge: this.forceDirectedCharge(),
739
- chargeDistance: this.forceDirectedChargeDistance(),
740
- theta: this.forceDirectedTheta(),
741
- gravity: this.forceDirectedGravity()
742
- });
743
- case "Hierarchy":
744
- return new GraphLayouts.Hierarchy(this._graphData, this._size.width, this._size.height, {
745
- rankdir: this.hierarchyRankDirection(),
746
- nodesep: this.hierarchyNodeSeparation(),
747
- edgesep: this.hierarchyEdgeSeparation(),
748
- ranksep: this.hierarchyRankSeparation(),
749
- digraph: this.hierarchyDigraph()
750
- });
751
- default:
752
- }
753
- return null; // new GraphLayouts.None(this.graphData, this._size.width, this._size.height);
754
- }
755
-
756
- getNeighborMap(vertex) {
757
- const vertices = {};
758
- const edges = {};
759
-
760
- if (vertex) {
761
- const nedges = this._graphData.nodeEdges(vertex.id());
762
- for (let i = 0; i < nedges.length; ++i) {
763
- const edge = this._graphData.edge(nedges[i]);
764
- edges[edge.id()] = edge;
765
- if (edge._sourceVertex.id() !== vertex.id()) {
766
- vertices[edge._sourceVertex.id()] = edge._sourceVertex;
767
- }
768
- if (edge._targetVertex.id() !== vertex.id()) {
769
- vertices[edge._targetVertex.id()] = edge._targetVertex;
770
- }
771
- }
772
- }
773
-
774
- return {
775
- vertices,
776
- edges
777
- };
778
- }
779
-
780
- highlightVerticies(vertexMap?: { [id: string]: boolean }) {
781
- const context = this;
782
- const vertexElements = this.svgV.selectAll(".graphVertex");
783
- vertexElements
784
- .classed("graphVertex-highlighted", d => !vertexMap || vertexMap[d.id()])
785
- .style("filter", d => vertexMap && vertexMap[d.id()] ? "url(#" + this.id() + "_glow)" : null)
786
- .transition().duration(this.transitionDuration())
787
- .on("end", function (d) {
788
- if (vertexMap && vertexMap[d.id()]) {
789
- if (d._placeholderElement.node() && d._placeholderElement.node().parentNode) {
790
- d._placeholderElement.node().parentNode.appendChild(d._placeholderElement.node());
791
- }
792
- }
793
- })
794
- .style("opacity", function (d) {
795
- if (!vertexMap || vertexMap[d.id()]) {
796
- return 1;
797
- }
798
- return context.highlight.opacity;
799
- })
800
- ;
801
- return this;
802
- }
803
-
804
- highlightEdges(edgeMap) {
805
- const context = this;
806
- const edgeElements = this.svgE.selectAll(".graphEdge");
807
- edgeElements
808
- .classed("graphEdge-highlighted", function (d) { return !edgeMap || edgeMap[d.id()]; })
809
- .style("stroke-width", function (o) {
810
- if (edgeMap && edgeMap[o.id()]) {
811
- return context.highlight.edge;
812
- }
813
- return "1px";
814
- }).transition().duration(this.transitionDuration())
815
- .style("opacity", function (o) {
816
- if (!edgeMap || edgeMap[o.id()]) {
817
- return 1;
818
- }
819
- return context.highlight.opacity;
820
- })
821
- ;
822
- return this;
823
- }
824
-
825
- highlightVertex(_element, d) {
826
- if (this.highlightOnMouseOverVertex()) {
827
- if (d) {
828
- const highlight = this.getNeighborMap(d);
829
- highlight.vertices[d.id()] = d;
830
- this.highlightVerticies(highlight.vertices);
831
- this.highlightEdges(highlight.edges);
832
- } else {
833
- this.highlightVerticies(null);
834
- this.highlightEdges(null);
835
- }
836
- }
837
- }
838
-
839
- highlightEdge(_element, d) {
840
- if (this.highlightOnMouseOverEdge()) {
841
- if (d) {
842
- const vertices = {};
843
- vertices[d._sourceVertex.id()] = d._sourceVertex;
844
- vertices[d._targetVertex.id()] = d._targetVertex;
845
- const edges = {};
846
- edges[d.id()] = d;
847
- this.highlightVerticies(vertices);
848
- this.highlightEdges(edges);
849
- } else {
850
- this.highlightVerticies(null);
851
- this.highlightEdges(null);
852
- }
853
- }
854
- }
855
-
856
- refreshIncidentEdges(d, skipPushMarkers) {
857
- this._graphData.nodeEdges(d.id()).forEach(glEdge => {
858
- const edge: any = this._graphData.edge(glEdge);
859
- edge
860
- .points([], false, skipPushMarkers)
861
- ;
862
- });
863
- }
864
-
865
- // Events ---
866
- centroids(): Vertex[] {
867
- return (this._graphData.nodes() as any[]).filter(vertex => vertex.centroid());
868
- }
869
-
870
- selectionChanged() {
871
- if (this.highlightSelectedPathToCentroid()) {
872
- const highlightedEdges = {};
873
- this.centroids().forEach(centroid => {
874
- this.selection().forEach(selection => {
875
- this._graphData.undirectedShortestPath(centroid.id(), selection.id()).forEach(e => {
876
- highlightedEdges[e.id()] = true;
877
- });
878
- });
879
- });
880
- this.svgE.selectAll(".graphEdge")
881
- .classed("shortest-path", d => highlightedEdges[d.id()] === true)
882
- ;
883
- }
884
- }
885
-
886
- vertex_click(_row, _col, _sel, more) {
887
- if (more && more.vertex) {
888
- more.vertex._placeholderElement.node().parentNode.appendChild(more.vertex._placeholderElement.node());
889
- }
890
- IGraph.prototype.vertex_click.apply(this, arguments);
891
- }
892
-
893
- vertex_dblclick(_row, _col, _sel, _opts) {
894
- }
895
-
896
- vertex_contextmenu(_row, _col, _sel, _opts) {
897
- }
898
-
899
- vertex_mouseover(element, d) {
900
- this.highlightVertex(element, d);
901
- }
902
-
903
- vertex_mouseout(_element, _d) {
904
- this.highlightVertex(null, null);
905
- }
906
-
907
- edge_mouseover(element, d) {
908
- this.highlightEdge(element, d);
909
- }
910
-
911
- edge_mouseout(_element, _d) {
912
- this.highlightEdge(null, null);
913
- }
914
-
915
- addMarkers(clearFirst: boolean = false) {
916
- if (clearFirst) {
917
- this.defs.select("#" + this._id + "_arrowHead").remove();
918
- this.defs.select("#" + this._id + "_circleFoot").remove();
919
- this.defs.select("#" + this._id + "_circleHead").remove();
920
- this.defs.select("#" + this._id + "_glow").remove();
921
- }
922
- this.defs.append("marker")
923
- .attr("class", "marker")
924
- .attr("id", this._id + "_arrowHead")
925
- .attr("viewBox", "0 0 10 10")
926
- .attr("refX", 10)
927
- .attr("refY", 5)
928
- .attr("markerWidth", 8)
929
- .attr("markerHeight", 8)
930
- .attr("markerUnits", "strokeWidth")
931
- .attr("orient", "auto")
932
- .append("polyline")
933
- .attr("points", "0,0 10,5 0,10 1,5")
934
- ;
935
- this.defs.append("marker")
936
- .attr("class", "marker")
937
- .attr("id", this._id + "_circleFoot")
938
- .attr("viewBox", "0 0 10 10")
939
- .attr("refX", 1)
940
- .attr("refY", 5)
941
- .attr("markerWidth", 7)
942
- .attr("markerHeight", 7)
943
- .attr("markerUnits", "strokeWidth")
944
- .attr("orient", "auto")
945
- .append("circle")
946
- .attr("cx", 5)
947
- .attr("cy", 5)
948
- .attr("r", 4)
949
- ;
950
- this.defs.append("marker")
951
- .attr("class", "marker")
952
- .attr("id", this._id + "_circleHead")
953
- .attr("viewBox", "0 0 10 10")
954
- .attr("refX", 9)
955
- .attr("refY", 5)
956
- .attr("markerWidth", 7)
957
- .attr("markerHeight", 7)
958
- .attr("markerUnits", "strokeWidth")
959
- .attr("orient", "auto")
960
- .append("circle")
961
- .attr("cx", 5)
962
- .attr("cy", 5)
963
- .attr("r", 4)
964
- ;
965
- }
966
-
967
- // Progess Events ---
968
- progress(what: "start" | "end" | "layout-start" | "layout-tick" | "layout-end") {
969
- }
970
- }
971
- Graph.prototype._class += " graph_Graph";
972
- Graph.prototype.implements(IGraph.prototype);
973
- Graph.prototype.implements(ITooltip.prototype);
974
-
975
- export interface Graph {
976
- allowDragging(): boolean;
977
- allowDragging(_: boolean): this;
978
- dragSingleNeighbors(): boolean;
979
- dragSingleNeighbors(_: boolean): this;
980
- layout(): GraphLayoutType;
981
- layout(_: GraphLayoutType): this;
982
- // scale(): string;
983
- // scale(_: string): this;
984
- applyScaleOnLayout(): boolean;
985
- applyScaleOnLayout(_: boolean): this;
986
- highlightOnMouseOverVertex(): boolean;
987
- highlightOnMouseOverVertex(_: boolean): this;
988
- highlightOnMouseOverEdge(): boolean;
989
- highlightOnMouseOverEdge(_: boolean): this;
990
- transitionDuration(): number;
991
- transitionDuration(_: number): this;
992
- showEdges(): boolean;
993
- showEdges(_: boolean): this;
994
- snapToGrid(): number;
995
- snapToGrid(_: number): this;
996
- selectionClearOnBackgroundClick(): boolean;
997
- selectionClearOnBackgroundClick(_: boolean): this;
998
-
999
- centroidColor(): string;
1000
- centroidColor(_: string): this;
1001
- highlightSelectedPathToCentroid(): boolean;
1002
- highlightSelectedPathToCentroid(_: boolean): this;
1003
-
1004
- hierarchyRankDirection(): string;
1005
- hierarchyRankDirection(_: string): this;
1006
- hierarchyNodeSeparation(): number;
1007
- hierarchyNodeSeparation(_: number): this;
1008
- hierarchyEdgeSeparation(): number;
1009
- hierarchyEdgeSeparation(_: number): this;
1010
- hierarchyRankSeparation(): number;
1011
- hierarchyRankSeparation(_: number): this;
1012
- hierarchyDigraph(): boolean;
1013
- hierarchyDigraph(_: boolean): this;
1014
-
1015
- forceDirectedLinkDistance(): number;
1016
- forceDirectedLinkDistance(_: number): this;
1017
- forceDirectedLinkStrength(): number;
1018
- forceDirectedLinkStrength(_: number): this;
1019
- forceDirectedFriction(): number;
1020
- forceDirectedFriction(_: number): this;
1021
- forceDirectedCharge(): number;
1022
- forceDirectedCharge(_: number): this;
1023
- forceDirectedChargeDistance(): number;
1024
- forceDirectedChargeDistance(_: number): this;
1025
- forceDirectedTheta(): number;
1026
- forceDirectedTheta(_: number): this;
1027
- forceDirectedGravity(): number;
1028
- forceDirectedGravity(_: number): this;
1029
-
1030
- // IGraph ---
1031
- edge_click(_row, _col, _sel, _more): void;
1032
- edge_dblclick(_row, _col, _sel, _more): void;
1033
-
1034
- // ITooltip ---
1035
- tooltip;
1036
- tooltipHTML(_): string;
1037
- tooltipFormat(_): string;
1038
- }
1039
-
1040
- Graph.prototype.publish("allowDragging", true, "boolean", "Allow Dragging of Vertices", null, { tags: ["Advanced"] });
1041
- Graph.prototype.publish("dragSingleNeighbors", false, "boolean", "Dragging a Vertex also moves its singleton neighbors", null, { tags: ["Advanced"] });
1042
- Graph.prototype.publish("layout", "Circle", "set", "Default Layout", ["Circle", "ForceDirected", "ForceDirected2", "Hierarchy", "None"], { tags: ["Basic"] });
1043
- Graph.prototype.publish("scale", "100%", "set", "Zoom Level", ["all", "width", "selection", "100%", "90%", "75%", "50%", "25%", "10%"], { tags: ["Basic"] });
1044
- Graph.prototype.publish("applyScaleOnLayout", false, "boolean", "Shrink to fit on Layout", null, { tags: ["Basic"] });
1045
- Graph.prototype.publish("highlightOnMouseOverVertex", false, "boolean", "Highlight Vertex on Mouse Over", null, { tags: ["Basic"] });
1046
- Graph.prototype.publish("highlightOnMouseOverEdge", false, "boolean", "Highlight Edge on Mouse Over", null, { tags: ["Basic"] });
1047
- Graph.prototype.publish("transitionDuration", 250, "number", "Transition Duration", null, { tags: ["Intermediate"] });
1048
- Graph.prototype.publish("showEdges", true, "boolean", "Show Edges", null, { tags: ["Intermediate"] });
1049
- Graph.prototype.publish("snapToGrid", 0, "number", "Snap to Grid", null, { tags: ["Private"] });
1050
- Graph.prototype.publish("selectionClearOnBackgroundClick", false, "boolean", "Clear selection on background click");
1051
-
1052
- Graph.prototype.publish("centroidColor", "#00A000", "html-color", "Centroid Color", null, { tags: ["Basic"] });
1053
- Graph.prototype.publish("highlightSelectedPathToCentroid", false, "boolean", "Highlight path to Center Vertex (for selected vertices)", null, { tags: ["Basic"] });
1054
-
1055
- Graph.prototype.publish("hierarchyRankDirection", "TB", "set", "Direction for Rank Nodes", ["TB", "BT", "LR", "RL"], { tags: ["Advanced"] });
1056
- Graph.prototype.publish("hierarchyNodeSeparation", 50, "number", "Number of pixels that separate nodes horizontally in the layout", null, { tags: ["Advanced"] });
1057
- Graph.prototype.publish("hierarchyEdgeSeparation", 10, "number", "Number of pixels that separate edges horizontally in the layout", null, { tags: ["Advanced"] });
1058
- Graph.prototype.publish("hierarchyRankSeparation", 50, "number", "Number of pixels between each rank in the layout", null, { tags: ["Advanced"] });
1059
- Graph.prototype.publish("hierarchyDigraph", true, "boolean", "Directional Graph", null, { tags: ["Advanced"] });
1060
-
1061
- Graph.prototype.publish("forceDirectedLinkDistance", 300, "number", "Target distance between linked nodes", null, { tags: ["Advanced"] });
1062
- Graph.prototype.publish("forceDirectedLinkStrength", 1, "number", "Strength (rigidity) of links", null, { tags: ["Advanced"] });
1063
- Graph.prototype.publish("forceDirectedFriction", 0.9, "number", "Friction coefficient", null, { tags: ["Advanced"] });
1064
- Graph.prototype.publish("forceDirectedCharge", -25, "number", "Charge strength ", null, { tags: ["Advanced"] });
1065
- Graph.prototype.publish("forceDirectedChargeDistance", 10000, "number", "Maximum distance over which charge forces are applied", null, { tags: ["Advanced"] });
1066
- Graph.prototype.publish("forceDirectedTheta", 0.8, "number", "Barnes–Hut approximation criterion", null, { tags: ["Advanced"] });
1067
- Graph.prototype.publish("forceDirectedGravity", 0.1, "number", "Gravitational strength", null, { tags: ["Advanced"] });
1068
-
1069
- const _origScale = Graph.prototype.scale;
1070
- //@ts-ignore
1071
- Graph.prototype.scale = function (_?, transitionDuration?) {
1072
- const retVal = _origScale.apply(this, arguments as any);
1073
- if (arguments.length) {
1074
- this.zoomTo(_, transitionDuration);
1075
- }
1076
- return retVal;
1077
- };
1
+ import { IGraph, ITooltip } from "@hpcc-js/api";
2
+ import { d3Event, drag as d3Drag, ISize, Platform, select as d3Select, Spacer, SVGGlowFilter, SVGZoomWidget, ToggleButton, Utility, Widget } from "@hpcc-js/common";
3
+
4
+ import "d3-transition";
5
+ import { Edge } from "./Edge.ts";
6
+ import { GraphData } from "./GraphData.ts";
7
+ import * as GraphLayouts from "./GraphLayouts.ts";
8
+ import { Subgraph } from "./Subgraph.ts";
9
+ import { Vertex } from "./Vertex.ts";
10
+
11
+ import "./Graph.css";
12
+
13
+ export interface Lineage {
14
+ parent: Widget;
15
+ child: Widget;
16
+ }
17
+ export interface IGraphData {
18
+ subgraphs?: Widget[];
19
+ vertices: Widget[];
20
+ edges: Edge[];
21
+ hierarchy?: Lineage[];
22
+ }
23
+ export type GraphLayoutType = "Hierarchy" | "DOT" | "ForceDirected" | "ForceDirected2" | "Neato" | "FDP" | "Circle" | "TwoPI" | "Circo" | "None";
24
+
25
+ export class Graph extends SVGZoomWidget {
26
+ static Subgraph = Subgraph;
27
+ static Vertex = Vertex;
28
+ static Edge = Edge;
29
+
30
+ private _toggleHierarchy = new ToggleButton().faChar("fa-sitemap").tooltip("Hierarchy").on("click", () => this.layoutClick("Hierarchy"));
31
+ private _toggleForceDirected = new ToggleButton().faChar("fa-expand").tooltip("Force Directed").on("click", () => this.layoutClick("ForceDirected"));
32
+ private _toggleForceDirected2 = new ToggleButton().faChar("fa-arrows").tooltip("Spring").on("click", () => this.layoutClick("ForceDirected2"));
33
+ private _toggleCircle = new ToggleButton().faChar("fa-circle-o").tooltip("Circle").on("click", () => this.layoutClick("Circle"));
34
+
35
+ private _graphData: GraphData;
36
+ protected highlight;
37
+ protected _selection;
38
+ protected _dragging;
39
+ protected forceLayout;
40
+ protected _d3Drag;
41
+ protected defs;
42
+ protected _centroidFilter: SVGGlowFilter;
43
+ protected svgFragment;
44
+ protected svg;
45
+ protected svgC;
46
+ protected svgE;
47
+ protected svgV;
48
+
49
+ constructor() {
50
+ super();
51
+ IGraph.call(this);
52
+ ITooltip.call(this);
53
+ this.tooltipHTML(function (d: any) {
54
+ let content;
55
+ if (d instanceof Subgraph) {
56
+ content = d.title().replace(/\n/g, "<br>");
57
+ } else if (d instanceof Vertex || d instanceof Edge) {
58
+ content = d.text().replace(/\n/g, "<br>");
59
+ }
60
+ if (content) {
61
+ return `<p style="text-align:center">${content}</p>`;
62
+ }
63
+ return null;
64
+ });
65
+
66
+ this._drawStartPos = "origin";
67
+
68
+ const buttons: Widget[] = [
69
+ this._toggleHierarchy,
70
+ this._toggleForceDirected,
71
+ this._toggleForceDirected2,
72
+ this._toggleCircle,
73
+ new Spacer()];
74
+ this._iconBar.buttons(buttons.concat(this._iconBar.buttons()));
75
+
76
+ this._graphData = new GraphData();
77
+ this.highlight = {
78
+ zoom: 1.1,
79
+ opacity: 0.33,
80
+ edge: "1.25px"
81
+ };
82
+ this._selection = new Utility.Selection(this);
83
+ this.zoomToFitLimit(1);
84
+ }
85
+
86
+ iconBarButtons(): Widget[] {
87
+ return this._iconBar.buttons();
88
+ }
89
+
90
+ layoutClick(layout: GraphLayoutType) {
91
+ this.layout(layout);
92
+ if (layout !== "ForceDirected2") this.applyScaleOnLayout(true);
93
+ this
94
+ .layout(layout)
95
+ .render(w => {
96
+ this.applyScaleOnLayout(false);
97
+ });
98
+ }
99
+
100
+ // Properties ---
101
+ getOffsetPos() {
102
+ return { x: 0, y: 0 };
103
+ }
104
+
105
+ size(): ISize;
106
+ size(_): this;
107
+ size(_?): ISize | this {
108
+ const retVal = super.size.apply(this, arguments as any);
109
+ return retVal;
110
+ }
111
+
112
+ clear() {
113
+ this.data({ subgraphs: [], vertices: [], edges: [], hierarchy: [] }, false);
114
+ }
115
+
116
+ _dataHash = 0;
117
+ data(): IGraphData;
118
+ data(_: IGraphData, merge?: boolean): this;
119
+ data(_?: IGraphData, merge?: boolean): IGraphData | this {
120
+ const retVal = super.data.apply(this, arguments as any);
121
+ if (arguments.length) {
122
+ if (!merge) {
123
+ this._graphData = new GraphData();
124
+ this._renderCount = 0;
125
+ }
126
+ const data = this._graphData.setData(_.subgraphs || [], _.vertices || [], _.edges || [], _.hierarchy || [], merge || false);
127
+ if (data.addedVertices.length) {
128
+ this._dataHash++;
129
+ }
130
+
131
+ const context = this;
132
+ data.addedVertices.forEach(function (item) {
133
+ item._graphID = context._id;
134
+ });
135
+ data.addedEdges.forEach(function (item) {
136
+ item._graphID = context._id;
137
+ });
138
+
139
+ // Recalculate edge arcs ---
140
+ const dupMap = {};
141
+ this._graphData.edges().forEach(function (item) {
142
+ if (!dupMap[item._sourceVertex._id]) {
143
+ dupMap[item._sourceVertex._id] = {};
144
+ }
145
+ if (!dupMap[item._sourceVertex._id][item._targetVertex._id]) {
146
+ dupMap[item._sourceVertex._id][item._targetVertex._id] = 0;
147
+ }
148
+ const dupEdgeCount = ++dupMap[item._sourceVertex._id][item._targetVertex._id];
149
+ item.arcDepth(16 * dupEdgeCount);
150
+ });
151
+ }
152
+ return retVal;
153
+ }
154
+
155
+ graphData(): any {
156
+ return this._graphData;
157
+ }
158
+
159
+ selection(_: Widget[]): this;
160
+ selection(): Widget[];
161
+ selection(_?: Widget[]): Widget[] | this {
162
+ if (!arguments.length) return this._selection.get();
163
+ this._selection.set(_);
164
+ return this;
165
+ }
166
+
167
+ private _linkcolor: string;
168
+ linkcolor_default(): string;
169
+ linkcolor_default(_: string): this;
170
+ linkcolor_default(_?: string): string | this {
171
+ if (!arguments.length) return this._linkcolor;
172
+ this._linkcolor = _;
173
+ return this;
174
+ }
175
+
176
+ private _linktooltip: string;
177
+ linktooltip_default(): string;
178
+ linktooltip_default(_: string): this;
179
+ linktooltip_default(_?: string): string | this {
180
+ if (!arguments.length) return this._linktooltip;
181
+ this._linktooltip = _;
182
+ return this;
183
+ }
184
+
185
+ // Drag ---
186
+ private _neighborOffsets: Array<{ neighbor: Vertex, offsetX: number, offsetY: number }> = [];
187
+ dragstart(d) {
188
+ if (this.allowDragging()) {
189
+ const event = d3Event();
190
+ event.sourceEvent.stopPropagation();
191
+
192
+ d.__drag_dx = event.x - d.x();
193
+ d.__drag_dy = event.y - d.y();
194
+ this._dragging = true;
195
+ if (this.forceLayout) {
196
+ if (!event.active) this.forceLayout.force.alphaTarget(0.3).restart();
197
+ const forceNode = this.forceLayout.vertexMap[d.id()];
198
+ forceNode.fixed = true;
199
+ forceNode.fx = forceNode.x;
200
+ forceNode.fy = forceNode.y;
201
+ }
202
+
203
+ this._neighborOffsets = [];
204
+ if (this.dragSingleNeighbors()) {
205
+ this._neighborOffsets = this._graphData.singleNeighbors(d.id()).map((neighbor: any) => {
206
+ d3Select(neighbor.target()).raise();
207
+ return {
208
+ neighbor,
209
+ offsetX: d.x() - neighbor.x(),
210
+ offsetY: d.y() - neighbor.y()
211
+ };
212
+ });
213
+ }
214
+
215
+ // Safe Raise - does not interfere with current click event ---
216
+ const target = d.target();
217
+ let nextSibling = target.nextSibling;
218
+ while (nextSibling) {
219
+ target.parentNode.insertBefore(nextSibling, target);
220
+ nextSibling = target.nextSibling;
221
+ }
222
+
223
+ if (Platform.svgMarkerGlitch) {
224
+ this._graphData.nodeEdges(d.id()).forEach(glEdge => {
225
+ const edge = this._graphData.edge(glEdge);
226
+ this._pushMarkers(edge.element());
227
+ });
228
+ }
229
+ }
230
+ }
231
+
232
+ dragging(d) {
233
+ if (this.allowDragging()) {
234
+ const event = d3Event();
235
+ event.sourceEvent.stopPropagation();
236
+ d.move({ x: event.x - d.__drag_dx, y: event.y - d.__drag_dy });
237
+ if (this.forceLayout) {
238
+ const forceNode = this.forceLayout.vertexMap[d.id()];
239
+ forceNode.fixed = true;
240
+ forceNode.fx = event.x - d.__drag_dx;
241
+ forceNode.fy = event.y - d.__drag_dy;
242
+ }
243
+
244
+ // Drag singleton child nodes
245
+ this._neighborOffsets.forEach(neighborOffset => {
246
+ const neighborX = event.x - d.__drag_dx - neighborOffset.offsetX;
247
+ const neighborY = event.y - d.__drag_dy - neighborOffset.offsetY;
248
+ if (this.forceLayout) {
249
+ const forceNode = this.forceLayout.vertexMap[neighborOffset.neighbor.id()];
250
+ forceNode.fixed = true;
251
+ forceNode.fx = neighborX;
252
+ forceNode.fy = neighborY;
253
+ }
254
+ neighborOffset.neighbor.move({ x: neighborX, y: neighborY });
255
+ });
256
+
257
+ this.refreshIncidentEdges(d, true);
258
+ }
259
+ }
260
+
261
+ dragend(d) {
262
+ if (this.allowDragging()) {
263
+ d3Event().sourceEvent.stopPropagation();
264
+ this._dragging = false;
265
+ if (this.snapToGrid()) {
266
+ const snapLoc = d.calcSnap(this.snapToGrid());
267
+ d.move(snapLoc[0]);
268
+ this.refreshIncidentEdges(d, true);
269
+ }
270
+ if (this.forceLayout) {
271
+ const forceNode = this.forceLayout.vertexMap[d.id()];
272
+ forceNode.fixed = false;
273
+ forceNode.fx = null;
274
+ forceNode.fy = null;
275
+
276
+ this._neighborOffsets.forEach(neighborOffset => {
277
+ const forceNode = this.forceLayout.vertexMap[neighborOffset.neighbor.id()];
278
+ forceNode.fixed = false;
279
+ forceNode.fx = null;
280
+ forceNode.fy = null;
281
+ });
282
+ }
283
+ this._neighborOffsets = [];
284
+
285
+ if (Platform.svgMarkerGlitch) {
286
+ this._graphData.nodeEdges(d.id()).forEach(function (this, id) {
287
+ const edge = this._graphData.edge(id);
288
+ this._popMarkers(edge.element());
289
+ });
290
+ }
291
+ }
292
+ }
293
+
294
+ enter(domNode, element) {
295
+ super.enter(domNode, element);
296
+
297
+ this._zoomGrab.on("click.clear", () => {
298
+ if (this.selectionClearOnBackgroundClick()) {
299
+ this._selection.clear();
300
+ }
301
+ });
302
+
303
+ this._d3Drag = d3Drag()
304
+ // .origin(function (d) {
305
+ // return d.pos();
306
+ // })
307
+ .on("start", d => this.dragstart(d))
308
+ .on("end", d => this.dragend(d))
309
+ .on("drag", d => this.dragging(d))
310
+ ;
311
+ // SVG ---
312
+ this.defs = this._renderElement.append("defs");
313
+ this.addMarkers();
314
+ this._centroidFilter = new SVGGlowFilter(this.defs, this._id + "_glow");
315
+
316
+ // element.call(this.zoom);
317
+ this.svg = this._renderElement.append("svg:g");
318
+ // this._svgBrush = this.svg.append("g").attr("class", "selectionBrush").call(this.brush);
319
+ // this._svgBrush.select(".background").style("cursor", null);
320
+ // context._svgBrush.call(context.brush.clear());
321
+ this.svgC = this.svg.append("g").attr("id", this._id + "C");
322
+ this.svgE = this.svg.append("g").attr("id", this._id + "E");
323
+ this.svgV = this.svg.append("g").attr("id", this._id + "V");
324
+ }
325
+
326
+ getBounds(items, layoutEngine?) {
327
+ const vBounds = [[null, null], [null, null]];
328
+ items.forEach(function (item) {
329
+ const pos = layoutEngine ? layoutEngine.nodePos(item._id) : { x: item.x(), y: item.y(), width: item.width(), height: item.height() };
330
+ const leftX = pos.x - pos.width / 2;
331
+ const rightX = pos.x + pos.width / 2;
332
+ const topY = pos.y - pos.height / 2;
333
+ const bottomY = pos.y + pos.height / 2;
334
+ if (vBounds[0][0] === null || vBounds[0][0] > leftX) {
335
+ vBounds[0][0] = leftX;
336
+ }
337
+ if (vBounds[0][1] === null || vBounds[0][1] > topY) {
338
+ vBounds[0][1] = topY;
339
+ }
340
+ if (vBounds[1][0] === null || vBounds[1][0] < rightX) {
341
+ vBounds[1][0] = rightX;
342
+ }
343
+ if (vBounds[1][1] === null || vBounds[1][1] < bottomY) {
344
+ vBounds[1][1] = bottomY;
345
+ }
346
+ });
347
+ return vBounds;
348
+ }
349
+
350
+ getVertexBounds(layoutEngine) {
351
+ return this.getBounds(this._graphData.nodes(), layoutEngine);
352
+ }
353
+
354
+ getSelectionBounds(layoutEngine) {
355
+ return this.getBounds(this._selection.get(), layoutEngine);
356
+ }
357
+
358
+ centerOnItem(item: Widget) {
359
+ const bbox = item.getBBox(true);
360
+ const deltaX = bbox.x + bbox.width / 2;
361
+ const deltaY = bbox.y + bbox.height / 2;
362
+ const itemBBox = {
363
+ x: item.x() + deltaX - bbox.width / 2,
364
+ y: item.y() + deltaY - bbox.height / 2,
365
+ width: bbox.width,
366
+ height: bbox.height
367
+ };
368
+ this.centerOnBBox(itemBBox);
369
+ }
370
+
371
+ zoomToItem(item: Widget) {
372
+ const bbox = item.getBBox(true);
373
+ const deltaX = bbox.x + bbox.width / 2;
374
+ const deltaY = bbox.y + bbox.height / 2;
375
+ const itemBBox = {
376
+ x: item.x() + deltaX - bbox.width / 2,
377
+ y: item.y() + deltaY - bbox.height / 2,
378
+ width: bbox.width,
379
+ height: bbox.height
380
+ };
381
+ this.zoomToBBox(itemBBox);
382
+ }
383
+
384
+ // Render ---
385
+ updateVertices(rootElement, rootSuffix, data) {
386
+ const width = this.width();
387
+ const height = this.height();
388
+ const context = this;
389
+
390
+ const vertexElements = rootElement.selectAll("#" + this._id + rootSuffix + " > .graphVertex").data(data, function (d) { return d.id(); });
391
+ vertexElements.enter().append("g")
392
+ .attr("class", "graphVertex")
393
+ .style("opacity", 1e-6)
394
+ // TODO: Events need to be optional ---
395
+ .on("click.selectionBag", function (d) {
396
+ context._selection.click(d, d3Event());
397
+ context.selectionChanged();
398
+ })
399
+ .on("click", function (this: SVGElement, d) {
400
+ const vertexElement = d3Select(this).select(".graph_Vertex");
401
+ let selected = false;
402
+ if (!vertexElement.empty()) {
403
+ selected = vertexElement.classed("selected");
404
+ }
405
+ context.vertex_click(context.rowToObj(d.data()), "", selected, {
406
+ vertex: d
407
+ });
408
+ })
409
+ .on("dblclick", function (this: SVGElement, d) {
410
+ d3Event().stopPropagation();
411
+ const vertexElement = d3Select(this).select(".graph_Vertex");
412
+ let selected = false;
413
+ if (!vertexElement.empty()) {
414
+ selected = vertexElement.classed("selected");
415
+ }
416
+ context.vertex_dblclick(context.rowToObj(d.data()), "", selected, {
417
+ vertex: d
418
+ });
419
+ })
420
+ .on("contextmenu", function (this: SVGElement, d) {
421
+ const vertexElement = d3Select(this).select(".graph_Vertex");
422
+ let selected = false;
423
+ if (!vertexElement.empty()) {
424
+ selected = vertexElement.classed("selected");
425
+ }
426
+ context.vertex_contextmenu(context.rowToObj(d.data()), "", selected, {
427
+ vertex: d
428
+ });
429
+ })
430
+ .on("mouseout.tooltip", this.tooltip.hide)
431
+ .on("mousemove.tooltip", this.tooltip.show)
432
+ .on("mouseover", function (this: SVGElement, d) {
433
+ if (context._dragging)
434
+ return;
435
+ context.vertex_mouseover(d3Select(this), d);
436
+ })
437
+ .on("mouseout", function (this: SVGElement, d) {
438
+ if (context._dragging)
439
+ return;
440
+ context.vertex_mouseout(d3Select(this), d);
441
+ })
442
+ .each(createV)
443
+ .transition()
444
+ .duration(750)
445
+ .style("opacity", 1)
446
+ ;
447
+ function createV(this: SVGElement, d) {
448
+ d3Select(this).style("cursor", context.allowDragging() ? "move" : "pointer");
449
+ d
450
+ .target(this)
451
+ .pos({ x: d.x() || width / 2, y: d.y() || height / 2 })
452
+ .animationFrameRender()
453
+ ;
454
+ if (context.allowDragging()) {
455
+ d3Select(this)
456
+ .call(context._d3Drag)
457
+ ;
458
+ }
459
+ if (d.dispatch) {
460
+ d.dispatch.on("sizestart", function (d2) {
461
+ d2.allowResize(context.allowDragging());
462
+ if (context.allowDragging()) {
463
+ context._dragging = true;
464
+ }
465
+ });
466
+ d.dispatch.on("size", function (d2) {
467
+ context.refreshIncidentEdges(d2, false);
468
+ });
469
+ d.dispatch.on("sizeend", function (d2) {
470
+ context._dragging = false;
471
+ if (context.snapToGrid()) {
472
+ const snapLoc = d2.calcSnap(context.snapToGrid());
473
+ d2
474
+ .pos(snapLoc[0])
475
+ .size(snapLoc[1])
476
+ .render()
477
+ ;
478
+ context.refreshIncidentEdges(d2, false);
479
+ }
480
+ });
481
+ }
482
+ }
483
+
484
+ vertexElements
485
+ .each(updateV)
486
+ ;
487
+ function updateV(d) {
488
+ d
489
+ .animationFrameRender()
490
+ ;
491
+ }
492
+
493
+ vertexElements.exit()
494
+ .each(function (d) {
495
+ d.target(null);
496
+ })
497
+ .remove()
498
+ ;
499
+
500
+ vertexElements.order();
501
+ }
502
+
503
+ update(domNode, element) {
504
+ super.update(domNode, element);
505
+ this.tooltip.hide();
506
+ this._centroidFilter.update(this.centroidColor());
507
+
508
+ // IconBar ---
509
+ const layout = this.layout();
510
+ this._toggleHierarchy.selected(layout === "Hierarchy").render();
511
+ this._toggleForceDirected.selected(layout === "ForceDirected").render();
512
+ this._toggleForceDirected2.selected(layout === "ForceDirected2").render();
513
+ this._toggleCircle.selected(layout === "Circle").render();
514
+
515
+ // Create ---
516
+ const context = this;
517
+
518
+ this.updateVertices(this.svgC, "C", this._graphData.nodes().filter(v => this.layout() === "Hierarchy" ? (v instanceof Subgraph) : false));
519
+ this.updateVertices(this.svgV, "V", this._graphData.nodes().filter(v => !(v instanceof Subgraph)));
520
+
521
+ const edgeElements = this.svgE.selectAll("#" + this._id + "E > .graphEdge").data(this.showEdges() ? this._graphData.edges() : [], function (d) { return d.id(); });
522
+ edgeElements.enter().append("g")
523
+ .attr("class", "graphEdge")
524
+ .style("opacity", 1e-6)
525
+ .on("click.selectionBag", function (d) {
526
+ context._selection.click(d, d3Event());
527
+ })
528
+ .on("click", function (this: SVGElement, d) {
529
+ const edgeElement = d3Select(this).select(".graph_Edge");
530
+ let selected = false;
531
+ if (!edgeElement.empty()) {
532
+ selected = edgeElement.classed("selected");
533
+ }
534
+ context.edge_click(context.rowToObj(d.data()), "", selected, {
535
+ edge: d
536
+ });
537
+ })
538
+ .on("dblclick", function (this: SVGElement, d) {
539
+ const edgeElement = d3Select(this).select(".graph_Edge");
540
+ let selected = false;
541
+ if (!edgeElement.empty()) {
542
+ selected = edgeElement.classed("selected");
543
+ }
544
+ context.edge_dblclick(context.rowToObj(d.data()), "", selected, {
545
+ edge: d
546
+ });
547
+ })
548
+ .on("mouseout.tooltip", this.tooltip.hide)
549
+ .on("mousemove.tooltip", this.tooltip.show)
550
+ .on("mouseover", function (this: SVGElement, d) {
551
+ if (context._dragging)
552
+ return;
553
+ context.edge_mouseover(d3Select(this), d);
554
+ })
555
+ .on("mouseout", function (this: SVGElement, d) {
556
+ if (context._dragging)
557
+ return;
558
+ context.edge_mouseout(d3Select(this), d);
559
+ })
560
+ .each(createE)
561
+ .transition()
562
+ .duration(750)
563
+ .style("opacity", 1)
564
+ ;
565
+ function createE(this: SVGElement, d) {
566
+ d
567
+ .target(this)
568
+ .animationFrameRender()
569
+ ;
570
+ }
571
+
572
+ edgeElements
573
+ .each(updateE)
574
+ ;
575
+ function updateE(d) {
576
+ d
577
+ .animationFrameRender()
578
+ ;
579
+ }
580
+
581
+ edgeElements.exit()
582
+ .each(function (d) {
583
+ d.target(null);
584
+ })
585
+ .remove()
586
+ ;
587
+
588
+ if (!this._renderCount) {
589
+ this._renderCount++;
590
+ this.layout(this.layout());
591
+ }
592
+ }
593
+
594
+ exit(domNode, element) {
595
+ this._graphData.nodes().forEach(v => v.target(null));
596
+ this._graphData.edges().forEach(e => e.target(null));
597
+ super.exit(domNode, element);
598
+ }
599
+
600
+ static profileID = 0;
601
+ render(callback?: (w: Widget) => void): this {
602
+ this.progress("start");
603
+ super.render(w => {
604
+ this.doLayout().then(() => {
605
+ this.progress("end");
606
+ if (callback) {
607
+ callback(w);
608
+ }
609
+ });
610
+ });
611
+ return this;
612
+ }
613
+
614
+ // Methods ---
615
+ _prevLayout;
616
+ _prevDataHash;
617
+ doLayout(transitionDuration = 0): Promise<void> {
618
+ return new Promise((resolve, reject) => {
619
+ requestAnimationFrame(() => {
620
+ if (this._prevLayout !== this.layout() || this._prevDataHash !== this._dataHash) {
621
+ this._prevLayout = this.layout();
622
+ this._prevDataHash = this._dataHash;
623
+ this._doLayout(transitionDuration).then(() => {
624
+ resolve();
625
+ });
626
+ } else {
627
+ resolve();
628
+ }
629
+ });
630
+ });
631
+ }
632
+
633
+ _doLayout(transitionDuration = 0): Promise<void> {
634
+ return new Promise((resolve, reject) => {
635
+ this.progress("layout-start");
636
+ if (this.forceLayout) {
637
+ this.forceLayout.force.stop();
638
+ this.forceLayout = null;
639
+ }
640
+
641
+ const context = this;
642
+ const layoutEngine = this.getLayoutEngine() as GraphLayouts.ForceDirected;
643
+ if (this.layout() === "ForceDirected2") {
644
+ this.forceLayout = layoutEngine;
645
+ this.forceLayout.force
646
+ .on("tick", function (this: SVGElement) {
647
+ context.progress("layout-tick");
648
+ layoutEngine.vertices.forEach(function (item) {
649
+ if (item.fixed) {
650
+ // item.x = item.px;
651
+ // item.y = item.py;
652
+ } else {
653
+ // item.px = item.x;
654
+ // item.py = item.y;
655
+
656
+ // Might have been cleared ---
657
+ const vertex = context._graphData.node(item.id);
658
+ if (vertex) {
659
+ vertex
660
+ .move({ x: item.x, y: item.y })
661
+ ;
662
+ }
663
+ }
664
+ });
665
+ context._graphData.edges().forEach((item: any) => {
666
+ item
667
+ .points([], false, false)
668
+ ;
669
+ });
670
+ if (context.applyScaleOnLayout()) {
671
+ // const vBounds = context.getVertexBounds(layoutEngine);
672
+ // context.shrinkToFit(vBounds);
673
+ }
674
+ })
675
+ .on("end", function (this: SVGElement) {
676
+ context.progress("layout-end");
677
+ })
678
+ ;
679
+ this.forceLayout.force.restart();
680
+ resolve();
681
+ } else if (layoutEngine) {
682
+ this.forceLayout = null;
683
+ context._dragging = true;
684
+ context._graphData.nodes().forEach(function (item) {
685
+ const pos = layoutEngine.nodePos(item.id());
686
+ if (item instanceof Graph.Subgraph) {
687
+ item
688
+ .pos({ x: pos.x, y: pos.y })
689
+ .size({ width: pos.width, height: pos.height })
690
+ .animationFrameRender()
691
+ ;
692
+ } else {
693
+ item.move({ x: pos.x, y: pos.y }, 0);
694
+ }
695
+ });
696
+ context._graphData.edges().forEach((item: any) => {
697
+ const points = layoutEngine.edgePoints(item);
698
+ item.points(points, transitionDuration);
699
+ });
700
+ if (context.applyScaleOnLayout()) {
701
+ requestAnimationFrame(() => {
702
+ context.zoomToFit();
703
+ resolve();
704
+ });
705
+ }
706
+ this._fixIEMarkers();
707
+ setTimeout(function () {
708
+ context._dragging = false;
709
+ }, transitionDuration ? transitionDuration + 50 : 50); // Prevents highlighting during morph ---
710
+ this.progress("layout-end");
711
+ if (!context.applyScaleOnLayout()) {
712
+ resolve();
713
+ }
714
+ }
715
+ });
716
+ }
717
+
718
+ getLayoutEngine() {
719
+ switch (this.layout()) {
720
+ case "Circle":
721
+ return new GraphLayouts.Circle(this._graphData, this._size.width, this._size.height);
722
+ case "ForceDirected":
723
+ return new GraphLayouts.ForceDirected(this._graphData, this._size.width, this._size.height, {
724
+ oneShot: true,
725
+ linkDistance: this.forceDirectedLinkDistance(),
726
+ linkStrength: this.forceDirectedLinkStrength(),
727
+ friction: this.forceDirectedFriction(),
728
+ charge: this.forceDirectedCharge(),
729
+ chargeDistance: this.forceDirectedChargeDistance(),
730
+ theta: this.forceDirectedTheta(),
731
+ gravity: this.forceDirectedGravity()
732
+ });
733
+ case "ForceDirected2":
734
+ return new GraphLayouts.ForceDirected(this._graphData, this._size.width, this._size.height, {
735
+ linkDistance: this.forceDirectedLinkDistance(),
736
+ linkStrength: this.forceDirectedLinkStrength(),
737
+ friction: this.forceDirectedFriction(),
738
+ charge: this.forceDirectedCharge(),
739
+ chargeDistance: this.forceDirectedChargeDistance(),
740
+ theta: this.forceDirectedTheta(),
741
+ gravity: this.forceDirectedGravity()
742
+ });
743
+ case "Hierarchy":
744
+ return new GraphLayouts.Hierarchy(this._graphData, this._size.width, this._size.height, {
745
+ rankdir: this.hierarchyRankDirection(),
746
+ nodesep: this.hierarchyNodeSeparation(),
747
+ edgesep: this.hierarchyEdgeSeparation(),
748
+ ranksep: this.hierarchyRankSeparation(),
749
+ digraph: this.hierarchyDigraph()
750
+ });
751
+ default:
752
+ }
753
+ return null; // new GraphLayouts.None(this.graphData, this._size.width, this._size.height);
754
+ }
755
+
756
+ getNeighborMap(vertex) {
757
+ const vertices = {};
758
+ const edges = {};
759
+
760
+ if (vertex) {
761
+ const nedges = this._graphData.nodeEdges(vertex.id());
762
+ for (let i = 0; i < nedges.length; ++i) {
763
+ const edge = this._graphData.edge(nedges[i]);
764
+ edges[edge.id()] = edge;
765
+ if (edge._sourceVertex.id() !== vertex.id()) {
766
+ vertices[edge._sourceVertex.id()] = edge._sourceVertex;
767
+ }
768
+ if (edge._targetVertex.id() !== vertex.id()) {
769
+ vertices[edge._targetVertex.id()] = edge._targetVertex;
770
+ }
771
+ }
772
+ }
773
+
774
+ return {
775
+ vertices,
776
+ edges
777
+ };
778
+ }
779
+
780
+ highlightVerticies(vertexMap?: { [id: string]: boolean }) {
781
+ const context = this;
782
+ const vertexElements = this.svgV.selectAll(".graphVertex");
783
+ vertexElements
784
+ .classed("graphVertex-highlighted", d => !vertexMap || vertexMap[d.id()])
785
+ .style("filter", d => vertexMap && vertexMap[d.id()] ? "url(#" + this.id() + "_glow)" : null)
786
+ .transition().duration(this.transitionDuration())
787
+ .on("end", function (d) {
788
+ if (vertexMap && vertexMap[d.id()]) {
789
+ if (d._placeholderElement.node() && d._placeholderElement.node().parentNode) {
790
+ d._placeholderElement.node().parentNode.appendChild(d._placeholderElement.node());
791
+ }
792
+ }
793
+ })
794
+ .style("opacity", function (d) {
795
+ if (!vertexMap || vertexMap[d.id()]) {
796
+ return 1;
797
+ }
798
+ return context.highlight.opacity;
799
+ })
800
+ ;
801
+ return this;
802
+ }
803
+
804
+ highlightEdges(edgeMap) {
805
+ const context = this;
806
+ const edgeElements = this.svgE.selectAll(".graphEdge");
807
+ edgeElements
808
+ .classed("graphEdge-highlighted", function (d) { return !edgeMap || edgeMap[d.id()]; })
809
+ .style("stroke-width", function (o) {
810
+ if (edgeMap && edgeMap[o.id()]) {
811
+ return context.highlight.edge;
812
+ }
813
+ return "1px";
814
+ }).transition().duration(this.transitionDuration())
815
+ .style("opacity", function (o) {
816
+ if (!edgeMap || edgeMap[o.id()]) {
817
+ return 1;
818
+ }
819
+ return context.highlight.opacity;
820
+ })
821
+ ;
822
+ return this;
823
+ }
824
+
825
+ highlightVertex(_element, d) {
826
+ if (this.highlightOnMouseOverVertex()) {
827
+ if (d) {
828
+ const highlight = this.getNeighborMap(d);
829
+ highlight.vertices[d.id()] = d;
830
+ this.highlightVerticies(highlight.vertices);
831
+ this.highlightEdges(highlight.edges);
832
+ } else {
833
+ this.highlightVerticies(null);
834
+ this.highlightEdges(null);
835
+ }
836
+ }
837
+ }
838
+
839
+ highlightEdge(_element, d) {
840
+ if (this.highlightOnMouseOverEdge()) {
841
+ if (d) {
842
+ const vertices = {};
843
+ vertices[d._sourceVertex.id()] = d._sourceVertex;
844
+ vertices[d._targetVertex.id()] = d._targetVertex;
845
+ const edges = {};
846
+ edges[d.id()] = d;
847
+ this.highlightVerticies(vertices);
848
+ this.highlightEdges(edges);
849
+ } else {
850
+ this.highlightVerticies(null);
851
+ this.highlightEdges(null);
852
+ }
853
+ }
854
+ }
855
+
856
+ refreshIncidentEdges(d, skipPushMarkers) {
857
+ this._graphData.nodeEdges(d.id()).forEach(glEdge => {
858
+ const edge: any = this._graphData.edge(glEdge);
859
+ edge
860
+ .points([], false, skipPushMarkers)
861
+ ;
862
+ });
863
+ }
864
+
865
+ // Events ---
866
+ centroids(): Vertex[] {
867
+ return (this._graphData.nodes() as any[]).filter(vertex => vertex.centroid());
868
+ }
869
+
870
+ selectionChanged() {
871
+ if (this.highlightSelectedPathToCentroid()) {
872
+ const highlightedEdges = {};
873
+ this.centroids().forEach(centroid => {
874
+ this.selection().forEach(selection => {
875
+ this._graphData.undirectedShortestPath(centroid.id(), selection.id()).forEach(e => {
876
+ highlightedEdges[e.id()] = true;
877
+ });
878
+ });
879
+ });
880
+ this.svgE.selectAll(".graphEdge")
881
+ .classed("shortest-path", d => highlightedEdges[d.id()] === true)
882
+ ;
883
+ }
884
+ }
885
+
886
+ vertex_click(_row, _col, _sel, more) {
887
+ if (more && more.vertex) {
888
+ more.vertex._placeholderElement.node().parentNode.appendChild(more.vertex._placeholderElement.node());
889
+ }
890
+ IGraph.prototype.vertex_click.apply(this, arguments);
891
+ }
892
+
893
+ vertex_dblclick(_row, _col, _sel, _opts) {
894
+ }
895
+
896
+ vertex_contextmenu(_row, _col, _sel, _opts) {
897
+ }
898
+
899
+ vertex_mouseover(element, d) {
900
+ this.highlightVertex(element, d);
901
+ }
902
+
903
+ vertex_mouseout(_element, _d) {
904
+ this.highlightVertex(null, null);
905
+ }
906
+
907
+ edge_mouseover(element, d) {
908
+ this.highlightEdge(element, d);
909
+ }
910
+
911
+ edge_mouseout(_element, _d) {
912
+ this.highlightEdge(null, null);
913
+ }
914
+
915
+ addMarkers(clearFirst: boolean = false) {
916
+ if (clearFirst) {
917
+ this.defs.select("#" + this._id + "_arrowHead").remove();
918
+ this.defs.select("#" + this._id + "_circleFoot").remove();
919
+ this.defs.select("#" + this._id + "_circleHead").remove();
920
+ this.defs.select("#" + this._id + "_glow").remove();
921
+ }
922
+ this.defs.append("marker")
923
+ .attr("class", "marker")
924
+ .attr("id", this._id + "_arrowHead")
925
+ .attr("viewBox", "0 0 10 10")
926
+ .attr("refX", 10)
927
+ .attr("refY", 5)
928
+ .attr("markerWidth", 8)
929
+ .attr("markerHeight", 8)
930
+ .attr("markerUnits", "strokeWidth")
931
+ .attr("orient", "auto")
932
+ .append("polyline")
933
+ .attr("points", "0,0 10,5 0,10 1,5")
934
+ ;
935
+ this.defs.append("marker")
936
+ .attr("class", "marker")
937
+ .attr("id", this._id + "_circleFoot")
938
+ .attr("viewBox", "0 0 10 10")
939
+ .attr("refX", 1)
940
+ .attr("refY", 5)
941
+ .attr("markerWidth", 7)
942
+ .attr("markerHeight", 7)
943
+ .attr("markerUnits", "strokeWidth")
944
+ .attr("orient", "auto")
945
+ .append("circle")
946
+ .attr("cx", 5)
947
+ .attr("cy", 5)
948
+ .attr("r", 4)
949
+ ;
950
+ this.defs.append("marker")
951
+ .attr("class", "marker")
952
+ .attr("id", this._id + "_circleHead")
953
+ .attr("viewBox", "0 0 10 10")
954
+ .attr("refX", 9)
955
+ .attr("refY", 5)
956
+ .attr("markerWidth", 7)
957
+ .attr("markerHeight", 7)
958
+ .attr("markerUnits", "strokeWidth")
959
+ .attr("orient", "auto")
960
+ .append("circle")
961
+ .attr("cx", 5)
962
+ .attr("cy", 5)
963
+ .attr("r", 4)
964
+ ;
965
+ }
966
+
967
+ // Progess Events ---
968
+ progress(what: "start" | "end" | "layout-start" | "layout-tick" | "layout-end") {
969
+ }
970
+ }
971
+ Graph.prototype._class += " graph_Graph";
972
+ Graph.prototype.implements(IGraph.prototype);
973
+ Graph.prototype.implements(ITooltip.prototype);
974
+
975
+ export interface Graph {
976
+ allowDragging(): boolean;
977
+ allowDragging(_: boolean): this;
978
+ dragSingleNeighbors(): boolean;
979
+ dragSingleNeighbors(_: boolean): this;
980
+ layout(): GraphLayoutType;
981
+ layout(_: GraphLayoutType): this;
982
+ // scale(): string;
983
+ // scale(_: string): this;
984
+ applyScaleOnLayout(): boolean;
985
+ applyScaleOnLayout(_: boolean): this;
986
+ highlightOnMouseOverVertex(): boolean;
987
+ highlightOnMouseOverVertex(_: boolean): this;
988
+ highlightOnMouseOverEdge(): boolean;
989
+ highlightOnMouseOverEdge(_: boolean): this;
990
+ transitionDuration(): number;
991
+ transitionDuration(_: number): this;
992
+ showEdges(): boolean;
993
+ showEdges(_: boolean): this;
994
+ snapToGrid(): number;
995
+ snapToGrid(_: number): this;
996
+ selectionClearOnBackgroundClick(): boolean;
997
+ selectionClearOnBackgroundClick(_: boolean): this;
998
+
999
+ centroidColor(): string;
1000
+ centroidColor(_: string): this;
1001
+ highlightSelectedPathToCentroid(): boolean;
1002
+ highlightSelectedPathToCentroid(_: boolean): this;
1003
+
1004
+ hierarchyRankDirection(): string;
1005
+ hierarchyRankDirection(_: string): this;
1006
+ hierarchyNodeSeparation(): number;
1007
+ hierarchyNodeSeparation(_: number): this;
1008
+ hierarchyEdgeSeparation(): number;
1009
+ hierarchyEdgeSeparation(_: number): this;
1010
+ hierarchyRankSeparation(): number;
1011
+ hierarchyRankSeparation(_: number): this;
1012
+ hierarchyDigraph(): boolean;
1013
+ hierarchyDigraph(_: boolean): this;
1014
+
1015
+ forceDirectedLinkDistance(): number;
1016
+ forceDirectedLinkDistance(_: number): this;
1017
+ forceDirectedLinkStrength(): number;
1018
+ forceDirectedLinkStrength(_: number): this;
1019
+ forceDirectedFriction(): number;
1020
+ forceDirectedFriction(_: number): this;
1021
+ forceDirectedCharge(): number;
1022
+ forceDirectedCharge(_: number): this;
1023
+ forceDirectedChargeDistance(): number;
1024
+ forceDirectedChargeDistance(_: number): this;
1025
+ forceDirectedTheta(): number;
1026
+ forceDirectedTheta(_: number): this;
1027
+ forceDirectedGravity(): number;
1028
+ forceDirectedGravity(_: number): this;
1029
+
1030
+ // IGraph ---
1031
+ edge_click(_row, _col, _sel, _more): void;
1032
+ edge_dblclick(_row, _col, _sel, _more): void;
1033
+
1034
+ // ITooltip ---
1035
+ tooltip;
1036
+ tooltipHTML(_): string;
1037
+ tooltipFormat(_): string;
1038
+ }
1039
+
1040
+ Graph.prototype.publish("allowDragging", true, "boolean", "Allow Dragging of Vertices", null, { tags: ["Advanced"] });
1041
+ Graph.prototype.publish("dragSingleNeighbors", false, "boolean", "Dragging a Vertex also moves its singleton neighbors", null, { tags: ["Advanced"] });
1042
+ Graph.prototype.publish("layout", "Circle", "set", "Default Layout", ["Circle", "ForceDirected", "ForceDirected2", "Hierarchy", "None"], { tags: ["Basic"] });
1043
+ Graph.prototype.publish("scale", "100%", "set", "Zoom Level", ["all", "width", "selection", "100%", "90%", "75%", "50%", "25%", "10%"], { tags: ["Basic"] });
1044
+ Graph.prototype.publish("applyScaleOnLayout", false, "boolean", "Shrink to fit on Layout", null, { tags: ["Basic"] });
1045
+ Graph.prototype.publish("highlightOnMouseOverVertex", false, "boolean", "Highlight Vertex on Mouse Over", null, { tags: ["Basic"] });
1046
+ Graph.prototype.publish("highlightOnMouseOverEdge", false, "boolean", "Highlight Edge on Mouse Over", null, { tags: ["Basic"] });
1047
+ Graph.prototype.publish("transitionDuration", 250, "number", "Transition Duration", null, { tags: ["Intermediate"] });
1048
+ Graph.prototype.publish("showEdges", true, "boolean", "Show Edges", null, { tags: ["Intermediate"] });
1049
+ Graph.prototype.publish("snapToGrid", 0, "number", "Snap to Grid", null, { tags: ["Private"] });
1050
+ Graph.prototype.publish("selectionClearOnBackgroundClick", false, "boolean", "Clear selection on background click");
1051
+
1052
+ Graph.prototype.publish("centroidColor", "#00A000", "html-color", "Centroid Color", null, { tags: ["Basic"] });
1053
+ Graph.prototype.publish("highlightSelectedPathToCentroid", false, "boolean", "Highlight path to Center Vertex (for selected vertices)", null, { tags: ["Basic"] });
1054
+
1055
+ Graph.prototype.publish("hierarchyRankDirection", "TB", "set", "Direction for Rank Nodes", ["TB", "BT", "LR", "RL"], { tags: ["Advanced"] });
1056
+ Graph.prototype.publish("hierarchyNodeSeparation", 50, "number", "Number of pixels that separate nodes horizontally in the layout", null, { tags: ["Advanced"] });
1057
+ Graph.prototype.publish("hierarchyEdgeSeparation", 10, "number", "Number of pixels that separate edges horizontally in the layout", null, { tags: ["Advanced"] });
1058
+ Graph.prototype.publish("hierarchyRankSeparation", 50, "number", "Number of pixels between each rank in the layout", null, { tags: ["Advanced"] });
1059
+ Graph.prototype.publish("hierarchyDigraph", true, "boolean", "Directional Graph", null, { tags: ["Advanced"] });
1060
+
1061
+ Graph.prototype.publish("forceDirectedLinkDistance", 300, "number", "Target distance between linked nodes", null, { tags: ["Advanced"] });
1062
+ Graph.prototype.publish("forceDirectedLinkStrength", 1, "number", "Strength (rigidity) of links", null, { tags: ["Advanced"] });
1063
+ Graph.prototype.publish("forceDirectedFriction", 0.9, "number", "Friction coefficient", null, { tags: ["Advanced"] });
1064
+ Graph.prototype.publish("forceDirectedCharge", -25, "number", "Charge strength ", null, { tags: ["Advanced"] });
1065
+ Graph.prototype.publish("forceDirectedChargeDistance", 10000, "number", "Maximum distance over which charge forces are applied", null, { tags: ["Advanced"] });
1066
+ Graph.prototype.publish("forceDirectedTheta", 0.8, "number", "Barnes–Hut approximation criterion", null, { tags: ["Advanced"] });
1067
+ Graph.prototype.publish("forceDirectedGravity", 0.1, "number", "Gravitational strength", null, { tags: ["Advanced"] });
1068
+
1069
+ const _origScale = Graph.prototype.scale;
1070
+ //@ts-ignore
1071
+ Graph.prototype.scale = function (_?, transitionDuration?) {
1072
+ const retVal = _origScale.apply(this, arguments as any);
1073
+ if (arguments.length) {
1074
+ this.zoomTo(_, transitionDuration);
1075
+ }
1076
+ return retVal;
1077
+ };