@unovis/ts 1.6.6-beta.0 → 1.6.6-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 (35) hide show
  1. package/components/area/index.js +1 -3
  2. package/components/area/index.js.map +1 -1
  3. package/components/crosshair/index.js +4 -3
  4. package/components/crosshair/index.js.map +1 -1
  5. package/components/graph/config.d.ts +13 -0
  6. package/components/graph/config.js +1 -1
  7. package/components/graph/config.js.map +1 -1
  8. package/components/graph/index.d.ts +26 -0
  9. package/components/graph/index.js +166 -4
  10. package/components/graph/index.js.map +1 -1
  11. package/components/graph/modules/link/index.js +40 -5
  12. package/components/graph/modules/link/index.js.map +1 -1
  13. package/components/scatter/index.d.ts +4 -0
  14. package/components/scatter/index.js +12 -0
  15. package/components/scatter/index.js.map +1 -1
  16. package/components/stacked-bar/index.d.ts +4 -1
  17. package/components/stacked-bar/index.js +17 -6
  18. package/components/stacked-bar/index.js.map +1 -1
  19. package/components/timeline/config.d.ts +2 -0
  20. package/components/timeline/config.js +1 -1
  21. package/components/timeline/config.js.map +1 -1
  22. package/components/timeline/index.js +2 -4
  23. package/components/timeline/index.js.map +1 -1
  24. package/components/topojson-map/index.d.ts +2 -2
  25. package/components/topojson-map/index.js +24 -5
  26. package/components/topojson-map/index.js.map +1 -1
  27. package/core/component/index.d.ts +4 -1
  28. package/core/component/index.js +6 -6
  29. package/core/component/index.js.map +1 -1
  30. package/package.json +6 -5
  31. package/types/misc.js +2 -0
  32. package/types/misc.js.map +1 -0
  33. package/types.d.ts +1 -0
  34. package/types.js +1 -0
  35. package/types.js.map +1 -1
