[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:google-income-statement-ribbon-infographic":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Google Income Statement Ribbon 信息图\n\n## 这个示例构建了什么\n\n这个示例构建了一个独立的 Google 损益表财务信息图查看器。界面展示的是一种从左到右的拆解方式，其中收入类别汇入一个共享根节点，而支出类别则从该根节点向外分叉，因此整个图读起来更像是一份紧凑的报表摘要，而不是一棵通用树。\n\n最显眼的部分包括按数据比例缩放的节点列、宽大的丝带式连接、针对部分 Google 业务线的品牌化标注，以及分层渐变背景。用户可以通过大面积可点击的 ribbon 区域检查流向带，也可以点击空白画布来清除 checked 状态。\n\n它的主要价值在于封装方式。与相关的教程式示例相比，这个版本只暴露最终完成的查看器，因此更适合作为面向生产场景的参考来研究。\n\n## 数据是如何组织的\n\n源数据一开始是一个嵌套对象，具有两个顶层分支：`revenues` 和 `expenses`。每个条目都携带业务字段，例如 `name`、`amount`、`color`、`year_over_year_change`、`percentage_of_revenue`、`margin` 以及可选的 `desc`，而嵌套的 `details` 数组则描述更深层级的拆分。\n\n在执行 `setJsonData` 之前，`getMyJsonData()` 会把这份嵌套结构转换为 `RGJsonData`，其中包含一个合成的根节点、一个扁平的 `nodes` 数组以及一个扁平的 `lines` 数组。递归辅助函数 `findNodesFromOrignData()` 还会把每个节点负载中的 `details` 移除，从而让剩余指标可以直接在节点插槽内部渲染。\n\n这一步预处理不只是把树拍平。收入条目按 `child -> parent` 连接，支出条目按 `parent -> child` 连接，并且每条连线都会把基于百分比的偏移和高度元数据存入 `line.data`。正是这些额外的几何数据，使自定义连线插槽能够渲染出按比例变化的 ribbon，而不是统一粗细的笔画。\n\n在真实应用中，这种结构同样可以表示损益表、预算分配树、类别贡献图，或任何既关心金额又关心流向占比的拆分视图。\n\n## relation-graph 是如何使用的\n\n入口文件保持了最小化组合：`RGProvider` 包裹 `StepFinalVersion`，而实际的查看器组件则通过 `RGHooks.useGraphInstance()` 读取图实例。这个 hook 是示例中的核心，因为它同时用于图初始化阶段和自定义连线插槽内部。\n\n图本身被配置为一个从左到右的树布局，并具有较大的水平与垂直间距。配置项让结构层的默认值保持简单：矩形基础节点、标准曲线连线、左右连接点、默认 3px 连线宽度，以及默认 1px 节点边框宽度。这些默认值主要承担图骨架的作用，因为真正可见的表现层会被插槽和 CSS 覆盖。\n\n`RelationGraph` 绑定了两个图事件：`onLineClick` 和 `onCanvasClick`。本地的连线处理函数只做日志输出，因此这个示例保持在查看器模式，而不会打开编辑器或面板。不过，画布处理函数是有实际作用的，因为它会调用 `clearChecked()` 来重置 checked 状态。\n\n最重要的自定义点是 `RGSlotOnNode` 和 `RGSlotOnLine`。节点插槽会渲染一个 HTML 块，其高度依赖 `node.data.amount`，然后在上方堆叠节点标签、美元金额，以及可选的百分比或同比文本。连线插槽会通过 `getLinkByLine()` 解析当前实时连线，再使用 `createAreaLinePathWithOffset()` 重建 SVG 面积路径，并通过 `graphInstance.onLineClick(...)` 把点击事件转发回 relation-graph。\n\n样式表则补全了最终效果。它用固定的径向渐变替换了朴素的画布背景，移除了可见的节点边框，为 checked 节点增加了光晕，并让自定义 ribbon 路径在 hover 和 checked 状态下通过提高透明度表现反馈。这些覆盖项共同把一个标准树布局转换成了面向展示的信息图。\n\n## 关键交互\n\n图会在挂载时初始化。`initializeGraph()` 加载转换后的 JSON 数据，将图移动到中心，按视口自适应缩放，然后再轻微缩小一点，以便完整构图拥有更充足的留白空间。\n\n连线检查是通过自定义 ribbon 主体实现的，而不是通过细窄的默认线条。每个填充的 SVG 面积都会在整个表面上捕获点击，并把该事件转发到 relation-graph 内置的连线点击处理逻辑中，这使宽大的财务流向更容易被检查。\n\n点击画布会起到重置作用。点击空白区域会清除 checked 状态，从而移除图元素上的活动视觉强调。\n\n整个交互模型被刻意保持得很轻。这个示例没有加入编辑、钻取或侧边面板检查功能。甚至应用层的 `onLineClick` 处理函数也只是记录被点击的连线，因此重点始终放在图形呈现和 checked 状态反馈上。\n\n## 关键代码片段\n\n这个片段表明，这个独立示例只是对最终实现的一层 provider 包装。\n\n```tsx\nconst Example: React.FC = () => {\n\n    return (\n        \u003CRGProvider>\u003CStepFinalVersion />\u003C/RGProvider>\n    );\n};\n```\n\n这个片段展示了从左到右树布局的基础配置，以及支撑自定义渲染层的图默认值。\n\n```tsx\nconst graphOptions: RGOptions = {\n    debug: false,\n    layout: {\n        layoutName: 'tree',\n        from: 'left',\n        treeNodeGapH: 300,\n        treeNodeGapV: 100\n    },\n    defaultNodeShape: RGNodeShape.rect,\n    defaultLineShape: RGLineShape.StandardCurve,\n    defaultLineWidth: 3,\n    defaultJunctionPoint: RGJunctionPoint.lr,\n    defaultNodeBorderWidth: 1\n};\n```\n\n这个片段展示了挂载时的数据加载与视口调整。\n\n```tsx\nconst initializeGraph = async () => {\n    const myJsonData = await getMyJsonData();\n    await graphInstance.setJsonData(myJsonData);\n    graphInstance.moveToCenter();\n    graphInstance.zoomToFit();\n    graphInstance.zoom(-10); // Zoom out by 10% based on current zoom level\n};\n```\n\n这个片段证明，收入连线会以 child-to-parent ribbon 的形式存储，并带有基于百分比的偏移信息。\n\n```ts\nif (income) {\n    allJsonLines.push({\n        from: cJsonNode.id,\n        to: parentJsonNode.id,\n        color: cJsonNode.color,\n        data: {\n            fromOffsetYPercent: 0,\n            fromHeightPercent: 1,\n            toOffsetYPercent: sumAmount / parentJsonNode.data.amount,\n            toHeightPercent: cJsonNode.data.amount / parentJsonNode.data.amount,\n        }\n    });\n}\n```\n\n这个片段证明，支出连线会反转方向，同时保留相同的比例几何思路。\n\n```ts\nallJsonLines.push({\n    from: parentJsonNode.id,\n    to: cJsonNode.id,\n    color: cJsonNode.color,\n    data: {\n        fromOffsetYPercent: sumAmount / parentJsonNode.data.amount,\n        fromHeightPercent: cJsonNode.data.amount / parentJsonNode.data.amount,\n        toOffsetYPercent: 0,\n        toHeightPercent: 1,\n    }\n});\n```\n\n这个片段展示了由金额驱动的节点标记，以及层叠显示的财务标签。\n\n```tsx\n\u003Cdiv style={{width: '40px', height: `${(node.data?.amount / 100) * 200}px`, position: 'relative'}}>\n    \u003Cdiv style={{\n        position: 'absolute',\n        top: '0px',\n        left: '0px',\n        color: '#0c63ff',\n        width: '200px',\n        whiteSpace: 'nowrap',\n        textAlign: 'left'\n    }}>\n        \u003Cdiv style={{transform: 'translateY(-110%)'}}>\n            \u003Cdiv style={{fontSize: '24px'}}>{node.text}\u003C/div>\n            \u003Cdiv style={{fontSize: '22px'}}>$ {node.data.amount} B\u003C/div>\n```\n\n这个片段展示了自定义连线插槽如何根据实时连线位置重建 ribbon 几何形状，并保持连线可点击。\n\n```tsx\nconst graphInstance = RGHooks.useGraphInstance();\nconst link = graphInstance.getLinkByLine(lineConfig.line)!;\nconst path = createAreaLinePathWithOffset(\n    link.fromNode,\n    link.toNode,\n    lineConfig.line,\n    1\n);\n\nconst onClick = (e) => {\n    graphInstance.onLineClick(lineConfig.line, e);\n};\n```\n\n这个片段展示了最终呈现同样依赖 CSS，而不只是图配置。\n\n```scss\n.rg-node-peel.rg-node-checked {\n    .rg-node {\n        border: none;\n        box-shadow: 0 0 0 10px var(--rg-node-color);\n    }\n}\n\n.my-rg-line {\n    opacity: 0.5;\n    pointer-events: fill;\n    cursor: pointer;\n```\n\n## 这个示例的独特之处\n\n对比数据表明，这个示例与 `demo-for-google-income-statement` 最接近，但差异很关键：这组文件移除了六步教程外壳，只保留最终完成的查看器。因此，当目标是复用完整信息图模式，而不是讲解其构建过程时，它是更好的起点。\n\n它少见的实现模式在于围绕一个合成根节点进行双向预处理。收入分支向内指，支出分支向外指，而两侧都保留百分比元数据，供连线插槽后续转换为按比例变化的 ribbon。这比许多其他示例共享的普通从左到右树形骨架更具专门性。\n\n与 `node-content-lines` 相比，这里的关键启发并不是富节点内容中的连接器定位。这里更突出的思路是自定义边渲染：示例在预处理期间存储几何比例，并在渲染时根据实时节点位置重新计算填充的流向带。\n\n与 `canvas-event` 和 `customize-fullscreen-action` 相比，这个示例使用了相似的 hook 初始化查看器基线，但服务于不同目的。那些示例强调事件埋点或外围页面行为，而这个示例则专注于经过打磨的财务叙事，包括按金额缩放的列、ribbon 流向、品牌化标注、checked 光晕以及渐变画布。\n\n## 这种模式还适用于哪里\n\n这种模式非常适合迁移到其他报表式视图中，特别是当同一份数据集既需要层级关系，又需要按比例展示流向时。示例场景包括运营成本拆分、部门预算分配、业务单元贡献图，以及可持续性或资源流向摘要。\n\n它也适用于那些需要精致叙事型图形而不是编辑界面的仪表盘。团队可以保留相同的递归预处理与连线几何方案，同时把 Google 专属的标签、颜色、logo 和说明文字替换成自己的业务领域内容。\n",false,500,1782615394714]