@unovis/ts 1.6.6 → 1.6.7-stellar-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 (54) hide show
  1. package/components/area/index.js +7 -3
  2. package/components/area/index.js.map +1 -1
  3. package/components/area/style.js +1 -1
  4. package/components/area/style.js.map +1 -1
  5. package/components/bullet-legend/style.js +1 -1
  6. package/components/bullet-legend/style.js.map +1 -1
  7. package/components/chord-diagram/style.js +1 -1
  8. package/components/chord-diagram/style.js.map +1 -1
  9. package/components/donut/style.js +1 -1
  10. package/components/donut/style.js.map +1 -1
  11. package/components/flow-legend/style.js +1 -1
  12. package/components/flow-legend/style.js.map +1 -1
  13. package/components/free-brush/style.js +1 -1
  14. package/components/free-brush/style.js.map +1 -1
  15. package/components/graph/config.d.ts +6 -0
  16. package/components/graph/config.js +1 -1
  17. package/components/graph/config.js.map +1 -1
  18. package/components/graph/index.d.ts +18 -0
  19. package/components/graph/index.js +251 -7
  20. package/components/graph/index.js.map +1 -1
  21. package/components/graph/modules/link/style.js +1 -1
  22. package/components/graph/modules/link/style.js.map +1 -1
  23. package/components/graph/modules/node/style.js +1 -1
  24. package/components/graph/modules/node/style.js.map +1 -1
  25. package/components/graph/modules/panel/style.js +1 -1
  26. package/components/graph/modules/panel/style.js.map +1 -1
  27. package/components/grouped-bar/style.js +1 -1
  28. package/components/grouped-bar/style.js.map +1 -1
  29. package/components/plotband/style.js +1 -1
  30. package/components/plotband/style.js.map +1 -1
  31. package/components/plotline/style.js +1 -1
  32. package/components/plotline/style.js.map +1 -1
  33. package/components/sankey/modules/label.js +8 -2
  34. package/components/sankey/modules/label.js.map +1 -1
  35. package/components/stacked-bar/style.js +1 -1
  36. package/components/stacked-bar/style.js.map +1 -1
  37. package/components/tooltip/style.js +1 -1
  38. package/components/tooltip/style.js.map +1 -1
  39. package/components/vis-controls/style.js +1 -1
  40. package/components/vis-controls/style.js.map +1 -1
  41. package/index.js +2 -1
  42. package/index.js.map +1 -1
  43. package/package.json +1 -1
  44. package/styles/index.js +1 -1
  45. package/styles/index.js.map +1 -1
  46. package/utils/index.d.ts +1 -0
  47. package/utils/index.js +2 -1
  48. package/utils/index.js.map +1 -1
  49. package/utils/style.d.ts +0 -1
  50. package/utils/style.js +2 -2
  51. package/utils/style.js.map +1 -1
  52. package/utils/theme.d.ts +1 -0
  53. package/utils/theme.js +4 -0
  54. package/utils/theme.js.map +1 -0
@@ -2,12 +2,12 @@ import { __awaiter } from 'tslib';
2
2
  import { min, max, extent } from 'd3-array';
3
3
  import { select, pointer } from 'd3-selection';
4
4
  import { brush as brush$1 } from 'd3-brush';
5
- import { zoom, zoomIdentity, zoomTransform } from 'd3-zoom';
5
+ import { zoom, zoomTransform, zoomIdentity } from 'd3-zoom';
6
6
  import { drag } from 'd3-drag';
7
7
  import { interval } from 'd3-timer';
8
8
  import { ComponentCore } from '../../core/component/index.js';
9
9
  import { GraphDataModel } from '../../data-models/graph.js';
10
- import { isNumber, isFunction, clamp, getBoolean, getNumber, shallowDiff, isPlainObject, isEqual } from '../../utils/data.js';
10
+ import { isFunction, getBoolean, isNumber, clamp, getNumber, shallowDiff, isPlainObject, isEqual } from '../../utils/data.js';
11
11
  import { smartTransition } from '../../utils/d3.js';