@@ -7,7 +7,7 @@ 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 { getBoolean, isFunction, 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,10 @@ class Graph extends ComponentCore {
35
35
  this._shouldSetPanels = false;
36
36
  this._isAutoFitDisabled = false;
37
37
  this._isDragging = false;
38
+ /** Set of node ids (string) that are currently collapsed */
39
+ this._collapsedNodeIds = new Set();
40
+ /** Incremented every time datamodel.data is replaced; used to detect stale layout .then() callbacks */
41
+ this._dataVersion = 0;
38
42
  // A map for storing link total path lengths to optimize rendering performance
39
43
  this._linkPathLengthMap = new Map();
40
44
  this._linkFlowFrameElapsed = 0;
@@ -87,8 +91,11 @@ class Graph extends ComponentCore {
87
91
  const { config } = this;
88
92
  if (!config.shouldDataUpdate(this.datamodel.data, data, this.datamodel))
89
93
  return;
94
+ this._fullData = data;
95
+ const visibleData = config.nodeExpandable ? this._computeVisibleData(data) : data;
90
96
  this.datamodel.nodeSort = config.nodeSort;
91
- this.datamodel.data = data;
97
+ this.datamodel.data = visibleData;
98
+ this._dataVersion++;
92
99
  this._shouldRecalculateLayout = true;
93
100
  if (config.layoutAutofit)
94
101
  this._shouldFitLayout = true;
@@ -107,6 +114,92 @@ class Graph extends ComponentCore {
107
114
  this._isAutoFitDisabled = false;
108
115
  this._shouldSetPanels = true;
109
116
  }
117
+ /** Compute the visible subset of `data` based on `_collapsedNodeIds` */
118
+ _computeVisibleData(data) {
119
+ var _a;
120
+ const { config } = this;
121
+ if (!config.nodeExpandable || !config.nodeChildren)
122
+ return data;
123
+ const allNodes = data.nodes;
124
+ const allLinks = (_a = data.links) !== null && _a !== void 0 ? _a : [];
125
+ // Build a map of id → node for quick lookup (use the node's id property)
126
+ const nodeById = new Map();
127
+ for (const n of allNodes) {
128
+ if (n.id !== undefined)
129
+ nodeById.set(n.id, n);
130
+ }
131
+ // Collect ids to hide (all descendants of collapsed nodes)
132
+ const hiddenIds = this._getHiddenNodeIds(allNodes, nodeById);
133
+ const visibleIdSet = new Set(allNodes.filter(n => !hiddenIds.has(n.id)).map(n => n.id));
134
+ const visibleNodes = allNodes.filter(n => !hiddenIds.has(n.id));
135
+ // Helper: resolve a link endpoint (number index, string id, or node object) to the node's 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
+ const srcId = resolveId(l.source);
146
+ const tgtId = resolveId(l.target);
147
+ return visibleIdSet.has(srcId) && visibleIdSet.has(tgtId);
148
+ });
149
+ return { nodes: visibleNodes, links: visibleLinks };
150
+ }
151
+ _getHiddenNodeIds(allNodes, nodeById) {
152
+ var _a;
153
+ const { config } = this;
154
+ const hidden = new Set();
155
+ const hide = (id) => {
156
+ var _a;
157
+ if (id === undefined || hidden.has(id))
158
+ return;
159
+ hidden.add(id);
160
+ const node = nodeById.get(id);
161
+ if (node) {
162
+ const children = getBoolean(node, config.nodeExpandable, 0)
163
+ ? (isFunction(config.nodeChildren) ? config.nodeChildren(node, 0) : (_a = config.nodeChildren) !== null && _a !== void 0 ? _a : [])
164
+ : [];
165
+ for (const childId of children)
166
+ hide(childId);
167
+ }
168
+ };
169
+ for (const id of this._collapsedNodeIds) {
170
+ const node = nodeById.get(id);
171
+ if (node && getBoolean(node, config.nodeExpandable, 0) && config.nodeChildren) {
172
+ const nodeChildIds = isFunction(config.nodeChildren) ? config.nodeChildren(node, 0) : (_a = config.nodeChildren) !== null && _a !== void 0 ? _a : [];
173
+ for (const childId of nodeChildIds !== null && nodeChildIds !== void 0 ? nodeChildIds : [])
174
+ hide(childId);
175
+ }
176
+ }
177
+ return hidden;
178
+ }
179
+ _applyExpandCollapse() {
180
+ const { config } = this;
181
+ if (!config.nodeExpandable || !this._fullData)
182
+ return;
183
+ const visibleData = this._computeVisibleData(this._fullData);
184
+ this.datamodel.nodeSort = config.nodeSort;
185
+ this.datamodel.data = visibleData;
186
+ this._dataVersion++;
187
+ // Clear any fixed (dragged) positions so the fresh layout output is used
188
+ for (const node of this.datamodel.nodes) {
189
+ delete node._state.fx;
190
+ delete node._state.fy;
191
+ }
192
+ this._shouldRecalculateLayout = true;
193
+ if (config.layoutAutofit)
194
+ this._shouldFitLayout = true;
195
+ this._shouldSetPanels = true;
196
+ // Only call _render() if the component has been sized. When called before the first
197
+ // container render (e.g. from a useEffect on mount), _width is 0 and layout functions
198
+ // produce NaN coordinates (Infinity * 0). The flags above ensure the next
199
+ // container-triggered render will recalculate and draw with correct dimensions.
200
+ if (this._width > 0)
201
+ this._render();
202
+ }
110
203
  get bleed() {
111
204
  const padding = this.config.fitViewPadding; // Extra padding to take into account labels and selection outlines
112
205
  return isNumber(padding)
@@ -163,11 +256,13 @@ class Graph extends ComponentCore {
163
256
  this._zoomBehavior.filter(isFunction(zoomEventFilter)
164
257
  ? zoomEventFilter
165
258
  : (e) => (!e.ctrlKey || e.type === 'wheel') && !e.button && !e.shiftKey); // Default filter
259
+ const renderDataVersion = this._dataVersion;
166
260
  this._layoutCalculationPromise.then(() => {
167
261
  var _a, _b, _c;
168
262
  // If the component has been destroyed while the layout calculation
169
- // was in progress, we cancel the render
170
- if (this.isDestroyed())
263
+ // was in progress, or data has been replaced (e.g. React re-render
264
+ // fired between layout resolving and this callback), cancel the render
265
+ if (this.isDestroyed() || this._dataVersion !== renderDataVersion)
171
266
  return;
172
267
  this._initPanelsData();
173
268
  // Fit the view
@@ -338,6 +433,8 @@ class Graph extends ComponentCore {
338
433
  }
339
434
  }
340
435
  _fit(duration = 0, nodeIds, alignment = this.config.fitViewAlign) {
436
+ if (this.isDestroyed())
437
+ return;
341
438
  const { datamodel, config: { nodeSize } } = this;
342
439
  const nodes = (nodeIds === null || nodeIds === void 0 ? void 0 : nodeIds.length) ? datamodel.nodes.filter(n => nodeIds.includes(n.id)) : datamodel.nodes;
343
440
  const maxNodeSize = getMaxNodeSize(nodes, nodeSize);
@@ -492,6 +589,10 @@ class Graph extends ComponentCore {
492
589
  }
493
590
  // eslint-disable-next-line @typescript-eslint/no-empty-function
494
591
  _onNodeClick(d) {
592
+ const { config } = this;
593
+ if (config.nodeExpandable && getBoolean(d, config.nodeExpandable, d._index)) {
594
+ this.toggleNodeExpand(d._id);
595
+ }
495
596
  }
496
597
  // eslint-disable-next-line @typescript-eslint/no-empty-function
497
598
  _onNodeMouseOut(d) {
@@ -870,6 +971,67 @@ class Graph extends ComponentCore {
870
971
  setNodeStateById(nodeId, state) {
871
972
  this.datamodel.setNodeStateById(nodeId, state);
872
973
  }
974
+ /** Toggle the expanded/collapsed state of a node by its id.
975
+ * Only works when `nodeExpandable` and `nodeChildren` config options are set. */
976
+ toggleNodeExpand(nodeId) {
977
+ var _a, _b;
978
+ const { config } = this;
979
+ const id = String(nodeId);
980
+ const wasCollapsed = this._collapsedNodeIds.has(id);
981
+ if (wasCollapsed) {
982
+ this._collapsedNodeIds.delete(id);
983
+ }
984
+ else {
985
+ this._collapsedNodeIds.add(id);
986
+ }
987
+ const expanded = wasCollapsed; // after toggle
988
+ // Find the node in the full data to fire the callback
989
+ const node = (_a = this._fullData) === null || _a === void 0 ? void 0 : _a.nodes.find(n => String(n.id) === id);
990
+ if (node)
991
+ (_b = config.onNodeExpand) === null || _b === void 0 ? void 0 : _b.call(config, node, expanded);
992
+ this._applyExpandCollapse();
993
+ }
994
+ /** Expand a node by its id. No-op if already expanded. */
995
+ expandNode(nodeId) {
996
+ var _a, _b, _c;
997
+ const id = String(nodeId);
998
+ if (!this._collapsedNodeIds.has(id))
999
+ return;
1000
+ this._collapsedNodeIds.delete(id);
1001
+ const node = (_a = this._fullData) === null || _a === void 0 ? void 0 : _a.nodes.find(n => String(n.id) === id);
1002
+ if (node)
1003
+ (_c = (_b = this.config).onNodeExpand) === null || _c === void 0 ? void 0 : _c.call(_b, node, true);
1004
+ this._applyExpandCollapse();
1005
+ }
1006
+ /** Collapse a node by its id. No-op if already collapsed. */
1007
+ collapseNode(nodeId) {
1008
+ var _a, _b;
1009
+ const { config } = this;
1010
+ const id = String(nodeId);
1011
+ if (this._collapsedNodeIds.has(id))
1012
+ return;
1013
+ this._collapsedNodeIds.add(id);
1014
+ const node = (_a = this._fullData) === null || _a === void 0 ? void 0 : _a.nodes.find(n => String(n.id) === id);
1015
+ if (node)
1016
+ (_b = config.onNodeExpand) === null || _b === void 0 ? void 0 : _b.call(config, node, false);
1017
+ this._applyExpandCollapse();
1018
+ }
1019
+ /** Returns whether a node is currently collapsed. */
1020
+ isNodeCollapsed(nodeId) {
1021
+ return this._collapsedNodeIds.has(String(nodeId));
1022
+ }
1023
+ /** Set the complete set of collapsed node ids in one shot.
1024
+ * All currently collapsed nodes not in the new set are expanded; all ids in the new set are
1025
+ * collapsed. A single layout recalculation is triggered at the end, avoiding the race condition
1026
+ * that occurs when `collapseNode`/`expandNode` are called in a loop.
1027
+ * `nodeCollapsible` is still respected for the collapse direction.
1028
+ */
1029
+ setCollapsedNodes(nodeIds) {
1030
+ this._collapsedNodeIds = new Set(nodeIds.map(id => String(id)));
1031
+ this._isAutoFitDisabled = false;
1032
+ this._shouldFitLayout = true;
1033
+ this._applyExpandCollapse();
1034
+ }
873
1035
  /** Call a partial render to update the positions of the nodes and their links.
874
1036
  * This can be useful when you've changed the node positions manually outside
875
1037
  * of the component and want to update the graph.