[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:en:bidirectional-tree-layout":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Root-Centered Bidirectional Tree with Reversible Parent Arrows\n\n## What This Example Builds\n\nThis example builds a two-sided tree centered on one root node. One branch flows into the root, another branch expands away from it, and the whole graph can be switched between a horizontal tree and a vertical tree without changing the dataset itself.\n\nWhat users see is a compact hierarchy viewer with rectangular nodes, green child-side branches, and a differently styled incoming branch. The most important detail is that the incoming side is not just recolored after layout: its lines can also switch to reversed arrowheads, which makes directionality explicit when the graph is used for upstream versus downstream relationships.\n\nThe demo also includes a floating utility window. It holds the orientation selector, the parent-arrow selector, and a shared settings panel for canvas drag mode, wheel behavior, and image export.\n\n## How the Data Is Organized\n\nThe graph data is defined inline as one `RGJsonData` object with a single `rootId`, a flat `nodes` array, and a flat `lines` array. The structure does not precompute explicit \"left side\" or \"right side\" styling metadata. Instead, it relies on relation-graph's tree layout result to determine which branch ended up on the negative side of the root.\n\nThe incoming branch is represented by edges that point into the root, such as `R-b -> a`, while the outgoing branch uses normal root-to-child edges such as `a -> b`. That same pattern can represent parent and child relations, upstream and downstream dependencies, lineage around a focal asset, or any split hierarchy where one side should be read as \"toward the center\" and the other as \"away from the center.\"\n\nBefore `setJsonData()` runs, the only preprocessing is orientation-specific option assembly. The code chooses a `tree` preset, node gaps, junction points, and default line shapes based on the current selector value. The branch-specific coloring and arrow logic happens later, after layout metadata is available on each node.\n\n## How relation-graph Is Used\n\nThe example is wrapped in `RGProvider`, then `MyGraph` retrieves the active graph instance with `RGHooks.useGraphInstance()`. The rendered `RelationGraph` starts with an empty `options` prop, and the real configuration is applied at runtime through `graphInstance.setOptions()` followed by `graphInstance.setJsonData()`.\n\nFor layout, the demo stays on the built-in `tree` layout and swaps between two presets. Horizontal mode uses `from: 'left'`, `treeNodeGapH: 150`, `treeNodeGapV: 20`, `RGJunctionPoint.lr`, and `RGLineShape.Curve2`. Vertical mode uses `from: 'top'`, `treeNodeGapH: 20`, `treeNodeGapV: 150`, `RGJunctionPoint.tb`, and `RGLineShape.StandardCurve`. Both modes keep rectangular nodes with a fixed `130 x 40` default size and zero border width.\n\nThe main runtime technique is post-layout restyling. After the dataset is loaded, the code reads `graphInstance.getNodes()` and checks `node.lot.level`. Nodes with negative levels are treated as the incoming branch. Those nodes are recolored, their IDs are collected, and then `graphInstance.getLines()` plus `graphInstance.updateLine()` are used to restyle all connected lines. When the reverse-arrow option is enabled, those lines switch to `RGLineShape.StandardOrthogonal`, red color, `showStartArrow: true`, and `showEndArrow: false`. Otherwise the same branch keeps orthogonal routing with its default arrow direction and amber color.\n\nNo node, line, canvas, or viewport slots are customized here. The example relies on the default graph renderers and uses SCSS overrides instead: node text is forced to white, and checked lines receive a stronger orange stroke and label treatment.\n\nThe shared `DraggableWindow` helper adds secondary graph-instance usage. Its `CanvasSettingsPanel` reads graph store options through `RGHooks.useGraphStore()`, changes `wheelEventAction` and `dragEventAction` with `graphInstance.setOptions()`, and exports the current canvas through `prepareForImageGeneration()` and `restoreAfterImageGeneration()`.\n\nThis is a viewer-style example rather than an editor. Node and line click handlers are wired into `RelationGraph`, but they only log the clicked objects and do not modify the graph.\n\n## Key Interactions\n\nThe primary interaction is orientation switching. Choosing `Horizontal Tree` or `Vertical Tree` rebuilds the graph with a different tree preset, different junction defaults, and different baseline line shapes, then recenters and refits the result.\n\nThe second core interaction is the parent-arrow toggle. It does not reload data or recompute the graph structure. Instead, it reruns the post-layout styling pass and flips arrowheads only on the incoming branch connected through negative `node.lot.level` nodes.\n\nThe floating utility window adds two supporting interactions. Users can switch wheel behavior between scroll, zoom, and none, and they can switch canvas dragging between selection, move, and none. The same panel can export the current graph view as an image by asking relation-graph for an export-ready canvas DOM first.\n\nThe node and line click handlers are intentionally minor in this demo. They are present for inspection and debugging, but they are not the entry point for any visible feature.\n\n## Key Code Fragments\n\nThis fragment shows that the data is a single inline tree with one focal root and an incoming branch that points into that root.\n\n```tsx\nconst myJsonData: RGJsonData = {\n    rootId: 'a',\n      nodes: [\n        { id: 'a', text: 'Root Node a' },\n        { id: 'R-b', text: 'R-b' }, { id: 'R-b-1', text: 'R-b-1' }, { id: 'R-b-2', text: 'R-b-2' }, { id: 'R-b-3', text: 'R-b-3' },\n        { id: 'R-c', text: 'R-c' }, { id: 'R-c-1', text: 'R-c-1' }, { id: 'R-c-2', text: 'R-c-2' },\n```\n\nThis fragment shows the horizontal preset: the demo stays on `layoutName: 'tree'` but swaps direction, spacing, junction points, and default line shape as one preset.\n\n```tsx\nif (activeTabName === 'h') {\n    layoutOptions = {\n        layoutName: 'tree',\n        from: 'left',\n        treeNodeGapH: 150,\n        treeNodeGapV: 20\n    };\n    defaultJunctionPoint = RGJunctionPoint.lr;\n    defaultLineShape = RGLineShape.Curve2;\n}\n```\n\nThis fragment shows that orientation changes rebuild the configured graph, then center and fit it in the viewport.\n\n```tsx\nconst graphOptions: RGOptions = {\n    debug: false,\n    layout: layoutOptions,\n    defaultNodeShape: RGNodeShape.rect,\n    defaultNodeWidth: 130,\n    defaultNodeHeight: 40,\n    defaultLineShape,\n    defaultJunctionPoint: defaultJunctionPoint,\n    defaultNodeBorderWidth: 0\n};\n\ngraphInstance.setOptions(graphOptions);\nawait graphInstance.setJsonData(myJsonData);\n```\n\nThis fragment shows the post-layout classification step that derives branch identity from `node.lot.level` instead of from extra input flags.\n\n```tsx\nfor (const node of graphInstance.getNodes()) {\n    if (node.lot && node.lot.level !== undefined && node.lot.level \u003C 0) {\n        graphInstance.updateNode(node, { color: '#ca8a04' });\n        leftNodes.push(node);\n    } else {\n        graphInstance.updateNode(node, { color: '#3f9802' });\n    }\n}\nconst leftNodeIds: string[] = leftNodes.map(n => n.id);\n```\n\nThis fragment shows the branch-specific arrow reversal. The toggle changes existing line styles without reloading the dataset.\n\n```tsx\nif (leftNodeIds.includes(line.from) || leftNodeIds.includes(line.to)) {\n    if (reverseParentArrows) {\n        graphInstance.updateLine(line, {\n            lineShape: RGLineShape.StandardOrthogonal,\n            showStartArrow: true,\n            showEndArrow: false,\n            color: '#ff0000'\n        });\n    }\n```\n\nThis fragment shows how the shared utility panel uses relation-graph's export lifecycle before creating the image blob.\n\n```tsx\nconst canvasDom = await graphInstance.prepareForImageGeneration();\nlet graphBackgroundColor = graphInstance.getOptions().backgroundColor;\nif (!graphBackgroundColor || graphBackgroundColor === 'transparent') {\n    graphBackgroundColor = '#ffffff';\n}\nconst imageBlob = await domToImageByModernScreenshot(canvasDom, {\n    backgroundColor: graphBackgroundColor\n});\n```\n\n## What Makes This Example Distinct\n\nAccording to the comparison data, this example is not just another tree-orientation switcher. Its distinctive combination is a root-centered bidirectional `tree`, post-layout branch classification through `node.lot.level`, polarity-based recoloring, and optional arrow reversal on the incoming side, all inside a minimal viewer shell.\n\nCompared with [`bothway-tree2`](./bothway-tree2), this version spends its control budget on parent-side arrow semantics and orthogonal branch restyling rather than on branch-specific line-label placement. Compared with [`layout-tree`](./layout-tree), the focus is not generic one-direction tree relayout, but a centered two-sided hierarchy where one branch keeps its own color and arrow policy. Compared with [`io-tree-layout`](./io-tree-layout), it stays on the built-in `tree` layout rather than switching to `io-tree` routing and junction-anchor rewriting.\n\nThe rarity metadata also supports a narrower claim: the orientation selector, parent-arrow selector, orientation-specific junction defaults, and runtime restyling of existing lines are all rare features in the example set. That makes this demo a strong starting point when the problem is not \"draw any tree,\" but \"draw one focal node with a meaningfully different branch on the reverse side.\"\n\n## Where Else This Pattern Applies\n\nThis pattern transfers well to upstream and downstream dependency inspection, such as package dependency viewers, data lineage around one table, or service maps where callers and callees should sit on opposite sides of a focal service.\n\nIt also fits product structures that need parent-side versus child-side emphasis, including bill-of-material trees, inheritance viewers, category ancestry explorers, and organizational contexts where supervisors and reports should be visually separated without moving to two different diagrams.\n\nThe post-layout classification technique is reusable beyond trees. Any graph where layout metadata can identify a branch, depth range, or side of the focal node can use the same `getNodes()` and `updateLine()` pattern to apply branch-specific arrows, colors, or routing rules after the layout engine has already done the positioning work.\n",false,500,1782615394449]