@unovis/ts 1.5.0-alpha.7 → 1.5.0-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.
Files changed (70) hide show
  1. package/components/axis/config.d.ts +10 -1
  2. package/components/axis/config.js +1 -1
  3. package/components/axis/config.js.map +1 -1
  4. package/components/axis/index.d.ts +19 -19
  5. package/components/axis/index.js +63 -8
  6. package/components/axis/index.js.map +1 -1
  7. package/components/axis/style.d.ts +1 -0
  8. package/components/axis/style.js +7 -2
  9. package/components/axis/style.js.map +1 -1
  10. package/components/graph/config.d.ts +29 -5
  11. package/components/graph/config.js +3 -3
  12. package/components/graph/config.js.map +1 -1
  13. package/components/graph/index.d.ts +18 -6
  14. package/components/graph/index.js +230 -89
  15. package/components/graph/index.js.map +1 -1
  16. package/components/graph/modules/link/index.d.ts +2 -1
  17. package/components/graph/modules/link/index.js +36 -17
  18. package/components/graph/modules/link/index.js.map +1 -1
  19. package/components/graph/modules/node/index.d.ts +2 -1
  20. package/components/graph/modules/node/index.js +47 -23
  21. package/components/graph/modules/node/index.js.map +1 -1
  22. package/components/graph/modules/node/style.d.ts +2 -0
  23. package/components/graph/modules/node/style.js +34 -4
  24. package/components/graph/modules/node/style.js.map +1 -1
  25. package/components/graph/modules/panel/index.js +1 -0
  26. package/components/graph/modules/panel/index.js.map +1 -1
  27. package/components/graph/style.d.ts +1 -0
  28. package/components/graph/style.js +22 -1
  29. package/components/graph/style.js.map +1 -1
  30. package/components/graph/types.d.ts +8 -0
  31. package/components/graph/types.js +8 -2
  32. package/components/graph/types.js.map +1 -1
  33. package/components/leaflet-map/modules/map.js +2 -1
  34. package/components/leaflet-map/modules/map.js.map +1 -1
  35. package/components/scatter/index.d.ts +1 -0
  36. package/components/scatter/index.js +19 -12
  37. package/components/scatter/index.js.map +1 -1
  38. package/components/scatter/modules/point.js +1 -3
  39. package/components/scatter/modules/point.js.map +1 -1
  40. package/components/scatter/types.d.ts +2 -0
  41. package/components/topojson-map/config.js +2 -2
  42. package/components/topojson-map/config.js.map +1 -1
  43. package/components/topojson-map/index.js +22 -5
  44. package/components/topojson-map/index.js.map +1 -1
  45. package/containers/single-container/index.js +2 -2
  46. package/containers/single-container/index.js.map +1 -1
  47. package/containers/xy-container/index.js +2 -0
  48. package/containers/xy-container/index.js.map +1 -1
  49. package/core/container/config.d.ts +2 -4
  50. package/core/container/config.js.map +1 -1
  51. package/core/container/index.d.ts +2 -1
  52. package/core/container/index.js +20 -14
  53. package/core/container/index.js.map +1 -1
  54. package/data-models/graph.d.ts +2 -0
  55. package/data-models/graph.js +6 -0
  56. package/data-models/graph.js.map +1 -1
  57. package/index.d.ts +1 -4
  58. package/index.js +13 -5
  59. package/index.js.map +1 -1
  60. package/maps/world-simple.json.js +2430 -42
  61. package/package.json +1 -1
  62. package/types/graph.d.ts +2 -0
  63. package/types.js +1 -1
  64. package/utils/index.d.ts +12 -0
  65. package/utils/index.js +13 -0
  66. package/utils/index.js.map +1 -0
  67. package/utils/scale.js +4 -0
  68. package/utils/scale.js.map +1 -0
  69. package/utils/type.js +2 -0
  70. package/utils/type.js.map +1 -0
