[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:en:metric-rollup-graph-editor":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Building a Graph-Based Metric Rollup Editor\n\n## What This Example Builds\n\nThis example builds a compact relation-graph editor for metric or cost rollups. The screen starts with a dotted full-screen canvas, a purple summary node, two blue value nodes, an orange authoring toolbar, and a floating helper window above the graph.\n\nUsers can drag new value or summary nodes onto the canvas, click a line tool to connect inputs into a summary node, double-click values and names to edit them, delete the currently selected node or line, switch to a tree layout, and open a shared settings or export panel. The main point is that the graph is not just a diagram: the incoming links define how summary totals are recalculated.\n\n## How the Data Is Organized\n\nThe initial dataset is declared inline as one `RGJsonData` object with `nodes` and `lines`. Each starter node already carries fixed `x` and `y` coordinates plus custom metadata in `data`, especially `myNodeType` and `numberValue`. That makes the graph both visual and semantic: node type controls behavior, while `numberValue` is the field used for aggregation.\n\nBefore `setJsonData(...)` runs, the code normalizes every starter line by guaranteeing an `id`, switching the line shape to `Curve7`, and setting explicit junction points. Runtime creation uses a second layer of data definitions: the toolbar contains reusable node templates for `value-node` and `sum-node`, plus one line template for summarization links. In a real system, the same structure could represent KPI rollups, budget breakdowns, project cost models, staffing totals, or any dependency graph where upstream numeric inputs feed derived summaries.\n\n## How relation-graph Is Used\n\n`RGProvider` wraps the demo so relation-graph hooks can resolve the active graph instance across the graph component, the toolbar, and the floating settings panel. In `MyGraph`, `RGHooks.useGraphInstance()` loads the starter dataset with `setJsonData(...)`, recalculates summary nodes, then recenters and fits the scene with `moveToCenter()` and `zoomToFit()`.\n\nThe graph options keep the base scene simple and editor-oriented. The graph starts in `fixed` layout, uses border-based junction points, draws lines with `StandardCurve`, and moves the built-in toolbar to the right. A separate toolbar action later replaces that with a top-down tree layout through `updateOptions(...)` and `doLayout()`.\n\nTwo relation-graph slots define the custom UI. `RGSlotOnNode` replaces the default node body so value nodes render an inline numeric editor, summary nodes render the computed total, and every node shows an editable name badge below the card. `RGSlotOnView` injects the orange `SummarizerToolbar` directly into the graph viewport instead of relying on an external page sidebar.\n\nThe authoring flow uses graph-instance APIs rather than external stateful layout code. The toolbar starts node creation with `startCreatingNodePlot(...)`, creates ids with `generateNewUUID()`, inserts new items with `addNodes(...)`, starts line creation with `startCreatingLinePlot(...)`, validates the target node before `addLines(...)`, and removes the current selection with `removeNode(...)` or `removeLink(...)`. `RGHooks.useCreatingNode()` and `RGHooks.useCreatingLine()` expose creation mode so the toolbar can visibly highlight the active tool.\n\nThe floating `DraggableWindow` is a shared helper rather than logic unique to this example, but it still matters to the finished workflow. Its settings panel reads graph store state with `RGHooks.useGraphStore()`, changes wheel and drag behavior through `setOptions(...)`, and exports the current canvas by calling `prepareForImageGeneration()`, `domToImageByModernScreenshot(...)`, and `restoreAfterImageGeneration()`. Local SCSS finishes the editor look with a dot-grid background, an externally positioned node-name badge, and a stronger checked-state style for line labels.\n\n## Key Interactions\n\nDragging either toolbar template starts relation-graph's node-plot flow and inserts a new typed node where the drop finishes. New nodes inherit the template color, shape, and `myNodeType`, so the same toolbar controls both editable value nodes and computed summary nodes.\n\nClicking the line tool starts guided line creation. The code blocks value nodes from being used as the target, so new links are treated as inputs into a summary node rather than arbitrary connections. After a valid link is added, the example reruns the aggregation logic immediately.\n\nDouble-clicking the number inside a value node opens an inline input. Committing the edit writes the new `numberValue` back with `updateNodeData(...)` and recalculates all summary nodes. Double-clicking the name badge uses the same inline editor pattern, but it updates `node.text` through `updateNode(...)` instead of recalculating values.\n\nNode and line deletion is selection-aware. Clicking a node or line stores it as the current selection, the matching delete button appears in the toolbar, and clicking the canvas background clears both the local selection state and relation-graph's checked state. Separate from authoring, the floating helper window can be dragged, toggled into a settings panel, and used to export the current graph as an image.\n\n## Key Code Fragments\n\nThis fragment shows that the starter graph is fixed-positioned and that node metadata already carries the type and numeric value used later by the editor.\n\n```tsx\nconst myJsonData: RGJsonData = {\n  rootId: 'my-root',\n  nodes: [\n    { id: 'my-root', text: '成本', color: 'rgba(214,103,239,0.59)', x: -50, y: -2, data: { myNodeType: 'sum-node', numberValue: '510000' }},\n    { id: 'newNode-1', text: '材料成本', color: '#5da0f8', x: -167, y: -176, data: { myNodeType: 'value-node', numberValue: '10000' }},\n    { id: 'newNode-3', text: '人力成本', color: '#5da0f8', x: 29, y: -176, data: { myNodeType: 'value-node', numberValue: '500000' }}\n  ],\n  lines: [\n```\n\nThis fragment proves that summary values are derived from graph topology by reading incoming nodes and writing the computed total back into node data.\n\n```tsx\nconst incomingNodes = graphInstance.getNodeIncomingNodes(node);\n\nlet sum = 0;\nincomingNodes.forEach(inNode => {\n  let val = 0;\n  if (inNode.data?.myNodeType === 'sum-node') {\n    val = analyzeNodesForSum(inNode, newPath);\n  } else {\n    val = parseFloat(inNode.data?.numberValue || '0');\n  }\n  sum += isNaN(val) ? 0 : val;\n});\ngraphInstance.updateNodeData(node, { numberValue: sum.toFixed(3) });\n```\n\nThis fragment shows how `RGSlotOnNode` turns the node body into an editable property surface instead of a plain label.\n\n```tsx\n{node.data?.myNodeType === 'value-node' ? (\n  \u003CMyEditableProperty\n    dataType=\"number\"\n    currentValue={node.data.numberValue}\n    onChange={(val) => {\n      graphInstance.updateNodeData(node, { numberValue: String(val) });\n      reCalcSumNodeValue();\n    }}\n  />\n) : (\n  \u003Cdiv className=\"font-bold px-2\">{node.data?.numberValue}\u003C/div>\n)}\n```\n\nThis fragment shows the guided line-authoring rule: the toolbar starts relation-graph's create-line flow, rejects invalid targets, and recalculates after insertion.\n\n```tsx\ngraphInstance.startCreatingLinePlot(e.nativeEvent, {\n  template: { ...lineTemplate },\n  onCreateLine: (from, to, finalTemplate) => {\n    if ('id' in to) {\n      if (to.data?.myNodeType === 'value-node') {\n        return alert('Value node cannot be a summary target!');\n      }\n      graphInstance.addLines([{ ...finalTemplate, from: (from as RGNode).id, to: (to as RGNode).id, text: 'Summarizes' }]);\n      onLineCreated();\n    }\n  }\n});\n```\n\nThis fragment shows that the floating helper window exposes runtime canvas behavior changes and image export through relation-graph instance APIs.\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});\nawait graphInstance.restoreAfterImageGeneration();\n```\n\n## What Makes This Example Distinct\n\nThe comparison data shows that this example is not distinctive merely because it uses slots, fixed coordinates, or a floating helper window. Nearby examples such as `drag-to-create-nodes-with-preset-styles`, `create-object-from-menu`, `use-d3-layout`, and `canvas-selection` already overlap with parts of that implementation space. What is rarer here is the combination of editor features around a rollup model.\n\nAgainst `drag-to-create-nodes-with-preset-styles`, this is not a generic style-authoring canvas. The created nodes have semantic types, the accepted connections are constrained, and successful edits feed directly into recomputed totals. Against `create-object-from-menu`, the workflow is persistent and guided rather than menu-driven: node creation, line creation, rename, numeric editing, and deletion stay visible in one overlay interface. Against `use-d3-layout` and `canvas-selection`, the emphasis shifts away from external layout or selection mechanics and toward topology-driven calculation.\n\nThe prepared rarity and comparison records point to the same conclusion: this example is a strong starting point when the requirement is a lightweight graph editor whose edges mean aggregation. Its most unusual combination is template-driven node creation, validated line authoring, inline slot-based property editing, recursive summary recomputation, selection-aware deletion, optional relayout, and shared canvas settings or export utilities on one screen.\n\n## Where Else This Pattern Applies\n\nThis pattern transfers well to KPI rollup designers, budget and cost breakdown planners, revenue decomposition tools, and operational scorecard editors. In those scenarios, users need to model how upstream values feed one or more summary nodes and immediately verify the resulting totals.\n\nIt also fits dependency-based planning tools where the graph itself is the calculation model, such as staffing estimates, component cost allocation, or layered target planning. The same slot-based editing pattern can be extended to more fields per node, stricter validation rules, or domain-specific node templates without changing the overall authoring structure.\n",false,500,1782615411646]