12
12
  import { GraphLayoutType, GraphFitViewAlignment, GraphNodeSelectionHighlightMode, GraphLinkArrowStyle } from './types.js';
13
13
  import { GraphDefaultConfig } from './config.js';
@@ -35,6 +35,11 @@ class Graph extends ComponentCore {
35
35
  this._shouldSetPanels = false;
36
36
  this._isAutoFitDisabled = false;
37
37
  this._isDragging = false;
38
+ this._collapsedNodeIds = new Set();
39
+ this._dataVersion = 0;
40
+ this._lastExpandCollapseTime = 0;
41
+ this._isExpandCollapseInProgress = false;
42
+ this._expandCollapseRenderVersion = -1;
38
43
  // A map for storing link total path lengths to optimize rendering performance
39
44
  this._linkPathLengthMap = new Map();
40
45
  this._linkFlowFrameElapsed = 0;
@@ -84,11 +89,16 @@ class Graph extends ComponentCore {
84
89
  return this._selectedLink;
85
90
  }
86
91
  setData(data) {
92
+ var _a;
87
93
  const { config } = this;
88
- if (!config.shouldDataUpdate(this.datamodel.data, data, this.datamodel))
94
+ const prevData = (_a = this._fullData) !== null && _a !== void 0 ? _a : this.datamodel.data;
95
+ if (!config.shouldDataUpdate(prevData, data, this.datamodel))
89
96
  return;
97
+ this._fullData = data;
98
+ const visibleData = config.nodeExpandable ? this._computeVisibleData(data) : data;
90
99
  this.datamodel.nodeSort = config.nodeSort;
91
- this.datamodel.data = data;
100
+ this.datamodel.data = visibleData;
101
+ this._dataVersion++;
92
102
  this._shouldRecalculateLayout = true;
93
103
  if (config.layoutAutofit)
94
104
  this._shouldFitLayout = true;
@@ -107,6 +117,138 @@ class Graph extends ComponentCore {
107
117
  this._isAutoFitDisabled = false;
108
118
  this._shouldSetPanels = true;
109
119
  }
120
+ _computeVisibleData(data) {
121
+ var _a;
122
+ const { config } = this;
123
+ if (!config.nodeExpandable || !config.nodeChildren)
124
+ return data;
125
+ const allNodes = data.nodes;
126
+ const allLinks = (_a = data.links) !== null && _a !== void 0 ? _a : [];
127
+ // Build an id -> node map.
128
+ const nodeById = new Map();
129
+ for (const n of allNodes) {
130
+ if (n.id !== undefined)
131
+ nodeById.set(n.id, n);
132
+ }
133
+ const { visibleIdSet, visibleChildLinkSet, childIdsByParentId } = this._getVisibleNodeIds(allNodes, nodeById);
134
+ const visibleNodes = allNodes.filter(n => n.id !== undefined && visibleIdSet.has(n.id));
135
+ // Resolve a link endpoint to a node id.
136
+ const resolveId = (endpoint) => {
137
+ var _a;
138
+ if (typeof endpoint === 'object')
139
+ return endpoint.id;
140
+ if (typeof endpoint === 'number')
141
+ return (_a = allNodes[endpoint]) === null || _a === void 0 ? void 0 : _a.id;
142
+ return endpoint;
143
+ };
144
+ const visibleLinks = allLinks.filter(l => {
145
+ var _a, _b;
146
+ const srcId = resolveId(l.source);
147
+ const tgtId = resolveId(l.target);
148
+ if (srcId === undefined || tgtId === undefined || !visibleIdSet.has(srcId) || !visibleIdSet.has(tgtId))
149
+ return false;
150
+ const isParentChildLink = ((_a = childIdsByParentId.get(srcId)) === null || _a === void 0 ? void 0 : _a.includes(tgtId)) || ((_b = childIdsByParentId.get(tgtId)) === null || _b === void 0 ? void 0 : _b.includes(srcId));
151
+ if (!isParentChildLink)
152
+ return true;
153
+ return visibleChildLinkSet.has(this._getExpandCollapseLinkKey(srcId, tgtId)) ||
154
+ visibleChildLinkSet.has(this._getExpandCollapseLinkKey(tgtId, srcId));
155
+ });
156
+ return { nodes: visibleNodes, links: visibleLinks };
157
+ }
158
+ _getVisibleNodeIds(allNodes, nodeById) {
159
+ var _a, _b;
160
+ const { config } = this;
161
+ const visible = new Set();
162
+ const visibleChildLinkSet = new Set();
163
+ const hasParent = new Set();
164
+ const childIdsByParentId = new Map();
165
+ for (const node of allNodes) {
166
+ if (node.id === undefined)
167
+ continue;
168
+ const children = (_a = (isFunction(config.nodeChildren) ? config.nodeChildren(node, 0) : config.nodeChildren)) !== null && _a !== void 0 ? _a : [];
169
+ childIdsByParentId.set(node.id, children);
170
+ for (const childId of children)
171
+ hasParent.add(childId);
172
+ }
173
+ const queue = [];
174
+ for (const node of allNodes) {
175
+ if (node.id === undefined)
176
+ continue;
177
+ if (!hasParent.has(node.id)) {
178
+ visible.add(node.id);
179
+ queue.push(node.id);
180
+ }
181
+ }
182
+ // Fallback for cyclic-only graphs.
183
+ if (!queue.length) {
184
+ for (const node of allNodes) {
185
+ if (node.id === undefined)
186
+ continue;
187
+ visible.add(node.id);
188
+ queue.push(node.id);
189
+ }
190
+ }
191
+ while (queue.length) {
192
+ const parentId = queue.shift();
193
+ const parentNode = nodeById.get(parentId);
194
+ if (!parentNode)
195
+ continue;
196
+ const isParentExpandable = getBoolean(parentNode, config.nodeExpandable, 0);
197
+ const isParentCollapsed = this._collapsedNodeIds.has(String(parentId));
198
+ if (isParentExpandable && isParentCollapsed)
199
+ continue;
200
+ const children = (_b = childIdsByParentId.get(parentId)) !== null && _b !== void 0 ? _b : [];
201
+ for (const childId of children) {
202
+ if (!nodeById.has(childId))
203
+ continue;
204
+ visibleChildLinkSet.add(this._getExpandCollapseLinkKey(parentId, childId));
205
+ if (!visible.has(childId)) {
206
+ visible.add(childId);
207
+ queue.push(childId);
208
+ }
209
+ }
210
+ }
211
+ return {
212
+ visibleIdSet: visible,
213
+ visibleChildLinkSet,
214
+ childIdsByParentId,
215
+ };
216
+ }
217
+ _getExpandCollapseLinkKey(sourceId, targetId) {
218
+ return `${typeof sourceId}:${String(sourceId)}->${typeof targetId}:${String(targetId)}`;
219
+ }
220
+ _applyExpandCollapse() {
221
+ const { config } = this;
222
+ if (!config.nodeExpandable || !this._fullData)
223
+ return;
224
+ const visibleData = this._computeVisibleData(this._fullData);
225
+ this.datamodel.nodeSort = config.nodeSort;
226
+ this.datamodel.data = visibleData;
227
+ this._dataVersion++;
228
+ this._lastExpandCollapseTime = Date.now();
229
+ // Clear dragged positions before layout.
230
+ for (const node of this.datamodel.nodes) {
231
+ delete node._state.fx;
232
+ delete node._state.fy;
233
+ }
234
+ this._shouldRecalculateLayout = true;
235
+ // Use one controlled fit for this cycle.
236
+ if (config.layoutAutofit)
237
+ this._shouldFitLayout = false;
238
+ this._isAutoFitDisabled = false;
239
+ this._shouldSetPanels = true;
240
+ // Do not lock interaction before the first size pass.
241
+ if (this._width <= 0) {
242
+ this._isExpandCollapseInProgress = false;
243
+ this._expandCollapseRenderVersion = -1;
244
+ return;
245
+ }
246
+ this._isExpandCollapseInProgress = true;
247
+ this._expandCollapseRenderVersion = this._dataVersion;
248
+ this._render();
249
+ // Keep viewport and node transitions aligned.
250
+ this.fitView(this.config.duration);
251
+ }
110
252
  get bleed() {
111
253
  const padding = this.config.fitViewPadding; // Extra padding to take into account labels and selection outlines
112
254
  return isNumber(padding)
@@ -163,11 +305,11 @@ class Graph extends ComponentCore {
163
305
  this._zoomBehavior.filter(isFunction(zoomEventFilter)
164
306
  ? zoomEventFilter
165
307
  : (e) => (!e.ctrlKey || e.type === 'wheel') && !e.button && !e.shiftKey); // Default filter
308
+ const renderDataVersion = this._dataVersion;
166
309
  this._layoutCalculationPromise.then(() => {
167
310
  var _a, _b, _c;
168
- // If the component has been destroyed while the layout calculation
169
- // was in progress, we cancel the render
170
- if (this.isDestroyed())
311
+ // Cancel if destroyed or data changed.
312
+ if (this.isDestroyed() || this._dataVersion !== renderDataVersion)
171
313
  return;
172
314
  this._initPanelsData();
173
315
  // Fit the view
@@ -209,9 +351,50 @@ class Graph extends ComponentCore {
209
351
  this._setCustomAttributesThrottled();
210
352
  // On render complete callback
211
353
  (_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);
354
+ if (renderDataVersion === this._expandCollapseRenderVersion) {
355
+ if (this._expandCollapseVisibilityCheckTimer) {
356
+ window.clearTimeout(this._expandCollapseVisibilityCheckTimer);
357
+ this._expandCollapseVisibilityCheckTimer = undefined;
358
+ }
359
+ const delay = Math.max(animDuration, 0) + 50;
360
+ this._expandCollapseVisibilityCheckTimer = window.setTimeout(() => {
361
+ this._expandCollapseVisibilityCheckTimer = undefined;
362
+ if (this.isDestroyed())
363
+ return;
364
+ this._ensureGraphVisibleInViewport();
365
+ this._isExpandCollapseInProgress = false;
366
+ }, delay);
367
+ }
212
368
  this._isFirstRender = false;
213
369
  });
214
370
  }
371
+ _ensureGraphVisibleInViewport() {
372
+ var _a;
373
+ if (this.isDestroyed())
374
+ return;
375
+ const { datamodel, config: { nodeSize } } = this;
376
+ if (!((_a = datamodel.nodes) === null || _a === void 0 ? void 0 : _a.length))
377
+ return;
378
+ const maxNodeSize = getMaxNodeSize(datamodel.nodes, nodeSize);
379
+ const xExtent = [
380
+ min(datamodel.nodes, d => getX(d) - maxNodeSize / 2 - (max((d._panels || []).map(p => p._padding.left)) || 0)),
381
+ max(datamodel.nodes, d => getX(d) + maxNodeSize / 2 + (max((d._panels || []).map(p => p._padding.right)) || 0)),
382
+ ];
383
+ const yExtent = [
384
+ min(datamodel.nodes, d => getY(d) - maxNodeSize / 2 - (max((d._panels || []).map(p => p._padding.top)) || 0)),
385
+ max(datamodel.nodes, d => getY(d) + maxNodeSize / 2 + (max((d._panels || []).map(p => p._padding.bottom)) || 0)),
386
+ ];
387
+ if (xExtent.some(item => item === undefined) || yExtent.some(item => item === undefined))
388
+ return;
389
+ const transform = zoomTransform(this.g.node());
390
+ const left = transform.applyX(xExtent[0]);
391
+ const right = transform.applyX(xExtent[1]);
392
+ const top = transform.applyY(yExtent[0]);
393
+ const bottom = transform.applyY(yExtent[1]);
394
+ const isFullyOffscreen = right < 0 || left > this._width || bottom < 0 || top > this._height;
395
+ if (isFullyOffscreen)
396
+ this.fitView(0);
397
+ }
215
398
  _drawNodes(duration) {
216
399
  const { config, datamodel } = this;
217
400
  const nodes = datamodel.nodes;
@@ -494,6 +677,17 @@ class Graph extends ComponentCore {
494
677
  }
495
678
  // eslint-disable-next-line @typescript-eslint/no-empty-function
496
679
  _onNodeClick(d) {
680
+ const { config } = this;
681
+ if (config.nodeExpandable && getBoolean(d, config.nodeExpandable, d._index)) {
682
+ // Ignore clicks during an active expand/collapse render.
683
+ if (this._isExpandCollapseInProgress)
684
+ return;
685
+ // Ignore the second click in a double-click.
686
+ const clickCooldown = 300;
687
+ if (Date.now() - this._lastExpandCollapseTime < clickCooldown)
688
+ return;
689
+ this.toggleNodeExpand(d._id);
690
+ }
497
691
  }
498
692
  // eslint-disable-next-line @typescript-eslint/no-empty-function
499
693
  _onNodeMouseOut(d) {
@@ -872,6 +1066,56 @@ class Graph extends ComponentCore {
872
1066
  setNodeStateById(nodeId, state) {
873
1067
  this.datamodel.setNodeStateById(nodeId, state);
874
1068
  }
1069
+ toggleNodeExpand(nodeId) {
1070
+ var _a, _b;
1071
+ const { config } = this;
1072
+ const id = String(nodeId);
1073
+ const wasCollapsed = this._collapsedNodeIds.has(id);
1074
+ if (wasCollapsed) {
1075
+ this._collapsedNodeIds.delete(id);
1076
+ }
1077
+ else {
1078
+ this._collapsedNodeIds.add(id);
1079
+ }
1080
+ const expanded = wasCollapsed; // after toggle
1081
+ // Use the full-data node for the callback.
1082
+ const node = (_a = this._fullData) === null || _a === void 0 ? void 0 : _a.nodes.find(n => String(n.id) === id);
1083
+ if (node)
1084
+ (_b = config.onNodeExpand) === null || _b === void 0 ? void 0 : _b.call(config, node, expanded);
1085
+ this._applyExpandCollapse();
1086
+ }
1087
+ expandNode(nodeId) {
1088
+ var _a, _b, _c;
1089
+ const id = String(nodeId);
1090
+ if (!this._collapsedNodeIds.has(id))
1091
+ return;
1092
+ this._collapsedNodeIds.delete(id);
1093
+ const node = (_a = this._fullData) === null || _a === void 0 ? void 0 : _a.nodes.find(n => String(n.id) === id);
1094
+ if (node)
1095
+ (_c = (_b = this.config).onNodeExpand) === null || _c === void 0 ? void 0 : _c.call(_b, node, true);
1096
+ this._applyExpandCollapse();
1097
+ }
1098
+ collapseNode(nodeId) {
1099
+ var _a, _b;
1100
+ const { config } = this;
1101
+ const id = String(nodeId);
1102
+ if (this._collapsedNodeIds.has(id))
1103
+ return;
1104
+ this._collapsedNodeIds.add(id);
1105
+ const node = (_a = this._fullData) === null || _a === void 0 ? void 0 : _a.nodes.find(n => String(n.id) === id);
1106
+ if (node)
1107
+ (_b = config.onNodeExpand) === null || _b === void 0 ? void 0 : _b.call(config, node, false);
1108
+ this._applyExpandCollapse();
1109
+ }
1110
+ isNodeCollapsed(nodeId) {
1111
+ return this._collapsedNodeIds.has(String(nodeId));
1112
+ }
1113
+ setCollapsedNodes(nodeIds) {
1114
+ this._collapsedNodeIds = new Set(nodeIds.map(id => String(id)));
1115
+ this._isAutoFitDisabled = false;
1116
+ this._shouldFitLayout = true;
1117
+ this._applyExpandCollapse();
1118
+ }
875
1119
  /** Call a partial render to update the positions of the nodes and their links.
876
1120
  * This can be useful when you've changed the node positions manually outside
877
1121
  * of the component and want to update the graph.