[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:en:force-layout-live-parameter-tuning":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Force Layout Live Parameter Tuning\n\n## What This Example Builds\n\nThis example builds a full-screen force-directed graph playground around one static branching dataset. The canvas shows uniform circular nodes and straight teal links, while a floating white control window lets the user tune node repulsion, line elasticity, and the number of layout iterations.\n\nThe main point is not custom rendering or graph editing. The useful part is that the graph is loaded once, then the running force solver is adjusted in place. The same floating window also exposes shared workspace utilities such as wheel-mode switching, drag-mode switching, and image export.\n\n## How the Data Is Organized\n\nThe graph data is assembled inline inside `MyGraph.tsx`. It uses one `RGJsonData` object with `rootId: 'a'`, a `rawNodes` array, and a `rawLines` array. Before calling `setJsonData`, the example maps every raw line to a new object with a generated `id`, so the final dataset passed into relation-graph has explicit line identifiers.\n\nThe sample data forms a rooted branching structure: one root node `a`, four top-level branches (`b`, `c`, `d`, `e`), and deeper child groups below each branch. In total, the file defines 103 nodes and 102 links. In a production app, the same structure could represent an organization tree, dependency expansion, category taxonomy, incident propagation tree, or any hierarchy-like network that still benefits from force spacing instead of a strict tree layout.\n\n## How relation-graph Is Used\n\nThe page is wrapped in `RGProvider`, and `MyGraph` obtains the provider-scoped graph instance through `RGHooks.useGraphInstance()`. The `RelationGraph` component receives a focused set of graph options: circular nodes, 60x60 default node size, straight border-attached links, cyan and teal default colors, and the built-in `force` layout.\n\nThe graph instance API drives the runtime behavior. `initializeGraph()` loads the prepared `RGJsonData`, centers the graph, and sets a fixed zoom level. `updateMyOptions()` reaches into `graphInstance.layoutor` as `RGLayouts.ForceLayout` and pushes new physics values without rebuilding the dataset. A separate `restartForceLayout()` path rewrites `layout.maxLayoutTimes`, calls `doLayout()`, then recenters and re-zooms the canvas so the user can compare continuous layout with a fixed iteration budget.\n\nThere are no node slots, line slots, or editing APIs in this example. Styling is done through graph options plus a local SCSS file that forces white node text and defines checked-line overrides. The draggable helper window and its settings panel come from shared local components and use relation-graph hooks again for canvas options and image generation.\n\n## Key Interactions\n\n- The `Node Repulsion` slider changes `force_node_repulsion` on the active force layouter.\n- The `Line Elastic` slider changes `force_line_elastic` on the active force layouter.\n- A segmented selector switches between `Layout Forever` and `Layout Fixed Times(...)`.\n- When fixed mode is selected, a range input controls `maxLayoutTimes` and triggers a fresh `doLayout()` run.\n- The floating helper window can be dragged and minimized, so the user can inspect the graph while keeping controls nearby.\n- The helper window's settings view changes wheel behavior, changes canvas drag behavior, and downloads an image snapshot.\n\nNode and line click handlers are present, but in the reviewed source they only log the clicked objects to the console. They do not drive layout, selection, or editing behavior here.\n\n## Key Code Fragments\n\nThis fragment shows that the example uses relation-graph's built-in `force` layout together with a minimal visual style instead of custom node rendering.\n\n```tsx\nconst graphOptions: RGOptions = {\n  debug: true,\n  defaultNodeBorderWidth: 0,\n  defaultNodeShape: RGNodeShape.circle,\n  defaultNodeWidth: 60,\n  defaultNodeHeight: 60,\n  defaultLineColor: 'rgba(0, 186, 189, 1)',\n  defaultNodeColor: 'rgba(0, 206, 209, 1)',\n  defaultLineShape: RGLineShape.StandardStraight,\n  layout: { layoutName: 'force', maxLayoutTimes: Number.MAX_SAFE_INTEGER },\n  defaultJunctionPoint: RGJunctionPoint.border\n};\n```\n\nThis fragment shows that the graph data is prepared once by adding explicit line IDs before loading it into the graph instance.\n\n```tsx\nconst linesWithIds = rawLines.map((line, index) => ({\n  ...line,\n  id: `l${index + 1}`\n}));\n\nconst myJsonData: RGJsonData = {\n  rootId: 'a',\n  nodes: rawNodes,\n  lines: linesWithIds\n};\n```\n\nThis fragment shows the one-time data load and the explicit viewport reset that follows it.\n\n```tsx\nawait graphInstance.setJsonData(myJsonData);\ngraphInstance.moveToCenter();\ngraphInstance.setZoom(30);\n```\n\nThis fragment shows the first runtime control path: update the active `ForceLayout` instance directly when the sliders change.\n\n```tsx\nconst forceLayout = graphInstance.layoutor as InstanceType\u003Ctypeof RGLayouts.ForceLayout>;\nif (forceLayout) {\n  forceLayout.updateOptions(myForceLayoutOptions);\n}\n```\n\nThis fragment shows the second runtime control path: switch the relayout mode by rewriting layout options and rerunning the solver.\n\n```tsx\ngraphInstance.updateOptions({\n  layout: {\n    layoutName: 'force',\n    maxLayoutTimes: layoutForever ? Number.MAX_SAFE_INTEGER : maxLayoutTimes\n  }\n});\nawait graphInstance.doLayout();\ngraphInstance.moveToCenter();\ngraphInstance.setZoom(30);\n```\n\nThis fragment shows how the floating UI separates continuous layout from slider-capped layout runs.\n\n```tsx\n\u003CSimpleUISelect currentValue={layoutForever} data={[\n  { value: true, text: 'Layout Forever' },\n  { value: false, text: `Layout Fixed Times(${maxLayoutTimes})` }\n]} onChange={(newValue: boolean) => { setLayoutForever(newValue); }} />\n{!layoutForever && \u003Cinput\n  type=\"range\"\n  min=\"10\"\n  max=\"1000\"\n  step=\"20\"\n  value={maxLayoutTimes}\n```\n\n## What Makes This Example Distinct\n\nThis example is distinct because it treats the built-in `force` layout as a minimal live baseline rather than as a large control surface. The graph stays on one inline dataset, the visible force controls stay limited to repulsion and line elasticity, and iteration control is kept in a separate continuous-versus-fixed relayout switch.\n\nCompared with `layout-force-options`, this version is easier to read as a starting point because it does not add dataset presets or a broader panel of global force coefficients. Compared with `performance-test-force-layout`, it avoids performance scaffolding such as synthetic scale presets, minimap navigation, and custom node rendering. Compared with `layout-force-options-pro`, it stays at global solver tuning instead of per-node or per-line force overrides and runtime graph mutation.\n\nAnother distinct aspect is the visual restraint. The page keeps borderless 60px circles, straight teal links, and one draggable white helper window, so the force behavior itself remains the main thing being demonstrated.\n\n## Where Else This Pattern Applies\n\nThis pattern transfers well to internal tools where teams need to tune spacing behavior before committing to a final graph design. For example, the same approach can be used for dependency graphs, service topology maps, organization structures, or taxonomy explorers where the data stays stable but the desired force behavior is still being calibrated.\n\nIt is also a practical pattern for operator-facing workbenches. A product can load one graph once, let users adjust solver intensity and rerun duration, then preserve the same dataset while they compare readability, overlap reduction, and export quality under different layout settings.\n",false,500,1782615385371]