@data-navigator/inspector 1.0.0 → 1.0.2

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.
package/README.md CHANGED
@@ -26,6 +26,29 @@ Data Navigator is organized into 3 composable modules:
26
26
 
27
27
  These modules can be used together or independently. Visit [the docs](https://dig.cmu.edu/data-navigator/getting-started/) for a step-by-step guide to building your first navigable chart.
28
28
 
29
+ ## Inspector
30
+
31
+ The optional **[@data-navigator/inspector](https://www.npmjs.com/package/@data-navigator/inspector)** package provides a visual graph of your data-navigator structure, useful for debugging and understanding navigation paths.
32
+
33
+ ```
34
+ npm install @data-navigator/inspector data-navigator d3-array d3-drag d3-force d3-scale d3-scale-chromatic d3-selection
35
+ ```
36
+
37
+ ```js
38
+ import dataNavigator from 'data-navigator';
39
+ import { Inspector } from '@data-navigator/inspector';
40
+
41
+ const inspector = Inspector({
42
+ structure: myStructure, // made using data navigator
43
+ container: 'inspector-container' // render to DOM
44
+ });
45
+
46
+ // Sync with navigation events
47
+ inspector.highlight(nodeId);
48
+ inspector.clear();
49
+ inspector.destroy();
50
+ ```
51
+
29
52
  ## Contributing
30
53
 
31
54
  See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions and development workflow.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@data-navigator/inspector",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "author": "Frank Elavsky",
6
6
  "license": "MIT",
package/src/tree-graph.js CHANGED
@@ -131,17 +131,20 @@ export function TreeGraph(
131
131
  });
132
132
 
133
133
  // Compute layout positions.
134
+ // Upper levels (dimensions, divisions) are compressed; leaves get more room.
134
135
  const padding = { top: 30, bottom: 20, left: 30, right: 30 };
135
136
  const usableWidth = width - padding.left - padding.right;
136
137
  const usableHeight = height - padding.top - padding.bottom;
137
138
 
138
- // Determine rows: level0 (optional), level1, level2, leaves
139
139
  const hasLevel0 = level0.length > 0;
140
- const rowCount = (hasLevel0 ? 1 : 0) + 1 + 1 + 1; // l0?, l1, l2, leaves
141
- const rowSpacing = usableHeight / (rowCount - 1 || 1);
140
+ const upperRows = (hasLevel0 ? 1 : 0) + 1 + 1; // l0?, l1, l2
141
+ // Upper hierarchy gets 40% of height, leaves get 60%
142
+ const upperHeight = usableHeight * 0.4;
143
+ const upperSpacing = upperRows > 1 ? upperHeight / (upperRows - 1) : upperHeight;
144
+ const leafYPos = padding.top + usableHeight * 0.85;
142
145
 
143
146
  let currentRow = 0;
144
- const yFor = (row) => padding.top + row * rowSpacing;
147
+ const yFor = (row) => padding.top + row * upperSpacing;
145
148
 
146
149
  // Level 0
147
150
  if (hasLevel0) {
@@ -192,7 +195,7 @@ export function TreeGraph(
192
195
  currentRow++;
193
196
 
194
197
  // Leaf nodes
195
- const leafY = yFor(currentRow);
198
+ const leafY = leafYPos;
196
199
  if (dimOrder.length === 0 || !dimensions) {
197
200
  // No dimensions — flat row
198
201
  leafNodes.forEach((n, i) => {
@@ -232,7 +235,7 @@ export function TreeGraph(
232
235
  const rows = div2Ids.length || 1;
233
236
 
234
237
  // Grid occupies the leaf area but may expand vertically
235
- const gridTop = leafY - rowSpacing * 0.3;
238
+ const gridTop = leafY - (leafY - yFor(currentRow - 1)) * 0.15;
236
239
  const gridBottom = height - padding.bottom;
237
240
  const gridHeight = gridBottom - gridTop;
238
241
  const colSpacing = usableWidth / (cols + 1);
@@ -270,20 +273,46 @@ export function TreeGraph(
270
273
  .attr('aria-label', hide ? null : description)
271
274
  .attr('style', 'max-width: 100%; height: auto; height: intrinsic;');
272
275
 
273
- // Draw sibling links (dashed, behind parent-child links).
276
+ // Draw sibling links as arced paths (dashed, behind parent-child links).
277
+ // Regular siblings arc above (horizontal) or left (vertical).
278
+ // Wrap-around (circular) siblings arc below/right with a larger curve.
279
+ const siblingArc = (d) => {
280
+ const s = nodeObjById[d.source];
281
+ const t = nodeObjById[d.target];
282
+ if (!s || !t) return '';
283
+ const sx = s.x, sy = s.y, tx = t.x, ty = t.y;
284
+ const dx = tx - sx, dy = ty - sy;
285
+ const dist = Math.sqrt(dx * dx + dy * dy);
286
+ const isHorizontal = Math.abs(dx) > Math.abs(dy);
287
+ // Detect wrap-around: source is to the right of target (horizontal)
288
+ // or below target (vertical) — these are circular edges.
289
+ const isWrap = isHorizontal ? (sx > tx) : (sy > ty);
290
+ // Arc offset scales with distance but stays subtle
291
+ const arcAmount = Math.min(dist * 0.3, 40);
292
+ const midX = (sx + tx) / 2;
293
+ const midY = (sy + ty) / 2;
294
+ let cx, cy;
295
+ if (isHorizontal) {
296
+ cx = midX;
297
+ cy = isWrap ? midY + arcAmount : midY - arcAmount;
298
+ } else {
299
+ cx = isWrap ? midX + arcAmount : midX - arcAmount;
300
+ cy = midY;
301
+ }
302
+ return `M ${sx} ${sy} Q ${cx} ${cy} ${tx} ${ty}`;
303
+ };
304
+
274
305
  svg.append('g')
275
306
  .attr('class', 'tree-sibling-links')
276
- .selectAll('line')
307
+ .selectAll('path')
277
308
  .data(siblingLinks)
278
- .join('line')
279
- .attr('x1', d => nodeObjById[d.source]?.x || 0)
280
- .attr('y1', d => nodeObjById[d.source]?.y || 0)
281
- .attr('x2', d => nodeObjById[d.target]?.x || 0)
282
- .attr('y2', d => nodeObjById[d.target]?.y || 0)
283
- .attr('stroke', '#bbb')
284
- .attr('stroke-opacity', 0.25)
309
+ .join('path')
310
+ .attr('d', siblingArc)
311
+ .attr('fill', 'none')
312
+ .attr('stroke', '#888')
313
+ .attr('stroke-opacity', 0.6)
285
314
  .attr('stroke-width', 1)
286
- .attr('stroke-dasharray', '3,2');
315
+ .attr('stroke-dasharray', '4,3');
287
316
 
288
317
  // Draw parent-child links (solid).
289
318
  svg.append('g')