@flanksource/clicky-ui 0.3.45 → 0.3.47
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/NOTICE.md +11 -0
- package/README.md +22 -0
- package/dist/ai.cjs +13 -8
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.js +7 -2
- package/dist/ai.js.map +1 -1
- package/dist/components/ErrorWrapper.cjs +2 -3
- package/dist/components/ErrorWrapper.cjs.map +1 -1
- package/dist/components/ErrorWrapper.d.ts.map +1 -1
- package/dist/components/ErrorWrapper.js +3 -4
- package/dist/components/ErrorWrapper.js.map +1 -1
- package/dist/data/AccessMark.cjs +23 -0
- package/dist/data/AccessMark.cjs.map +1 -0
- package/dist/data/AccessMark.d.ts +11 -0
- package/dist/data/AccessMark.d.ts.map +1 -0
- package/dist/data/AccessMark.js +23 -0
- package/dist/data/AccessMark.js.map +1 -0
- package/dist/data/Clicky.cjs +3 -0
- package/dist/data/Clicky.cjs.map +1 -1
- package/dist/data/Clicky.js +3 -0
- package/dist/data/Clicky.js.map +1 -1
- package/dist/data/CodeDiff.cjs +116 -23
- package/dist/data/CodeDiff.cjs.map +1 -1
- package/dist/data/CodeDiff.d.ts +26 -0
- package/dist/data/CodeDiff.d.ts.map +1 -1
- package/dist/data/CodeDiff.js +116 -23
- package/dist/data/CodeDiff.js.map +1 -1
- package/dist/data/FilterPill.cjs +15 -2
- package/dist/data/FilterPill.cjs.map +1 -1
- package/dist/data/FilterPill.d.ts +1 -1
- package/dist/data/FilterPill.d.ts.map +1 -1
- package/dist/data/FilterPill.js +15 -2
- package/dist/data/FilterPill.js.map +1 -1
- package/dist/data/GraphDiagram.cjs +65 -13
- package/dist/data/GraphDiagram.cjs.map +1 -1
- package/dist/data/GraphDiagram.d.ts +9 -2
- package/dist/data/GraphDiagram.d.ts.map +1 -1
- package/dist/data/GraphDiagram.js +66 -14
- package/dist/data/GraphDiagram.js.map +1 -1
- package/dist/data/GraphDiagramGroups.cjs +100 -0
- package/dist/data/GraphDiagramGroups.cjs.map +1 -0
- package/dist/data/GraphDiagramGroups.d.ts +24 -0
- package/dist/data/GraphDiagramGroups.d.ts.map +1 -0
- package/dist/data/GraphDiagramGroups.js +100 -0
- package/dist/data/GraphDiagramGroups.js.map +1 -0
- package/dist/data/GraphDiagramNodes.cjs +17 -31
- package/dist/data/GraphDiagramNodes.cjs.map +1 -1
- package/dist/data/GraphDiagramNodes.d.ts +6 -13
- package/dist/data/GraphDiagramNodes.d.ts.map +1 -1
- package/dist/data/GraphDiagramNodes.js +17 -31
- package/dist/data/GraphDiagramNodes.js.map +1 -1
- package/dist/data/LogViewer.cjs +27 -8
- package/dist/data/LogViewer.cjs.map +1 -1
- package/dist/data/LogViewer.d.ts +11 -3
- package/dist/data/LogViewer.d.ts.map +1 -1
- package/dist/data/LogViewer.js +27 -8
- package/dist/data/LogViewer.js.map +1 -1
- package/dist/data/Properties.cjs +2 -15
- package/dist/data/Properties.cjs.map +1 -1
- package/dist/data/Properties.d.ts +4 -0
- package/dist/data/Properties.d.ts.map +1 -1
- package/dist/data/Properties.js +2 -15
- package/dist/data/Properties.js.map +1 -1
- package/dist/data/PropertyValue.cjs +37 -0
- package/dist/data/PropertyValue.cjs.map +1 -0
- package/dist/data/PropertyValue.d.ts +6 -0
- package/dist/data/PropertyValue.d.ts.map +1 -0
- package/dist/data/PropertyValue.js +37 -0
- package/dist/data/PropertyValue.js.map +1 -0
- package/dist/data/Tree.cjs +4 -3
- package/dist/data/Tree.cjs.map +1 -1
- package/dist/data/Tree.d.ts +3 -1
- package/dist/data/Tree.d.ts.map +1 -1
- package/dist/data/Tree.js +4 -3
- package/dist/data/Tree.js.map +1 -1
- package/dist/data/TreeNode.cjs +1 -1
- package/dist/data/TreeNode.cjs.map +1 -1
- package/dist/data/TreeNode.d.ts.map +1 -1
- package/dist/data/TreeNode.js +1 -1
- package/dist/data/TreeNode.js.map +1 -1
- package/dist/data/ai/ChatFab.cjs +1 -1
- package/dist/data/ai/ChatFab.cjs.map +1 -1
- package/dist/data/ai/ChatFab.js +1 -1
- package/dist/data/ai/ChatFab.js.map +1 -1
- package/dist/data/ai/ChatWindow.cjs +106 -171
- package/dist/data/ai/ChatWindow.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.d.ts +6 -2
- package/dist/data/ai/ChatWindow.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.header.cjs +104 -0
- package/dist/data/ai/ChatWindow.header.cjs.map +1 -0
- package/dist/data/ai/ChatWindow.header.d.ts +29 -0
- package/dist/data/ai/ChatWindow.header.d.ts.map +1 -0
- package/dist/data/ai/ChatWindow.header.js +104 -0
- package/dist/data/ai/ChatWindow.header.js.map +1 -0
- package/dist/data/ai/ChatWindow.js +107 -172
- package/dist/data/ai/ChatWindow.js.map +1 -1
- package/dist/data/ai/ChatWindow.placement.cjs +95 -0
- package/dist/data/ai/ChatWindow.placement.cjs.map +1 -0
- package/dist/data/ai/ChatWindow.placement.d.ts +32 -0
- package/dist/data/ai/ChatWindow.placement.d.ts.map +1 -0
- package/dist/data/ai/ChatWindow.placement.js +95 -0
- package/dist/data/ai/ChatWindow.placement.js.map +1 -0
- package/dist/data/ai/SessionChatComposer.cjs +3 -1
- package/dist/data/ai/SessionChatComposer.cjs.map +1 -1
- package/dist/data/ai/SessionChatComposer.d.ts +4 -1
- package/dist/data/ai/SessionChatComposer.d.ts.map +1 -1
- package/dist/data/ai/SessionChatComposer.js +3 -1
- package/dist/data/ai/SessionChatComposer.js.map +1 -1
- package/dist/data/ai/SessionInspector.cjs +68 -72
- package/dist/data/ai/SessionInspector.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.composer-slot.cjs +8 -0
- package/dist/data/ai/SessionInspector.composer-slot.cjs.map +1 -0
- package/dist/data/ai/SessionInspector.composer-slot.d.ts +9 -0
- package/dist/data/ai/SessionInspector.composer-slot.d.ts.map +1 -0
- package/dist/data/ai/SessionInspector.composer-slot.js +8 -0
- package/dist/data/ai/SessionInspector.composer-slot.js.map +1 -0
- package/dist/data/ai/SessionInspector.d.ts +6 -1
- package/dist/data/ai/SessionInspector.d.ts.map +1 -1
- package/dist/data/ai/SessionInspector.js +69 -73
- package/dist/data/ai/SessionInspector.js.map +1 -1
- package/dist/data/ai/SessionInspector.legacy.cjs +41 -42
- package/dist/data/ai/SessionInspector.legacy.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.legacy.d.ts.map +1 -1
- package/dist/data/ai/SessionInspector.legacy.js +41 -42
- package/dist/data/ai/SessionInspector.legacy.js.map +1 -1
- package/dist/data/ai/SessionInspector.remote-view.cjs +14 -9
- package/dist/data/ai/SessionInspector.remote-view.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.remote-view.d.ts +6 -2
- package/dist/data/ai/SessionInspector.remote-view.d.ts.map +1 -1
- package/dist/data/ai/SessionInspector.remote-view.js +14 -9
- package/dist/data/ai/SessionInspector.remote-view.js.map +1 -1
- package/dist/data/ai/SessionInspector.remote.cjs +84 -16
- package/dist/data/ai/SessionInspector.remote.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.remote.d.ts +42 -7
- package/dist/data/ai/SessionInspector.remote.d.ts.map +1 -1
- package/dist/data/ai/SessionInspector.remote.js +86 -18
- package/dist/data/ai/SessionInspector.remote.js.map +1 -1
- package/dist/data/ai/SessionInspector.transcript.cjs +5 -0
- package/dist/data/ai/SessionInspector.transcript.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.transcript.d.ts.map +1 -1
- package/dist/data/ai/SessionInspector.transcript.js +5 -0
- package/dist/data/ai/SessionInspector.transcript.js.map +1 -1
- package/dist/data/ai/SessionTail.cjs +63 -0
- package/dist/data/ai/SessionTail.cjs.map +1 -0
- package/dist/data/ai/SessionTail.d.ts +14 -0
- package/dist/data/ai/SessionTail.d.ts.map +1 -0
- package/dist/data/ai/SessionTail.js +63 -0
- package/dist/data/ai/SessionTail.js.map +1 -0
- package/dist/data/ai/SessionViewer.cjs +10 -3
- package/dist/data/ai/SessionViewer.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.d.ts +7 -1
- package/dist/data/ai/SessionViewer.d.ts.map +1 -1
- package/dist/data/ai/SessionViewer.js +10 -3
- package/dist/data/ai/SessionViewer.js.map +1 -1
- package/dist/data/ai/SessionViewer.unified.d.ts +14 -0
- package/dist/data/ai/SessionViewer.unified.d.ts.map +1 -1
- package/dist/data/ai/index.d.ts +4 -3
- package/dist/data/ai/index.d.ts.map +1 -1
- package/dist/data/ai/use-session-scroll.cjs +30 -6
- package/dist/data/ai/use-session-scroll.cjs.map +1 -1
- package/dist/data/ai/use-session-scroll.d.ts +7 -0
- package/dist/data/ai/use-session-scroll.d.ts.map +1 -1
- package/dist/data/ai/use-session-scroll.js +30 -6
- package/dist/data/ai/use-session-scroll.js.map +1 -1
- package/dist/data/call-graph/CallGraph.cjs +50 -31
- package/dist/data/call-graph/CallGraph.cjs.map +1 -1
- package/dist/data/call-graph/CallGraph.d.ts +10 -1
- package/dist/data/call-graph/CallGraph.d.ts.map +1 -1
- package/dist/data/call-graph/CallGraph.js +52 -33
- package/dist/data/call-graph/CallGraph.js.map +1 -1
- package/dist/data/call-graph/CallGraphMembers.cjs +5 -4
- package/dist/data/call-graph/CallGraphMembers.cjs.map +1 -1
- package/dist/data/call-graph/CallGraphMembers.d.ts +1 -1
- package/dist/data/call-graph/CallGraphMembers.d.ts.map +1 -1
- package/dist/data/call-graph/CallGraphMembers.js +5 -4
- package/dist/data/call-graph/CallGraphMembers.js.map +1 -1
- package/dist/data/call-graph/CallGraphToolbar.cjs +17 -5
- package/dist/data/call-graph/CallGraphToolbar.cjs.map +1 -1
- package/dist/data/call-graph/CallGraphToolbar.d.ts +10 -0
- package/dist/data/call-graph/CallGraphToolbar.d.ts.map +1 -1
- package/dist/data/call-graph/CallGraphToolbar.js +18 -6
- package/dist/data/call-graph/CallGraphToolbar.js.map +1 -1
- package/dist/data/call-graph/call-graph-model.cjs +169 -18
- package/dist/data/call-graph/call-graph-model.cjs.map +1 -1
- package/dist/data/call-graph/call-graph-model.d.ts +40 -4
- package/dist/data/call-graph/call-graph-model.d.ts.map +1 -1
- package/dist/data/call-graph/call-graph-model.js +170 -19
- package/dist/data/call-graph/call-graph-model.js.map +1 -1
- package/dist/data/call-graph/call-graph-vocabulary.cjs +19 -1
- package/dist/data/call-graph/call-graph-vocabulary.cjs.map +1 -1
- package/dist/data/call-graph/call-graph-vocabulary.d.ts +4 -0
- package/dist/data/call-graph/call-graph-vocabulary.d.ts.map +1 -1
- package/dist/data/call-graph/call-graph-vocabulary.js +19 -1
- package/dist/data/call-graph/call-graph-vocabulary.js.map +1 -1
- package/dist/data/call-graph/call-graph.fixtures.d.ts.map +1 -1
- package/dist/data/call-graph/index.d.ts +2 -2
- package/dist/data/call-graph/index.d.ts.map +1 -1
- package/dist/data/call-graph/types.cjs.map +1 -1
- package/dist/data/call-graph/types.d.ts +7 -2
- package/dist/data/call-graph/types.d.ts.map +1 -1
- package/dist/data/call-graph/types.js.map +1 -1
- package/dist/data/chat/Chat.cjs +1 -1
- package/dist/data/chat/Chat.cjs.map +1 -1
- package/dist/data/chat/Chat.js +1 -1
- package/dist/data/chat/Chat.js.map +1 -1
- package/dist/data/chat/ChatRuntimeToolbar.cjs +4 -4
- package/dist/data/chat/ChatRuntimeToolbar.cjs.map +1 -1
- package/dist/data/chat/ChatRuntimeToolbar.d.ts.map +1 -1
- package/dist/data/chat/ChatRuntimeToolbar.js +4 -4
- package/dist/data/chat/ChatRuntimeToolbar.js.map +1 -1
- package/dist/data/chat/PromptInput.cjs +4 -2
- package/dist/data/chat/PromptInput.cjs.map +1 -1
- package/dist/data/chat/PromptInput.d.ts +5 -1
- package/dist/data/chat/PromptInput.d.ts.map +1 -1
- package/dist/data/chat/PromptInput.js +4 -2
- package/dist/data/chat/PromptInput.js.map +1 -1
- package/dist/data/data-access.cjs +8 -0
- package/dist/data/data-access.cjs.map +1 -0
- package/dist/data/data-access.d.ts +5 -0
- package/dist/data/data-access.d.ts.map +1 -0
- package/dist/data/data-access.js +8 -0
- package/dist/data/data-access.js.map +1 -0
- package/dist/data/diagnostics/ErrorDetails.cjs +13 -2
- package/dist/data/diagnostics/ErrorDetails.cjs.map +1 -1
- package/dist/data/diagnostics/ErrorDetails.d.ts +3 -1
- package/dist/data/diagnostics/ErrorDetails.d.ts.map +1 -1
- package/dist/data/diagnostics/ErrorDetails.js +14 -3
- package/dist/data/diagnostics/ErrorDetails.js.map +1 -1
- package/dist/data/diagnostics/FrameSourceWindow.cjs +3 -2
- package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -1
- package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -1
- package/dist/data/diagnostics/FrameSourceWindow.js +3 -2
- package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -1
- package/dist/data/diagnostics/error-diagnostics.cjs +5 -0
- package/dist/data/diagnostics/error-diagnostics.cjs.map +1 -1
- package/dist/data/diagnostics/error-diagnostics.d.ts +1 -0
- package/dist/data/diagnostics/error-diagnostics.d.ts.map +1 -1
- package/dist/data/diagnostics/error-diagnostics.js +6 -1
- package/dist/data/diagnostics/error-diagnostics.js.map +1 -1
- package/dist/data/git/GitChangeList.cjs +6 -2
- package/dist/data/git/GitChangeList.cjs.map +1 -1
- package/dist/data/git/GitChangeList.d.ts +6 -1
- package/dist/data/git/GitChangeList.d.ts.map +1 -1
- package/dist/data/git/GitChangeList.js +6 -2
- package/dist/data/git/GitChangeList.js.map +1 -1
- package/dist/data/graph-columns-layout.cjs +47 -16
- package/dist/data/graph-columns-layout.cjs.map +1 -1
- package/dist/data/graph-columns-layout.d.ts +34 -0
- package/dist/data/graph-columns-layout.d.ts.map +1 -1
- package/dist/data/graph-columns-layout.js +47 -16
- package/dist/data/graph-columns-layout.js.map +1 -1
- package/dist/data/graph-diagram-model.cjs +5 -0
- package/dist/data/graph-diagram-model.cjs.map +1 -1
- package/dist/data/graph-diagram-model.d.ts +19 -0
- package/dist/data/graph-diagram-model.d.ts.map +1 -1
- package/dist/data/graph-diagram-model.js +5 -0
- package/dist/data/graph-diagram-model.js.map +1 -1
- package/dist/data/graph-group-window.cjs +60 -0
- package/dist/data/graph-group-window.cjs.map +1 -0
- package/dist/data/graph-group-window.d.ts +33 -0
- package/dist/data/graph-group-window.d.ts.map +1 -0
- package/dist/data/graph-group-window.js +60 -0
- package/dist/data/graph-group-window.js.map +1 -0
- package/dist/data/har/HarPanel.cjs +3 -1
- package/dist/data/har/HarPanel.cjs.map +1 -1
- package/dist/data/har/HarPanel.d.ts +4 -1
- package/dist/data/har/HarPanel.d.ts.map +1 -1
- package/dist/data/har/HarPanel.js +3 -1
- package/dist/data/har/HarPanel.js.map +1 -1
- package/dist/data/test-runner/Expectation.cjs +57 -0
- package/dist/data/test-runner/Expectation.cjs.map +1 -0
- package/dist/data/test-runner/Expectation.d.ts +19 -0
- package/dist/data/test-runner/Expectation.d.ts.map +1 -0
- package/dist/data/test-runner/Expectation.js +57 -0
- package/dist/data/test-runner/Expectation.js.map +1 -0
- package/dist/data/test-runner/MarkIcons.cjs +38 -0
- package/dist/data/test-runner/MarkIcons.cjs.map +1 -0
- package/dist/data/test-runner/MarkIcons.d.ts +15 -0
- package/dist/data/test-runner/MarkIcons.d.ts.map +1 -0
- package/dist/data/test-runner/MarkIcons.js +38 -0
- package/dist/data/test-runner/MarkIcons.js.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.cjs +74 -42
- package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.js +76 -44
- package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
- package/dist/data/test-runner/TestFailureDetail.cjs +23 -27
- package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -1
- package/dist/data/test-runner/TestFailureDetail.d.ts +2 -2
- package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -1
- package/dist/data/test-runner/TestFailureDetail.js +23 -27
- package/dist/data/test-runner/TestFailureDetail.js.map +1 -1
- package/dist/data/test-runner/TestFilterBar.cjs +87 -93
- package/dist/data/test-runner/TestFilterBar.cjs.map +1 -1
- package/dist/data/test-runner/TestFilterBar.d.ts +3 -2
- package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -1
- package/dist/data/test-runner/TestFilterBar.js +89 -95
- package/dist/data/test-runner/TestFilterBar.js.map +1 -1
- package/dist/data/test-runner/TestRunSummary.cjs +35 -21
- package/dist/data/test-runner/TestRunSummary.cjs.map +1 -1
- package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -1
- package/dist/data/test-runner/TestRunSummary.js +35 -21
- package/dist/data/test-runner/TestRunSummary.js.map +1 -1
- package/dist/data/test-runner/TestRunner.cjs +52 -30
- package/dist/data/test-runner/TestRunner.cjs.map +1 -1
- package/dist/data/test-runner/TestRunner.d.ts +3 -2
- package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
- package/dist/data/test-runner/TestRunner.js +52 -30
- package/dist/data/test-runner/TestRunner.js.map +1 -1
- package/dist/data/test-runner/TestTree.cjs +5 -2
- package/dist/data/test-runner/TestTree.cjs.map +1 -1
- package/dist/data/test-runner/TestTree.d.ts +5 -2
- package/dist/data/test-runner/TestTree.d.ts.map +1 -1
- package/dist/data/test-runner/TestTree.js +5 -2
- package/dist/data/test-runner/TestTree.js.map +1 -1
- package/dist/data/test-runner/TestTreeNode.cjs +27 -19
- package/dist/data/test-runner/TestTreeNode.cjs.map +1 -1
- package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -1
- package/dist/data/test-runner/TestTreeNode.js +28 -20
- package/dist/data/test-runner/TestTreeNode.js.map +1 -1
- package/dist/data/test-runner/adapter.cjs +11 -0
- package/dist/data/test-runner/adapter.cjs.map +1 -1
- package/dist/data/test-runner/adapter.d.ts +18 -7
- package/dist/data/test-runner/adapter.d.ts.map +1 -1
- package/dist/data/test-runner/adapter.js +11 -0
- package/dist/data/test-runner/adapter.js.map +1 -1
- package/dist/data/test-runner/copy.cjs +114 -0
- package/dist/data/test-runner/copy.cjs.map +1 -0
- package/dist/data/test-runner/copy.d.ts +47 -0
- package/dist/data/test-runner/copy.d.ts.map +1 -0
- package/dist/data/test-runner/copy.js +114 -0
- package/dist/data/test-runner/copy.js.map +1 -0
- package/dist/data/test-runner/index.d.ts +5 -2
- package/dist/data/test-runner/index.d.ts.map +1 -1
- package/dist/data/test-runner/marks.cjs +143 -0
- package/dist/data/test-runner/marks.cjs.map +1 -0
- package/dist/data/test-runner/marks.d.ts +30 -0
- package/dist/data/test-runner/marks.d.ts.map +1 -0
- package/dist/data/test-runner/marks.js +143 -0
- package/dist/data/test-runner/marks.js.map +1 -0
- package/dist/data/test-runner/status.cjs +27 -44
- package/dist/data/test-runner/status.cjs.map +1 -1
- package/dist/data/test-runner/status.d.ts +12 -3
- package/dist/data/test-runner/status.d.ts.map +1 -1
- package/dist/data/test-runner/status.js +27 -44
- package/dist/data/test-runner/status.js.map +1 -1
- package/dist/data/use-group-windows.cjs +50 -0
- package/dist/data/use-group-windows.cjs.map +1 -0
- package/dist/data/use-group-windows.d.ts +27 -0
- package/dist/data/use-group-windows.d.ts.map +1 -0
- package/dist/data/use-group-windows.js +50 -0
- package/dist/data/use-group-windows.js.map +1 -0
- package/dist/data/verification/adapterViews.cjs +16 -41
- package/dist/data/verification/adapterViews.cjs.map +1 -1
- package/dist/data/verification/adapterViews.d.ts.map +1 -1
- package/dist/data/verification/adapterViews.js +16 -41
- package/dist/data/verification/adapterViews.js.map +1 -1
- package/dist/data/verification/adapters.cjs +115 -3
- package/dist/data/verification/adapters.cjs.map +1 -1
- package/dist/data/verification/adapters.d.ts.map +1 -1
- package/dist/data/verification/adapters.js +115 -3
- package/dist/data/verification/adapters.js.map +1 -1
- package/dist/data/verification/verifyReportTests.cjs.map +1 -1
- package/dist/data/verification/verifyReportTests.js.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +54 -3
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +6 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +58 -7
- package/dist/data.js.map +1 -1
- package/dist/hooks/event-hub.cjs +229 -0
- package/dist/hooks/event-hub.cjs.map +1 -0
- package/dist/hooks/event-hub.d.ts +26 -0
- package/dist/hooks/event-hub.d.ts.map +1 -0
- package/dist/hooks/event-hub.js +229 -0
- package/dist/hooks/event-hub.js.map +1 -0
- package/dist/hooks.cjs +2 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +2 -0
- package/dist/hooks.js.map +1 -1
- package/dist/icons/components/UiLogoEslint.cjs +10 -0
- package/dist/icons/components/UiLogoEslint.cjs.map +1 -0
- package/dist/icons/components/UiLogoEslint.d.ts +9 -0
- package/dist/icons/components/UiLogoEslint.d.ts.map +1 -0
- package/dist/icons/components/UiLogoEslint.js +10 -0
- package/dist/icons/components/UiLogoEslint.js.map +1 -0
- package/dist/icons/components/UiLogoGo.cjs +10 -0
- package/dist/icons/components/UiLogoGo.cjs.map +1 -0
- package/dist/icons/components/UiLogoGo.d.ts +9 -0
- package/dist/icons/components/UiLogoGo.d.ts.map +1 -0
- package/dist/icons/components/UiLogoGo.js +10 -0
- package/dist/icons/components/UiLogoGo.js.map +1 -0
- package/dist/icons/components/UiLogoJest.cjs +10 -0
- package/dist/icons/components/UiLogoJest.cjs.map +1 -0
- package/dist/icons/components/UiLogoJest.d.ts +9 -0
- package/dist/icons/components/UiLogoJest.d.ts.map +1 -0
- package/dist/icons/components/UiLogoJest.js +10 -0
- package/dist/icons/components/UiLogoJest.js.map +1 -0
- package/dist/icons/components/UiLogoMarkdown.cjs +10 -0
- package/dist/icons/components/UiLogoMarkdown.cjs.map +1 -0
- package/dist/icons/components/UiLogoMarkdown.d.ts +9 -0
- package/dist/icons/components/UiLogoMarkdown.d.ts.map +1 -0
- package/dist/icons/components/UiLogoMarkdown.js +10 -0
- package/dist/icons/components/UiLogoMarkdown.js.map +1 -0
- package/dist/icons/components/UiLogoOxc.cjs +10 -0
- package/dist/icons/components/UiLogoOxc.cjs.map +1 -0
- package/dist/icons/components/UiLogoOxc.d.ts +9 -0
- package/dist/icons/components/UiLogoOxc.d.ts.map +1 -0
- package/dist/icons/components/UiLogoOxc.js +10 -0
- package/dist/icons/components/UiLogoOxc.js.map +1 -0
- package/dist/icons/components/UiLogoPlaywright.cjs +10 -0
- package/dist/icons/components/UiLogoPlaywright.cjs.map +1 -0
- package/dist/icons/components/UiLogoPlaywright.d.ts +9 -0
- package/dist/icons/components/UiLogoPlaywright.d.ts.map +1 -0
- package/dist/icons/components/UiLogoPlaywright.js +10 -0
- package/dist/icons/components/UiLogoPlaywright.js.map +1 -0
- package/dist/icons/components/UiLogoPython.cjs +10 -0
- package/dist/icons/components/UiLogoPython.cjs.map +1 -0
- package/dist/icons/components/UiLogoPython.d.ts +9 -0
- package/dist/icons/components/UiLogoPython.d.ts.map +1 -0
- package/dist/icons/components/UiLogoPython.js +10 -0
- package/dist/icons/components/UiLogoPython.js.map +1 -0
- package/dist/icons/components/UiLogoReact.cjs +10 -0
- package/dist/icons/components/UiLogoReact.cjs.map +1 -0
- package/dist/icons/components/UiLogoReact.d.ts +9 -0
- package/dist/icons/components/UiLogoReact.d.ts.map +1 -0
- package/dist/icons/components/UiLogoReact.js +10 -0
- package/dist/icons/components/UiLogoReact.js.map +1 -0
- package/dist/icons/components/UiLogoRuff.cjs +10 -0
- package/dist/icons/components/UiLogoRuff.cjs.map +1 -0
- package/dist/icons/components/UiLogoRuff.d.ts +9 -0
- package/dist/icons/components/UiLogoRuff.d.ts.map +1 -0
- package/dist/icons/components/UiLogoRuff.js +10 -0
- package/dist/icons/components/UiLogoRuff.js.map +1 -0
- package/dist/icons/components/UiLogoTypescript.cjs +10 -0
- package/dist/icons/components/UiLogoTypescript.cjs.map +1 -0
- package/dist/icons/components/UiLogoTypescript.d.ts +9 -0
- package/dist/icons/components/UiLogoTypescript.d.ts.map +1 -0
- package/dist/icons/components/UiLogoTypescript.js +10 -0
- package/dist/icons/components/UiLogoTypescript.js.map +1 -0
- package/dist/icons/components/UiLogoVitest.cjs +10 -0
- package/dist/icons/components/UiLogoVitest.cjs.map +1 -0
- package/dist/icons/components/UiLogoVitest.d.ts +9 -0
- package/dist/icons/components/UiLogoVitest.d.ts.map +1 -0
- package/dist/icons/components/UiLogoVitest.js +10 -0
- package/dist/icons/components/UiLogoVitest.js.map +1 -0
- package/dist/icons/index.d.ts +11 -0
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/icons/registry.cjs +22 -0
- package/dist/icons/registry.cjs.map +1 -1
- package/dist/icons/registry.d.ts.map +1 -1
- package/dist/icons/registry.js +22 -0
- package/dist/icons/registry.js.map +1 -1
- package/dist/icons.cjs +22 -0
- package/dist/icons.cjs.map +1 -1
- package/dist/icons.js +22 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.cjs +56 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -7
- package/dist/index.js.map +1 -1
- package/dist/overlay/DropdownMenuSubmenu.cjs +4 -4
- package/dist/overlay/DropdownMenuSubmenu.cjs.map +1 -1
- package/dist/overlay/DropdownMenuSubmenu.js +4 -4
- package/dist/overlay/DropdownMenuSubmenu.js.map +1 -1
- package/dist/overlay/modalStack.cjs +13 -6
- package/dist/overlay/modalStack.cjs.map +1 -1
- package/dist/overlay/modalStack.d.ts.map +1 -1
- package/dist/overlay/modalStack.js +13 -6
- package/dist/overlay/modalStack.js.map +1 -1
- package/dist/rpc/InlineError.cjs +12 -43
- package/dist/rpc/InlineError.cjs.map +1 -1
- package/dist/rpc/InlineError.d.ts.map +1 -1
- package/dist/rpc/InlineError.js +13 -44
- package/dist/rpc/InlineError.js.map +1 -1
- package/dist/rpc/OperationScheduleDialog.cjs +63 -10
- package/dist/rpc/OperationScheduleDialog.cjs.map +1 -1
- package/dist/rpc/OperationScheduleDialog.d.ts +4 -0
- package/dist/rpc/OperationScheduleDialog.d.ts.map +1 -1
- package/dist/rpc/OperationScheduleDialog.js +63 -10
- package/dist/rpc/OperationScheduleDialog.js.map +1 -1
- package/dist/rpc/OperationSchedules.cjs +12 -2
- package/dist/rpc/OperationSchedules.cjs.map +1 -1
- package/dist/rpc/OperationSchedules.d.ts.map +1 -1
- package/dist/rpc/OperationSchedules.js +12 -2
- package/dist/rpc/OperationSchedules.js.map +1 -1
- package/dist/rpc/operation-schedule-model.cjs +2 -0
- package/dist/rpc/operation-schedule-model.cjs.map +1 -1
- package/dist/rpc/operation-schedule-model.d.ts.map +1 -1
- package/dist/rpc/operation-schedule-model.js +2 -0
- package/dist/rpc/operation-schedule-model.js.map +1 -1
- package/dist/rpc/operation-schedule-types.d.ts +7 -1
- package/dist/rpc/operation-schedule-types.d.ts.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/data/test-runner/frameworkIcon.cjs +0 -27
- package/dist/data/test-runner/frameworkIcon.cjs.map +0 -1
- package/dist/data/test-runner/frameworkIcon.d.ts +0 -3
- package/dist/data/test-runner/frameworkIcon.d.ts.map +0 -1
- package/dist/data/test-runner/frameworkIcon.js +0 -27
- package/dist/data/test-runner/frameworkIcon.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-columns-layout.cjs","sources":["../../src/data/graph-columns-layout.ts"],"sourcesContent":["// Level-column layout for GraphDiagram: pure math, no React. Split from\n// graph-layout.ts, which owns the shared types and `ringLayout`.\n\nimport { loopOverhang } from \"./graph-edge-routing\";\nimport type { GraphLayoutEdge, GraphLayoutNode, GraphLayoutPosition, GraphLayoutResult } from \"./graph-layout\";\n\nexport interface ColumnsLayoutNode extends GraphLayoutNode {\n /** Column of the node. May be negative: the lowest level is the first column. */\n level: number;\n /** Nodes sharing a group stay contiguous within their column and are boxed together. */\n group?: string;\n /** Drawn at `compactNodeHeight`; a group of only compact nodes stacks them at the compact gap and padding. */\n compact?: boolean;\n}\n\nexport interface ColumnsLayoutOptions {\n /** Node bounding-box width in px. Defaults to 168. */\n nodeWidth?: number;\n /** Node bounding-box height in px. Defaults to 60. */\n nodeHeight?: number;\n /** Horizontal gap between the node boxes of adjacent columns in px. Defaults to 96. */\n columnGap?: number;\n /** Vertical gap between stacked nodes, and between group rectangles, in px. Defaults to 20. */\n rowGap?: number;\n /** Space between a group's nodes and its rectangle in px. Defaults to 12. */\n groupPadding?: number;\n /** Height of a compact node in px. Defaults to 24. */\n compactNodeHeight?: number;\n /** Vertical gap between the nodes of a group of only compact nodes in px. Defaults to 4. */\n compactRowGap?: number;\n /** Space between a group of only compact nodes and its rectangle in px. Defaults to 8. */\n compactGroupPadding?: number;\n /** Space reserved around the columns before the viewport edge. Defaults to 24. */\n padding?: number;\n /**\n * Re-ordering passes over all columns after the first placement, alternating direction; a pass\n * is kept only if it leaves fewer crossings. Defaults to 4. 0 keeps the first placement.\n */\n sweeps?: number;\n}\n\nexport interface GraphLayoutGroupBox {\n /** `${level}:${group}` — a group spanning several columns gets one rectangle per column. */\n id: string;\n group: string;\n level: number;\n /** Top-left corner, in the same coordinate space as the layout's `width`/`height`. */\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface ColumnsLayoutResult extends GraphLayoutResult {\n groups: GraphLayoutGroupBox[];\n}\n\ntype ColumnMetrics = Required<ColumnsLayoutOptions>;\n\ninterface GroupRun {\n group: string | undefined;\n nodes: ColumnsLayoutNode[];\n}\n\n/** One ordering of every column, stacked: the node centres and group rectangles it gives. */\ninterface Arrangement {\n order: Map<number, ColumnsLayoutNode[]>;\n placed: Map<string, GraphLayoutPosition>;\n boxes: Map<number, GraphLayoutGroupBox[]>;\n}\n\n/** What stays fixed while columns are re-ordered. */\ninterface ColumnsContext {\n m: ColumnMetrics;\n levels: number[];\n levelOf: Map<string, number>;\n groupRank: Map<string | undefined, number>;\n neighbours: Map<string, string[]>;\n edges: readonly GraphLayoutEdge[];\n /** Centre x and top y of a level's column. */\n origin: (level: number) => { x: number; top: number; level: number };\n}\n\nconst DEFAULT_NODE_WIDTH = 168;\nconst DEFAULT_NODE_HEIGHT = 60;\nconst DEFAULT_COLUMN_GAP = 96;\nconst DEFAULT_ROW_GAP = 20;\nconst DEFAULT_GROUP_PADDING = 12;\nconst DEFAULT_COMPACT_NODE_HEIGHT = 24;\nconst DEFAULT_COMPACT_ROW_GAP = 4;\nconst DEFAULT_COMPACT_GROUP_PADDING = 8;\nconst DEFAULT_PADDING = 24;\nconst DEFAULT_SWEEPS = 4;\n\nfunction mustGet<K, V>(map: ReadonlyMap<K, V>, key: K, what: string): V {\n const value = map.get(key);\n if (value === undefined) {\n throw new Error(`columnsLayout: no ${what} for \"${String(key)}\"`);\n }\n return value;\n}\n\nfunction columnMetrics(options: ColumnsLayoutOptions): ColumnMetrics {\n return {\n nodeWidth: options.nodeWidth ?? DEFAULT_NODE_WIDTH,\n nodeHeight: options.nodeHeight ?? DEFAULT_NODE_HEIGHT,\n columnGap: options.columnGap ?? DEFAULT_COLUMN_GAP,\n rowGap: options.rowGap ?? DEFAULT_ROW_GAP,\n groupPadding: options.groupPadding ?? DEFAULT_GROUP_PADDING,\n compactNodeHeight: options.compactNodeHeight ?? DEFAULT_COMPACT_NODE_HEIGHT,\n compactRowGap: options.compactRowGap ?? DEFAULT_COMPACT_ROW_GAP,\n compactGroupPadding: options.compactGroupPadding ?? DEFAULT_COMPACT_GROUP_PADDING,\n padding: options.padding ?? DEFAULT_PADDING,\n sweeps: options.sweeps ?? DEFAULT_SWEEPS,\n };\n}\n\nfunction indexColumns(nodes: readonly ColumnsLayoutNode[]) {\n const columns = new Map<number, ColumnsLayoutNode[]>();\n const groupRank = new Map<string | undefined, number>();\n for (const node of nodes) {\n if (!Number.isFinite(node.level)) {\n throw new Error(`columnsLayout: node \"${node.id}\" has no finite level (got ${node.level})`);\n }\n if (!groupRank.has(node.group)) groupRank.set(node.group, groupRank.size);\n const column = columns.get(node.level);\n if (column) {\n column.push(node);\n } else {\n columns.set(node.level, [node]);\n }\n }\n return { columns, groupRank };\n}\n\nfunction indexNeighbours(\n nodes: readonly ColumnsLayoutNode[],\n edges: readonly GraphLayoutEdge[],\n): Map<string, string[]> {\n const neighbours = new Map<string, string[]>(nodes.map((node) => [node.id, []]));\n for (const edge of edges) {\n const from = neighbours.get(edge.from);\n const to = neighbours.get(edge.to);\n if (!from || !to) {\n const missing = from ? edge.to : edge.from;\n throw new Error(`columnsLayout: edge \"${edge.id}\" references unknown node \"${missing}\"`);\n }\n from.push(edge.to);\n to.push(edge.from);\n }\n return neighbours;\n}\n\n/**\n * How a run of nodes stacks: each node's height, the gap between them and the padding inside the run's\n * rectangle. A grouped run of only compact nodes takes the compact gap and padding; any other run the\n * regular ones, whatever its order, so a column's height does not depend on how it is ordered.\n */\nfunction runMetrics(group: string | undefined, nodes: readonly ColumnsLayoutNode[], m: ColumnMetrics) {\n const compact = group !== undefined && nodes.every((node) => node.compact === true);\n const pad = group === undefined ? 0 : compact ? m.compactGroupPadding : m.groupPadding;\n const gap = compact ? m.compactRowGap : m.rowGap;\n const heightOf = (node: ColumnsLayoutNode) => (node.compact ? m.compactNodeHeight : m.nodeHeight);\n const height = nodes.reduce((sum, node) => sum + heightOf(node), 0) + (nodes.length - 1) * gap + 2 * pad;\n return { pad, gap, heightOf, height };\n}\n\nfunction columnHeight(column: readonly ColumnsLayoutNode[], m: ColumnMetrics): number {\n const runs = new Map<string | undefined, ColumnsLayoutNode[]>();\n for (const node of column) runs.set(node.group, [...(runs.get(node.group) ?? []), node]);\n const stacked = [...runs].reduce((sum, [group, nodes]) => sum + runMetrics(group, nodes, m).height, 0);\n return stacked + (runs.size - 1) * m.rowGap;\n}\n\n/** Groups in order of first appearance; within a group by ascending weight, the column's order on ties. */\nfunction orderColumn(\n column: readonly ColumnsLayoutNode[],\n groupRank: ReadonlyMap<string | undefined, number>,\n weight: (node: ColumnsLayoutNode) => number,\n): GroupRun[] {\n const runs = new Map<number, { node: ColumnsLayoutNode; index: number; weight: number }[]>();\n column.forEach((node, index) => {\n const rank = mustGet(groupRank, node.group, \"group rank\");\n const entry = { node, index, weight: weight(node) };\n const run = runs.get(rank);\n if (run) {\n run.push(entry);\n } else {\n runs.set(rank, [entry]);\n }\n });\n return [...runs.entries()]\n .sort(([a], [b]) => a - b)\n .map(([, run]) => {\n // Infinity - Infinity is NaN, which falls through to the column-order tiebreak.\n const nodes = run.sort((a, b) => a.weight - b.weight || a.index - b.index).map((entry) => entry.node);\n return { group: nodes[0]?.group, nodes };\n });\n}\n\nfunction stackColumn(\n runs: readonly GroupRun[],\n origin: { x: number; top: number; level: number },\n m: ColumnMetrics,\n placed: Map<string, GraphLayoutPosition>,\n): GraphLayoutGroupBox[] {\n const boxes: GraphLayoutGroupBox[] = [];\n let cursor = origin.top;\n for (const { group, nodes } of runs) {\n const { pad, gap, heightOf, height } = runMetrics(group, nodes, m);\n let top = cursor + pad;\n for (const node of nodes) {\n placed.set(node.id, { x: origin.x, y: top + heightOf(node) / 2 });\n top += heightOf(node) + gap;\n }\n if (group !== undefined) {\n boxes.push({\n id: `${origin.level}:${group}`,\n group,\n level: origin.level,\n x: origin.x - m.nodeWidth / 2 - pad,\n y: cursor,\n width: m.nodeWidth + 2 * pad,\n height,\n });\n }\n cursor += height + m.rowGap;\n }\n return boxes;\n}\n\nfunction meanY(ids: readonly string[], placed: ReadonlyMap<string, GraphLayoutPosition>): number {\n if (ids.length === 0) return Number.POSITIVE_INFINITY;\n return ids.reduce((sum, id) => sum + mustGet(placed, id, \"position\").y, 0) / ids.length;\n}\n\n/**\n * Levels in the order they are first laid out — the one nearest 0, then\n * outward on each side — each paired with the already-placed adjacent level\n * its nodes are ordered against.\n */\nfunction placementOrder(levels: readonly number[]): [level: number, reference: number | undefined][] {\n const anchor = levels.reduce((best, level) => (Math.abs(level) < Math.abs(best) ? level : best));\n const outward = (side: readonly number[]) =>\n side.map((level, index): [number, number] => [level, side[index - 1] ?? anchor]);\n return [\n [anchor, undefined],\n ...outward(levels.filter((level) => level > anchor)),\n ...outward(levels.filter((level) => level < anchor).reverse()),\n ];\n}\n\n/** Orders one column by the mean y of its nodes' neighbours in the `references` levels, and stacks it. */\nfunction placeColumn(level: number, references: readonly number[], ctx: ColumnsContext, into: Arrangement): void {\n const runs = orderColumn(mustGet(into.order, level, \"column\"), ctx.groupRank, (node) =>\n meanY(\n mustGet(ctx.neighbours, node.id, \"neighbours\").filter((id) =>\n references.includes(mustGet(ctx.levelOf, id, \"level\")),\n ),\n into.placed,\n ),\n );\n into.order.set(\n level,\n runs.flatMap((run) => run.nodes),\n );\n into.boxes.set(level, stackColumn(runs, ctx.origin(level), ctx.m, into.placed));\n}\n\n/** Pairs of edges between adjacent columns that cross, taking each edge as a straight line. */\nfunction countCrossings(ctx: ColumnsContext, placed: ReadonlyMap<string, GraphLayoutPosition>): number {\n const spans = new Map<number, [lower: number, upper: number][]>();\n for (const edge of ctx.edges) {\n const levels = [mustGet(ctx.levelOf, edge.from, \"level\"), mustGet(ctx.levelOf, edge.to, \"level\")] as const;\n const [lower, upper] = levels[0] < levels[1] ? [edge.from, edge.to] : [edge.to, edge.from];\n const index = ctx.levels.indexOf(Math.min(...levels));\n if (levels[0] === levels[1] || ctx.levels[index + 1] !== Math.max(...levels)) continue;\n const span: [number, number] = [mustGet(placed, lower, \"position\").y, mustGet(placed, upper, \"position\").y];\n spans.set(index, [...(spans.get(index) ?? []), span]);\n }\n let crossings = 0;\n for (const gap of spans.values()) {\n gap.forEach((a, index) => {\n crossings += gap.slice(index + 1).filter((b) => (a[0] - b[0]) * (a[1] - b[1]) < 0).length;\n });\n }\n return crossings;\n}\n\n/** A copy of `from` with every column re-ordered against both its neighbours, in the order given. */\nfunction sweep(from: Arrangement, sequence: readonly number[], ctx: ColumnsContext): Arrangement {\n const next: Arrangement = { order: new Map(from.order), placed: new Map(from.placed), boxes: new Map(from.boxes) };\n for (const level of sequence) {\n const index = ctx.levels.indexOf(level);\n const adjacent = [ctx.levels[index - 1], ctx.levels[index + 1]].filter((other): other is number => other !== undefined);\n placeColumn(level, adjacent, ctx, next);\n }\n return next;\n}\n\n/**\n * Orders the columns. The first pass places the level nearest 0 and works\n * outward, ordering each column against the one just placed. Then up to\n * `sweeps` passes re-order every column against both its neighbours, inward\n * then outward, and the arrangement with the fewest crossings is kept: the\n * first pass's unless a later one strictly beats it.\n */\nfunction arrange(columns: ReadonlyMap<number, ColumnsLayoutNode[]>, ctx: ColumnsContext): Arrangement {\n let current: Arrangement = { order: new Map(columns), placed: new Map(), boxes: new Map() };\n for (const [level, reference] of placementOrder(ctx.levels)) {\n placeColumn(level, reference === undefined ? [] : [reference], ctx, current);\n }\n let best = current;\n let fewest = countCrossings(ctx, best.placed);\n for (let pass = 0; pass < ctx.m.sweeps && fewest > 0; pass++) {\n current = sweep(current, pass % 2 === 0 ? [...ctx.levels].reverse() : ctx.levels, ctx);\n const crossings = countCrossings(ctx, current.placed);\n if (crossings < fewest) {\n best = current;\n fewest = crossings;\n }\n }\n return best;\n}\n\n/**\n * Places nodes in left-to-right columns by `level` (the lowest level first),\n * stacking each column top to bottom and centring it against the tallest one.\n *\n * Within a column, nodes of one group are contiguous, groups appearing in the\n * order they first occur in `nodes`. Inside a group nodes are ordered by the\n * mean y of their neighbours in the adjacent columns, so edges between columns\n * cross less (see `arrange`); nodes with no neighbour there go last, and ties\n * keep input order.\n *\n * Returns node centres plus one rectangle per (column, group). Extra width is\n * reserved on each side for the loops `routeEdges` draws outside the first and\n * last columns.\n */\nexport function columnsLayout(\n nodes: readonly ColumnsLayoutNode[],\n edges: readonly GraphLayoutEdge[],\n options: ColumnsLayoutOptions = {},\n): ColumnsLayoutResult {\n const m = columnMetrics(options);\n const { columns, groupRank } = indexColumns(nodes);\n const neighbours = indexNeighbours(nodes, edges);\n const levels = [...columns.keys()].sort((a, b) => a - b);\n if (levels.length === 0) {\n return { width: 2 * m.padding, height: 2 * m.padding, positions: {}, groups: [] };\n }\n\n const minLevel = Math.min(...levels);\n const tallest = Math.max(...[...columns.values()].map((column) => columnHeight(column, m)));\n const inset = m.padding + (nodes.some((node) => node.group !== undefined) ? m.groupPadding : 0);\n const { placed, boxes } = arrange(columns, {\n m,\n levels,\n levelOf: new Map(nodes.map((node) => [node.id, node.level])),\n groupRank,\n neighbours,\n edges,\n origin: (level) => ({\n x: inset + m.nodeWidth / 2 + (level - minLevel) * (m.nodeWidth + m.columnGap),\n top: m.padding + (tallest - columnHeight(mustGet(columns, level, \"column\"), m)) / 2,\n level,\n }),\n });\n\n const nodeHeights = Object.fromEntries(nodes.filter((node) => node.compact).map((node) => [node.id, m.compactNodeHeight]));\n const room = loopOverhang(edges, Object.fromEntries(placed), { ...m, nodeHeights });\n const span = (Math.max(...levels) - minLevel) * (m.nodeWidth + m.columnGap) + m.nodeWidth;\n return {\n width: room.left + 2 * inset + span + room.right,\n height: tallest + 2 * m.padding,\n positions: Object.fromEntries([...placed].map(([id, { x, y }]) => [id, { x: x + room.left, y }])),\n groups: [...boxes.values()]\n .flat()\n .map((box) => ({ ...box, x: box.x + room.left }))\n .sort((a, b) => a.level - b.level || a.y - b.y),\n };\n}\n"],"names":["loopOverhang"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"graph-columns-layout.cjs","sources":["../../src/data/graph-columns-layout.ts"],"sourcesContent":["// Level-column layout for GraphDiagram: pure math, no React. Split from\n// graph-layout.ts, which owns the shared types and `ringLayout`.\n\nimport { loopOverhang } from \"./graph-edge-routing\";\nimport type { GraphLayoutEdge, GraphLayoutNode, GraphLayoutPosition, GraphLayoutResult } from \"./graph-layout\";\n\nexport interface ColumnsLayoutNode extends GraphLayoutNode {\n /** Column of the node. May be negative: the lowest level is the first column. */\n level: number;\n /** Nodes sharing a group stay contiguous within their column and are boxed together. */\n group?: string;\n /** Drawn at `compactNodeHeight`; a group of only compact nodes stacks them at the compact gap and padding. */\n compact?: boolean;\n}\n\n/** The most members a group shows at once in a column; a group with more scrolls them in a window this many rows tall. */\nexport const GROUP_MEMBER_WINDOW = 10;\n/** Height of the \"rows a–b of n\" footer under a windowed group's rows. */\nconst GROUP_WINDOW_FOOTER_HEIGHT = 18;\n\nexport interface ColumnsLayoutOptions {\n /** Group ids drawn as records with a header and flush compact rows. */\n records?: readonly string[];\n /**\n * Group ids drawn as their header alone, `recordHeaderHeight` tall. Each holds one node per column it\n * is drawn in, the stand-in its edges attach to, placed at the header's middle.\n */\n collapsed?: readonly string[];\n /** Height of a record's header in px. Defaults to 24. */\n recordHeaderHeight?: number;\n /** Node bounding-box width in px. Defaults to 168. */\n nodeWidth?: number;\n /** Node bounding-box height in px. Defaults to 60. */\n nodeHeight?: number;\n /** Horizontal gap between the node boxes of adjacent columns in px. Defaults to 96. */\n columnGap?: number;\n /** Vertical gap between stacked nodes, and between group rectangles, in px. Defaults to 20. */\n rowGap?: number;\n /** Space between a group's nodes and its rectangle in px. Defaults to 12. */\n groupPadding?: number;\n /** Height of a compact node in px. Defaults to 24. */\n compactNodeHeight?: number;\n /** Vertical gap between the nodes of a group of only compact nodes in px. Defaults to 4. */\n compactRowGap?: number;\n /** Space between a group of only compact nodes and its rectangle in px. Defaults to 8. */\n compactGroupPadding?: number;\n /** Space reserved around the columns before the viewport edge. Defaults to 24. */\n padding?: number;\n /**\n * Re-ordering passes over all columns after the first placement, alternating direction; a pass\n * is kept only if it leaves fewer crossings. Defaults to 4. 0 keeps the first placement.\n */\n sweeps?: number;\n}\n\nexport interface GraphLayoutGroupBox {\n /** `${level}:${group}` — a group spanning several columns gets one rectangle per column. */\n id: string;\n group: string;\n level: number;\n /** Top-left corner, in the same coordinate space as the layout's `width`/`height`. */\n x: number;\n y: number;\n width: number;\n height: number;\n record: boolean;\n headerHeight: number;\n /** Set when the group holds more members in this column than `GROUP_MEMBER_WINDOW`: they scroll in a window. */\n window?: GroupMemberWindow;\n /** Drawn as its header alone: see `ColumnsLayoutOptions.collapsed`. */\n collapsed?: true;\n}\n\n/**\n * The rows a group with more members than `GROUP_MEMBER_WINDOW` shows at once. The layout places every\n * member as if the group were drawn whole; a member shown from row `start` is drawn `tops[start]` higher.\n */\nexport interface GroupMemberWindow {\n /** The members in this column, top to bottom. */\n members: string[];\n /** Each member's top, from the first member's top. */\n tops: number[];\n /** How many rows show at once. */\n rows: number;\n /** Top of the area members are drawn in, and its height. */\n top: number;\n height: number;\n /** Height of the footer below that area, inside the group's rectangle. */\n footerHeight: number;\n}\n\nexport interface ColumnsLayoutResult extends GraphLayoutResult {\n groups: GraphLayoutGroupBox[];\n}\n\ntype ColumnMetrics = Required<ColumnsLayoutOptions>;\n\ninterface GroupRun {\n group: string | undefined;\n nodes: ColumnsLayoutNode[];\n}\n\n/** One ordering of every column, stacked: the node centres and group rectangles it gives. */\ninterface Arrangement {\n order: Map<number, ColumnsLayoutNode[]>;\n placed: Map<string, GraphLayoutPosition>;\n boxes: Map<number, GraphLayoutGroupBox[]>;\n}\n\n/** What stays fixed while columns are re-ordered. */\ninterface ColumnsContext {\n m: ColumnMetrics;\n levels: number[];\n levelOf: Map<string, number>;\n groupRank: Map<string | undefined, number>;\n neighbours: Map<string, string[]>;\n edges: readonly GraphLayoutEdge[];\n /** Centre x and top y of a level's column. */\n origin: (level: number) => { x: number; top: number; level: number };\n}\n\nconst DEFAULT_NODE_WIDTH = 168;\nconst DEFAULT_NODE_HEIGHT = 60;\nconst DEFAULT_COLUMN_GAP = 96;\nconst DEFAULT_ROW_GAP = 20;\nconst DEFAULT_GROUP_PADDING = 12;\nconst DEFAULT_COMPACT_NODE_HEIGHT = 24;\nconst DEFAULT_COMPACT_ROW_GAP = 4;\nconst DEFAULT_COMPACT_GROUP_PADDING = 8;\nconst DEFAULT_PADDING = 24;\nconst DEFAULT_SWEEPS = 4;\n\nfunction mustGet<K, V>(map: ReadonlyMap<K, V>, key: K, what: string): V {\n const value = map.get(key);\n if (value === undefined) {\n throw new Error(`columnsLayout: no ${what} for \"${String(key)}\"`);\n }\n return value;\n}\n\nfunction columnMetrics(options: ColumnsLayoutOptions): ColumnMetrics {\n return {\n records: options.records ?? [],\n collapsed: options.collapsed ?? [],\n recordHeaderHeight: options.recordHeaderHeight ?? 24,\n nodeWidth: options.nodeWidth ?? DEFAULT_NODE_WIDTH,\n nodeHeight: options.nodeHeight ?? DEFAULT_NODE_HEIGHT,\n columnGap: options.columnGap ?? DEFAULT_COLUMN_GAP,\n rowGap: options.rowGap ?? DEFAULT_ROW_GAP,\n groupPadding: options.groupPadding ?? DEFAULT_GROUP_PADDING,\n compactNodeHeight: options.compactNodeHeight ?? DEFAULT_COMPACT_NODE_HEIGHT,\n compactRowGap: options.compactRowGap ?? DEFAULT_COMPACT_ROW_GAP,\n compactGroupPadding: options.compactGroupPadding ?? DEFAULT_COMPACT_GROUP_PADDING,\n padding: options.padding ?? DEFAULT_PADDING,\n sweeps: options.sweeps ?? DEFAULT_SWEEPS,\n };\n}\n\nfunction indexColumns(nodes: readonly ColumnsLayoutNode[]) {\n const columns = new Map<number, ColumnsLayoutNode[]>();\n const groupRank = new Map<string | undefined, number>();\n for (const node of nodes) {\n if (!Number.isFinite(node.level)) {\n throw new Error(`columnsLayout: node \"${node.id}\" has no finite level (got ${node.level})`);\n }\n if (!groupRank.has(node.group)) groupRank.set(node.group, groupRank.size);\n const column = columns.get(node.level);\n if (column) {\n column.push(node);\n } else {\n columns.set(node.level, [node]);\n }\n }\n return { columns, groupRank };\n}\n\nfunction indexNeighbours(\n nodes: readonly ColumnsLayoutNode[],\n edges: readonly GraphLayoutEdge[],\n): Map<string, string[]> {\n const neighbours = new Map<string, string[]>(nodes.map((node) => [node.id, []]));\n for (const edge of edges) {\n const from = neighbours.get(edge.from);\n const to = neighbours.get(edge.to);\n if (!from || !to) {\n const missing = from ? edge.to : edge.from;\n throw new Error(`columnsLayout: edge \"${edge.id}\" references unknown node \"${missing}\"`);\n }\n from.push(edge.to);\n to.push(edge.from);\n }\n return neighbours;\n}\n\ninterface RunMetrics {\n pad: number;\n gap: number;\n heightOf: (node: ColumnsLayoutNode) => number;\n /** Room above the first node inside the run's rectangle: a record's header. */\n header: number;\n height: number;\n /** The height members are drawn in, when there are more of them than the window holds. */\n view?: number;\n collapsed: boolean;\n}\n\nfunction collapsedRun(group: string, nodes: readonly ColumnsLayoutNode[], m: ColumnMetrics): RunMetrics {\n if (nodes.length !== 1) throw new Error(`columnsLayout: collapsed group \"${group}\" holds ${nodes.length} nodes in a column; it draws one stand-in`);\n return { pad: 0, gap: 0, heightOf: () => m.recordHeaderHeight, header: 0, height: m.recordHeaderHeight, collapsed: true };\n}\n\n/**\n * How a run of nodes stacks: each node's height, the gap between them and the padding inside the run's\n * rectangle. A grouped run of only compact nodes takes the compact gap and padding; any other run the\n * regular ones, whatever its order, so a column's height does not depend on how it is ordered. A group\n * of more than `GROUP_MEMBER_WINDOW` members is only as tall as that many of its tallest rows, over a footer.\n */\nfunction runMetrics(group: string | undefined, nodes: readonly ColumnsLayoutNode[], m: ColumnMetrics, record: boolean): RunMetrics {\n if (group !== undefined && m.collapsed.includes(group)) return collapsedRun(group, nodes, m);\n let stack: Omit<RunMetrics, \"height\" | \"collapsed\">;\n if (record) {\n const regular = nodes.find((node) => !node.compact);\n if (regular) throw new Error(`columnsLayout: record group \"${group}\" holds non-compact node \"${regular.id}\"`);\n stack = { pad: 0, gap: 0, heightOf: () => m.compactNodeHeight, header: m.recordHeaderHeight };\n } else {\n const compact = group !== undefined && nodes.every((node) => node.compact === true);\n const heightOf = (node: ColumnsLayoutNode) => (node.compact ? m.compactNodeHeight : m.nodeHeight);\n stack = { pad: group === undefined ? 0 : compact ? m.compactGroupPadding : m.groupPadding, gap: compact ? m.compactRowGap : m.rowGap, heightOf, header: 0 };\n }\n const chrome = stack.header + 2 * stack.pad;\n if (group === undefined || nodes.length <= GROUP_MEMBER_WINDOW) {\n const stacked = nodes.reduce((sum, node) => sum + stack.heightOf(node), 0) + (nodes.length - 1) * stack.gap;\n return { ...stack, height: chrome + stacked, collapsed: false };\n }\n const view = GROUP_MEMBER_WINDOW * Math.max(...nodes.map(stack.heightOf)) + (GROUP_MEMBER_WINDOW - 1) * stack.gap;\n return { ...stack, view, height: chrome + view + GROUP_WINDOW_FOOTER_HEIGHT, collapsed: false };\n}\n\nfunction columnHeight(column: readonly ColumnsLayoutNode[], m: ColumnMetrics): number {\n const runs = new Map<string | undefined, ColumnsLayoutNode[]>();\n for (const node of column) runs.set(node.group, [...(runs.get(node.group) ?? []), node]);\n const stacked = [...runs].reduce((sum, [group, nodes]) => sum + runMetrics(group, nodes, m, group !== undefined && m.records.includes(group)).height, 0);\n return stacked + (runs.size - 1) * m.rowGap;\n}\n\n/** Groups in order of first appearance; within a group by ascending weight, the column's order on ties. */\nfunction orderColumn(\n column: readonly ColumnsLayoutNode[],\n groupRank: ReadonlyMap<string | undefined, number>,\n weight: (node: ColumnsLayoutNode) => number,\n): GroupRun[] {\n const runs = new Map<number, { node: ColumnsLayoutNode; index: number; weight: number }[]>();\n column.forEach((node, index) => {\n const rank = mustGet(groupRank, node.group, \"group rank\");\n const entry = { node, index, weight: weight(node) };\n const run = runs.get(rank);\n if (run) {\n run.push(entry);\n } else {\n runs.set(rank, [entry]);\n }\n });\n return [...runs.entries()]\n .sort(([a], [b]) => a - b)\n .map(([, run]) => {\n // Infinity - Infinity is NaN, which falls through to the column-order tiebreak.\n const nodes = run.sort((a, b) => a.weight - b.weight || a.index - b.index).map((entry) => entry.node);\n return { group: nodes[0]?.group, nodes };\n });\n}\n\nfunction stackColumn(\n runs: readonly GroupRun[],\n origin: { x: number; top: number; level: number },\n m: ColumnMetrics,\n placed: Map<string, GraphLayoutPosition>,\n): GraphLayoutGroupBox[] {\n const boxes: GraphLayoutGroupBox[] = [];\n let cursor = origin.top;\n for (const { group, nodes } of runs) {\n const record = group !== undefined && m.records.includes(group);\n const run = runMetrics(group, nodes, m, record);\n const first = cursor + run.pad + run.header;\n const tops: number[] = [];\n let top = first;\n for (const node of nodes) {\n tops.push(top - first);\n placed.set(node.id, { x: origin.x, y: top + run.heightOf(node) / 2 });\n top += run.heightOf(node) + run.gap;\n }\n if (group !== undefined) {\n const memberWindow: GroupMemberWindow | undefined = run.view === undefined ? undefined\n : { members: nodes.map((node) => node.id), tops, rows: GROUP_MEMBER_WINDOW, top: first, height: run.view, footerHeight: GROUP_WINDOW_FOOTER_HEIGHT };\n boxes.push({\n id: `${origin.level}:${group}`,\n group,\n level: origin.level,\n x: origin.x - m.nodeWidth / 2 - run.pad,\n y: cursor,\n width: m.nodeWidth + 2 * run.pad,\n height: run.height,\n record,\n headerHeight: run.collapsed ? m.recordHeaderHeight : run.header,\n ...(memberWindow ? { window: memberWindow } : {}),\n ...(run.collapsed ? { collapsed: true as const } : {}),\n });\n }\n cursor += run.height + m.rowGap;\n }\n return boxes;\n}\n\nfunction meanY(ids: readonly string[], placed: ReadonlyMap<string, GraphLayoutPosition>): number {\n if (ids.length === 0) return Number.POSITIVE_INFINITY;\n return ids.reduce((sum, id) => sum + mustGet(placed, id, \"position\").y, 0) / ids.length;\n}\n\n/**\n * Levels in the order they are first laid out — the one nearest 0, then\n * outward on each side — each paired with the already-placed adjacent level\n * its nodes are ordered against.\n */\nfunction placementOrder(levels: readonly number[]): [level: number, reference: number | undefined][] {\n const anchor = levels.reduce((best, level) => (Math.abs(level) < Math.abs(best) ? level : best));\n const outward = (side: readonly number[]) =>\n side.map((level, index): [number, number] => [level, side[index - 1] ?? anchor]);\n return [\n [anchor, undefined],\n ...outward(levels.filter((level) => level > anchor)),\n ...outward(levels.filter((level) => level < anchor).reverse()),\n ];\n}\n\n/** Orders one column by the mean y of its nodes' neighbours in the `references` levels, and stacks it. */\nfunction placeColumn(level: number, references: readonly number[], ctx: ColumnsContext, into: Arrangement): void {\n const runs = orderColumn(mustGet(into.order, level, \"column\"), ctx.groupRank, (node) =>\n meanY(\n mustGet(ctx.neighbours, node.id, \"neighbours\").filter((id) =>\n references.includes(mustGet(ctx.levelOf, id, \"level\")),\n ),\n into.placed,\n ),\n );\n into.order.set(\n level,\n runs.flatMap((run) => run.nodes),\n );\n into.boxes.set(level, stackColumn(runs, ctx.origin(level), ctx.m, into.placed));\n}\n\n/** Pairs of edges between adjacent columns that cross, taking each edge as a straight line. */\nfunction countCrossings(ctx: ColumnsContext, placed: ReadonlyMap<string, GraphLayoutPosition>): number {\n const spans = new Map<number, [lower: number, upper: number][]>();\n for (const edge of ctx.edges) {\n const levels = [mustGet(ctx.levelOf, edge.from, \"level\"), mustGet(ctx.levelOf, edge.to, \"level\")] as const;\n const [lower, upper] = levels[0] < levels[1] ? [edge.from, edge.to] : [edge.to, edge.from];\n const index = ctx.levels.indexOf(Math.min(...levels));\n if (levels[0] === levels[1] || ctx.levels[index + 1] !== Math.max(...levels)) continue;\n const span: [number, number] = [mustGet(placed, lower, \"position\").y, mustGet(placed, upper, \"position\").y];\n spans.set(index, [...(spans.get(index) ?? []), span]);\n }\n let crossings = 0;\n for (const gap of spans.values()) {\n gap.forEach((a, index) => {\n crossings += gap.slice(index + 1).filter((b) => (a[0] - b[0]) * (a[1] - b[1]) < 0).length;\n });\n }\n return crossings;\n}\n\n/** A copy of `from` with every column re-ordered against both its neighbours, in the order given. */\nfunction sweep(from: Arrangement, sequence: readonly number[], ctx: ColumnsContext): Arrangement {\n const next: Arrangement = { order: new Map(from.order), placed: new Map(from.placed), boxes: new Map(from.boxes) };\n for (const level of sequence) {\n const index = ctx.levels.indexOf(level);\n const adjacent = [ctx.levels[index - 1], ctx.levels[index + 1]].filter((other): other is number => other !== undefined);\n placeColumn(level, adjacent, ctx, next);\n }\n return next;\n}\n\n/**\n * Orders the columns. The first pass places the level nearest 0 and works\n * outward, ordering each column against the one just placed. Then up to\n * `sweeps` passes re-order every column against both its neighbours, inward\n * then outward, and the arrangement with the fewest crossings is kept: the\n * first pass's unless a later one strictly beats it.\n */\nfunction arrange(columns: ReadonlyMap<number, ColumnsLayoutNode[]>, ctx: ColumnsContext): Arrangement {\n let current: Arrangement = { order: new Map(columns), placed: new Map(), boxes: new Map() };\n for (const [level, reference] of placementOrder(ctx.levels)) {\n placeColumn(level, reference === undefined ? [] : [reference], ctx, current);\n }\n let best = current;\n let fewest = countCrossings(ctx, best.placed);\n for (let pass = 0; pass < ctx.m.sweeps && fewest > 0; pass++) {\n current = sweep(current, pass % 2 === 0 ? [...ctx.levels].reverse() : ctx.levels, ctx);\n const crossings = countCrossings(ctx, current.placed);\n if (crossings < fewest) {\n best = current;\n fewest = crossings;\n }\n }\n return best;\n}\n\n/**\n * Places nodes in left-to-right columns by `level` (the lowest level first),\n * stacking each column top to bottom and centring it against the tallest one.\n *\n * Within a column, nodes of one group are contiguous, groups appearing in the\n * order they first occur in `nodes`. Inside a group nodes are ordered by the\n * mean y of their neighbours in the adjacent columns, so edges between columns\n * cross less (see `arrange`); nodes with no neighbour there go last, and ties\n * keep input order.\n *\n * Returns node centres plus one rectangle per (column, group). Extra width is\n * reserved on each side for the loops `routeEdges` draws outside the first and\n * last columns.\n */\nexport function columnsLayout(\n nodes: readonly ColumnsLayoutNode[],\n edges: readonly GraphLayoutEdge[],\n options: ColumnsLayoutOptions = {},\n): ColumnsLayoutResult {\n const m = columnMetrics(options);\n const { columns, groupRank } = indexColumns(nodes);\n const neighbours = indexNeighbours(nodes, edges);\n const levels = [...columns.keys()].sort((a, b) => a - b);\n if (levels.length === 0) {\n return { width: 2 * m.padding, height: 2 * m.padding, positions: {}, groups: [] };\n }\n\n const minLevel = Math.min(...levels);\n const tallest = Math.max(...[...columns.values()].map((column) => columnHeight(column, m)));\n const inset = m.padding + (nodes.some((node) => node.group !== undefined) ? m.groupPadding : 0);\n const { placed, boxes } = arrange(columns, {\n m,\n levels,\n levelOf: new Map(nodes.map((node) => [node.id, node.level])),\n groupRank,\n neighbours,\n edges,\n origin: (level) => ({\n x: inset + m.nodeWidth / 2 + (level - minLevel) * (m.nodeWidth + m.columnGap),\n top: m.padding + (tallest - columnHeight(mustGet(columns, level, \"column\"), m)) / 2,\n level,\n }),\n });\n\n const nodeHeights = Object.fromEntries(nodes.filter((node) => node.compact).map((node) => [node.id, m.compactNodeHeight]));\n const room = loopOverhang(edges, Object.fromEntries(placed), { ...m, nodeHeights });\n const span = (Math.max(...levels) - minLevel) * (m.nodeWidth + m.columnGap) + m.nodeWidth;\n return {\n width: room.left + 2 * inset + span + room.right,\n height: tallest + 2 * m.padding,\n positions: Object.fromEntries([...placed].map(([id, { x, y }]) => [id, { x: x + room.left, y }])),\n groups: [...boxes.values()]\n .flat()\n .map((box) => ({ ...box, x: box.x + room.left }))\n .sort((a, b) => a.level - b.level || a.y - b.y),\n };\n}\n"],"names":["loopOverhang"],"mappings":"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"}
|
|
@@ -7,7 +7,18 @@ export interface ColumnsLayoutNode extends GraphLayoutNode {
|
|
|
7
7
|
/** Drawn at `compactNodeHeight`; a group of only compact nodes stacks them at the compact gap and padding. */
|
|
8
8
|
compact?: boolean;
|
|
9
9
|
}
|
|
10
|
+
/** The most members a group shows at once in a column; a group with more scrolls them in a window this many rows tall. */
|
|
11
|
+
export declare const GROUP_MEMBER_WINDOW = 10;
|
|
10
12
|
export interface ColumnsLayoutOptions {
|
|
13
|
+
/** Group ids drawn as records with a header and flush compact rows. */
|
|
14
|
+
records?: readonly string[];
|
|
15
|
+
/**
|
|
16
|
+
* Group ids drawn as their header alone, `recordHeaderHeight` tall. Each holds one node per column it
|
|
17
|
+
* is drawn in, the stand-in its edges attach to, placed at the header's middle.
|
|
18
|
+
*/
|
|
19
|
+
collapsed?: readonly string[];
|
|
20
|
+
/** Height of a record's header in px. Defaults to 24. */
|
|
21
|
+
recordHeaderHeight?: number;
|
|
11
22
|
/** Node bounding-box width in px. Defaults to 168. */
|
|
12
23
|
nodeWidth?: number;
|
|
13
24
|
/** Node bounding-box height in px. Defaults to 60. */
|
|
@@ -42,6 +53,29 @@ export interface GraphLayoutGroupBox {
|
|
|
42
53
|
y: number;
|
|
43
54
|
width: number;
|
|
44
55
|
height: number;
|
|
56
|
+
record: boolean;
|
|
57
|
+
headerHeight: number;
|
|
58
|
+
/** Set when the group holds more members in this column than `GROUP_MEMBER_WINDOW`: they scroll in a window. */
|
|
59
|
+
window?: GroupMemberWindow;
|
|
60
|
+
/** Drawn as its header alone: see `ColumnsLayoutOptions.collapsed`. */
|
|
61
|
+
collapsed?: true;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The rows a group with more members than `GROUP_MEMBER_WINDOW` shows at once. The layout places every
|
|
65
|
+
* member as if the group were drawn whole; a member shown from row `start` is drawn `tops[start]` higher.
|
|
66
|
+
*/
|
|
67
|
+
export interface GroupMemberWindow {
|
|
68
|
+
/** The members in this column, top to bottom. */
|
|
69
|
+
members: string[];
|
|
70
|
+
/** Each member's top, from the first member's top. */
|
|
71
|
+
tops: number[];
|
|
72
|
+
/** How many rows show at once. */
|
|
73
|
+
rows: number;
|
|
74
|
+
/** Top of the area members are drawn in, and its height. */
|
|
75
|
+
top: number;
|
|
76
|
+
height: number;
|
|
77
|
+
/** Height of the footer below that area, inside the group's rectangle. */
|
|
78
|
+
footerHeight: number;
|
|
45
79
|
}
|
|
46
80
|
export interface ColumnsLayoutResult extends GraphLayoutResult {
|
|
47
81
|
groups: GraphLayoutGroupBox[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-columns-layout.d.ts","sourceRoot":"","sources":["../../src/data/graph-columns-layout.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAuB,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAE/G,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8GAA8G;IAC9G,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,sFAAsF;IACtF,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"graph-columns-layout.d.ts","sourceRoot":"","sources":["../../src/data/graph-columns-layout.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAuB,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAE/G,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8GAA8G;IAC9G,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,0HAA0H;AAC1H,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAItC,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,yDAAyD;IACzD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,sFAAsF;IACtF,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,gHAAgH;IAChH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,uEAAuE;IACvE,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sDAAsD;IACtD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,MAAM,EAAE,mBAAmB,EAAE,CAAC;CAC/B;AAyTD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,KAAK,EAAE,SAAS,eAAe,EAAE,EACjC,OAAO,GAAE,oBAAyB,GACjC,mBAAmB,CAsCrB"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { loopOverhang } from "./graph-edge-routing.js";
|
|
2
|
+
const GROUP_MEMBER_WINDOW = 10;
|
|
3
|
+
const GROUP_WINDOW_FOOTER_HEIGHT = 18;
|
|
2
4
|
const DEFAULT_NODE_WIDTH = 168;
|
|
3
5
|
const DEFAULT_NODE_HEIGHT = 60;
|
|
4
6
|
const DEFAULT_COLUMN_GAP = 96;
|
|
@@ -18,6 +20,9 @@ function mustGet(map, key, what) {
|
|
|
18
20
|
}
|
|
19
21
|
function columnMetrics(options) {
|
|
20
22
|
return {
|
|
23
|
+
records: options.records ?? [],
|
|
24
|
+
collapsed: options.collapsed ?? [],
|
|
25
|
+
recordHeaderHeight: options.recordHeaderHeight ?? 24,
|
|
21
26
|
nodeWidth: options.nodeWidth ?? DEFAULT_NODE_WIDTH,
|
|
22
27
|
nodeHeight: options.nodeHeight ?? DEFAULT_NODE_HEIGHT,
|
|
23
28
|
columnGap: options.columnGap ?? DEFAULT_COLUMN_GAP,
|
|
@@ -61,18 +66,34 @@ function indexNeighbours(nodes, edges) {
|
|
|
61
66
|
}
|
|
62
67
|
return neighbours;
|
|
63
68
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
function collapsedRun(group, nodes, m) {
|
|
70
|
+
if (nodes.length !== 1) throw new Error(`columnsLayout: collapsed group "${group}" holds ${nodes.length} nodes in a column; it draws one stand-in`);
|
|
71
|
+
return { pad: 0, gap: 0, heightOf: () => m.recordHeaderHeight, header: 0, height: m.recordHeaderHeight, collapsed: true };
|
|
72
|
+
}
|
|
73
|
+
function runMetrics(group, nodes, m, record) {
|
|
74
|
+
if (group !== void 0 && m.collapsed.includes(group)) return collapsedRun(group, nodes, m);
|
|
75
|
+
let stack;
|
|
76
|
+
if (record) {
|
|
77
|
+
const regular = nodes.find((node) => !node.compact);
|
|
78
|
+
if (regular) throw new Error(`columnsLayout: record group "${group}" holds non-compact node "${regular.id}"`);
|
|
79
|
+
stack = { pad: 0, gap: 0, heightOf: () => m.compactNodeHeight, header: m.recordHeaderHeight };
|
|
80
|
+
} else {
|
|
81
|
+
const compact = group !== void 0 && nodes.every((node) => node.compact === true);
|
|
82
|
+
const heightOf = (node) => node.compact ? m.compactNodeHeight : m.nodeHeight;
|
|
83
|
+
stack = { pad: group === void 0 ? 0 : compact ? m.compactGroupPadding : m.groupPadding, gap: compact ? m.compactRowGap : m.rowGap, heightOf, header: 0 };
|
|
84
|
+
}
|
|
85
|
+
const chrome = stack.header + 2 * stack.pad;
|
|
86
|
+
if (group === void 0 || nodes.length <= GROUP_MEMBER_WINDOW) {
|
|
87
|
+
const stacked = nodes.reduce((sum, node) => sum + stack.heightOf(node), 0) + (nodes.length - 1) * stack.gap;
|
|
88
|
+
return { ...stack, height: chrome + stacked, collapsed: false };
|
|
89
|
+
}
|
|
90
|
+
const view = GROUP_MEMBER_WINDOW * Math.max(...nodes.map(stack.heightOf)) + (GROUP_MEMBER_WINDOW - 1) * stack.gap;
|
|
91
|
+
return { ...stack, view, height: chrome + view + GROUP_WINDOW_FOOTER_HEIGHT, collapsed: false };
|
|
71
92
|
}
|
|
72
93
|
function columnHeight(column, m) {
|
|
73
94
|
const runs = /* @__PURE__ */ new Map();
|
|
74
95
|
for (const node of column) runs.set(node.group, [...runs.get(node.group) ?? [], node]);
|
|
75
|
-
const stacked = [...runs].reduce((sum, [group, nodes]) => sum + runMetrics(group, nodes, m).height, 0);
|
|
96
|
+
const stacked = [...runs].reduce((sum, [group, nodes]) => sum + runMetrics(group, nodes, m, group !== void 0 && m.records.includes(group)).height, 0);
|
|
76
97
|
return stacked + (runs.size - 1) * m.rowGap;
|
|
77
98
|
}
|
|
78
99
|
function orderColumn(column, groupRank, weight) {
|
|
@@ -97,24 +118,33 @@ function stackColumn(runs, origin, m, placed) {
|
|
|
97
118
|
const boxes = [];
|
|
98
119
|
let cursor = origin.top;
|
|
99
120
|
for (const { group, nodes } of runs) {
|
|
100
|
-
const
|
|
101
|
-
|
|
121
|
+
const record = group !== void 0 && m.records.includes(group);
|
|
122
|
+
const run = runMetrics(group, nodes, m, record);
|
|
123
|
+
const first = cursor + run.pad + run.header;
|
|
124
|
+
const tops = [];
|
|
125
|
+
let top = first;
|
|
102
126
|
for (const node of nodes) {
|
|
103
|
-
|
|
104
|
-
top
|
|
127
|
+
tops.push(top - first);
|
|
128
|
+
placed.set(node.id, { x: origin.x, y: top + run.heightOf(node) / 2 });
|
|
129
|
+
top += run.heightOf(node) + run.gap;
|
|
105
130
|
}
|
|
106
131
|
if (group !== void 0) {
|
|
132
|
+
const memberWindow = run.view === void 0 ? void 0 : { members: nodes.map((node) => node.id), tops, rows: GROUP_MEMBER_WINDOW, top: first, height: run.view, footerHeight: GROUP_WINDOW_FOOTER_HEIGHT };
|
|
107
133
|
boxes.push({
|
|
108
134
|
id: `${origin.level}:${group}`,
|
|
109
135
|
group,
|
|
110
136
|
level: origin.level,
|
|
111
|
-
x: origin.x - m.nodeWidth / 2 - pad,
|
|
137
|
+
x: origin.x - m.nodeWidth / 2 - run.pad,
|
|
112
138
|
y: cursor,
|
|
113
|
-
width: m.nodeWidth + 2 * pad,
|
|
114
|
-
height
|
|
139
|
+
width: m.nodeWidth + 2 * run.pad,
|
|
140
|
+
height: run.height,
|
|
141
|
+
record,
|
|
142
|
+
headerHeight: run.collapsed ? m.recordHeaderHeight : run.header,
|
|
143
|
+
...memberWindow ? { window: memberWindow } : {},
|
|
144
|
+
...run.collapsed ? { collapsed: true } : {}
|
|
115
145
|
});
|
|
116
146
|
}
|
|
117
|
-
cursor += height + m.rowGap;
|
|
147
|
+
cursor += run.height + m.rowGap;
|
|
118
148
|
}
|
|
119
149
|
return boxes;
|
|
120
150
|
}
|
|
@@ -227,6 +257,7 @@ function columnsLayout(nodes, edges, options = {}) {
|
|
|
227
257
|
};
|
|
228
258
|
}
|
|
229
259
|
export {
|
|
260
|
+
GROUP_MEMBER_WINDOW,
|
|
230
261
|
columnsLayout
|
|
231
262
|
};
|
|
232
263
|
//# sourceMappingURL=graph-columns-layout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-columns-layout.js","sources":["../../src/data/graph-columns-layout.ts"],"sourcesContent":["// Level-column layout for GraphDiagram: pure math, no React. Split from\n// graph-layout.ts, which owns the shared types and `ringLayout`.\n\nimport { loopOverhang } from \"./graph-edge-routing\";\nimport type { GraphLayoutEdge, GraphLayoutNode, GraphLayoutPosition, GraphLayoutResult } from \"./graph-layout\";\n\nexport interface ColumnsLayoutNode extends GraphLayoutNode {\n /** Column of the node. May be negative: the lowest level is the first column. */\n level: number;\n /** Nodes sharing a group stay contiguous within their column and are boxed together. */\n group?: string;\n /** Drawn at `compactNodeHeight`; a group of only compact nodes stacks them at the compact gap and padding. */\n compact?: boolean;\n}\n\nexport interface ColumnsLayoutOptions {\n /** Node bounding-box width in px. Defaults to 168. */\n nodeWidth?: number;\n /** Node bounding-box height in px. Defaults to 60. */\n nodeHeight?: number;\n /** Horizontal gap between the node boxes of adjacent columns in px. Defaults to 96. */\n columnGap?: number;\n /** Vertical gap between stacked nodes, and between group rectangles, in px. Defaults to 20. */\n rowGap?: number;\n /** Space between a group's nodes and its rectangle in px. Defaults to 12. */\n groupPadding?: number;\n /** Height of a compact node in px. Defaults to 24. */\n compactNodeHeight?: number;\n /** Vertical gap between the nodes of a group of only compact nodes in px. Defaults to 4. */\n compactRowGap?: number;\n /** Space between a group of only compact nodes and its rectangle in px. Defaults to 8. */\n compactGroupPadding?: number;\n /** Space reserved around the columns before the viewport edge. Defaults to 24. */\n padding?: number;\n /**\n * Re-ordering passes over all columns after the first placement, alternating direction; a pass\n * is kept only if it leaves fewer crossings. Defaults to 4. 0 keeps the first placement.\n */\n sweeps?: number;\n}\n\nexport interface GraphLayoutGroupBox {\n /** `${level}:${group}` — a group spanning several columns gets one rectangle per column. */\n id: string;\n group: string;\n level: number;\n /** Top-left corner, in the same coordinate space as the layout's `width`/`height`. */\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface ColumnsLayoutResult extends GraphLayoutResult {\n groups: GraphLayoutGroupBox[];\n}\n\ntype ColumnMetrics = Required<ColumnsLayoutOptions>;\n\ninterface GroupRun {\n group: string | undefined;\n nodes: ColumnsLayoutNode[];\n}\n\n/** One ordering of every column, stacked: the node centres and group rectangles it gives. */\ninterface Arrangement {\n order: Map<number, ColumnsLayoutNode[]>;\n placed: Map<string, GraphLayoutPosition>;\n boxes: Map<number, GraphLayoutGroupBox[]>;\n}\n\n/** What stays fixed while columns are re-ordered. */\ninterface ColumnsContext {\n m: ColumnMetrics;\n levels: number[];\n levelOf: Map<string, number>;\n groupRank: Map<string | undefined, number>;\n neighbours: Map<string, string[]>;\n edges: readonly GraphLayoutEdge[];\n /** Centre x and top y of a level's column. */\n origin: (level: number) => { x: number; top: number; level: number };\n}\n\nconst DEFAULT_NODE_WIDTH = 168;\nconst DEFAULT_NODE_HEIGHT = 60;\nconst DEFAULT_COLUMN_GAP = 96;\nconst DEFAULT_ROW_GAP = 20;\nconst DEFAULT_GROUP_PADDING = 12;\nconst DEFAULT_COMPACT_NODE_HEIGHT = 24;\nconst DEFAULT_COMPACT_ROW_GAP = 4;\nconst DEFAULT_COMPACT_GROUP_PADDING = 8;\nconst DEFAULT_PADDING = 24;\nconst DEFAULT_SWEEPS = 4;\n\nfunction mustGet<K, V>(map: ReadonlyMap<K, V>, key: K, what: string): V {\n const value = map.get(key);\n if (value === undefined) {\n throw new Error(`columnsLayout: no ${what} for \"${String(key)}\"`);\n }\n return value;\n}\n\nfunction columnMetrics(options: ColumnsLayoutOptions): ColumnMetrics {\n return {\n nodeWidth: options.nodeWidth ?? DEFAULT_NODE_WIDTH,\n nodeHeight: options.nodeHeight ?? DEFAULT_NODE_HEIGHT,\n columnGap: options.columnGap ?? DEFAULT_COLUMN_GAP,\n rowGap: options.rowGap ?? DEFAULT_ROW_GAP,\n groupPadding: options.groupPadding ?? DEFAULT_GROUP_PADDING,\n compactNodeHeight: options.compactNodeHeight ?? DEFAULT_COMPACT_NODE_HEIGHT,\n compactRowGap: options.compactRowGap ?? DEFAULT_COMPACT_ROW_GAP,\n compactGroupPadding: options.compactGroupPadding ?? DEFAULT_COMPACT_GROUP_PADDING,\n padding: options.padding ?? DEFAULT_PADDING,\n sweeps: options.sweeps ?? DEFAULT_SWEEPS,\n };\n}\n\nfunction indexColumns(nodes: readonly ColumnsLayoutNode[]) {\n const columns = new Map<number, ColumnsLayoutNode[]>();\n const groupRank = new Map<string | undefined, number>();\n for (const node of nodes) {\n if (!Number.isFinite(node.level)) {\n throw new Error(`columnsLayout: node \"${node.id}\" has no finite level (got ${node.level})`);\n }\n if (!groupRank.has(node.group)) groupRank.set(node.group, groupRank.size);\n const column = columns.get(node.level);\n if (column) {\n column.push(node);\n } else {\n columns.set(node.level, [node]);\n }\n }\n return { columns, groupRank };\n}\n\nfunction indexNeighbours(\n nodes: readonly ColumnsLayoutNode[],\n edges: readonly GraphLayoutEdge[],\n): Map<string, string[]> {\n const neighbours = new Map<string, string[]>(nodes.map((node) => [node.id, []]));\n for (const edge of edges) {\n const from = neighbours.get(edge.from);\n const to = neighbours.get(edge.to);\n if (!from || !to) {\n const missing = from ? edge.to : edge.from;\n throw new Error(`columnsLayout: edge \"${edge.id}\" references unknown node \"${missing}\"`);\n }\n from.push(edge.to);\n to.push(edge.from);\n }\n return neighbours;\n}\n\n/**\n * How a run of nodes stacks: each node's height, the gap between them and the padding inside the run's\n * rectangle. A grouped run of only compact nodes takes the compact gap and padding; any other run the\n * regular ones, whatever its order, so a column's height does not depend on how it is ordered.\n */\nfunction runMetrics(group: string | undefined, nodes: readonly ColumnsLayoutNode[], m: ColumnMetrics) {\n const compact = group !== undefined && nodes.every((node) => node.compact === true);\n const pad = group === undefined ? 0 : compact ? m.compactGroupPadding : m.groupPadding;\n const gap = compact ? m.compactRowGap : m.rowGap;\n const heightOf = (node: ColumnsLayoutNode) => (node.compact ? m.compactNodeHeight : m.nodeHeight);\n const height = nodes.reduce((sum, node) => sum + heightOf(node), 0) + (nodes.length - 1) * gap + 2 * pad;\n return { pad, gap, heightOf, height };\n}\n\nfunction columnHeight(column: readonly ColumnsLayoutNode[], m: ColumnMetrics): number {\n const runs = new Map<string | undefined, ColumnsLayoutNode[]>();\n for (const node of column) runs.set(node.group, [...(runs.get(node.group) ?? []), node]);\n const stacked = [...runs].reduce((sum, [group, nodes]) => sum + runMetrics(group, nodes, m).height, 0);\n return stacked + (runs.size - 1) * m.rowGap;\n}\n\n/** Groups in order of first appearance; within a group by ascending weight, the column's order on ties. */\nfunction orderColumn(\n column: readonly ColumnsLayoutNode[],\n groupRank: ReadonlyMap<string | undefined, number>,\n weight: (node: ColumnsLayoutNode) => number,\n): GroupRun[] {\n const runs = new Map<number, { node: ColumnsLayoutNode; index: number; weight: number }[]>();\n column.forEach((node, index) => {\n const rank = mustGet(groupRank, node.group, \"group rank\");\n const entry = { node, index, weight: weight(node) };\n const run = runs.get(rank);\n if (run) {\n run.push(entry);\n } else {\n runs.set(rank, [entry]);\n }\n });\n return [...runs.entries()]\n .sort(([a], [b]) => a - b)\n .map(([, run]) => {\n // Infinity - Infinity is NaN, which falls through to the column-order tiebreak.\n const nodes = run.sort((a, b) => a.weight - b.weight || a.index - b.index).map((entry) => entry.node);\n return { group: nodes[0]?.group, nodes };\n });\n}\n\nfunction stackColumn(\n runs: readonly GroupRun[],\n origin: { x: number; top: number; level: number },\n m: ColumnMetrics,\n placed: Map<string, GraphLayoutPosition>,\n): GraphLayoutGroupBox[] {\n const boxes: GraphLayoutGroupBox[] = [];\n let cursor = origin.top;\n for (const { group, nodes } of runs) {\n const { pad, gap, heightOf, height } = runMetrics(group, nodes, m);\n let top = cursor + pad;\n for (const node of nodes) {\n placed.set(node.id, { x: origin.x, y: top + heightOf(node) / 2 });\n top += heightOf(node) + gap;\n }\n if (group !== undefined) {\n boxes.push({\n id: `${origin.level}:${group}`,\n group,\n level: origin.level,\n x: origin.x - m.nodeWidth / 2 - pad,\n y: cursor,\n width: m.nodeWidth + 2 * pad,\n height,\n });\n }\n cursor += height + m.rowGap;\n }\n return boxes;\n}\n\nfunction meanY(ids: readonly string[], placed: ReadonlyMap<string, GraphLayoutPosition>): number {\n if (ids.length === 0) return Number.POSITIVE_INFINITY;\n return ids.reduce((sum, id) => sum + mustGet(placed, id, \"position\").y, 0) / ids.length;\n}\n\n/**\n * Levels in the order they are first laid out — the one nearest 0, then\n * outward on each side — each paired with the already-placed adjacent level\n * its nodes are ordered against.\n */\nfunction placementOrder(levels: readonly number[]): [level: number, reference: number | undefined][] {\n const anchor = levels.reduce((best, level) => (Math.abs(level) < Math.abs(best) ? level : best));\n const outward = (side: readonly number[]) =>\n side.map((level, index): [number, number] => [level, side[index - 1] ?? anchor]);\n return [\n [anchor, undefined],\n ...outward(levels.filter((level) => level > anchor)),\n ...outward(levels.filter((level) => level < anchor).reverse()),\n ];\n}\n\n/** Orders one column by the mean y of its nodes' neighbours in the `references` levels, and stacks it. */\nfunction placeColumn(level: number, references: readonly number[], ctx: ColumnsContext, into: Arrangement): void {\n const runs = orderColumn(mustGet(into.order, level, \"column\"), ctx.groupRank, (node) =>\n meanY(\n mustGet(ctx.neighbours, node.id, \"neighbours\").filter((id) =>\n references.includes(mustGet(ctx.levelOf, id, \"level\")),\n ),\n into.placed,\n ),\n );\n into.order.set(\n level,\n runs.flatMap((run) => run.nodes),\n );\n into.boxes.set(level, stackColumn(runs, ctx.origin(level), ctx.m, into.placed));\n}\n\n/** Pairs of edges between adjacent columns that cross, taking each edge as a straight line. */\nfunction countCrossings(ctx: ColumnsContext, placed: ReadonlyMap<string, GraphLayoutPosition>): number {\n const spans = new Map<number, [lower: number, upper: number][]>();\n for (const edge of ctx.edges) {\n const levels = [mustGet(ctx.levelOf, edge.from, \"level\"), mustGet(ctx.levelOf, edge.to, \"level\")] as const;\n const [lower, upper] = levels[0] < levels[1] ? [edge.from, edge.to] : [edge.to, edge.from];\n const index = ctx.levels.indexOf(Math.min(...levels));\n if (levels[0] === levels[1] || ctx.levels[index + 1] !== Math.max(...levels)) continue;\n const span: [number, number] = [mustGet(placed, lower, \"position\").y, mustGet(placed, upper, \"position\").y];\n spans.set(index, [...(spans.get(index) ?? []), span]);\n }\n let crossings = 0;\n for (const gap of spans.values()) {\n gap.forEach((a, index) => {\n crossings += gap.slice(index + 1).filter((b) => (a[0] - b[0]) * (a[1] - b[1]) < 0).length;\n });\n }\n return crossings;\n}\n\n/** A copy of `from` with every column re-ordered against both its neighbours, in the order given. */\nfunction sweep(from: Arrangement, sequence: readonly number[], ctx: ColumnsContext): Arrangement {\n const next: Arrangement = { order: new Map(from.order), placed: new Map(from.placed), boxes: new Map(from.boxes) };\n for (const level of sequence) {\n const index = ctx.levels.indexOf(level);\n const adjacent = [ctx.levels[index - 1], ctx.levels[index + 1]].filter((other): other is number => other !== undefined);\n placeColumn(level, adjacent, ctx, next);\n }\n return next;\n}\n\n/**\n * Orders the columns. The first pass places the level nearest 0 and works\n * outward, ordering each column against the one just placed. Then up to\n * `sweeps` passes re-order every column against both its neighbours, inward\n * then outward, and the arrangement with the fewest crossings is kept: the\n * first pass's unless a later one strictly beats it.\n */\nfunction arrange(columns: ReadonlyMap<number, ColumnsLayoutNode[]>, ctx: ColumnsContext): Arrangement {\n let current: Arrangement = { order: new Map(columns), placed: new Map(), boxes: new Map() };\n for (const [level, reference] of placementOrder(ctx.levels)) {\n placeColumn(level, reference === undefined ? [] : [reference], ctx, current);\n }\n let best = current;\n let fewest = countCrossings(ctx, best.placed);\n for (let pass = 0; pass < ctx.m.sweeps && fewest > 0; pass++) {\n current = sweep(current, pass % 2 === 0 ? [...ctx.levels].reverse() : ctx.levels, ctx);\n const crossings = countCrossings(ctx, current.placed);\n if (crossings < fewest) {\n best = current;\n fewest = crossings;\n }\n }\n return best;\n}\n\n/**\n * Places nodes in left-to-right columns by `level` (the lowest level first),\n * stacking each column top to bottom and centring it against the tallest one.\n *\n * Within a column, nodes of one group are contiguous, groups appearing in the\n * order they first occur in `nodes`. Inside a group nodes are ordered by the\n * mean y of their neighbours in the adjacent columns, so edges between columns\n * cross less (see `arrange`); nodes with no neighbour there go last, and ties\n * keep input order.\n *\n * Returns node centres plus one rectangle per (column, group). Extra width is\n * reserved on each side for the loops `routeEdges` draws outside the first and\n * last columns.\n */\nexport function columnsLayout(\n nodes: readonly ColumnsLayoutNode[],\n edges: readonly GraphLayoutEdge[],\n options: ColumnsLayoutOptions = {},\n): ColumnsLayoutResult {\n const m = columnMetrics(options);\n const { columns, groupRank } = indexColumns(nodes);\n const neighbours = indexNeighbours(nodes, edges);\n const levels = [...columns.keys()].sort((a, b) => a - b);\n if (levels.length === 0) {\n return { width: 2 * m.padding, height: 2 * m.padding, positions: {}, groups: [] };\n }\n\n const minLevel = Math.min(...levels);\n const tallest = Math.max(...[...columns.values()].map((column) => columnHeight(column, m)));\n const inset = m.padding + (nodes.some((node) => node.group !== undefined) ? m.groupPadding : 0);\n const { placed, boxes } = arrange(columns, {\n m,\n levels,\n levelOf: new Map(nodes.map((node) => [node.id, node.level])),\n groupRank,\n neighbours,\n edges,\n origin: (level) => ({\n x: inset + m.nodeWidth / 2 + (level - minLevel) * (m.nodeWidth + m.columnGap),\n top: m.padding + (tallest - columnHeight(mustGet(columns, level, \"column\"), m)) / 2,\n level,\n }),\n });\n\n const nodeHeights = Object.fromEntries(nodes.filter((node) => node.compact).map((node) => [node.id, m.compactNodeHeight]));\n const room = loopOverhang(edges, Object.fromEntries(placed), { ...m, nodeHeights });\n const span = (Math.max(...levels) - minLevel) * (m.nodeWidth + m.columnGap) + m.nodeWidth;\n return {\n width: room.left + 2 * inset + span + room.right,\n height: tallest + 2 * m.padding,\n positions: Object.fromEntries([...placed].map(([id, { x, y }]) => [id, { x: x + room.left, y }])),\n groups: [...boxes.values()]\n .flat()\n .map((box) => ({ ...box, x: box.x + room.left }))\n .sort((a, b) => a.level - b.level || a.y - b.y),\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"graph-columns-layout.js","sources":["../../src/data/graph-columns-layout.ts"],"sourcesContent":["// Level-column layout for GraphDiagram: pure math, no React. Split from\n// graph-layout.ts, which owns the shared types and `ringLayout`.\n\nimport { loopOverhang } from \"./graph-edge-routing\";\nimport type { GraphLayoutEdge, GraphLayoutNode, GraphLayoutPosition, GraphLayoutResult } from \"./graph-layout\";\n\nexport interface ColumnsLayoutNode extends GraphLayoutNode {\n /** Column of the node. May be negative: the lowest level is the first column. */\n level: number;\n /** Nodes sharing a group stay contiguous within their column and are boxed together. */\n group?: string;\n /** Drawn at `compactNodeHeight`; a group of only compact nodes stacks them at the compact gap and padding. */\n compact?: boolean;\n}\n\n/** The most members a group shows at once in a column; a group with more scrolls them in a window this many rows tall. */\nexport const GROUP_MEMBER_WINDOW = 10;\n/** Height of the \"rows a–b of n\" footer under a windowed group's rows. */\nconst GROUP_WINDOW_FOOTER_HEIGHT = 18;\n\nexport interface ColumnsLayoutOptions {\n /** Group ids drawn as records with a header and flush compact rows. */\n records?: readonly string[];\n /**\n * Group ids drawn as their header alone, `recordHeaderHeight` tall. Each holds one node per column it\n * is drawn in, the stand-in its edges attach to, placed at the header's middle.\n */\n collapsed?: readonly string[];\n /** Height of a record's header in px. Defaults to 24. */\n recordHeaderHeight?: number;\n /** Node bounding-box width in px. Defaults to 168. */\n nodeWidth?: number;\n /** Node bounding-box height in px. Defaults to 60. */\n nodeHeight?: number;\n /** Horizontal gap between the node boxes of adjacent columns in px. Defaults to 96. */\n columnGap?: number;\n /** Vertical gap between stacked nodes, and between group rectangles, in px. Defaults to 20. */\n rowGap?: number;\n /** Space between a group's nodes and its rectangle in px. Defaults to 12. */\n groupPadding?: number;\n /** Height of a compact node in px. Defaults to 24. */\n compactNodeHeight?: number;\n /** Vertical gap between the nodes of a group of only compact nodes in px. Defaults to 4. */\n compactRowGap?: number;\n /** Space between a group of only compact nodes and its rectangle in px. Defaults to 8. */\n compactGroupPadding?: number;\n /** Space reserved around the columns before the viewport edge. Defaults to 24. */\n padding?: number;\n /**\n * Re-ordering passes over all columns after the first placement, alternating direction; a pass\n * is kept only if it leaves fewer crossings. Defaults to 4. 0 keeps the first placement.\n */\n sweeps?: number;\n}\n\nexport interface GraphLayoutGroupBox {\n /** `${level}:${group}` — a group spanning several columns gets one rectangle per column. */\n id: string;\n group: string;\n level: number;\n /** Top-left corner, in the same coordinate space as the layout's `width`/`height`. */\n x: number;\n y: number;\n width: number;\n height: number;\n record: boolean;\n headerHeight: number;\n /** Set when the group holds more members in this column than `GROUP_MEMBER_WINDOW`: they scroll in a window. */\n window?: GroupMemberWindow;\n /** Drawn as its header alone: see `ColumnsLayoutOptions.collapsed`. */\n collapsed?: true;\n}\n\n/**\n * The rows a group with more members than `GROUP_MEMBER_WINDOW` shows at once. The layout places every\n * member as if the group were drawn whole; a member shown from row `start` is drawn `tops[start]` higher.\n */\nexport interface GroupMemberWindow {\n /** The members in this column, top to bottom. */\n members: string[];\n /** Each member's top, from the first member's top. */\n tops: number[];\n /** How many rows show at once. */\n rows: number;\n /** Top of the area members are drawn in, and its height. */\n top: number;\n height: number;\n /** Height of the footer below that area, inside the group's rectangle. */\n footerHeight: number;\n}\n\nexport interface ColumnsLayoutResult extends GraphLayoutResult {\n groups: GraphLayoutGroupBox[];\n}\n\ntype ColumnMetrics = Required<ColumnsLayoutOptions>;\n\ninterface GroupRun {\n group: string | undefined;\n nodes: ColumnsLayoutNode[];\n}\n\n/** One ordering of every column, stacked: the node centres and group rectangles it gives. */\ninterface Arrangement {\n order: Map<number, ColumnsLayoutNode[]>;\n placed: Map<string, GraphLayoutPosition>;\n boxes: Map<number, GraphLayoutGroupBox[]>;\n}\n\n/** What stays fixed while columns are re-ordered. */\ninterface ColumnsContext {\n m: ColumnMetrics;\n levels: number[];\n levelOf: Map<string, number>;\n groupRank: Map<string | undefined, number>;\n neighbours: Map<string, string[]>;\n edges: readonly GraphLayoutEdge[];\n /** Centre x and top y of a level's column. */\n origin: (level: number) => { x: number; top: number; level: number };\n}\n\nconst DEFAULT_NODE_WIDTH = 168;\nconst DEFAULT_NODE_HEIGHT = 60;\nconst DEFAULT_COLUMN_GAP = 96;\nconst DEFAULT_ROW_GAP = 20;\nconst DEFAULT_GROUP_PADDING = 12;\nconst DEFAULT_COMPACT_NODE_HEIGHT = 24;\nconst DEFAULT_COMPACT_ROW_GAP = 4;\nconst DEFAULT_COMPACT_GROUP_PADDING = 8;\nconst DEFAULT_PADDING = 24;\nconst DEFAULT_SWEEPS = 4;\n\nfunction mustGet<K, V>(map: ReadonlyMap<K, V>, key: K, what: string): V {\n const value = map.get(key);\n if (value === undefined) {\n throw new Error(`columnsLayout: no ${what} for \"${String(key)}\"`);\n }\n return value;\n}\n\nfunction columnMetrics(options: ColumnsLayoutOptions): ColumnMetrics {\n return {\n records: options.records ?? [],\n collapsed: options.collapsed ?? [],\n recordHeaderHeight: options.recordHeaderHeight ?? 24,\n nodeWidth: options.nodeWidth ?? DEFAULT_NODE_WIDTH,\n nodeHeight: options.nodeHeight ?? DEFAULT_NODE_HEIGHT,\n columnGap: options.columnGap ?? DEFAULT_COLUMN_GAP,\n rowGap: options.rowGap ?? DEFAULT_ROW_GAP,\n groupPadding: options.groupPadding ?? DEFAULT_GROUP_PADDING,\n compactNodeHeight: options.compactNodeHeight ?? DEFAULT_COMPACT_NODE_HEIGHT,\n compactRowGap: options.compactRowGap ?? DEFAULT_COMPACT_ROW_GAP,\n compactGroupPadding: options.compactGroupPadding ?? DEFAULT_COMPACT_GROUP_PADDING,\n padding: options.padding ?? DEFAULT_PADDING,\n sweeps: options.sweeps ?? DEFAULT_SWEEPS,\n };\n}\n\nfunction indexColumns(nodes: readonly ColumnsLayoutNode[]) {\n const columns = new Map<number, ColumnsLayoutNode[]>();\n const groupRank = new Map<string | undefined, number>();\n for (const node of nodes) {\n if (!Number.isFinite(node.level)) {\n throw new Error(`columnsLayout: node \"${node.id}\" has no finite level (got ${node.level})`);\n }\n if (!groupRank.has(node.group)) groupRank.set(node.group, groupRank.size);\n const column = columns.get(node.level);\n if (column) {\n column.push(node);\n } else {\n columns.set(node.level, [node]);\n }\n }\n return { columns, groupRank };\n}\n\nfunction indexNeighbours(\n nodes: readonly ColumnsLayoutNode[],\n edges: readonly GraphLayoutEdge[],\n): Map<string, string[]> {\n const neighbours = new Map<string, string[]>(nodes.map((node) => [node.id, []]));\n for (const edge of edges) {\n const from = neighbours.get(edge.from);\n const to = neighbours.get(edge.to);\n if (!from || !to) {\n const missing = from ? edge.to : edge.from;\n throw new Error(`columnsLayout: edge \"${edge.id}\" references unknown node \"${missing}\"`);\n }\n from.push(edge.to);\n to.push(edge.from);\n }\n return neighbours;\n}\n\ninterface RunMetrics {\n pad: number;\n gap: number;\n heightOf: (node: ColumnsLayoutNode) => number;\n /** Room above the first node inside the run's rectangle: a record's header. */\n header: number;\n height: number;\n /** The height members are drawn in, when there are more of them than the window holds. */\n view?: number;\n collapsed: boolean;\n}\n\nfunction collapsedRun(group: string, nodes: readonly ColumnsLayoutNode[], m: ColumnMetrics): RunMetrics {\n if (nodes.length !== 1) throw new Error(`columnsLayout: collapsed group \"${group}\" holds ${nodes.length} nodes in a column; it draws one stand-in`);\n return { pad: 0, gap: 0, heightOf: () => m.recordHeaderHeight, header: 0, height: m.recordHeaderHeight, collapsed: true };\n}\n\n/**\n * How a run of nodes stacks: each node's height, the gap between them and the padding inside the run's\n * rectangle. A grouped run of only compact nodes takes the compact gap and padding; any other run the\n * regular ones, whatever its order, so a column's height does not depend on how it is ordered. A group\n * of more than `GROUP_MEMBER_WINDOW` members is only as tall as that many of its tallest rows, over a footer.\n */\nfunction runMetrics(group: string | undefined, nodes: readonly ColumnsLayoutNode[], m: ColumnMetrics, record: boolean): RunMetrics {\n if (group !== undefined && m.collapsed.includes(group)) return collapsedRun(group, nodes, m);\n let stack: Omit<RunMetrics, \"height\" | \"collapsed\">;\n if (record) {\n const regular = nodes.find((node) => !node.compact);\n if (regular) throw new Error(`columnsLayout: record group \"${group}\" holds non-compact node \"${regular.id}\"`);\n stack = { pad: 0, gap: 0, heightOf: () => m.compactNodeHeight, header: m.recordHeaderHeight };\n } else {\n const compact = group !== undefined && nodes.every((node) => node.compact === true);\n const heightOf = (node: ColumnsLayoutNode) => (node.compact ? m.compactNodeHeight : m.nodeHeight);\n stack = { pad: group === undefined ? 0 : compact ? m.compactGroupPadding : m.groupPadding, gap: compact ? m.compactRowGap : m.rowGap, heightOf, header: 0 };\n }\n const chrome = stack.header + 2 * stack.pad;\n if (group === undefined || nodes.length <= GROUP_MEMBER_WINDOW) {\n const stacked = nodes.reduce((sum, node) => sum + stack.heightOf(node), 0) + (nodes.length - 1) * stack.gap;\n return { ...stack, height: chrome + stacked, collapsed: false };\n }\n const view = GROUP_MEMBER_WINDOW * Math.max(...nodes.map(stack.heightOf)) + (GROUP_MEMBER_WINDOW - 1) * stack.gap;\n return { ...stack, view, height: chrome + view + GROUP_WINDOW_FOOTER_HEIGHT, collapsed: false };\n}\n\nfunction columnHeight(column: readonly ColumnsLayoutNode[], m: ColumnMetrics): number {\n const runs = new Map<string | undefined, ColumnsLayoutNode[]>();\n for (const node of column) runs.set(node.group, [...(runs.get(node.group) ?? []), node]);\n const stacked = [...runs].reduce((sum, [group, nodes]) => sum + runMetrics(group, nodes, m, group !== undefined && m.records.includes(group)).height, 0);\n return stacked + (runs.size - 1) * m.rowGap;\n}\n\n/** Groups in order of first appearance; within a group by ascending weight, the column's order on ties. */\nfunction orderColumn(\n column: readonly ColumnsLayoutNode[],\n groupRank: ReadonlyMap<string | undefined, number>,\n weight: (node: ColumnsLayoutNode) => number,\n): GroupRun[] {\n const runs = new Map<number, { node: ColumnsLayoutNode; index: number; weight: number }[]>();\n column.forEach((node, index) => {\n const rank = mustGet(groupRank, node.group, \"group rank\");\n const entry = { node, index, weight: weight(node) };\n const run = runs.get(rank);\n if (run) {\n run.push(entry);\n } else {\n runs.set(rank, [entry]);\n }\n });\n return [...runs.entries()]\n .sort(([a], [b]) => a - b)\n .map(([, run]) => {\n // Infinity - Infinity is NaN, which falls through to the column-order tiebreak.\n const nodes = run.sort((a, b) => a.weight - b.weight || a.index - b.index).map((entry) => entry.node);\n return { group: nodes[0]?.group, nodes };\n });\n}\n\nfunction stackColumn(\n runs: readonly GroupRun[],\n origin: { x: number; top: number; level: number },\n m: ColumnMetrics,\n placed: Map<string, GraphLayoutPosition>,\n): GraphLayoutGroupBox[] {\n const boxes: GraphLayoutGroupBox[] = [];\n let cursor = origin.top;\n for (const { group, nodes } of runs) {\n const record = group !== undefined && m.records.includes(group);\n const run = runMetrics(group, nodes, m, record);\n const first = cursor + run.pad + run.header;\n const tops: number[] = [];\n let top = first;\n for (const node of nodes) {\n tops.push(top - first);\n placed.set(node.id, { x: origin.x, y: top + run.heightOf(node) / 2 });\n top += run.heightOf(node) + run.gap;\n }\n if (group !== undefined) {\n const memberWindow: GroupMemberWindow | undefined = run.view === undefined ? undefined\n : { members: nodes.map((node) => node.id), tops, rows: GROUP_MEMBER_WINDOW, top: first, height: run.view, footerHeight: GROUP_WINDOW_FOOTER_HEIGHT };\n boxes.push({\n id: `${origin.level}:${group}`,\n group,\n level: origin.level,\n x: origin.x - m.nodeWidth / 2 - run.pad,\n y: cursor,\n width: m.nodeWidth + 2 * run.pad,\n height: run.height,\n record,\n headerHeight: run.collapsed ? m.recordHeaderHeight : run.header,\n ...(memberWindow ? { window: memberWindow } : {}),\n ...(run.collapsed ? { collapsed: true as const } : {}),\n });\n }\n cursor += run.height + m.rowGap;\n }\n return boxes;\n}\n\nfunction meanY(ids: readonly string[], placed: ReadonlyMap<string, GraphLayoutPosition>): number {\n if (ids.length === 0) return Number.POSITIVE_INFINITY;\n return ids.reduce((sum, id) => sum + mustGet(placed, id, \"position\").y, 0) / ids.length;\n}\n\n/**\n * Levels in the order they are first laid out — the one nearest 0, then\n * outward on each side — each paired with the already-placed adjacent level\n * its nodes are ordered against.\n */\nfunction placementOrder(levels: readonly number[]): [level: number, reference: number | undefined][] {\n const anchor = levels.reduce((best, level) => (Math.abs(level) < Math.abs(best) ? level : best));\n const outward = (side: readonly number[]) =>\n side.map((level, index): [number, number] => [level, side[index - 1] ?? anchor]);\n return [\n [anchor, undefined],\n ...outward(levels.filter((level) => level > anchor)),\n ...outward(levels.filter((level) => level < anchor).reverse()),\n ];\n}\n\n/** Orders one column by the mean y of its nodes' neighbours in the `references` levels, and stacks it. */\nfunction placeColumn(level: number, references: readonly number[], ctx: ColumnsContext, into: Arrangement): void {\n const runs = orderColumn(mustGet(into.order, level, \"column\"), ctx.groupRank, (node) =>\n meanY(\n mustGet(ctx.neighbours, node.id, \"neighbours\").filter((id) =>\n references.includes(mustGet(ctx.levelOf, id, \"level\")),\n ),\n into.placed,\n ),\n );\n into.order.set(\n level,\n runs.flatMap((run) => run.nodes),\n );\n into.boxes.set(level, stackColumn(runs, ctx.origin(level), ctx.m, into.placed));\n}\n\n/** Pairs of edges between adjacent columns that cross, taking each edge as a straight line. */\nfunction countCrossings(ctx: ColumnsContext, placed: ReadonlyMap<string, GraphLayoutPosition>): number {\n const spans = new Map<number, [lower: number, upper: number][]>();\n for (const edge of ctx.edges) {\n const levels = [mustGet(ctx.levelOf, edge.from, \"level\"), mustGet(ctx.levelOf, edge.to, \"level\")] as const;\n const [lower, upper] = levels[0] < levels[1] ? [edge.from, edge.to] : [edge.to, edge.from];\n const index = ctx.levels.indexOf(Math.min(...levels));\n if (levels[0] === levels[1] || ctx.levels[index + 1] !== Math.max(...levels)) continue;\n const span: [number, number] = [mustGet(placed, lower, \"position\").y, mustGet(placed, upper, \"position\").y];\n spans.set(index, [...(spans.get(index) ?? []), span]);\n }\n let crossings = 0;\n for (const gap of spans.values()) {\n gap.forEach((a, index) => {\n crossings += gap.slice(index + 1).filter((b) => (a[0] - b[0]) * (a[1] - b[1]) < 0).length;\n });\n }\n return crossings;\n}\n\n/** A copy of `from` with every column re-ordered against both its neighbours, in the order given. */\nfunction sweep(from: Arrangement, sequence: readonly number[], ctx: ColumnsContext): Arrangement {\n const next: Arrangement = { order: new Map(from.order), placed: new Map(from.placed), boxes: new Map(from.boxes) };\n for (const level of sequence) {\n const index = ctx.levels.indexOf(level);\n const adjacent = [ctx.levels[index - 1], ctx.levels[index + 1]].filter((other): other is number => other !== undefined);\n placeColumn(level, adjacent, ctx, next);\n }\n return next;\n}\n\n/**\n * Orders the columns. The first pass places the level nearest 0 and works\n * outward, ordering each column against the one just placed. Then up to\n * `sweeps` passes re-order every column against both its neighbours, inward\n * then outward, and the arrangement with the fewest crossings is kept: the\n * first pass's unless a later one strictly beats it.\n */\nfunction arrange(columns: ReadonlyMap<number, ColumnsLayoutNode[]>, ctx: ColumnsContext): Arrangement {\n let current: Arrangement = { order: new Map(columns), placed: new Map(), boxes: new Map() };\n for (const [level, reference] of placementOrder(ctx.levels)) {\n placeColumn(level, reference === undefined ? [] : [reference], ctx, current);\n }\n let best = current;\n let fewest = countCrossings(ctx, best.placed);\n for (let pass = 0; pass < ctx.m.sweeps && fewest > 0; pass++) {\n current = sweep(current, pass % 2 === 0 ? [...ctx.levels].reverse() : ctx.levels, ctx);\n const crossings = countCrossings(ctx, current.placed);\n if (crossings < fewest) {\n best = current;\n fewest = crossings;\n }\n }\n return best;\n}\n\n/**\n * Places nodes in left-to-right columns by `level` (the lowest level first),\n * stacking each column top to bottom and centring it against the tallest one.\n *\n * Within a column, nodes of one group are contiguous, groups appearing in the\n * order they first occur in `nodes`. Inside a group nodes are ordered by the\n * mean y of their neighbours in the adjacent columns, so edges between columns\n * cross less (see `arrange`); nodes with no neighbour there go last, and ties\n * keep input order.\n *\n * Returns node centres plus one rectangle per (column, group). Extra width is\n * reserved on each side for the loops `routeEdges` draws outside the first and\n * last columns.\n */\nexport function columnsLayout(\n nodes: readonly ColumnsLayoutNode[],\n edges: readonly GraphLayoutEdge[],\n options: ColumnsLayoutOptions = {},\n): ColumnsLayoutResult {\n const m = columnMetrics(options);\n const { columns, groupRank } = indexColumns(nodes);\n const neighbours = indexNeighbours(nodes, edges);\n const levels = [...columns.keys()].sort((a, b) => a - b);\n if (levels.length === 0) {\n return { width: 2 * m.padding, height: 2 * m.padding, positions: {}, groups: [] };\n }\n\n const minLevel = Math.min(...levels);\n const tallest = Math.max(...[...columns.values()].map((column) => columnHeight(column, m)));\n const inset = m.padding + (nodes.some((node) => node.group !== undefined) ? m.groupPadding : 0);\n const { placed, boxes } = arrange(columns, {\n m,\n levels,\n levelOf: new Map(nodes.map((node) => [node.id, node.level])),\n groupRank,\n neighbours,\n edges,\n origin: (level) => ({\n x: inset + m.nodeWidth / 2 + (level - minLevel) * (m.nodeWidth + m.columnGap),\n top: m.padding + (tallest - columnHeight(mustGet(columns, level, \"column\"), m)) / 2,\n level,\n }),\n });\n\n const nodeHeights = Object.fromEntries(nodes.filter((node) => node.compact).map((node) => [node.id, m.compactNodeHeight]));\n const room = loopOverhang(edges, Object.fromEntries(placed), { ...m, nodeHeights });\n const span = (Math.max(...levels) - minLevel) * (m.nodeWidth + m.columnGap) + m.nodeWidth;\n return {\n width: room.left + 2 * inset + span + room.right,\n height: tallest + 2 * m.padding,\n positions: Object.fromEntries([...placed].map(([id, { x, y }]) => [id, { x: x + room.left, y }])),\n groups: [...boxes.values()]\n .flat()\n .map((box) => ({ ...box, x: box.x + room.left }))\n .sort((a, b) => a.level - b.level || a.y - b.y),\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
function groupName(group, id) {
|
|
4
|
+
if ((group == null ? void 0 : group.title) !== void 0) return group.title;
|
|
5
|
+
return typeof (group == null ? void 0 : group.label) === "string" ? group.label : id;
|
|
6
|
+
}
|
|
3
7
|
const DENSE_EDGE_COUNT = 32;
|
|
4
8
|
function edgeFocusOn(mode, edgeCount) {
|
|
5
9
|
return mode === "on" || mode === "auto" && edgeCount > DENSE_EDGE_COUNT;
|
|
@@ -67,6 +71,7 @@ exports.edgeFocusOn = edgeFocusOn;
|
|
|
67
71
|
exports.edgeLabelWidth = edgeLabelWidth;
|
|
68
72
|
exports.edgeName = edgeName;
|
|
69
73
|
exports.focusedEdgeIds = focusedEdgeIds;
|
|
74
|
+
exports.groupName = groupName;
|
|
70
75
|
exports.hoverHandlers = hoverHandlers;
|
|
71
76
|
exports.nodeName = nodeName;
|
|
72
77
|
exports.requireEntry = requireEntry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-diagram-model.cjs","sources":["../../src/data/graph-diagram-model.ts"],"sourcesContent":["import type { KeyboardEvent, ReactNode } from \"react\";\nimport type { BadgeTone } from \"./Badge\";\nimport type { GraphLayoutPosition } from \"./graph-layout\";\n\nexport interface GraphDiagramNode {\n id: string;\n label: ReactNode;\n /** Drawn before the label, on the same line. Give the icon its own `title` so it names itself. */\n icon?: ReactNode;\n detail?: ReactNode;\n badge?: ReactNode;\n tone?: BadgeTone;\n shape?: \"pill\" | \"rect\";\n /** Accessible name for the node, when `label` alone isn't enough. */\n ariaLabel?: string;\n /** Full text for the node, shown as its native tooltip. */\n title?: string;\n /** Column of the node. Required by the \"columns\" layout; may be negative. */\n level?: number;\n /** \"columns\" layout: nodes sharing a group are stacked together and boxed. */\n group?: string;\n /**\n * \"columns\" layout: \"compact\" draws the node on one short line at `compactNodeHeight`, its icon and\n * label only, for a member its group box already names the owner of. Defaults to \"regular\".\n */\n size?: \"regular\" | \"compact\";\n /** Dimmed styling, for a node that leads nowhere further: external, unresolved, not expandable. */\n muted?: boolean;\n /** Neighbours not drawn yet. Renders a `+N` control on the node's outer side. */\n expandCount?: number;\n}\n\nexport interface GraphDiagramEdge {\n id: string;\n from: string;\n to: string;\n /** Short text shown in a pill on the edge. */\n label?: string;\n /** Drawn in the pill before the label. An edge with an icon and no label still gets a pill. */\n icon?: ReactNode;\n /** What `icon` says, in words. It precedes the label in the edge's accessible name. */\n iconLabel?: string;\n /** Full text for the edge, shown as its native tooltip. */\n title?: string;\n tone?: BadgeTone;\n dashed?: boolean;\n}\n\nexport interface GraphDiagramGroup {\n id: string;\n label: ReactNode;\n /** Full text for the group, shown as its caption's native tooltip: what a shortened `label` stands for. */\n title?: string;\n}\n\nexport type GraphDiagramLayout = \"ring\" | \"columns\";\n\n/**\n * Whether edges compete for attention or take turns. With focus on, an edge\n * shows its label pill only while it is in focus — selected, hovered, or\n * attached to the selected or hovered node — and while anything is in focus\n * the other edges fade. \"auto\" turns it on past `DENSE_EDGE_COUNT` edges.\n */\nexport type GraphDiagramEdgeFocus = \"auto\" | \"on\" | \"off\";\n\n/** The most edges a diagram draws with every pill showing under `edgeFocus=\"auto\"`. */\nexport const DENSE_EDGE_COUNT = 32;\n\nexport function edgeFocusOn(mode: GraphDiagramEdgeFocus, edgeCount: number): boolean {\n return mode === \"on\" || (mode === \"auto\" && edgeCount > DENSE_EDGE_COUNT);\n}\n\n/** The edges in focus: the named ones, and every edge attached to a named node. */\nexport function focusedEdgeIds(\n edges: readonly { id: string; from: string; to: string }[],\n focus: { nodes: readonly (string | undefined)[]; edges: readonly (string | undefined)[] },\n): Set<string> {\n return new Set(\n edges\n .filter((edge) => focus.edges.includes(edge.id) || focus.nodes.includes(edge.from) || focus.nodes.includes(edge.to))\n .map((edge) => edge.id),\n );\n}\n\n/** Maps a point in layout coordinates to the CSS offsets of the stage it is drawn on. */\nexport type GraphPlacer = (point: GraphLayoutPosition) => { left: number | string; top: number | string };\n\nexport const TONE_TEXT_CLASS: Record<BadgeTone, string> = {\n neutral: \"text-muted-foreground\",\n success: \"text-emerald-600 dark:text-emerald-400\",\n danger: \"text-red-600 dark:text-red-400\",\n warning: \"text-amber-600 dark:text-amber-400\",\n info: \"text-sky-600 dark:text-sky-400\",\n};\n\nexport const TONE_NODE_CLASS: Record<BadgeTone, string> = {\n neutral: \"border-border bg-card text-foreground\",\n success: \"border-emerald-500/50 bg-emerald-500/10 text-foreground\",\n danger: \"border-red-500/50 bg-red-500/10 text-foreground\",\n warning: \"border-amber-500/50 bg-amber-500/10 text-foreground\",\n info: \"border-sky-500/50 bg-sky-500/10 text-foreground\",\n};\n\n/** The node's accessible name: its `ariaLabel`, else a plain-text label, else its id. */\nexport function nodeName(node: GraphDiagramNode): string {\n if (node.ariaLabel !== undefined) return node.ariaLabel;\n return typeof node.label === \"string\" ? node.label : node.id;\n}\n\n/** A selectable edge's accessible name: `<from> to <to>`, then what its pill shows, in words. */\nexport function edgeName(edge: GraphDiagramEdge, names: Record<string, string>): string {\n const ends = `${requireEntry(names, edge.from, \"node name\")} to ${requireEntry(names, edge.to, \"node name\")}`;\n const pill = [edge.iconLabel, edge.label].filter(Boolean).join(\" \");\n return pill === \"\" ? ends : `${ends}: ${pill}`;\n}\n\n// A pill is not measured: its size is estimated from its content, at the mean\n// glyph advance of its 10px medium label plus its padding and border.\nconst LABEL_CHAR_WIDTH = 5.4;\nconst LABEL_CHROME = 14;\n// The pill's icon slot (`text-sm`, so a 1em icon is 14px) and its `gap-1` to the text.\nconst LABEL_ICON_SIZE = 14;\nconst LABEL_ICON_GAP = 4;\n\n/** Estimated px width of an edge's pill. An icon counts as a fixed width, whatever it draws. */\nexport function edgeLabelWidth(edge: GraphDiagramEdge): number {\n const icon = edge.icon == null ? 0 : LABEL_ICON_SIZE + (edge.label ? LABEL_ICON_GAP : 0);\n return (edge.label?.length ?? 0) * LABEL_CHAR_WIDTH + icon + LABEL_CHROME;\n}\n\nexport function requireEntry<T>(entries: Record<string, T>, id: string, what: string): T {\n const entry = entries[id];\n if (entry === undefined) {\n throw new Error(`GraphDiagram: no ${what} computed for \"${id}\"`);\n }\n return entry;\n}\n\n/** Props that report an element as hovered while the pointer is over it or the keyboard focus is inside it. */\nexport function hoverHandlers(id: string, onHover: (id: string | undefined) => void) {\n const enter = () => onHover(id);\n const leave = () => onHover(undefined);\n return { onMouseEnter: enter, onMouseLeave: leave, onFocus: enter, onBlur: leave };\n}\n\n/** Enter and Space activate a focused `role=\"button\"` element, as they do a native button. */\nexport function activateOnKey(event: KeyboardEvent<Element>, activate: () => void): void {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n activate();\n }\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"graph-diagram-model.cjs","sources":["../../src/data/graph-diagram-model.ts"],"sourcesContent":["import type { KeyboardEvent, ReactNode } from \"react\";\nimport type { BadgeTone } from \"./Badge\";\nimport type { GraphLayoutPosition } from \"./graph-layout\";\n\nexport interface GraphDiagramNode {\n id: string;\n label: ReactNode;\n /** Drawn before the label, on the same line. Give the icon its own `title` so it names itself. */\n icon?: ReactNode;\n /** Right-aligned content on a record row, such as a column type. */\n aside?: ReactNode;\n /**\n * A small mark at the end of the node's line, such as how it is accessed (see `AccessMark`). It is\n * hidden from assistive technology, so the node's accessible name stays its own: give it a title.\n */\n mark?: ReactNode;\n detail?: ReactNode;\n badge?: ReactNode;\n tone?: BadgeTone;\n shape?: \"pill\" | \"rect\";\n /** Accessible name for the node, when `label` alone isn't enough. */\n ariaLabel?: string;\n /** Full text for the node, shown as its native tooltip. */\n title?: string;\n /** Column of the node. Required by the \"columns\" layout; may be negative. */\n level?: number;\n /** \"columns\" layout: nodes sharing a group are stacked together and boxed. */\n group?: string;\n /**\n * \"columns\" layout: \"compact\" draws the node on one short line at `compactNodeHeight`, its icon and\n * label only, for a member its group box already names the owner of. Defaults to \"regular\".\n */\n size?: \"regular\" | \"compact\";\n /** Dimmed styling, for a node that leads nowhere further: external, unresolved, not expandable. */\n muted?: boolean;\n /** Neighbours not drawn yet. Renders a `+N` control on the node's outer side. */\n expandCount?: number;\n}\n\nexport interface GraphDiagramEdge {\n id: string;\n from: string;\n to: string;\n /** Short text shown in a pill on the edge. */\n label?: string;\n /** Drawn in the pill before the label. An edge with an icon and no label still gets a pill. */\n icon?: ReactNode;\n /** What `icon` says, in words. It precedes the label in the edge's accessible name. */\n iconLabel?: string;\n /** Full text for the edge, shown as its native tooltip. */\n title?: string;\n tone?: BadgeTone;\n dashed?: boolean;\n}\n\nexport interface GraphDiagramGroup {\n id: string;\n label: ReactNode;\n /** A record draws a header and flush compact member rows. Defaults to a box. */\n variant?: \"box\" | \"record\";\n /** Full text for the group, shown as its caption's native tooltip: what a shortened `label` stands for. */\n title?: string;\n /**\n * \"columns\" layout: whether the group is drawn as its header alone. Set (true or false) to make it\n * collapsible: its header then shows a chevron calling `onGroupToggle`. A collapsed group holds one\n * node, the stand-in its edges attach to, which is not drawn: its header stands for it.\n */\n collapsed?: boolean;\n /** Right-aligned content in the group's header, such as what a collapsed group holds. */\n aside?: ReactNode;\n}\n\n/** The group's name in words: its title, else a plain-text label, else its id. */\nexport function groupName(group: GraphDiagramGroup | undefined, id: string): string {\n if (group?.title !== undefined) return group.title;\n return typeof group?.label === \"string\" ? group.label : id;\n}\n\nexport type GraphDiagramLayout = \"ring\" | \"columns\";\n\n/**\n * Whether edges compete for attention or take turns. With focus on, an edge\n * shows its label pill only while it is in focus — selected, hovered, or\n * attached to the selected or hovered node — and while anything is in focus\n * the other edges fade. \"auto\" turns it on past `DENSE_EDGE_COUNT` edges.\n */\nexport type GraphDiagramEdgeFocus = \"auto\" | \"on\" | \"off\";\n\n/** The most edges a diagram draws with every pill showing under `edgeFocus=\"auto\"`. */\nexport const DENSE_EDGE_COUNT = 32;\n\nexport function edgeFocusOn(mode: GraphDiagramEdgeFocus, edgeCount: number): boolean {\n return mode === \"on\" || (mode === \"auto\" && edgeCount > DENSE_EDGE_COUNT);\n}\n\n/** The edges in focus: the named ones, and every edge attached to a named node. */\nexport function focusedEdgeIds(\n edges: readonly { id: string; from: string; to: string }[],\n focus: { nodes: readonly (string | undefined)[]; edges: readonly (string | undefined)[] },\n): Set<string> {\n return new Set(\n edges\n .filter((edge) => focus.edges.includes(edge.id) || focus.nodes.includes(edge.from) || focus.nodes.includes(edge.to))\n .map((edge) => edge.id),\n );\n}\n\n/** Maps a point in layout coordinates to the CSS offsets of the stage it is drawn on. */\nexport type GraphPlacer = (point: GraphLayoutPosition) => { left: number | string; top: number | string };\n\nexport const TONE_TEXT_CLASS: Record<BadgeTone, string> = {\n neutral: \"text-muted-foreground\",\n success: \"text-emerald-600 dark:text-emerald-400\",\n danger: \"text-red-600 dark:text-red-400\",\n warning: \"text-amber-600 dark:text-amber-400\",\n info: \"text-sky-600 dark:text-sky-400\",\n};\n\nexport const TONE_NODE_CLASS: Record<BadgeTone, string> = {\n neutral: \"border-border bg-card text-foreground\",\n success: \"border-emerald-500/50 bg-emerald-500/10 text-foreground\",\n danger: \"border-red-500/50 bg-red-500/10 text-foreground\",\n warning: \"border-amber-500/50 bg-amber-500/10 text-foreground\",\n info: \"border-sky-500/50 bg-sky-500/10 text-foreground\",\n};\n\n/** The node's accessible name: its `ariaLabel`, else a plain-text label, else its id. */\nexport function nodeName(node: GraphDiagramNode): string {\n if (node.ariaLabel !== undefined) return node.ariaLabel;\n return typeof node.label === \"string\" ? node.label : node.id;\n}\n\n/** A selectable edge's accessible name: `<from> to <to>`, then what its pill shows, in words. */\nexport function edgeName(edge: GraphDiagramEdge, names: Record<string, string>): string {\n const ends = `${requireEntry(names, edge.from, \"node name\")} to ${requireEntry(names, edge.to, \"node name\")}`;\n const pill = [edge.iconLabel, edge.label].filter(Boolean).join(\" \");\n return pill === \"\" ? ends : `${ends}: ${pill}`;\n}\n\n// A pill is not measured: its size is estimated from its content, at the mean\n// glyph advance of its 10px medium label plus its padding and border.\nconst LABEL_CHAR_WIDTH = 5.4;\nconst LABEL_CHROME = 14;\n// The pill's icon slot (`text-sm`, so a 1em icon is 14px) and its `gap-1` to the text.\nconst LABEL_ICON_SIZE = 14;\nconst LABEL_ICON_GAP = 4;\n\n/** Estimated px width of an edge's pill. An icon counts as a fixed width, whatever it draws. */\nexport function edgeLabelWidth(edge: GraphDiagramEdge): number {\n const icon = edge.icon == null ? 0 : LABEL_ICON_SIZE + (edge.label ? LABEL_ICON_GAP : 0);\n return (edge.label?.length ?? 0) * LABEL_CHAR_WIDTH + icon + LABEL_CHROME;\n}\n\nexport function requireEntry<T>(entries: Record<string, T>, id: string, what: string): T {\n const entry = entries[id];\n if (entry === undefined) {\n throw new Error(`GraphDiagram: no ${what} computed for \"${id}\"`);\n }\n return entry;\n}\n\n/** Props that report an element as hovered while the pointer is over it or the keyboard focus is inside it. */\nexport function hoverHandlers(id: string, onHover: (id: string | undefined) => void) {\n const enter = () => onHover(id);\n const leave = () => onHover(undefined);\n return { onMouseEnter: enter, onMouseLeave: leave, onFocus: enter, onBlur: leave };\n}\n\n/** Enter and Space activate a focused `role=\"button\"` element, as they do a native button. */\nexport function activateOnKey(event: KeyboardEvent<Element>, activate: () => void): void {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n activate();\n }\n}\n"],"names":[],"mappings":";;AAyEO,SAAS,UAAU,OAAsC,IAAoB;AAClF,OAAI,+BAAO,WAAU,OAAW,QAAO,MAAM;AAC7C,SAAO,QAAO,+BAAO,WAAU,WAAW,MAAM,QAAQ;AAC1D;AAaO,MAAM,mBAAmB;AAEzB,SAAS,YAAY,MAA6B,WAA4B;AACnF,SAAO,SAAS,QAAS,SAAS,UAAU,YAAY;AAC1D;AAGO,SAAS,eACd,OACA,OACa;AACb,SAAO,IAAI;AAAA,IACT,MACG,OAAO,CAAC,SAAS,MAAM,MAAM,SAAS,KAAK,EAAE,KAAK,MAAM,MAAM,SAAS,KAAK,IAAI,KAAK,MAAM,MAAM,SAAS,KAAK,EAAE,CAAC,EAClH,IAAI,CAAC,SAAS,KAAK,EAAE;AAAA,EAAA;AAE5B;AAKO,MAAM,kBAA6C;AAAA,EACxD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AACR;AAEO,MAAM,kBAA6C;AAAA,EACxD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AACR;AAGO,SAAS,SAAS,MAAgC;AACvD,MAAI,KAAK,cAAc,OAAW,QAAO,KAAK;AAC9C,SAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAK;AAC5D;AAGO,SAAS,SAAS,MAAwB,OAAuC;AACtF,QAAM,OAAO,GAAG,aAAa,OAAO,KAAK,MAAM,WAAW,CAAC,OAAO,aAAa,OAAO,KAAK,IAAI,WAAW,CAAC;AAC3G,QAAM,OAAO,CAAC,KAAK,WAAW,KAAK,KAAK,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAClE,SAAO,SAAS,KAAK,OAAO,GAAG,IAAI,KAAK,IAAI;AAC9C;AAIA,MAAM,mBAAmB;AACzB,MAAM,eAAe;AAErB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AAGhB,SAAS,eAAe,MAAgC;;AAC7D,QAAM,OAAO,KAAK,QAAQ,OAAO,IAAI,mBAAmB,KAAK,QAAQ,iBAAiB;AACtF,YAAQ,UAAK,UAAL,mBAAY,WAAU,KAAK,mBAAmB,OAAO;AAC/D;AAEO,SAAS,aAAgB,SAA4B,IAAY,MAAiB;AACvF,QAAM,QAAQ,QAAQ,EAAE;AACxB,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI,MAAM,oBAAoB,IAAI,kBAAkB,EAAE,GAAG;AAAA,EACjE;AACA,SAAO;AACT;AAGO,SAAS,cAAc,IAAY,SAA2C;AACnF,QAAM,QAAQ,MAAM,QAAQ,EAAE;AAC9B,QAAM,QAAQ,MAAM,QAAQ,MAAS;AACrC,SAAO,EAAE,cAAc,OAAO,cAAc,OAAO,SAAS,OAAO,QAAQ,MAAA;AAC7E;AAGO,SAAS,cAAc,OAA+B,UAA4B;AACvF,MAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;AAC9C,UAAM,eAAA;AACN,aAAA;AAAA,EACF;AACF;;;;;;;;;;;;;"}
|
|
@@ -6,6 +6,13 @@ export interface GraphDiagramNode {
|
|
|
6
6
|
label: ReactNode;
|
|
7
7
|
/** Drawn before the label, on the same line. Give the icon its own `title` so it names itself. */
|
|
8
8
|
icon?: ReactNode;
|
|
9
|
+
/** Right-aligned content on a record row, such as a column type. */
|
|
10
|
+
aside?: ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* A small mark at the end of the node's line, such as how it is accessed (see `AccessMark`). It is
|
|
13
|
+
* hidden from assistive technology, so the node's accessible name stays its own: give it a title.
|
|
14
|
+
*/
|
|
15
|
+
mark?: ReactNode;
|
|
9
16
|
detail?: ReactNode;
|
|
10
17
|
badge?: ReactNode;
|
|
11
18
|
tone?: BadgeTone;
|
|
@@ -46,9 +53,21 @@ export interface GraphDiagramEdge {
|
|
|
46
53
|
export interface GraphDiagramGroup {
|
|
47
54
|
id: string;
|
|
48
55
|
label: ReactNode;
|
|
56
|
+
/** A record draws a header and flush compact member rows. Defaults to a box. */
|
|
57
|
+
variant?: "box" | "record";
|
|
49
58
|
/** Full text for the group, shown as its caption's native tooltip: what a shortened `label` stands for. */
|
|
50
59
|
title?: string;
|
|
60
|
+
/**
|
|
61
|
+
* "columns" layout: whether the group is drawn as its header alone. Set (true or false) to make it
|
|
62
|
+
* collapsible: its header then shows a chevron calling `onGroupToggle`. A collapsed group holds one
|
|
63
|
+
* node, the stand-in its edges attach to, which is not drawn: its header stands for it.
|
|
64
|
+
*/
|
|
65
|
+
collapsed?: boolean;
|
|
66
|
+
/** Right-aligned content in the group's header, such as what a collapsed group holds. */
|
|
67
|
+
aside?: ReactNode;
|
|
51
68
|
}
|
|
69
|
+
/** The group's name in words: its title, else a plain-text label, else its id. */
|
|
70
|
+
export declare function groupName(group: GraphDiagramGroup | undefined, id: string): string;
|
|
52
71
|
export type GraphDiagramLayout = "ring" | "columns";
|
|
53
72
|
/**
|
|
54
73
|
* Whether edges compete for attention or take turns. With focus on, an edge
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-diagram-model.d.ts","sourceRoot":"","sources":["../../src/data/graph-diagram-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,kGAAkG;IAClG,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,mGAAmG;IACnG,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,2GAA2G;IAC3G,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"graph-diagram-model.d.ts","sourceRoot":"","sources":["../../src/data/graph-diagram-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
function groupName(group, id) {
|
|
2
|
+
if ((group == null ? void 0 : group.title) !== void 0) return group.title;
|
|
3
|
+
return typeof (group == null ? void 0 : group.label) === "string" ? group.label : id;
|
|
4
|
+
}
|
|
1
5
|
const DENSE_EDGE_COUNT = 32;
|
|
2
6
|
function edgeFocusOn(mode, edgeCount) {
|
|
3
7
|
return mode === "on" || mode === "auto" && edgeCount > DENSE_EDGE_COUNT;
|
|
@@ -66,6 +70,7 @@ export {
|
|
|
66
70
|
edgeLabelWidth,
|
|
67
71
|
edgeName,
|
|
68
72
|
focusedEdgeIds,
|
|
73
|
+
groupName,
|
|
69
74
|
hoverHandlers,
|
|
70
75
|
nodeName,
|
|
71
76
|
requireEntry
|