[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:en:root-side-branch-visibility-toggles":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Root-Centered Tree with Independent Left and Right Branch Toggles\n\n## What This Example Builds\n\nThis example builds a bidirectional tree centered on one root node. Users see incoming branches on the left, outgoing branches on the right, and two pink action buttons placed outside the root so each side can be shown or hidden independently.\n\nThe main point is not generic expand or collapse behavior. It is a custom root control surface built with `RGSlotOnNode`, where one button targets the left side of the hierarchy and the other targets the right side.\n\n## How the Data Is Organized\n\nThe graph data is declared inline as one `RGJsonData` object with `rootId: 'a'`, 13 nodes, and 12 lines. The component does not preprocess the dataset before layout. Instead, it adds the `nodes` and `lines` arrays to the graph instance, runs `doLayout()`, and then writes two runtime flags, `leftExpanded` and `rightExpanded`, onto the root node.\n\nThat structure maps well to real data where one focal entity needs two directional neighborhoods, such as upstream and downstream lineage, parent and child relationships, supplier and customer trees, or cause and effect branches around one incident.\n\n## How relation-graph Is Used\n\nThe example is wrapped in `RGProvider`, and `RGHooks.useGraphInstance()` drives initialization and later updates. The graph uses a tree layout with `treeNodeGapH: 150`, `RGLineShape.StandardCurve`, `RGJunctionPoint.lr`, a gray default line color, and path-following line labels. The built-in toolbar is kept enabled and positioned horizontally at the bottom-right corner.\n\nThe most important customization is `RGSlotOnNode`. It replaces the default node body only when `node.lot.level === 0`, which makes the root a special interaction target while non-root nodes stay simple text blocks. The example then relies on instance APIs such as `addNodes`, `addLines`, `doLayout`, `getRootNode`, `getNodeRelatedNodes`, `updateNodeData`, `updateNode`, `moveToCenter`, and `zoomToFit`.\n\nThe floating description panel and settings overlay come from the shared `DraggableWindow` helper rather than example-specific graph logic. That helper uses `RGHooks.useGraphStore()` and `setOptions(...)` to switch wheel and drag behavior, and it supports image export through `prepareForImageGeneration()` and `restoreAfterImageGeneration()`. The local SCSS file is effectively a placeholder, so the visible styling is mainly delivered through the custom node slot markup and shared window component.\n\n## Key Interactions\n\n- Clicking the left root button toggles visibility for related nodes whose computed `lot.level` is negative.\n- Clicking the right root button toggles visibility for related nodes whose computed `lot.level` is positive.\n- The root buttons switch between plus and minus icons based on the root node's stored `leftExpanded` and `rightExpanded` flags.\n- Only the root node gets the extra controls; every other node remains read-only text.\n- The floating helper window can be dragged, minimized, reopened, and switched into a settings overlay.\n- The settings overlay changes wheel behavior, changes canvas drag behavior, and downloads the current graph as an image.\n\n## Key Code Fragments\n\nThis fragment shows that the example starts from static inline tree data rather than loading or rebuilding data on demand.\n\n```tsx\nconst treeJsonData: RGJsonData = {\n    rootId: 'a',\n    nodes: [\n        { id: 'a', text: 'Root Node a', width: 120, height: 80 },\n        { id: 'R-b', text: 'R-b' },\n        { id: 'R-c', text: 'R-c' },\n        { id: 'R-c-1', text: 'R-c-1' },\n        { id: 'R-c-2', text: 'R-c-2' },\n        { id: 'R-d', text: 'R-d' },\n        { id: 'b', text: 'b' },\n```\n\nThis fragment shows the initialization flow: load nodes and lines, run layout, add root metadata, then fit the viewport.\n\n```tsx\nconst initializeGraph = async () => {\n    graphInstance.addNodes(treeJsonData.nodes);\n    graphInstance.addLines(treeJsonData.lines);\n    await graphInstance.doLayout();\n    const rootNode = graphInstance.getRootNode();\n    graphInstance.updateNodeData(rootNode, {\n        leftExpanded: true,\n        rightExpanded: true,\n    });\n    graphInstance.moveToCenter();\n    graphInstance.zoomToFit();\n};\n```\n\nThis fragment proves that left-side visibility is not controlled by a built-in expand holder. It is computed from related nodes and the layout-side value in `lot.level`.\n\n```tsx\nconst toggleRootNodeLeft = async () => {\n    const rootNode = graphInstance.getRootNode();\n    if (rootNode) {\n        graphInstance.updateNodeData(rootNode, {\n            leftExpanded: !rootNode.data.leftExpanded\n        });\n        const relatedNodes = graphInstance.getNodeRelatedNodes(rootNode);\n        const leftNodes = relatedNodes.filter(n => n.lot.level \u003C 0);\n        leftNodes.forEach(node => {\n            graphInstance.updateNode(node, {\n                hidden: !rootNode.data.leftExpanded\n            });\n        });\n    }\n};\n```\n\nThis fragment shows how the root node gets a custom interaction surface while all other nodes fall back to plain text rendering.\n\n```tsx\n\u003CRGSlotOnNode>\n    {({ node }) => {\n        return node.lot.level === 0 ? (\n            \u003Cdiv className=\"px-6 py-1 w-full h-full flex place-items-center justify-center text-xs\">\n                \u003Cdiv className=\"px-3 py-0.5 bg-gray-100 bg-opacity-30 rounded text-black text-sm\">\n                    {node.text}\n                \u003C/div>\n                {/* left and right buttons omitted */}\n            \u003C/div>\n        ) : (\n            \u003Cdiv className=\"px-6 py-1 w-full h-full flex place-items-center justify-center text-xs\">\n                {node.text}\n            \u003C/div>\n        );\n    }}\n\u003C/RGSlotOnNode>\n```\n\nThis fragment shows that canvas settings and export are shared demo utilities, not the main example-specific graph behavior.\n\n```tsx\n\u003CSettingRow\n    label=\"Wheel Event:\"\n    options={[\n        { label: 'Scroll', value: 'scroll' },\n        { label: 'Zoom', value: 'zoom' },\n        { label: 'None', value: 'none' },\n    ]}\n    value={wheelMode}\n    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}\n/>\n```\n\n## What Makes This Example Distinct\n\nAccording to the comparison data, this example is most distinct when read against other expand-related demos. Compared with `expand-animation` and `expand-gradually`, it does not focus on relation-graph's built-in expand holders, initial collapse states, or relayout policy after expansion. Its main lesson is a custom root node UI that splits opposite sides of the same hierarchy into two independent actions.\n\nCompared with `open-all-close-all`, the control scope is much narrower and more targeted. That example orchestrates recursive whole-graph expansion and collapse, while this one keeps the dataset static and applies immediate visibility changes only to the nodes related to the root.\n\nCompared with other `RGSlotOnNode` examples such as `element-connect-to-node`, the custom slot is used less as a general visual composition technique and more as a root-only behavior surface. The combination that stands out is a centered bidirectional tree, root metadata flags, side-aware filtering through `getNodeRelatedNodes(...)` plus `lot.level`, and direct `hidden` updates on existing nodes.\n\n## Where Else This Pattern Applies\n\nThis pattern transfers well to viewers where one central entity needs separate controls for opposite relationship directions. Examples include upstream versus downstream data lineage, manager versus report branches in an org view, supplier versus customer dependencies, and cause versus consequence maps in incident analysis.\n\nIt is also useful when a product team wants custom controls that look and behave differently from relation-graph's default expand affordances. The same approach can be extended to root-scoped filters, directional emphasis, or one-click branch summaries without changing the underlying graph dataset format.\n",false,500,1782615427630]