@unovis/ts 1.4.2-alpha.2 → 1.4.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,19 @@
1
1
  import { __awaiter } from 'tslib';
2
2
  import { min, max } from 'd3-array';
3
3
  import { select, pointer } from 'd3-selection';
4
+ import { brush as brush$1 } from 'd3-brush';
4
5
  import { zoom, zoomTransform, zoomIdentity } from 'd3-zoom';
5
6
  import { drag } from 'd3-drag';
6
7
  import { interval } from 'd3-timer';
7
8
  import { ComponentCore } from '../../core/component/index.js';
8
9
  import { GraphDataModel } from '../../data-models/graph.js';
9
- import { isNumber, clamp, getBoolean, isFunction, shallowDiff, isPlainObject } from '../../utils/data.js';
10
+ import { isNumber, clamp, getBoolean, isFunction, shallowDiff } from '../../utils/data.js';
10
11
  import { smartTransition } from '../../utils/d3.js';
11
12
  import { GraphLayoutType, GraphLinkArrowStyle } from './types.js';
12
13
  import { GraphDefaultConfig } from './config.js';
13
- import { background, graphGroup, root } from './style.js';
14
+ import { background, graphGroup, brush, root } from './style.js';
14
15
  import * as style from './modules/node/style.js';
15
- import { nodes, gNode, gNodeExit, node, nodeGauge, sideLabelGroup, label, greyedOutNode } from './modules/node/style.js';
16
+ import { nodes, gNode, gNodeExit, brushed, brushable, node, nodeGauge, sideLabelGroup, label, greyedOutNode } from './modules/node/style.js';
16
17
  import { links, gLink, gLinkExit, link, greyedOutLink } from './modules/link/style.js';
17
18
  import { panels, gPanel, panel, panelSelection, label as label$1, labelText, sideIconGroup, sideIconShape, sideIconSymbol } from './modules/panel/style.js';
18
19
  import { createNodes, updateNodes, removeNodes, updateSelectedNodes, zoomNodesThrottled, zoomNodes } from './modules/node/index.js';
@@ -53,17 +54,28 @@ class Graph extends ComponentCore {
53
54
  this.setConfig(config);
54
55
  this._backgroundRect = this.g.append('rect').attr('class', background);
55
56
  this._graphGroup = this.g.append('g').attr('class', graphGroup);
57
+ this._brush = this.g.append('g').attr('class', brush);
56
58
  this._zoomBehavior = zoom()
57
59
  .scaleExtent(this.config.zoomScaleExtent)
58
- .on('zoom', (e) => this._onZoom(e.transform, e));
60
+ .on('zoom', (e) => this._onZoom(e.transform, e))
61
+ .filter(event => !event.shiftKey);
62
+ this._brushBehavior = brush$1()
63
+ .on('start brush end', this._onBrush.bind(this))
64
+ .filter(event => event.shiftKey)
65
+ .keyModifiers(false);
59
66
  this._panelsGroup = this._graphGroup.append('g').attr('class', panels);
60
67
  this._linksGroup = this._graphGroup.append('g').attr('class', links);
61
68
  this._nodesGroup = this._graphGroup.append('g').attr('class', nodes);
62
69
  this._defs = this._graphGroup.append('defs');
63
70
  this._getLinkArrowDefId = this._getLinkArrowDefId.bind(this);
64
71
  }
72
+ /** @deprecated Use selectedNodes instead */
65
73
  get selectedNode() {
66
- return this._selectedNode;
74
+ var _a;
75
+ return (_a = this._selectedNodes) === null || _a === void 0 ? void 0 : _a[0];
76
+ }
77
+ get selectedNodes() {
78
+ return this._selectedNodes;
67
79
  }
