[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:en:attribute-grouped-force-clustering":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Live Attribute-Grouped Force Clustering\n\n## What This Example Builds\n\nThis example builds a full-screen force-layout playground that behaves like a live segmentation field rather than a traditional relationship map. It starts with a few synthetic user nodes, keeps appending new users over time, and lets the viewer regroup the same population by Region, Major, Grade, or Gender. The main point is that the visible clustering comes from shared metadata values, not from visible links.\n\n## How the Data Is Organized\n\nThe data is synthetic and local. `DataLegendPanel.tsx` defines the lookup tables for grades, regions, majors, and genders, and `generateNodes(...)` reuses those same arrays to create each user record.\n\nEach node stores its categorical attributes in `node.data`, derives `node.color` from grade, derives `node.nodeShape` from gender, and writes the currently active grouping key into `node.data.currentGroupValue` before insertion. The initial load constructs an `RGJsonData`-shaped object with nodes and an empty `lines` array, but the graph is populated through `addNodes(...)` and `addLines(...)` instead of `setJsonData(...)`.\n\nIn a real application, the same structure could represent students, customers, devices, tickets, or any other entities that need to be grouped by categorical fields while staying visually comparable across several dimensions.\n\n## How relation-graph Is Used\n\nThe example is wrapped in `RGProvider`, and `RGHooks.useGraphInstance()` drives the graph lifecycle. The base `options` object enables debug mode, configures 60x60 translucent nodes, keeps the toolbar horizontal at the lower-right edge, and starts from a standard force layout with explicit repulsion and elasticity defaults.\n\nAfter the first node batch is inserted, the code stops the default auto layout and replaces the layouter with a `MyForceLayout` instance through `setLayoutor(...)`. That subclass extends `RGLayouts.ForceLayout`, copies `node.data.currentGroupValue` into its internal calculation state, and adds elastic attraction only when two nodes share the same active grouping value. The result is an edge-free cluster field whose behavior changes when the selected grouping dimension changes.\n\nSlots provide most of the visual customization. `RGSlotOnNode` renders a custom node body with a name pill, a major initial badge, and a region flag badge. `RGSlotOnView` pins the legend to the top-left corner. `RGSlotOnCanvas` adds the glowing circular `ElectricBorderCard` overlay behind the graph. The shared `DraggableWindow` helper adds the floating control window, a settings overlay that updates wheel and drag behavior through `setOptions(...)`, and screenshot export through `prepareForImageGeneration(...)` and `restoreAfterImageGeneration(...)`.\n\nThe stylesheet in `my-relation-graph.scss` completes the scene by forcing a black canvas background, styling checked nodes with a color halo, and preserving the dark technical look around the moving field.\n\n## Key Interactions\n\n- The Group By buttons switch clustering between Region, Major, Grade, and Gender by rewriting every node's `currentGroupValue` and restarting auto layout.\n- A recurring timer appends one new synthetic user at a time until the graph grows beyond roughly 300 nodes, so the scene keeps reforming after first paint.\n- The floating control window can be dragged, minimized, and switched into the shared settings overlay.\n- The settings overlay changes wheel behavior between `scroll`, `zoom`, and `none`, and canvas drag behavior between `selection`, `move`, and `none`.\n- The same settings overlay can export the current graph as an image.\n- The built-in toolbar remains available for standard graph actions, and the custom layouter is installed in a way that keeps relation-graph's layout controls usable.\n\n## Key Code Fragments\n\nThis fragment shows that the example keeps the force configuration explicit and positions the built-in toolbar as part of the workspace design.\n\n```tsx\n        toolBarDirection: 'h',\n        toolBarPositionH: 'right',\n        toolBarPositionV: 'bottom',\n        defaultLineShape: RGLineShape.StandardStraight, // 使用枚举值 1\n        defaultJunctionPoint: RGJunctionPoint.border,\n        layout: {\n            layoutName: 'force',\n            maxLayoutTimes: 500,\n            force_node_repulsion: 0.4,\n            force_line_elastic: 0.1\n        }\n```\n\nThis fragment shows how the custom layouter carries the active grouping value into its force-calculation state.\n\n```ts\n        this.visibleNodes.forEach((thisNode: RGNode) => {\n            const calcNode = {\n                rgNode: thisNode,\n                Fx: 0,\n                Fy: 0,\n                x: thisNode.x,\n                y: thisNode.y,\n                // ...\n                myGroupBy: thisNode.data.currentGroupValue // 记录颜色用于计算\n            };\n```\n\nThis fragment shows the core clustering rule: all visible nodes repel each other, but only same-group nodes receive extra attraction.\n\n```ts\n                    // 1. 计算斥力 (原有逻辑)\n                    this.addGravityByNode(__node1, __node2);\n\n                    // 2. 自定义逻辑：只有颜色相同时才增加弹性（类似引力）\n                    if (__node1.myGroupBy === __node2.myGroupBy) {\n                        this.addElasticByLine(\n                            __node1,\n                            __node2,\n                            1 // 弹性系数\n                        );\n                    }\n```\n\nThis fragment shows how each generated node is encoded with multiple visible attributes before it enters the graph.\n\n```tsx\n            node.data = {\n                userRegion: region.code,\n                userRegionIcon: region.icon,\n                userMajor: majors[Math.floor(Math.random() * majors.length)],\n                userGrade: randomGrade.grade,\n                userGender\n            };\n            node.color = randomGrade.color;\n            node.nodeShape = userGender === 'Male' ? RGNodeShape.rect : RGNodeShape.circle;\n            node.data.currentGroupValue = node.data[groupBy.current];\n```\n\nThis fragment shows how regrouping updates the active clustering key for the existing population and then restarts layout.\n\n```tsx\n        graphInstance.getNodes().forEach(node => {\n            graphInstance.updateNodeData(node, {\n                currentGroupValue: node.data[groupBy.current]\n            });\n        });\n        setTimeout(async () => {\n            graphInstance.startAutoLayout();\n        }, 200);\n```\n\n## What Makes This Example Distinct\n\nThis example is not notable because it is the only custom force-layout demo. Its value comes from a specific combination that the comparison data highlights.\n\n- Compared with `force-classifier`, it extends the same attribute-switch clustering idea into a stream-like scene where new nodes keep arriving after first paint, and it adds a stronger visual shell through the canvas overlay.\n- Compared with `customer-layout-force` and `customer-layout-force-circular`, it emphasizes semantic grouping of synthetic user metadata rather than color-only clustering, slider tuning, or orbit-style constraints.\n- Compared with `expand-forever`, graph growth is autonomous and edge-free. The motion comes from clustering physics and population growth, not from lazy tree expansion.\n- Compared with `toys-galaxy`, the stylized presentation still serves an analytical purpose: the motion explains metadata grouping, not orbital choreography.\n- The strongest combination here is metadata-driven custom force physics, timer-based node growth, edge-free clustering, multi-attribute badge nodes, and shared runtime canvas utilities in one view.\n\n## Where Else This Pattern Applies\n\nThis pattern can be migrated to dashboards that need to show how a population redistributes when the grouping rule changes. Examples include student cohorts regrouped by campus, major, or year; customers regrouped by region, segment, or lifecycle stage; devices regrouped by site, status, or firmware family; and job candidates regrouped by source, role family, or recruiting stage.\n\nIt also fits animated monitoring surfaces where new entities keep arriving and should immediately join the currently selected cluster logic. In those cases, the visible links can remain hidden, while node metadata, slot-based badges, and a custom force rule carry most of the meaning.\n",false,500,1782615401434]