@unovis/ts 1.5.0-alpha.8 → 1.5.0-nikita.1

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 (36) hide show
  1. package/components/graph/config.d.ts +23 -3
  2. package/components/graph/config.js +3 -3
  3. package/components/graph/config.js.map +1 -1
  4. package/components/graph/index.d.ts +15 -5
  5. package/components/graph/index.js +209 -86
  6. package/components/graph/index.js.map +1 -1
  7. package/components/graph/modules/link/index.d.ts +2 -1
  8. package/components/graph/modules/link/index.js +7 -4
  9. package/components/graph/modules/link/index.js.map +1 -1
  10. package/components/graph/modules/node/index.d.ts +2 -1
  11. package/components/graph/modules/node/index.js +47 -23
  12. package/components/graph/modules/node/index.js.map +1 -1
  13. package/components/graph/modules/node/style.d.ts +2 -0
  14. package/components/graph/modules/node/style.js +34 -4
  15. package/components/graph/modules/node/style.js.map +1 -1
  16. package/components/graph/modules/panel/index.js +1 -0
  17. package/components/graph/modules/panel/index.js.map +1 -1
  18. package/components/graph/style.d.ts +1 -0
  19. package/components/graph/style.js +22 -1
  20. package/components/graph/style.js.map +1 -1
  21. package/components/graph/types.d.ts +8 -0
  22. package/components/graph/types.js +8 -2
  23. package/components/graph/types.js.map +1 -1
  24. package/components/scatter/index.d.ts +1 -0
  25. package/components/scatter/index.js +19 -12
  26. package/components/scatter/index.js.map +1 -1
  27. package/components/scatter/modules/point.js +1 -3
  28. package/components/scatter/modules/point.js.map +1 -1
  29. package/components/scatter/types.d.ts +2 -0
  30. package/data-models/graph.d.ts +2 -0
  31. package/data-models/graph.js +6 -0
  32. package/data-models/graph.js.map +1 -1
  33. package/index.js +1 -1
  34. package/package.json +1 -1
  35. package/types/graph.d.ts +2 -0
  36. package/types.js +1 -1
@@ -1,23 +1,24 @@
1
1
  import { __awaiter } from 'tslib';
2
2
  import { min, max } from 'd3-array';
3
3
  import { select, pointer } from 'd3-selection';
4
- import { zoom, zoomTransform, zoomIdentity } from 'd3-zoom';
4
+ import { brush as brush$1 } from 'd3-brush';
5
+ import { zoom, zoomIdentity, zoomTransform } 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, isFunction, clamp, getBoolean, shallowDiff, isPlainObject, isEqual } from '../../utils/data.js';
10
+ import { isEqual, isNumber, isFunction, clamp, getBoolean, shallowDiff, isPlainObject } from '../../utils/data.js';
10
11
  import { smartTransition } from '../../utils/d3.js';