68
80
  get selectedLink() {
69
81
  return this._selectedLink;
@@ -82,8 +94,6 @@ class Graph extends ComponentCore {
82
94
  super.setConfig(config);
83
95
  this._shouldRecalculateLayout = this._shouldRecalculateLayout || this._shouldLayoutRecalculate();
84
96
  this._shouldFitLayout = this._shouldFitLayout || this._shouldRecalculateLayout;
85
- if (this._shouldFitLayout)
86
- this._isAutoFitDisabled = false;
87
97
  this._shouldSetPanels = true;
88
98
  }
89
99
  get bleed() {
@@ -91,7 +101,7 @@ class Graph extends ComponentCore {
91
101
  return { top: extraPadding, bottom: extraPadding, left: extraPadding, right: extraPadding };
92
102
  }
93
103
  _render(customDuration) {
94
- const { config: { disableZoom, duration, layoutAutofit }, datamodel } = this;
104
+ const { config: { disableBrush, disableZoom, duration, layoutAutofit }, datamodel } = this;
95
105
  if (!datamodel.nodes && !datamodel.links)
96
106
  return;
97
107
  const animDuration = isNumber(customDuration) ? customDuration : duration;
@@ -105,11 +115,31 @@ class Graph extends ComponentCore {
105
115
  this._prevWidth = this._width;
106
116
  this._prevHeight = this._height;
107
117
  }
118
+ // Handle brush behavior
119
+ if (!disableBrush) {
120
+ this._brushBehavior.extent([[0, 0], [this._width, this._height]]);
121
+ this._brush.call(this._brushBehavior);
122
+ // Activate the brush when the shift key is pressed
123
+ select(window)
124
+ .on('keydown.unovis-graph', e => e.key === 'Shift' && this._activateBrush())
125
+ .on('keyup.unovis-graph', e => e.key === 'Shift' && this._clearBrush());
126
+ this._zoomBehavior.filter(event => !event.shiftKey);
127
+ }
128
+ else {
129
+ this._brush.on('.brush', null);
130
+ select(window)
131
+ .on('keydown.unovis-graph', null)
132
+ .on('keyup.unovis-graph', null);
133
+ // Clear brush in case it was disabled in an active state
134
+ if (this._brush.classed('active'))
135
+ this._clearBrush();
136
+ }
108
137
  // Apply layout and render
109
138
  if (this._shouldRecalculateLayout || !this._layoutCalculationPromise) {
110
139
  this._layoutCalculationPromise = this._calculateLayout();
111
140
  }
112
141
  this._layoutCalculationPromise.then((isFirstRender) => {
142
+ var _a;
113
143
  // If the component has been destroyed while the layout calculation
114
144
  // was in progress, we cancel the render
115
145
  if (this.isDestroyed())
@@ -129,9 +159,10 @@ class Graph extends ComponentCore {
129
159
  this._drawLinks(animDuration);
130
160
  // Select Links / Nodes
131
161
  this._resetSelection();
132
- if (this.config.selectedNodeId) {
133
- const selectedNode = datamodel.nodes.find(node => node.id === this.config.selectedNodeId);
134
- this._selectNode(selectedNode);
162
+ if (this.config.selectedNodeId || this.config.selectedNodeIds) {
163
+ const selectedIds = (_a = this.config.selectedNodeIds) !== null && _a !== void 0 ? _a : [this.config.selectedNodeId];
164
+ const selectedNodes = selectedIds.map(id => datamodel.getNodeFromId(id));
165
+ this._selectNodes(selectedNodes);
135
166
  }
136
167
  if (this.config.selectedLinkId) {
137
168
  const selectedLink = datamodel.links.find(link => link.id === this.config.selectedLinkId);
@@ -186,9 +217,9 @@ class Graph extends ComponentCore {
186
217
  const thisRef = this;
187
218
  if (!config.disableDrag) {
188
219
  const dragBehaviour = drag()
189
- .on('start', function (event, d) { thisRef._onDragStarted(d, event, select(this)); })
190
- .on('drag', function (event, d) { thisRef._onDragged(d, event, nodeGroupsMerged); })
191
- .on('end', function (event, d) { thisRef._onDragEnded(d, event, select(this)); });
220
+ .on('start drag end', function (event, d) {
221
+ thisRef._handleDrag(d, event, select(this));
222
+ });
192
223
  nodeGroupsMerged.call(dragBehaviour);
193
224
  }
194
225
  else {
@@ -243,15 +274,8 @@ class Graph extends ComponentCore {
243
274
  _calculateLayout() {
244
275
  var _a, _b;
245
276
  return __awaiter(this, void 0, void 0, function* () {
246
- const { prevConfig, config, datamodel } = this;
277
+ const { config, datamodel } = this;
247
278
  const firstRender = this._isFirstRender;
248
- // If the layout type has changed, we need to reset the node positions if they were fixed before
249
- if (prevConfig.layoutType !== config.layoutType) {
250
- for (const node of datamodel.nodes) {
251
- delete node._state.fx;
252
- delete node._state.fy;
253
- }
254
- }
255
279
  switch (config.layoutType) {
256
280
  case GraphLayoutType.Precalculated:
257
281
  break;
@@ -332,21 +356,9 @@ class Graph extends ComponentCore {
332
356
  return transform;
333
357
  }
334
358
  _selectNode(node) {
335
- const { datamodel: { nodes, links } } = this;
359
+ const { datamodel: { links } } = this;
336
360
  if (!node)
337
361
  console.warn('Unovis | Graph: Select Node: Not found');
338
- this._selectedNode = node;
339
- // Apply grey out
340
- // Grey out all nodes
341
- nodes.forEach(n => {
342
- n._state.selected = false;
343
- n._state.greyout = true;
344
- });
345
- // Grey out all links
346
- links.forEach(l => {
347
- l._state.greyout = true;
348
- l._state.selected = false;
349
- });
350
362
  // Highlight selected
351
363
  if (node) {
352
364
  node._state.selected = true;
@@ -362,6 +374,24 @@ class Graph extends ComponentCore {
362
374
  }
363
375
  this._updateSelectedElements();
364
376
  }
377
+ _selectNodes(nodes) {
378
+ // Apply grey out
379
+ // Grey out all nodes
380
+ this.datamodel.nodes.forEach(n => {
381
+ n._state.selected = false;
382
+ n._state.greyout = true;
383
+ });
384
+ // Grey out all links
385
+ this.datamodel.links.forEach(l => {
386
+ l._state.greyout = true;
387
+ l._state.selected = false;
388
+ });
389
+ nodes.forEach(n => {
390
+ this._selectedNodes.push(n);
391
+ this._selectNode(n);
392
+ });
393
+ this._updateSelectedElements();
394
+ }
365
395
  _selectLink(link) {
366
396
  const { datamodel: { nodes, links } } = this;
367
397
  if (!link)
@@ -396,7 +426,7 @@ class Graph extends ComponentCore {
396
426
  }
397
427
  _resetSelection() {
398
428
  const { datamodel: { nodes, links } } = this;
399
- this._selectedNode = undefined;
429
+ this._selectedNodes = [];
400
430
  this._selectedLink = undefined;
401
431
  // Disable Grayout
402
432
  nodes.forEach(n => {
@@ -486,27 +516,17 @@ class Graph extends ComponentCore {
486
516
  this._linksGroup.selectAll(`.${gLink}`)
487
517
  .call((nodes.length > config.zoomThrottledUpdateNodeThreshold ? zoomLinksThrottled : zoomLinks), config, this._scale, this._getLinkArrowDefId);
488
518
  }
489
- _onDragStarted(d, event, nodeSelection) {
490
- var _a;
491
- const { config } = this;
492
- this._isDragging = true;
493
- d._state.isDragged = true;
494
- nodeSelection.call(updateNodes, config, 0, this._scale);
495
- (_a = config.onNodeDragStart) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
496
- }
497
- _onDragged(d, event, allNodesSelection) {
498
- var _a, _b, _c;
499
- const { config } = this;
519
+ _updateNodePosition(d, x, y) {
520
+ var _a, _b;
500
521
  const transform = zoomTransform(this.g.node());
501
522
  const scale = transform.k;
502
523
  // Prevent the node from being dragged offscreen or outside its panel
503
524
  const panels = (_b = (_a = this._panels) === null || _a === void 0 ? void 0 : _a.filter(p => p.nodes.includes(d._id))) !== null && _b !== void 0 ? _b : [];
504
- const nodeSizeValue = getNodeSize(d, config.nodeSize, d._index);
525
+ const nodeSizeValue = getNodeSize(d, this.config.nodeSize, d._index);
505
526
  const maxY = min([(this._height - transform.y) / scale, ...panels.map(p => p._y + p._height)]) - nodeSizeValue / 2;
506
527
  const maxX = min([(this._width - transform.x) / scale, ...panels.map(p => p._x + p._width)]) - nodeSizeValue / 2;
507
528
  const minY = max([-transform.y / scale, ...panels.map(p => p._y)]) + nodeSizeValue / 2;
508
529
  const minX = max([-transform.x / scale, ...panels.map(p => p._x)]) + nodeSizeValue / 2;
509
- let [x, y] = pointer(event, this._graphGroup.node());
510
530
  if (y < minY)
511
531
  y = minY;
512
532
  else if (y > maxY)
@@ -527,6 +547,62 @@ class Graph extends ComponentCore {
527
547
  delete d._state.fx;
528
548
  if (d._state.fy === d.y)
529
549
  delete d._state.fy;
550
+ }
551
+ _onBrush(event) {
552
+ var _a;
553
+ if (!event.selection || !event.sourceEvent)
554
+ return;
555
+ const { config } = this;
556
+ const transform = zoomTransform(this._graphGroup.node());
557
+ const [xMin, yMin] = transform.invert(event.selection[0]);
558
+ const [xMax, yMax] = transform.invert(event.selection[1]);
559
+ // Update brushed nodes
560
+ this._nodesGroup.selectAll(`.${gNode}`)
561
+ .each(n => {
562
+ const x = getX(n);
563
+ const y = getY(n);
564
+ n._state.brushed = x >= xMin && x <= xMax && y >= yMin && y <= yMax;
565
+ })
566
+ .classed(brushed, n => n._state.brushed);
567
+ const brushedNodes = this._nodesGroup.selectAll(`.${brushed}`)
568
+ .call(updateSelectedNodes, config, 0, this._scale);
569
+ this._brush.classed('active', event.type !== 'end');
570
+ (_a = config.onNodeSelectionBrush) === null || _a === void 0 ? void 0 : _a.call(config, brushedNodes.data(), event);
571
+ }
572
+ _handleDrag(d, event, nodeSelection) {
573
+ if (event.sourceEvent.shiftKey && d._state.brushed) {
574
+ this._dragSelectedNodes(event);
575
+ }
576
+ else if (!event.sourceEvent.shiftKey) {
577
+ switch (event.type) {
578
+ case 'start':
579
+ this._onDragStarted(d, event, nodeSelection);
580
+ break;
581
+ case 'drag':
582
+ this._onDragged(d, event);
583
+ break;
584
+ case 'end':
585
+ this._onDragEnded(d, event, nodeSelection);
586
+ break;
587
+ }
588
+ }
589
+ }
590
+ _onDragStarted(d, event, nodeSelection) {
591
+ var _a;
592
+ const { config } = this;
593
+ this._isDragging = true;
594
+ d._state.isDragged = true;
595
+ nodeSelection.call(updateNodes, config, 0, this._scale);
596
+ (_a = config.onNodeDragStart) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
597
+ }
598
+ _onDragged(d, event) {
599
+ var _a;
600
+ const { config } = this;
601
+ const transform = zoomTransform(this.g.node());
602
+ const scale = transform.k;
603
+ // Update node position
604
+ const [x, y] = pointer(event, this._graphGroup.node());
605
+ this._updateNodePosition(d, x, y);
530
606
  // Update affected DOM elements
531
607
  const nodeSelection = this._nodesGroup.selectAll(`.${gNode}`);
532
608
  const nodeToUpdate = nodeSelection.filter((n) => n._id === d._id);
@@ -541,7 +617,7 @@ class Graph extends ComponentCore {
541
617
  const linksToAnimate = linksToUpdate.filter(d => d._state.greyout);
542
618
  if (linksToAnimate.size())
543
619
  animateLinkFlow(linksToAnimate, config, this._scale);
544
- (_c = config.onNodeDrag) === null || _c === void 0 ? void 0 : _c.call(config, d, event);
620
+ (_a = config.onNodeDrag) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
545
621
  }
546
622
  _onDragEnded(d, event, nodeSelection) {
547
623
  var _a;
@@ -551,6 +627,49 @@ class Graph extends ComponentCore {
551
627
  nodeSelection.call(updateNodes, config, 0, this._scale);
552
628
  (_a = config.onNodeDragEnd) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
553
629
  }
630
+ _dragSelectedNodes(event) {
631
+ var _a, _b;
632
+ const { config } = this;
633
+ const curr = pointer(event, this._graphGroup.node());
634
+ const selectedNodes = smartTransition(this._nodesGroup.selectAll(`.${brushed}`));
635
+ if (event.type === 'start') {
636
+ this._groupDragInit = curr;
637
+ this._isDragging = true;
638
+ selectedNodes.each(n => {
639
+ n.x = getX(n);
640
+ n.y = getY(n);
641
+ n._state.isDragged = true;
642
+ });
643
+ }
644
+ else if (event.type === 'drag') {
645
+ const dx = curr[0] - this._groupDragInit[0];
646
+ const dy = curr[1] - this._groupDragInit[1];
647
+ selectedNodes.each(n => this._updateNodePosition(n, n.x + dx, n.y + dy));
648
+ const connectedLinks = smartTransition(this._linksGroup.selectAll(`.${gLink}`)
649
+ .filter(l => { var _a, _b, _c, _d; return ((_b = (_a = l.source) === null || _a === void 0 ? void 0 : _a._state) === null || _b === void 0 ? void 0 : _b.isDragged) || ((_d = (_c = l.target) === null || _c === void 0 ? void 0 : _c._state) === null || _d === void 0 ? void 0 : _d.isDragged); }));
650
+ connectedLinks.call(updateLinks, this.config, 0, this._scale, this._getLinkArrowDefId);
651
+ }
652
+ else {
653
+ this._isDragging = false;
654
+ selectedNodes.each(n => { n._state.isDragged = false; });
655
+ }
656
+ selectedNodes.call(updateNodes, config, 0, this._scale);
657
+ (_b = (_a = this.config).onNodeSelectionDrag) === null || _b === void 0 ? void 0 : _b.call(_a, selectedNodes.data(), event);
658
+ }
659
+ _activateBrush() {
660
+ this._brush.classed('active', true);
661
+ this._nodesGroup.selectAll(`.${gNode}`)
662
+ .classed(brushable, true);
663
+ }
664
+ _clearBrush() {
665
+ var _a;
666
+ this._brush.classed('active', false).call((_a = this._brushBehavior) === null || _a === void 0 ? void 0 : _a.clear);
667
+ this._nodesGroup.selectAll(`.${gNode}`)
668
+ .classed(brushable, false)
669
+ .classed(brushed, false)
670
+ .each(n => { n._state.brushed = false; })
671
+ .call(updateSelectedNodes, this.config, 0, this._scale);
672
+ }
554
673
  _shouldLayoutRecalculate() {
555
674
  const { prevConfig, config } = this;
556
675
  if (prevConfig.layoutType !== config.layoutType)
@@ -567,17 +686,6 @@ class Graph extends ComponentCore {
567
686
  if (Object.keys(dagreSettingsDiff).length)
568
687
  return true;
569
688
  }
570
- if (prevConfig.layoutType === GraphLayoutType.Elk) {
571
- if (isPlainObject(prevConfig.layoutElkSettings) && isPlainObject(config.layoutElkSettings)) {
572
- // Do a deeper comparison if `config.layoutElkSettings` is an object
573
- const elkSettingsDiff = shallowDiff(prevConfig.layoutElkSettings, config.layoutElkSettings);
574
- return Boolean(Object.keys(elkSettingsDiff).length);
575
- }
576
- else {
577
- // Otherwise, do a simple `===` comparison
578
- return prevConfig.layoutElkSettings !== config.layoutElkSettings;
579
- }
580
- }
581
689
  if (prevConfig.layoutType === GraphLayoutType.Parallel ||
582
690
  prevConfig.layoutType === GraphLayoutType.ParallelHorizontal ||
583
691
  prevConfig.layoutType === GraphLayoutType.Concentric) {