@unovis/ts 1.4.2-alpha.1 → 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;
@@ -89,7 +101,7 @@ class Graph extends ComponentCore {
89
101
  return { top: extraPadding, bottom: extraPadding, left: extraPadding, right: extraPadding };
90
102
  }
91
103
  _render(customDuration) {
92
- const { config: { disableZoom, duration, layoutAutofit }, datamodel } = this;
104
+ const { config: { disableBrush, disableZoom, duration, layoutAutofit }, datamodel } = this;
93
105
  if (!datamodel.nodes && !datamodel.links)
94
106
  return;
95
107
  const animDuration = isNumber(customDuration) ? customDuration : duration;
@@ -103,11 +115,31 @@ class Graph extends ComponentCore {
103
115
  this._prevWidth = this._width;
104
116
  this._prevHeight = this._height;
105
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
+ }
106
137
  // Apply layout and render
107
138
  if (this._shouldRecalculateLayout || !this._layoutCalculationPromise) {
108
139
  this._layoutCalculationPromise = this._calculateLayout();
109
140
  }
110
141
  this._layoutCalculationPromise.then((isFirstRender) => {
142
+ var _a;
111
143
  // If the component has been destroyed while the layout calculation
112
144
  // was in progress, we cancel the render
113
145
  if (this.isDestroyed())
@@ -127,9 +159,10 @@ class Graph extends ComponentCore {
127
159
  this._drawLinks(animDuration);
128
160
  // Select Links / Nodes
129
161
  this._resetSelection();
130
- if (this.config.selectedNodeId) {
131
- const selectedNode = datamodel.nodes.find(node => node.id === this.config.selectedNodeId);
132
- 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);
133
166
  }
134
167
  if (this.config.selectedLinkId) {
135
168
  const selectedLink = datamodel.links.find(link => link.id === this.config.selectedLinkId);
@@ -184,9 +217,9 @@ class Graph extends ComponentCore {
184
217
  const thisRef = this;
185
218
  if (!config.disableDrag) {
186
219
  const dragBehaviour = drag()
187
- .on('start', function (event, d) { thisRef._onDragStarted(d, event, select(this)); })
188
- .on('drag', function (event, d) { thisRef._onDragged(d, event, nodeGroupsMerged); })
189
- .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
+ });
190
223
  nodeGroupsMerged.call(dragBehaviour);
191
224
  }
192
225
  else {
@@ -241,15 +274,8 @@ class Graph extends ComponentCore {
241
274
  _calculateLayout() {
242
275
  var _a, _b;
243
276
  return __awaiter(this, void 0, void 0, function* () {
244
- const { prevConfig, config, datamodel } = this;
277
+ const { config, datamodel } = this;
245
278
  const firstRender = this._isFirstRender;
246
- // If the layout type has changed, we need to reset the node positions if they were fixed before
247
- if (prevConfig.layoutType !== config.layoutType) {
248
- for (const node of datamodel.nodes) {
249
- delete node._state.fx;
250
- delete node._state.fy;
251
- }
252
- }
253
279
  switch (config.layoutType) {
254
280
  case GraphLayoutType.Precalculated:
255
281
  break;
@@ -330,21 +356,9 @@ class Graph extends ComponentCore {
330
356
  return transform;
331
357
  }
332
358
  _selectNode(node) {
333
- const { datamodel: { nodes, links } } = this;
359
+ const { datamodel: { links } } = this;
334
360
  if (!node)
335
361
  console.warn('Unovis | Graph: Select Node: Not found');
336
- this._selectedNode = node;
337
- // Apply grey out
338
- // Grey out all nodes
339
- nodes.forEach(n => {
340
- n._state.selected = false;
341
- n._state.greyout = true;
342
- });
343
- // Grey out all links
344
- links.forEach(l => {
345
- l._state.greyout = true;
346
- l._state.selected = false;
347
- });
348
362
  // Highlight selected
349
363
  if (node) {
350
364
  node._state.selected = true;
@@ -360,6 +374,24 @@ class Graph extends ComponentCore {
360
374
  }
361
375
  this._updateSelectedElements();
362
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
+ }
363
395
  _selectLink(link) {
364
396
  const { datamodel: { nodes, links } } = this;
365
397
  if (!link)
@@ -394,7 +426,7 @@ class Graph extends ComponentCore {
394
426
  }
395
427
  _resetSelection() {
396
428
  const { datamodel: { nodes, links } } = this;
397
- this._selectedNode = undefined;
429
+ this._selectedNodes = [];
398
430
  this._selectedLink = undefined;
399
431
  // Disable Grayout
400
432
  nodes.forEach(n => {
@@ -484,27 +516,17 @@ class Graph extends ComponentCore {
484
516
  this._linksGroup.selectAll(`.${gLink}`)
485
517
  .call((nodes.length > config.zoomThrottledUpdateNodeThreshold ? zoomLinksThrottled : zoomLinks), config, this._scale, this._getLinkArrowDefId);
486
518
  }
487
- _onDragStarted(d, event, nodeSelection) {
488
- var _a;
489
- const { config } = this;
490
- this._isDragging = true;
491
- d._state.isDragged = true;
492
- nodeSelection.call(updateNodes, config, 0, this._scale);
493
- (_a = config.onNodeDragStart) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
494
- }
495
- _onDragged(d, event, allNodesSelection) {
496
- var _a, _b, _c;
497
- const { config } = this;
519
+ _updateNodePosition(d, x, y) {
520
+ var _a, _b;
498
521
  const transform = zoomTransform(this.g.node());
499
522
  const scale = transform.k;
500
523
  // Prevent the node from being dragged offscreen or outside its panel
501
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 : [];
502
- const nodeSizeValue = getNodeSize(d, config.nodeSize, d._index);
525
+ const nodeSizeValue = getNodeSize(d, this.config.nodeSize, d._index);
503
526
  const maxY = min([(this._height - transform.y) / scale, ...panels.map(p => p._y + p._height)]) - nodeSizeValue / 2;
504
527
  const maxX = min([(this._width - transform.x) / scale, ...panels.map(p => p._x + p._width)]) - nodeSizeValue / 2;
505
528
  const minY = max([-transform.y / scale, ...panels.map(p => p._y)]) + nodeSizeValue / 2;
506
529
  const minX = max([-transform.x / scale, ...panels.map(p => p._x)]) + nodeSizeValue / 2;
507
- let [x, y] = pointer(event, this._graphGroup.node());
508
530
  if (y < minY)
509
531
  y = minY;
510
532
  else if (y > maxY)
@@ -525,6 +547,62 @@ class Graph extends ComponentCore {
525
547
  delete d._state.fx;
526
548
  if (d._state.fy === d.y)
527
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);
528
606
  // Update affected DOM elements
529
607
  const nodeSelection = this._nodesGroup.selectAll(`.${gNode}`);
530
608
  const nodeToUpdate = nodeSelection.filter((n) => n._id === d._id);
@@ -539,7 +617,7 @@ class Graph extends ComponentCore {
539
617
  const linksToAnimate = linksToUpdate.filter(d => d._state.greyout);
540
618
  if (linksToAnimate.size())
541
619
  animateLinkFlow(linksToAnimate, config, this._scale);
542
- (_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);
543
621
  }
544
622
  _onDragEnded(d, event, nodeSelection) {
545
623
  var _a;
@@ -549,6 +627,49 @@ class Graph extends ComponentCore {
549
627
  nodeSelection.call(updateNodes, config, 0, this._scale);
550
628
  (_a = config.onNodeDragEnd) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
551
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
+ }
552
673
  _shouldLayoutRecalculate() {
553
674
  const { prevConfig, config } = this;
554
675
  if (prevConfig.layoutType !== config.layoutType)
@@ -565,17 +686,6 @@ class Graph extends ComponentCore {
565
686
  if (Object.keys(dagreSettingsDiff).length)
566
687
  return true;
567
688
  }
568
- if (prevConfig.layoutType === GraphLayoutType.Elk) {
569
- if (isPlainObject(prevConfig.layoutElkSettings) && isPlainObject(config.layoutElkSettings)) {
570
- // Do a deeper comparison if `config.layoutElkSettings` is an object
571
- const elkSettingsDiff = shallowDiff(prevConfig.layoutElkSettings, config.layoutElkSettings);
572
- return Boolean(Object.keys(elkSettingsDiff).length);
573
- }
574
- else {
575
- // Otherwise, do a simple `===` comparison
576
- return prevConfig.layoutElkSettings !== config.layoutElkSettings;
577
- }
578
- }
579
689
  if (prevConfig.layoutType === GraphLayoutType.Parallel ||
580
690
  prevConfig.layoutType === GraphLayoutType.ParallelHorizontal ||
581
691
  prevConfig.layoutType === GraphLayoutType.Concentric) {