Repository navigation
v0.9.0
Graph layout release: four new GraphCanvas layout engines (organic, orthogonal,
radial-tree, ux-navigation), a rewritten galaxy, a fixed force-clustered, and
interactive popovers.
Added
GraphCanvas—layout="organic", the first mode of the layout-engine rework and the
intended successor toforce/force-clustered. A headless, deterministic
organicLayout()(also exported): stress majorization seeded by classical MDS, with a
GTree-style overlap-removal stage that makes zero overlap andnodeMargin(as a minimum
box-to-box gap, default 24 px) hard post-conditions, and a warm-start rule under which
re-running the engine on its own output is a no-op. Nodes with explicitx/yare pinned
and held exactly. On the 31-node technology-layer benchmark, neighborhood preservation goes
from 0.34 (force) to 0.62 and scale-fitted stress from 0.151 to 0.039; 400-leaf stars,
which droveforcetoNaN, stay finite. Proof of concept: no clustering option yet, and
isolates are circle-packed rather than gridded.GraphCanvas—layout="orthogonal", the second mode of the rework and the intended
successor toux-navigation. A headless, deterministicorthogonalLayout()(also exported)
places nodes on a cell grid and routes every edge as an axis-parallel polyline over a lattice
of routing lanes: routes never pass through a node, never share a segment with another route,
and are returned boundary to boundary, index-aligned with the edges. GraphCanvas renders them
in place of bezier paths (edges touching a dragged node fall back to a live bezier).- Rows come from the structural forest when
isStructuralEdgeis given, otherwise from the
BFS layering with the fewest crossings; rows are crossing-minimized. - Honors the ux-navigation hooks: with
isPageNode, a page's views fan out to its right in
the same row;isNavigationRouteedges are routed last, around everything else. - Re-running on its own output keeps every node's cell; pinned nodes hold their cell.
- On the harness's Stage 1 exemplar (22 types / 33 relationships): 1 crossing, 0.73 bends
per edge, every hard check green. - Proof of concept: no incremental routing, and trunk sharing for tree edges
(busTreeEdges) is available fromorthogonalLayout()only, not through GraphCanvas.
- Rows come from the structural forest when
GraphCanvas—layout="radial-tree": each structural tree (perisStructuralEdge) is
laid out around its root, with each depth on its own ring and rings sized from their own
population; several trees are packed as separate trunks without overlap. Supports
collapsedNodeIdsand draws a boundary per trunk (showClusterBoundaries). Headless
radialTreeLayout()exported, withRadialTreeLayoutOptions(ringSpacing,
nodeSeparationGap,trunkPadding) andRingGeometry.GraphCanvas—showHierarchyRingsprop (layout="radial-tree"only, default false):
draws a ring per occupied depth around each trunk. Visual only.GraphCanvas—layout="ux-navigation"for app screen maps: page nodes form top-to-bottom
trees, a page's views fan out beside it, independent trees and orphan views are arranged in
rows. New propsisPageNode(page vs. view) andisNavigationRoute(route edges, hidden until
a page or view touching them is hovered; which routes show depends on whether the hovered node
is a collapsed page, an expanded page or a view). Structural-edge and route labels show only
when the edge is in focus. HeadlessuxNavLayout()andvisibleNavigationEdgeIds()exported.GraphCanvas—structuralEdgeCurvatureprop (default 0.22): curvature for structural
edges without their owncurvature; 0 draws them straight.GraphCanvas—nodePopover/edgePopoverrender props: interactive content in a
role="dialog"popover anchored to a clicked or focused node or edge, tracking pan and zoom,
closed on Escape, background click or click outside. Opening moves focus to the popover's
first focusable element; Escape closes it from anywhere in the canvas, including while focus
is still on the node or edge (content with nothing focusable, such as a click tooltip).GraphCanvas—renderNodereceives a fourth argument,props: { nodeId, tooltipId, onSelect, onPopoverOpen }. A custom renderer must callonPopoverOpen(element)on click when
it is defined, ornodePopoverand click-triggered tooltips never open;tooltipIdis the id
to put in the node'saria-describedby.GraphNode—popoverOpen,popoverPanelId,tooltipId,onFocusandonBlurprops,
used by GraphCanvas to wire popovers and tooltips to the default node.GraphCanvas—nodeTooltipTrigger/edgeTooltipTrigger('hover' | 'click', default
'hover').'click'renders the tooltip content in aPopoverso it can hold buttons and
links. Node and edge tooltips are now wired to their targets witharia-describedby.Popover— new compound component (Popover,Popover.Trigger,Popover.Panel) for
interactive, accessible content: controlled (open+onOpenChange) or uncontrolled,
placement,offset,disableOutsideClick,panelId, andautoFocus(move focus to the
panel's first focusable element on open; defaultfalse).GraphCollapseControl— the collapse/expand button GraphCanvas shows beside a hovered node
that has structural children, exported for custom overlays.Tooltip—idprop, so a trigger elsewhere in the DOM can reference it with
aria-describedby.GraphNodeData.weight— optional size weight forlayout="galaxy"(ignored by every other
layout; unrelated toGraphEdge.weight). A node without one weighs the sum of its structural
children; when no node has a weight, each node weighs its subtree leaf count. Weights are
resolved over the full tree, so collapsing a subtree never resizes its parent.GraphNode—bodyRadiusprop: renders the node as a round body of that radius.galaxyLayout—GalaxyLayoutNode.weight(orbit order) and.radius(true radius for a
round body, used instead of the box half-diagonal).ChatComposer—enterBehaviorprop ('send' | 'newline', default'send'): controls Enter key behavior in the message textarea. When set to'send', bare Enter submits the message and Shift+Enter inserts a newline (default behavior). When set to'newline', bare Enter inserts a newline and Shift+Enter also inserts a newline — Submit only via the Send button. Enables touch-friendly and mobile-optimized input modes without forking or DOM-patching the component. The shortcut hint text automatically hides desktop-specific wording whenenterBehavior='newline'.
Changed
GraphCanvasContextValue.layout— the union now includes'organic','orthogonal',
'ux-navigation'and'radial-tree'; an exhaustiveswitchover it needs the new cases.findClearLabelPosition— newisPolylineparameter samples the points as a polyline
(orthogonal routes). Without it, only exactly four points are read as a cubic bezier; five or
more points are now sampled as a quadratic from the first three, where they were previously
read as a cubic from the first four.GraphCanvas—layout="galaxy"rewritten to follow the original galaxy charts it was
built to recreate (Risacher's D3 galaxy chart, Galaxies of the Guardian):- Without a
renderNode, nodes are drawn as round bodies whose area grows with weight
(with the square root of weight, so items spanning several orders of magnitude stay
distinguishable), coloured bydomainColor, instead of label cards. Labels sit inside bodies
big enough to hold them, wrapping between words; smaller bodies are named on hover. When no
node has aweight, sizes come from subtree leaf counts with a higher floor. Selection,
keyboard, popover and collapse behave exactly as before. - Each parent's children orbit it heaviest first in a full, evenly spaced circle. Each
satellite sits just far enough out for its whole subtree to clear its parent and stay within
its own slot, so no two bodies overlap anywhere, by construction rather than by a settle
loop. Only children curl like a galaxy arm instead of running off in a line. - Independent trees are separate galaxies packed together compactly; lone nodes are packed as
their own group; a wide container spreads whole galaxies, never a galaxy's own orbits. - Boundary discs are off by default for galaxy (
showClusterBoundariesstill turns them
on; they are now the smallest circle around each system). Structural (orbit) edge labels show
only when the edge or one of its nodes is hovered or selected. Edges meet round bodies at the
circle rather than the bounding box. - Live simulation eases toward the same construction; a dragged node carries its subtree and a
held drag settles. Static layout is ~1-30 ms for typical graphs (was ~300 ms for 250 nodes). - Options:
startAnglenow defaults to π (first satellite at 9 o'clock; was −π/2).
nodeSpreadis now the default gap as a multiple of the median body radius (default 0.6;
was an orbit-distance multiplier, default 3.2).nodeMarginreplaces that gap when given (0
means touching).settleCycles,separateGroups,resolvedAspectScaleand
resolveAspectRatioScaleare deprecated and have no effect.
- Without a
Fixed
GraphCanvas—layout="force-clustered"no longer smears clusters into each other on
dense graphs. Each Louvain cluster is now laid out on its own (internal edges only), clusters
are spaced by their real size, and each cluster is rotated to face the clusters it connects
to. On the college-football benchmark (115 nodes, 613 edges) node overlaps go from 53 to 0
and overlapping cluster bubbles from 93% to 2%; layout is also 2-10x faster.