@@ -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,16 @@ 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
+ .on('start', (e) => this._onZoomStart(e.transform, e))
62
+ .on('end', (e) => this._onZoomEnd(e.transform, e));
63
+ this._brushBehavior = brush$1()
64
+ .on('start brush end', this._onBrush.bind(this))
65
+ .filter(event => event.shiftKey)
66
+ .keyModifiers(false);
59
67
  this._panelsGroup = this._graphGroup.append('g').attr('class', panels);
60
68
  this._linksGroup = this._graphGroup.append('g').attr('class', links);
61
69
  this._nodesGroup = this._graphGroup.append('g').attr('class', nodes);
@@ -63,13 +71,19 @@ class Graph extends ComponentCore {
63
71
  this._getLinkArrowDefId = this._getLinkArrowDefId.bind(this);
64
72
  }
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;
70
82
  }
71
83
  setData(data) {
72
84
  const { config } = this;
85
+ if (isEqual(this.datamodel.data, data))
86
+ return;
73
87
  this.datamodel.nodeSort = config.nodeSort;
74
88
  this.datamodel.data = data;
75
89
  this._shouldRecalculateLayout = true;
@@ -95,7 +109,7 @@ class Graph extends ComponentCore {
95
109
  return { top: extraPadding, bottom: extraPadding, left: extraPadding, right: extraPadding };
96
110
  }
97
111
  _render(customDuration) {
98
- const { config: { disableZoom, duration, layoutAutofit, zoomEventFilter }, datamodel } = this;
112
+ const { config: { disableBrush, disableZoom, duration, layoutAutofit, zoomEventFilter }, datamodel } = this;
99
113
  if (!datamodel.nodes && !datamodel.links)
100
114
  return;
101
115
  const animDuration = isNumber(customDuration) ? customDuration : duration;
@@ -109,6 +123,25 @@ class Graph extends ComponentCore {
109
123
  this._prevWidth = this._width;
110
124
  this._prevHeight = this._height;
111
125
  }
126
+ // Handle brush behavior
127
+ if (!disableBrush) {
128
+ this._brushBehavior.extent([[0, 0], [this._width, this._height]]);
129
+ this._brush.call(this._brushBehavior);
130
+ // Activate the brush when the shift key is pressed
131
+ select(window)
132
+ .on('keydown.unovis-graph', e => e.key === 'Shift' && this._activateBrush())
133
+ .on('keyup.unovis-graph', e => e.key === 'Shift' && this._clearBrush());
134
+ this._zoomBehavior.filter(event => !event.shiftKey);
135
+ }
136
+ else {
137
+ this._brush.on('.brush', null);
138
+ select(window)
139
+ .on('keydown.unovis-graph', null)
140
+ .on('keyup.unovis-graph', null);
141
+ // Clear brush in case it was disabled in an active state
142
+ if (this._brush.classed('active'))
143
+ this._clearBrush();
144
+ }
112
145
  // Apply layout and render
113
146
  if (this._shouldRecalculateLayout || !this._layoutCalculationPromise) {
114
147
  this._layoutCalculationPromise = this._calculateLayout();
@@ -119,20 +152,21 @@ class Graph extends ComponentCore {
119
152
  (_b = (_a = this.config).onLayoutCalculated) === null || _b === void 0 ? void 0 : _b.call(_a, datamodel.nodes, datamodel.links);
120
153
  });
121
154
  }
122
- // Redefine Zoom Behavior filter is specified in the config
123
- // https://d3js.org/d3-zoom#zoom_filter
155
+ // Redefining Zoom Behavior filter to the one specified in the config,
156
+ // or to the default one supporting `shiftKey` for node brushing
157
+ // See more: https://d3js.org/d3-zoom#zoom_filter
124
158
  this._zoomBehavior.filter(isFunction(zoomEventFilter)
125
159
  ? zoomEventFilter
126
- : (e) => !e.shiftKey); // Default filter
127
- this._layoutCalculationPromise.then((isFirstRender) => {
128
- var _a, _b;
160
+ : (e) => (!e.ctrlKey || e.type === 'wheel') && !e.button && !e.shiftKey); // Default filter
161
+ this._layoutCalculationPromise.then(() => {
162
+ var _a, _b, _c;
129
163
  // If the component has been destroyed while the layout calculation
130
164
  // was in progress, we cancel the render
131
165
  if (this.isDestroyed())
132
166
  return;
133
167
  this._initPanelsData();
134
168
  // Fit the view
135
- if (isFirstRender) {
169
+ if (this._isFirstRender) {
136
170
  this._fit();
137
171
  this._shouldFitLayout = false;
138
172
  }
@@ -140,19 +174,20 @@ class Graph extends ComponentCore {
140
174
  this._fit(duration);
141
175
  this._shouldFitLayout = false;
142
176
  }
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);
177
+ // Update Nodes and Links Selection State
178
+ this._resetSelectionGreyoutState();
179
+ if (this.config.selectedNodeId || this.config.selectedNodeIds) {
180
+ const selectedIds = (_a = this.config.selectedNodeIds) !== null && _a !== void 0 ? _a : [this.config.selectedNodeId];
181
+ const selectedNodes = selectedIds.map(id => datamodel.getNodeFromId(id));
182
+ this._setNodeSelectionState(selectedNodes);
151
183
  }
152
184
  if (this.config.selectedLinkId) {
153
185
  const selectedLink = datamodel.links.find(link => link.id === this.config.selectedLinkId);
154
- this._selectLink(selectedLink);
186
+ this._setLinkSelectionState(selectedLink);
155
187
  }
188
+ // Draw
189
+ this._drawNodes(animDuration);
190
+ this._drawLinks(animDuration);
156
191
  // Link flow animation timer
157
192
  if (!this._timer) {
158
193
  const refreshRateMs = 35;
@@ -163,10 +198,6 @@ class Graph extends ComponentCore {
163
198
  this.g.on('.zoom', null);
164
199
  else
165
200
  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
201
  // While the graph is animating we disable pointer events on the graph group
171
202
  if (animDuration) {
172
203
  this._graphGroup.attr('pointer-events', 'none');
@@ -180,9 +211,9 @@ class Graph extends ComponentCore {
180
211
  this._setUpComponentEventsThrottled();
181
212
  this._setCustomAttributesThrottled();
182
213
  // 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);
214
+ (_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);
215
+ this._isFirstRender = false;
184
216
  });
185
- this._isFirstRender = false;
186
217
  }
187
218
  _drawNodes(duration) {
188
219
  const { config, datamodel } = this;
@@ -204,9 +235,9 @@ class Graph extends ComponentCore {
204
235
  const thisRef = this;
205
236
  if (!config.disableDrag) {
206
237
  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)); });
238
+ .on('start drag end', function (event, d) {
239
+ thisRef._handleDrag(d, event, select(this));
240
+ });
210
241
  nodeGroupsMerged.call(dragBehaviour);
211
242
  }
212
243
  else {
@@ -261,7 +292,6 @@ class Graph extends ComponentCore {
261
292
  _calculateLayout() {
262
293
  return __awaiter(this, void 0, void 0, function* () {
263
294
  const { config, datamodel } = this;
264
- const firstRender = this._isFirstRender;
265
295
  // If the layout type has changed, we need to reset the node positions if they were fixed before
266
296
  if (this._currentLayoutType !== config.layoutType) {
267
297
  for (const node of datamodel.nodes) {
@@ -300,7 +330,6 @@ class Graph extends ComponentCore {
300
330
  this._initPanelsData();
301
331
  this._shouldRecalculateLayout = false;
302
332
  this._currentLayoutType = config.layoutType;
303
- return firstRender;
304
333
  });
305
334
  }
306
335
  _initPanelsData() {
@@ -311,9 +340,10 @@ class Graph extends ComponentCore {
311
340
  this._shouldSetPanels = false;
312
341
  }
313
342
  }
314
- _fit(duration = 0) {
343
+ _fit(duration = 0, nodeIds) {
315
344
  const { datamodel: { nodes } } = this;
316
- const transform = this._getTransform(nodes);
345
+ const fitViewNodes = (nodeIds === null || nodeIds === void 0 ? void 0 : nodeIds.length) ? nodes.filter(n => nodeIds.includes(n.id)) : nodes;
346
+ const transform = this._getTransform(fitViewNodes);
317
347
  smartTransition(this.g, duration)
318
348
  .call(this._zoomBehavior.transform, transform);
319
349
  this._onZoom(transform);
@@ -348,38 +378,45 @@ class Graph extends ComponentCore {
348
378
  .scale(clampedScale);
349
379
  return transform;
350
380
  }
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 => {
381
+ _setNodeSelectionState(nodesToSelect) {
382
+ const { config, datamodel } = this;
383
+ // Grey out all nodes and set us unselected
384
+ for (const n of datamodel.nodes) {
359
385
  n._state.selected = false;
360
- n._state.greyout = true;
361
- });
362
- // Grey out all links
363
- links.forEach(l => {
364
- l._state.greyout = true;
386
+ if (config.nodeSelectionHighlightMode !== GraphNodeSelectionHighlightMode.None) {
387
+ n._state.greyout = true;
388
+ }
389
+ }
390
+ // Grey out all links and set us unselected
391
+ for (const l of datamodel.links) {
365
392
  l._state.selected = false;
393
+ if (config.nodeSelectionHighlightMode !== GraphNodeSelectionHighlightMode.None) {
394
+ l._state.greyout = true;
395
+ }
396
+ }
397
+ // Filter out non-existing nodes
398
+ this._selectedNodes = nodesToSelect.filter(n => {
399
+ const doesNodeExist = Boolean(n);
400
+ if (!doesNodeExist)
401
+ console.warn('Unovis | Graph: Select Node: Not found');
402
+ return doesNodeExist;
366
403
  });
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));
404
+ // Set provided nodes as selected and ungreyout
405
+ for (const n of this._selectedNodes) {
406
+ n._state.selected = true;
407
+ n._state.greyout = false;
408
+ }
409
+ // Highlight connected links and nodes
410
+ if (config.nodeSelectionHighlightMode === GraphNodeSelectionHighlightMode.GreyoutNonConnected) {
411
+ const connectedLinks = datamodel.links.filter(l => this._selectedNodes.includes(l.source) || this._selectedNodes.includes(l.target));
372
412
  connectedLinks.forEach(l => {
373
- const source = l.source;
374
- const target = l.target;
375
- source._state.greyout = false;
376
- target._state.greyout = false;
413
+ l.source._state.greyout = false;
414
+ l.target._state.greyout = false;
377
415
  l._state.greyout = false;
378
416
  });
379
417
  }
380
- this._updateSelectedElements();
381
418
  }
382
- _selectLink(link) {
419
+ _setLinkSelectionState(link) {
383
420
  const { datamodel: { nodes, links } } = this;
384
421
  if (!link)
385
422
  console.warn('Unovis: Graph: Select Link: Not found');
@@ -409,13 +446,12 @@ class Graph extends ComponentCore {
409
446
  });
410
447
  if (link)
411
448
  link._state.selected = true;
412
- this._updateSelectedElements();
413
449
  }
414
- _resetSelection() {
450
+ _resetSelectionGreyoutState() {
415
451
  const { datamodel: { nodes, links } } = this;
416
- this._selectedNode = undefined;
452
+ this._selectedNodes = [];
417
453
  this._selectedLink = undefined;
418
- // Disable Grayout
454
+ // Disable Greyout
419
455
  nodes.forEach(n => {
420
456
  delete n._state.selected;
421
457
  delete n._state.greyout;
@@ -424,27 +460,28 @@ class Graph extends ComponentCore {
424
460
  delete l._state.greyout;
425
461
  delete l._state.selected;
426
462
  });
427
- this._updateSelectedElements();
428
463
  }
429
- _updateSelectedElements() {
464
+ _updateNodesLinksPartial() {
430
465
  const { config } = this;
431
466
  const linkElements = this._linksGroup.selectAll(`.${gLink}`);
432
- linkElements.call(updateSelectedLinks, config, this._scale);
467
+ linkElements.call(updateLinksPartial, config, this._scale);
433
468
  const nodeElements = this._nodesGroup.selectAll(`.${gNode}`);
434
- nodeElements.call(updateNodeSelectedGreyout, config);
435
- // this._drawPanels(nodeElements, 0)
469
+ nodeElements.call(updateNodesPartial, config, config.duration, this._scale);
436
470
  }
437
471
  _onBackgroundClick() {
438
- this._resetSelection();
472
+ this._resetSelectionGreyoutState();
473
+ this._updateNodesLinksPartial();
439
474
  }
440
475
  // eslint-disable-next-line @typescript-eslint/no-empty-function
441
476
  _onNodeClick(d) {
442
477
  }
443
478
  // eslint-disable-next-line @typescript-eslint/no-empty-function
444
479
  _onNodeMouseOut(d) {
480
+ this._updateNodesLinksPartial();
445
481
  }
446
482
  // eslint-disable-next-line @typescript-eslint/no-empty-function
447
483
  _onNodeMouseOver(d) {
484
+ this._updateNodesLinksPartial();
448
485
  }
449
486
  // eslint-disable-next-line @typescript-eslint/no-empty-function
450
487
  _onLinkClick(d) {
@@ -453,13 +490,13 @@ class Graph extends ComponentCore {
453
490
  if (this._isDragging)
454
491
  return;
455
492
  d._state.hovered = true;
456
- this._updateSelectedElements();
493
+ this._updateNodesLinksPartial();
457
494
  }
458
495
  _onLinkMouseOut(d) {
459
496
  if (this._isDragging)
460
497
  return;
461
498
  delete d._state.hovered;
462
- this._updateSelectedElements();
499
+ this._updateNodesLinksPartial();
463
500
  }
464
501
  _onLinkFlowTimerFrame(elapsed = 0) {
465
502
  const { config: { linkFlow, linkFlowAnimDuration }, datamodel: { links } } = this;
@@ -478,7 +515,7 @@ class Graph extends ComponentCore {
478
515
  this._scale = transform.k;
479
516
  this._graphGroup.attr('transform', transform.toString());
480
517
  if (isFunction(config.onZoom))
481
- config.onZoom(this._scale, config.zoomScaleExtent, event);
518
+ config.onZoom(this._scale, config.zoomScaleExtent, event, transform);
482
519
  // console.warn('Unovis | Graph: Zoom: ', transform)
483
520
  if (!this._initialTransform)
484
521
  this._initialTransform = transform;
@@ -503,27 +540,31 @@ class Graph extends ComponentCore {
503
540
  this._linksGroup.selectAll(`.${gLink}`)
504
541
  .call((nodes.length > config.zoomThrottledUpdateNodeThreshold ? zoomLinksThrottled : zoomLinks), config, this._scale, this._getLinkArrowDefId);
505
542
  }
506
- _onDragStarted(d, event, nodeSelection) {
507
- var _a;
543
+ _onZoomStart(t, event) {
508
544
  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);
545
+ const transform = t || event.transform;
546
+ this._scale = transform.k;
547
+ if (isFunction(config.onZoomStart))
548
+ config.onZoomStart(this._scale, config.zoomScaleExtent, event, transform);
513
549
  }
514
- _onDragged(d, event, allNodesSelection) {
515
- var _a, _b, _c;
550
+ _onZoomEnd(t, event) {
516
551
  const { config } = this;
552
+ const transform = t || event.transform;
553
+ this._scale = transform.k;
554
+ if (isFunction(config.onZoomEnd))
555
+ config.onZoomEnd(this._scale, config.zoomScaleExtent, event, transform);
556
+ }
557
+ _updateNodePosition(d, x, y) {
558
+ var _a, _b;
517
559
  const transform = zoomTransform(this.g.node());
518
560
  const scale = transform.k;
519
561
  // Prevent the node from being dragged offscreen or outside its panel
520
562
  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);
563
+ const nodeSizeValue = getNodeSize(d, this.config.nodeSize, d._index);
522
564
  const maxY = min([(this._height - transform.y) / scale, ...panels.map(p => p._y + p._height)]) - nodeSizeValue / 2;
523
565
  const maxX = min([(this._width - transform.x) / scale, ...panels.map(p => p._x + p._width)]) - nodeSizeValue / 2;
524
566
  const minY = max([-transform.y / scale, ...panels.map(p => p._y)]) + nodeSizeValue / 2;
525
567
  const minX = max([-transform.x / scale, ...panels.map(p => p._x)]) + nodeSizeValue / 2;
526
- let [x, y] = pointer(event, this._graphGroup.node());
527
568
  if (y < minY)
528
569
  y = minY;
529
570
  else if (y > maxY)
@@ -544,6 +585,62 @@ class Graph extends ComponentCore {
544
585
  delete d._state.fx;
545
586
  if (d._state.fy === d.y)
546
587
  delete d._state.fy;
588
+ }
589
+ _onBrush(event) {
590
+ var _a;
591
+ if (!event.selection || !event.sourceEvent)
592
+ return;
593
+ const { config } = this;
594
+ const transform = zoomTransform(this._graphGroup.node());
595
+ const [xMin, yMin] = transform.invert(event.selection[0]);
596
+ const [xMax, yMax] = transform.invert(event.selection[1]);
597
+ // Update brushed nodes
598
+ this._nodesGroup.selectAll(`.${gNode}`)
599
+ .each(n => {
600
+ const x = getX(n);
601
+ const y = getY(n);
602
+ n._state.brushed = x >= xMin && x <= xMax && y >= yMin && y <= yMax;
603
+ })
604
+ .classed(brushed, n => n._state.brushed);
605
+ const brushedNodes = this._nodesGroup.selectAll(`.${brushed}`)
606
+ .call(updateNodesPartial, config, 0, this._scale);
607
+ this._brush.classed('active', event.type !== 'end');
608
+ (_a = config.onNodeSelectionBrush) === null || _a === void 0 ? void 0 : _a.call(config, brushedNodes.data(), event);
609
+ }
610
+ _handleDrag(d, event, nodeSelection) {
611
+ if (event.sourceEvent.shiftKey && d._state.brushed) {
612
+ this._dragSelectedNodes(event);
613
+ }
614
+ else if (!event.sourceEvent.shiftKey) {
615
+ switch (event.type) {
616
+ case 'start':
617
+ this._onDragStarted(d, event, nodeSelection);
618
+ break;
619
+ case 'drag':
620
+ this._onDragged(d, event);
621
+ break;
622
+ case 'end':
623
+ this._onDragEnded(d, event, nodeSelection);
624
+ break;
625
+ }
626
+ }
627
+ }
628
+ _onDragStarted(d, event, nodeSelection) {
629
+ var _a;
630
+ const { config } = this;
631
+ this._isDragging = true;
632
+ d._state.isDragged = true;
633
+ nodeSelection.call(updateNodes, config, 0, this._scale);
634
+ (_a = config.onNodeDragStart) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
635
+ }
636
+ _onDragged(d, event) {
637
+ var _a;
638
+ const { config } = this;
639
+ const transform = zoomTransform(this.g.node());
640
+ const scale = transform.k;
641
+ // Update node position
642
+ const [x, y] = pointer(event, this._graphGroup.node());
643
+ this._updateNodePosition(d, x, y);
547
644
  // Update affected DOM elements
548
645
  const nodeSelection = this._nodesGroup.selectAll(`.${gNode}`);
549
646
  const nodeToUpdate = nodeSelection.filter((n) => n._id === d._id);
@@ -558,7 +655,7 @@ class Graph extends ComponentCore {
558
655
  const linksToAnimate = linksToUpdate.filter(d => d._state.greyout);
559
656
  if (linksToAnimate.size())
560
657
  animateLinkFlow(linksToAnimate, config, this._scale);
561
- (_c = config.onNodeDrag) === null || _c === void 0 ? void 0 : _c.call(config, d, event);
658
+ (_a = config.onNodeDrag) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
562
659
  }
563
660
  _onDragEnded(d, event, nodeSelection) {
564
661
  var _a;
@@ -568,6 +665,49 @@ class Graph extends ComponentCore {
568
665
  nodeSelection.call(updateNodes, config, 0, this._scale);
569
666
  (_a = config.onNodeDragEnd) === null || _a === void 0 ? void 0 : _a.call(config, d, event);
570
667
  }
668
+ _dragSelectedNodes(event) {
669
+ var _a, _b;
670
+ const { config } = this;
671
+ const curr = pointer(event, this._graphGroup.node());
672
+ const selectedNodes = smartTransition(this._nodesGroup.selectAll(`.${brushed}`));
673
+ if (event.type === 'start') {
674
+ this._groupDragInit = curr;
675
+ this._isDragging = true;
676
+ selectedNodes.each(n => {
677
+ n.x = getX(n);
678
+ n.y = getY(n);
679
+ n._state.isDragged = true;
680
+ });
681
+ }
682
+ else if (event.type === 'drag') {
683
+ const dx = curr[0] - this._groupDragInit[0];
684
+ const dy = curr[1] - this._groupDragInit[1];
685
+ selectedNodes.each(n => this._updateNodePosition(n, n.x + dx, n.y + dy));
686
+ const connectedLinks = smartTransition(this._linksGroup.selectAll(`.${gLink}`)
687
+ .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); }));
688
+ connectedLinks.call(updateLinks, this.config, 0, this._scale, this._getLinkArrowDefId);
689
+ }
690
+ else {
691
+ this._isDragging = false;
692
+ selectedNodes.each(n => { n._state.isDragged = false; });
693
+ }
694
+ selectedNodes.call(updateNodes, config, 0, this._scale);
695
+ (_b = (_a = this.config).onNodeSelectionDrag) === null || _b === void 0 ? void 0 : _b.call(_a, selectedNodes.data(), event);
696
+ }
697
+ _activateBrush() {
698
+ this._brush.classed('active', true);
699
+ this._nodesGroup.selectAll(`.${gNode}`)
700
+ .classed(brushable, true);
701
+ }
702
+ _clearBrush() {
703
+ var _a;
704
+ this._brush.classed('active', false).call((_a = this._brushBehavior) === null || _a === void 0 ? void 0 : _a.clear);
705
+ this._nodesGroup.selectAll(`.${gNode}`)
706
+ .classed(brushable, false)
707
+ .classed(brushed, false)
708
+ .each(n => { n._state.brushed = false; })
709
+ .call(updateNodesPartial, this.config, 0, this._scale);
710
+ }
571
711
  _shouldLayoutRecalculate() {
572
712
  const { prevConfig, config } = this;
573
713
  if (prevConfig.layoutType !== config.layoutType)
@@ -637,9 +777,10 @@ class Graph extends ComponentCore {
637
777
  getZoom() {
638
778
  return zoomTransform(this.g.node()).k;
639
779
  }
640
- fitView(duration = this.config.duration) {
641
- this._layoutCalculationPromise.then(() => {
642
- this._fit(duration);
780
+ fitView(duration = this.config.duration, nodeIds) {
781
+ var _a;
782
+ (_a = this._layoutCalculationPromise) === null || _a === void 0 ? void 0 : _a.then(() => {
783
+ this._fit(duration, nodeIds);
643
784
  });
644
785
  }
645
786
  /** Enable automatic fitting to container if it was disabled due to previous zoom / pan interactions */