11
- import { GraphLayoutType, GraphLinkArrowStyle } from './types.js';
12
+ import { GraphLayoutType, GraphNodeSelectionHighlightMode, 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
- import { createNodes, updateNodes, removeNodes, updateNodeSelectedGreyout, zoomNodesThrottled, zoomNodes } from './modules/node/index.js';
19
+ import { createNodes, updateNodes, removeNodes, updateNodesPartial, zoomNodesThrottled, zoomNodes } from './modules/node/index.js';
19
20
  import { getMaxNodeSize, getX, getY, getNodeSize } from './modules/node/helper.js';
20
- import { createLinks, updateLinks, removeLinks, updateSelectedLinks, animateLinkFlow, zoomLinksThrottled, zoomLinks } from './modules/link/index.js';
21
+ import { createLinks, updateLinks, removeLinks, updateLinksPartial, animateLinkFlow, zoomLinksThrottled, zoomLinks } from './modules/link/index.js';
21
22
  import { getArrowPath, getDoubleArrowPath } from './modules/link/helper.js';
22
23
  import { removePanels, createPanels, updatePanels } from './modules/panel/index.js';
23
24
  import { updatePanelNumNodes, updatePanelBBoxSize, initPanels, setPanelForNodes } from './modules/panel/helper.js';
@@ -53,9 +54,14 @@ 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
60
  .on('zoom', (e) => this._onZoom(e.transform, e));
61
+ this._brushBehavior = brush$1()
62
+ .on('start brush end', this._onBrush.bind(this))
63
+ .filter(event => event.shiftKey)
64
+ .keyModifiers(false);
59
65
  this._panelsGroup = this._graphGroup.append('g').attr('class', panels);
60
66
  this._linksGroup = this._graphGroup.append('g').attr('class', links);
61
67
  this._nodesGroup = this._graphGroup.append('g').attr('class', nodes);
@@ -63,13 +69,19 @@ class Graph extends ComponentCore {
63
69
  this._getLinkArrowDefId = this._getLinkArrowDefId.bind(this);
64
70
  }
65
71
  get selectedNode() {
66
- return this._selectedNode;
72
+ var _a;
73
+ return (_a = this._selectedNodes) === null || _a === void 0 ? void 0 : _a[0];
74
+ }
75
+ get selectedNodes() {
76
+ return this._selectedNodes;
67
77
  }
68
78
  get selectedLink() {
69
79
  return this._selectedLink;
70
80
  }
71
81
  setData(data) {
72
82
  const { config } = this;
83
+ if (isEqual(this.datamodel.data, data))
84
+ return;
73
85
  this.datamodel.nodeSort = config.nodeSort;
74
86
  this.datamodel.data = data;
75
87
  this._shouldRecalculateLayout = true;
@@ -95,7 +107,7 @@ class Graph extends ComponentCore {
95
107
  return { top: extraPadding, bottom: extraPadding, left: extraPadding, right: extraPadding };
96
108
  }
97
109
  _render(customDuration) {
98
- const { config: { disableZoom, duration, layoutAutofit, zoomEventFilter }, datamodel } = this;
110
+ const { config: { disableBrush, disableZoom, duration, layoutAutofit, zoomEventFilter }, datamodel } = this;
99
111
  if (!datamodel.nodes && !datamodel.links)
100
112
  return;
101
113
  const animDuration = isNumber(customDuration) ? customDuration : duration;
@@ -109,6 +121,25 @@ class Graph extends ComponentCore {
109
121
  this._prevWidth = this._width;
110
122
  this._prevHeight = this._height;
111
123
  }
124
+ // Handle brush behavior
125
+ if (!disableBrush) {
126
+ this._brushBehavior.extent([[0, 0], [this._width, this._height]]);
127
+ this._brush.call(this._brushBehavior);
128
+ // Activate the brush when the shift key is pressed
129
+ select(window)
130
+ .on('keydown.unovis-graph', e => e.key === 'Shift' && this._activateBrush())
131
+ .on('keyup.unovis-graph', e => e.key === 'Shift' && this._clearBrush());
132
+ this._zoomBehavior.filter(event => !event.shiftKey);
133
+ }
134
+ else {
135
+ this._brush.on('.brush', null);
136
+ select(window)
137
+ .on('keydown.unovis-graph', null)
138
+ .on('keyup.unovis-graph', null);
139
+ // Clear brush in case it was disabled in an active state
140
+ if (this._brush.classed('active'))
141
+ this._clearBrush();
142
+ }
112
143
  // Apply layout and render
113
144
  if (this._shouldRecalculateLayout || !this._layoutCalculationPromise) {
114
145
  this._layoutCalculationPromise = this._calculateLayout();
@@ -119,20 +150,21 @@ class Graph extends ComponentCore {
119
150
  (_b = (_a = this.config).onLayoutCalculated) === null || _b === void 0 ? void 0 : _b.call(_a, datamodel.nodes, datamodel.links);
120
151
  });
121
152
  }
122
- // Redefine Zoom Behavior filter is specified in the config
123
- // https://d3js.org/d3-zoom#zoom_filter
153
+ // Redefining Zoom Behavior filter to the one specified in the config,
154
+ // or to the default one supporting `shiftKey` for node brushing
155
+ // See more: https://d3js.org/d3-zoom#zoom_filter
124
156
  this._zoomBehavior.filter(isFunction(zoomEventFilter)
125
157
  ? zoomEventFilter
126
- : (e) => !e.shiftKey); // Default filter
127
- this._layoutCalculationPromise.then((isFirstRender) => {
128
- var _a, _b;
158
+ : (e) => (!e.ctrlKey || e.type === 'wheel') && !e.button && !e.shiftKey); // Default filter
159
+ this._layoutCalculationPromise.then(() => {
160
+ var _a, _b, _c;
129
161
  // If the component has been destroyed while the layout calculation
130
162
  // was in progress, we cancel the render
131
163
  if (this.isDestroyed())
132
164
  return;
133
165
  this._initPanelsData();
134
166
  // Fit the view
135
- if (isFirstRender) {
167
+ if (this._isFirstRender) {
136
168
  this._fit();
137
169
  this._shouldFitLayout = false;
138
170
  }
@@ -140,19 +172,20 @@ class Graph extends ComponentCore {
140
172
  this._fit(duration);
141
173
  this._shouldFitLayout = false;
142
174
  }
143
- // Draw
144
- this._drawNodes(animDuration);
145
- this._drawLinks(animDuration);
146
- // Select Links / Nodes
147
- this._resetSelection();
148
- if (this.config.selectedNodeId) {
149
- const selectedNode = datamodel.nodes.find(node => node.id === this.config.selectedNodeId);
150
- this._selectNode(selectedNode);
175
+ // Update Nodes and Links Selection State
176
+ this._resetSelectionGreyoutState();
177
+ if (this.config.selectedNodeId || this.config.selectedNodeIds) {
178
+ const selectedIds = (_a = this.config.selectedNodeIds) !== null && _a !== void 0 ? _a : [this.config.selectedNodeId];
179
+ const selectedNodes = selectedIds.map(id => datamodel.getNodeFromId(id));
180
+ this._setNodeSelectionState(selectedNodes);
151
181
  }
152
182
  if (this.config.selectedLinkId) {
153
183
  const selectedLink = datamodel.links.find(link => link.id === this.config.selectedLinkId);
154
- this._selectLink(selectedLink);
184
+ this._setLinkSelectionState(selectedLink);
155
185
  }
186
+ // Draw
187
+ this._drawNodes(animDuration);
188
+ this._drawLinks(animDuration);
156
189
  // Link flow animation timer
157
190
  if (!this._timer) {
158
191
  const refreshRateMs = 35;
@@ -163,10 +196,6 @@ class Graph extends ComponentCore {
163
196
  this.g.on('.zoom', null);
164
197
  else
165
198
  this.g.call(this._zoomBehavior).on('dblclick.zoom', null);
166
- if (!this._isFirstRender && !disableZoom) {
167
- const transform = zoomTransform(this.g.node());
168
- this._onZoom(transform);
169
- }
170
199
  // While the graph is animating we disable pointer events on the graph group
171
200
  if (animDuration) {
172
201
  this._graphGroup.attr('pointer-events', 'none');
@@ -180,9 +209,9 @@ class Graph extends ComponentCore {
180
209
  this._setUpComponentEventsThrottled();
181
210
  this._setCustomAttributesThrottled();
182
211
  // On render complete callback
183
- (_b = (_a = this.config).onRenderComplete) === null || _b === void 0 ? void 0 : _b.call(_a, this.g, datamodel.nodes, datamodel.links, this.config, animDuration, this._scale, this._width, this._height);
212
+ (_c = (_b = this.config).onRenderComplete) === null || _c === void 0 ? void 0 : _c.call(_b, this.g, datamodel.nodes, datamodel.links, this.config, animDuration, this._scale, this._containerWidth, this._containerHeight);
213
+ this._isFirstRender = false;
184
214
  });
185
- this._isFirstRender = false;
186
215
  }
187
216
  _drawNodes(duration) {
188
217
  const { config, datamodel } = this;
@@ -204,9 +233,9 @@ class Graph extends ComponentCore {
204
233
  const thisRef = this;
205
234
  if (!config.disableDrag) {
206
235
  const dragBehaviour = drag()
207
- .on('start', function (event, d) { thisRef._onDragStarted(d, event, select(this)); })
208
- .on('drag', function (event, d) { thisRef._onDragged(d, event, nodeGroupsMerged); })
209
- .on('end', function (event, d) { thisRef._onDragEnded(d, event, select(this)); });
236
+ .on('start drag end', function (event, d) {
237
+ thisRef._handleDrag(d, event, select(this));
238
+ });
210
239
  nodeGroupsMerged.call(dragBehaviour);
211
240
  }
212
241
  else {
@@ -261,7 +290,6 @@ class Graph extends ComponentCore {
261
290
  _calculateLayout() {
262
291
  return __awaiter(this, void 0, void 0, function* () {
263
292
  const { config, datamodel } = this;
264
- const firstRender = this._isFirstRender;
265
293
  // If the layout type has changed, we need to reset the node positions if they were fixed before
266
294
  if (this._currentLayoutType !== config.layoutType) {
267
295
  for (const node of datamodel.nodes) {
@@ -300,7 +328,6 @@ class Graph extends ComponentCore {
300
328
  this._initPanelsData();
301
329
  this._shouldRecalculateLayout = false;
302
330
  this._currentLayoutType = config.layoutType;
303
- return firstRender;
304
331
  });
305
332
  }
306
333
  _initPanelsData() {
@@ -348,38 +375,45 @@ class Graph extends ComponentCore {
348
375
  .scale(clampedScale);
349
376
  return transform;
350
377
  }
351
- _selectNode(node) {
352
- const { datamodel: { nodes, links } } = this;
353
- if (!node)
354
- console.warn('Unovis | Graph: Select Node: Not found');
355
- this._selectedNode = node;
356
- // Apply grey out
357
- // Grey out all nodes
358
- nodes.forEach(n => {
378
+ _setNodeSelectionState(nodesToSelect) {
379
+ const { config, datamodel } = this;
380
+ // Grey out all nodes and set us unselected
381
+ for (const n of datamodel.nodes) {
359
382
  n._state.selected = false;
360
- n._state.greyout = true;
361
- });
362
- // Grey out all links
363
- links.forEach(l => {
364
- l._state.greyout = true;
383
+ if (config.nodeSelectionHighlightMode !== GraphNodeSelectionHighlightMode.None) {
384
+ n._state.greyout = true;
385
+ }
386
+ }
387
+ // Grey out all links and set us unselected
388
+ for (const l of datamodel.links) {
365
389
  l._state.selected = false;
390
+ if (config.nodeSelectionHighlightMode !== GraphNodeSelectionHighlightMode.None) {
391
+ l._state.greyout = true;
392
+ }
393
+ }
394
+ // Filter out non-existing nodes
395
+ this._selectedNodes = nodesToSelect.filter(n => {
396
+ const doesNodeExist = Boolean(n);
397
+ if (!doesNodeExist)
398
+ console.warn('Unovis | Graph: Select Node: Not found');
399
+ return doesNodeExist;
366
400
  });
367
- // Highlight selected
368
- if (node) {
369
- node._state.selected = true;
370
- node._state.greyout = false;
371
- const connectedLinks = links.filter(l => (l.source === node) || (l.target === node));
401
+ // Set provided nodes as selected and ungreyout
402
+ for (const n of this._selectedNodes) {
403
+ n._state.selected = true;
404
+ n._state.greyout = false;
405
+ }
406
+ // Highlight connected links and nodes
407
+ if (config.nodeSelectionHighlightMode === GraphNodeSelectionHighlightMode.GreyoutNonConnected) {
408
+ const connectedLinks = datamodel.links.filter(l => this._selectedNodes.includes(l.source) || this._selectedNodes.includes(l.target));
372
409
  connectedLinks.forEach(l => {
373
- const source = l.source;
374
- const target = l.target;
375
- source._state.greyout = false;
376
- target._state.greyout = false;
410
+ l.source._state.greyout = false;
411
+ l.target._state.greyout = false;
377
412
  l._state.greyout = false;
378
413
  });
379
414
  }
380
- this._updateSelectedElements();
381
415
  }
382
- _selectLink(link) {
416
+ _setLinkSelectionState(link) {
383
417
  const { datamodel: { nodes, links } } = this;
384
418
  if (!link)
385
419
  console.warn('Unovis: Graph: Select Link: Not found');
@@ -409,13 +443,12 @@ class Graph extends ComponentCore {
409
443
  });
410
444
  if (link)
411
445
  link._state.selected = true;
412
- this._updateSelectedElements();
413
446
  }
414
- _resetSelection() {
447
+ _resetSelectionGreyoutState() {
415
448
  const { datamodel: { nodes, links } } = this;
416
- this._selectedNode = undefined;
449
+ this._selectedNodes = [];
417
450
  this._selectedLink = undefined;
418
- // Disable Grayout
451
+ // Disable Greyout
419
452
  nodes.forEach(n => {
420
453
  delete n._state.selected;
421
454
  delete n._state.greyout;
@@ -424,27 +457,28 @@ class Graph extends ComponentCore {
424
457
  delete l._state.greyout;
425
458
  delete l._state.selected;
426
459
  });
427
- this._updateSelectedElements();
428
460
  }
429
- _updateSelectedElements() {
461
+ _updateNodesLinksPartial() {
430
462
  const { config } = this;
431
463
  const linkElements = this._linksGroup.selectAll(`.${gLink}`);
432
- linkElements.call(updateSelectedLinks, config, this._scale);
464
+ linkElements.call(updateLinksPartial, config, this._scale);
433
465
  const nodeElements = this._nodesGroup.selectAll(`.${gNode}`);
434
- nodeElements.call(updateNodeSelectedGreyout, config);
435
- // this._drawPanels(nodeElements, 0)
466
+ nodeElements.call(updateNodesPartial, config, config.duration, this._scale);
436
467
  }
437
468
  _onBackgroundClick() {
438
- this._resetSelection();
469
+ this._resetSelectionGreyoutState();
470
+ this._updateNodesLinksPartial();
439
471
  }
440
472
  // eslint-disable-next-line @typescript-eslint/no-empty-function
441
473
  _onNodeClick(d) {
442
474
  }
443
475
  // eslint-disable-next-line @typescript-eslint/no-empty-function
444
476
  _onNodeMouseOut(d) {
477
+ this._updateNodesLinksPartial();
445
478
  }
446
479
  // eslint-disable-next-line @typescript-eslint/no-empty-function
447
480
  _onNodeMouseOver(d) {
481
+ this._updateNodesLinksPartial();
448
482
  }
449
483
  // eslint-disable-next-line @typescript-eslint/no-empty-function
450
484
  _onLinkClick(d) {
@@ -453,13 +487,13 @@ class Graph extends ComponentCore {
453
487
  if (this._isDragging)
454
488
  return;
455
489
  d._state.hovered = true;
456
- this._updateSelectedElements();
490
+ this._updateNodesLinksPartial();
457
491
  }
458
492
  _onLinkMouseOut(d) {
459
493
  if (this._isDragging)
460
494
  return;
461
495
  delete d._state.hovered;
462
- this._updateSelectedElements();
496
+ this._updateNodesLinksPartial();
463
497
  }
464
498
  _onLinkFlowTimerFrame(elapsed = 0) {
465
499
  const { config: { linkFlow, linkFlowAnimDuration }, datamodel: { links } } = this;
@@ -478,7 +512,7 @@ class Graph extends ComponentCore {
478
512
  this._scale = transform.k;
479
513
  this._graphGroup.attr('transform', transform.toString());
480
514
  if (isFunction(config.onZoom))
481
- config.onZoom(this._scale, config.zoomScaleExtent, event);
515
+ config.onZoom(this._scale, config.zoomScaleExtent, event, transform);
482
516
  // console.warn('Unovis | Graph: Zoom: ', transform)
483
517
  if (!this._initialTransform)
484
518
  this._initialTransform = transform;
@@ -503,27 +537,17 @@ class Graph extends ComponentCore {
503
537
  this._linksGroup.selectAll(`.${gLink}`)
504
538
  .call((nodes.length > config.zoomThrottledUpdateNodeThreshold ? zoomLinksThrottled : zoomLinks), config, this._scale, this._getLinkArrowDefId);
505
539
  }
506
- _onDragStarted(d, event, nodeSelection) {
507
- var _a;
508
- const { config } = this;
509
- this._isDragging = true;
510
- d._state.isDragged = true;
511
- nodeSelection.call(updateNodes, config, 0, this._scale);
512
- (_a = config.onNodeDragStart) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
513
- }
514
- _onDragged(d, event, allNodesSelection) {
515
- var _a, _b, _c;
516
- const { config } = this;
540
+ _updateNodePosition(d, x, y) {
541
+ var _a, _b;
517
542
  const transform = zoomTransform(this.g.node());
518
543
  const scale = transform.k;
519
544
  // Prevent the node from being dragged offscreen or outside its panel
520
545
  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 : [];
521
- const nodeSizeValue = getNodeSize(d, config.nodeSize, d._index);
546
+ const nodeSizeValue = getNodeSize(d, this.config.nodeSize, d._index);
522
547
  const maxY = min([(this._height - transform.y) / scale, ...panels.map(p => p._y + p._height)]) - nodeSizeValue / 2;
523
548
  const maxX = min([(this._width - transform.x) / scale, ...panels.map(p => p._x + p._width)]) - nodeSizeValue / 2;
524
549
  const minY = max([-transform.y / scale, ...panels.map(p => p._y)]) + nodeSizeValue / 2;
525
550
  const minX = max([-transform.x / scale, ...panels.map(p => p._x)]) + nodeSizeValue / 2;
526
- let [x, y] = pointer(event, this._graphGroup.node());
527
551
  if (y < minY)
528
552
  y = minY;
529
553
  else if (y > maxY)
@@ -544,6 +568,62 @@ class Graph extends ComponentCore {
544
568
  delete d._state.fx;
545
569
  if (d._state.fy === d.y)
546
570
  delete d._state.fy;
571
+ }
572
+ _onBrush(event) {
573
+ var _a;
574
+ if (!event.selection || !event.sourceEvent)
575
+ return;
576
+ const { config } = this;
577
+ const transform = zoomTransform(this._graphGroup.node());
578
+ const [xMin, yMin] = transform.invert(event.selection[0]);
579
+ const [xMax, yMax] = transform.invert(event.selection[1]);
580
+ // Update brushed nodes
581
+ this._nodesGroup.selectAll(`.${gNode}`)
582
+ .each(n => {
583
+ const x = getX(n);
584
+ const y = getY(n);
585
+ n._state.brushed = x >= xMin && x <= xMax && y >= yMin && y <= yMax;
586
+ })
587
+ .classed(brushed, n => n._state.brushed);
588
+ const brushedNodes = this._nodesGroup.selectAll(`.${brushed}`)
589
+ .call(updateNodesPartial, config, 0, this._scale);
590
+ this._brush.classed('active', event.type !== 'end');
591
+ (_a = config.onNodeSelectionBrush) === null || _a === void 0 ? void 0 : _a.call(config, brushedNodes.data(), event);
592
+ }
593
+ _handleDrag(d, event, nodeSelection) {
594
+ if (event.sourceEvent.shiftKey && d._state.brushed) {
595
+ this._dragSelectedNodes(event);
596
+ }
597
+ else if (!event.sourceEvent.shiftKey) {
598
+ switch (event.type) {
599
+ case 'start':
600
+ this._onDragStarted(d, event, nodeSelection);
601
+ break;
602
+ case 'drag':
603
+ this._onDragged(d, event);
604
+ break;
605
+ case 'end':
606
+ this._onDragEnded(d, event, nodeSelection);
607
+ break;
608
+ }
609
+ }
610
+ }
611
+ _onDragStarted(d, event, nodeSelection) {
612
+ var _a;
613
+ const { config } = this;
614
+ this._isDragging = true;
615
+ d._state.isDragged = true;
616
+ nodeSelection.call(updateNodes, config, 0, this._scale);
617
+ (_a = config.onNodeDragStart) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
618
+ }
619
+ _onDragged(d, event) {
620
+ var _a;
621
+ const { config } = this;
622
+ const transform = zoomTransform(this.g.node());
623
+ const scale = transform.k;
624
+ // Update node position
625
+ const [x, y] = pointer(event, this._graphGroup.node());
626
+ this._updateNodePosition(d, x, y);
547
627
  // Update affected DOM elements
548
628
  const nodeSelection = this._nodesGroup.selectAll(`.${gNode}`);
549
629
  const nodeToUpdate = nodeSelection.filter((n) => n._id === d._id);
@@ -558,7 +638,7 @@ class Graph extends ComponentCore {
558
638
  const linksToAnimate = linksToUpdate.filter(d => d._state.greyout);
559
639
  if (linksToAnimate.size())
560
640
  animateLinkFlow(linksToAnimate, config, this._scale);
561
- (_c = config.onNodeDrag) === null || _c === void 0 ? void 0 : _c.call(config, d, event);
641
+ (_a = config.onNodeDrag) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
562
642
  }
563
643
  _onDragEnded(d, event, nodeSelection) {
564
644
  var _a;
@@ -568,6 +648,49 @@ class Graph extends ComponentCore {
568
648
  nodeSelection.call(updateNodes, config, 0, this._scale);
569
649
  (_a = config.onNodeDragEnd) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
570
650
  }
651
+ _dragSelectedNodes(event) {
652
+ var _a, _b;
653
+ const { config } = this;
654
+ const curr = pointer(event, this._graphGroup.node());
655
+ const selectedNodes = smartTransition(this._nodesGroup.selectAll(`.${brushed}`));
656
+ if (event.type === 'start') {
657
+ this._groupDragInit = curr;
658
+ this._isDragging = true;
659
+ selectedNodes.each(n => {
660
+ n.x = getX(n);
661
+ n.y = getY(n);
662
+ n._state.isDragged = true;
663
+ });
664
+ }
665
+ else if (event.type === 'drag') {
666
+ const dx = curr[0] - this._groupDragInit[0];
667
+ const dy = curr[1] - this._groupDragInit[1];
668
+ selectedNodes.each(n => this._updateNodePosition(n, n.x + dx, n.y + dy));
669
+ const connectedLinks = smartTransition(this._linksGroup.selectAll(`.${gLink}`)
670
+ .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); }));
671
+ connectedLinks.call(updateLinks, this.config, 0, this._scale, this._getLinkArrowDefId);
672
+ }
673
+ else {
674
+ this._isDragging = false;
675
+ selectedNodes.each(n => { n._state.isDragged = false; });
676
+ }
677
+ selectedNodes.call(updateNodes, config, 0, this._scale);
678
+ (_b = (_a = this.config).onNodeSelectionDrag) === null || _b === void 0 ? void 0 : _b.call(_a, selectedNodes.data(), event);
679
+ }
680
+ _activateBrush() {
681
+ this._brush.classed('active', true);
682
+ this._nodesGroup.selectAll(`.${gNode}`)
683
+ .classed(brushable, true);
684
+ }
685
+ _clearBrush() {
686
+ var _a;
687
+ this._brush.classed('active', false).call((_a = this._brushBehavior) === null || _a === void 0 ? void 0 : _a.clear);
688
+ this._nodesGroup.selectAll(`.${gNode}`)
689
+ .classed(brushable, false)
690
+ .classed(brushed, false)
691
+ .each(n => { n._state.brushed = false; })
692
+ .call(updateNodesPartial, this.config, 0, this._scale);
693
+ }
571
694
  _shouldLayoutRecalculate() {
572
695
  const { prevConfig, config } = this;
573
696
  if (prevConfig.layoutType !== config.layoutType)