[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:en:force-classifier":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Attribute-Switchable Force Clustering Without Relationship Lines\n\n## What This Example Builds\n\nThis example renders an edge-free field of 100 synthetic user nodes and lets the user regroup that field by one attribute at a time. The visible controls switch the active grouping between region, major, grade, and gender, and the force layout recomputes the clusters after each change. Each node is rendered as a compact person badge with a name, a major initial, a region flag, a grade color, and a gender-based shape, while a floating legend explains those encodings.\n\nThe most important idea is that the graph is behaving like a classifier playground rather than a relationship diagram. There are no visible links. The layout itself becomes the encoding, and the selected attribute controls which nodes attract each other.\n\n## How the Data Is Organized\n\nThe data is a flat runtime-generated node list, not a predefined graph dataset loaded from JSON. `initializeGraph()` creates 100 random user records, assigns them random coordinates, and pushes them into the graph with `addNodes()` and `addLines()` instead of `setJsonData()`. The line list is intentionally empty.\n\nEach node carries its display text plus a metadata object with `userRegion`, `userRegionIcon`, `userMajor`, `userGrade`, `userGender`, and `currentGroupValue`. The first five fields describe the entity. `currentGroupValue` is a derived field that mirrors the currently selected grouping key and is the only attribute the custom force layout needs to decide whether two nodes should attract each other.\n\nBefore the custom layout runs, the example also maps metadata into visual properties: grade becomes `node.color`, gender becomes `node.nodeShape`, and the initial grouping is copied into `currentGroupValue`. In a real application, the same structure can represent employees, customers, students, devices, or cases that need to be regrouped by different categorical facets without changing the underlying entity list.\n\n## How relation-graph Is Used\n\nThe example is wrapped in `RGProvider`, then uses `RGHooks.useGraphInstance()` to control the graph imperatively. `RelationGraph` is mounted with force-layout defaults, transparent white node styling, a straight-line configuration, and the toolbar placed at the lower right. Even though the graph has no meaningful edges, the standard graph runtime still provides the viewport, toolbar, slots, and layout lifecycle.\n\nThe main customization point is layout replacement. After nodes are added, the code instantiates `MyForceLayout`, a subclass of `RGLayouts.ForceLayout`, and installs it with `setLayoutor(myLayout, true, true)`. That keeps the relation-graph lifecycle intact while swapping in different force rules. The graph instance API then drives initial placement and later regrouping through `stopAutoLayout()`, `startAutoLayout()`, `getNodes()`, `updateNodeData()`, `moveToCenter()`, `setZoom()`, `sleep()`, and `zoomToFit()`.\n\nSlots do most of the rendering work. `RGSlotOnNode` turns each node into a multi-attribute badge instead of the default node body, and `RGSlotOnView` mounts `DataLegendPanel` directly on the canvas so the legend stays visible while the graph moves underneath it. The shared `DraggableWindow` component adds a floating control surface with drag, minimize, canvas settings, and image export behavior. Local SCSS styles the active grouping buttons and overrides checked node and line styles for the relation-graph shell.\n\n## Key Interactions\n\nThe primary interaction is the group-by button row in the floating window. Clicking one of those buttons updates `currentGroupBy`, rewrites every node's `currentGroupValue`, stops the current force pass, and restarts auto layout so the cloud reforms around the newly selected category.\n\nThe floating window itself is interactive: it can be dragged to a new screen position, minimized, and switched into a settings mode. That settings panel changes wheel behavior between scroll, zoom, and none, changes canvas drag behavior between selection, move, and none, and exposes an image export action. The export flow uses relation-graph's image-preparation hooks before capturing the canvas DOM.\n\nThe graph area is otherwise intentionally simple. There is no edge editing, no link labels to inspect, and no node click workflow. The experience is centered on regrouping the same entity set and observing how the layout responds.\n\n## Key Code Fragments\n\nThis initialization path shows that the example bypasses `setJsonData()` and installs a custom layout on the live graph instance.\n\n```tsx\nconst data: RGJsonData = {\n  rootId: 'a',\n  nodes: randomUsers,\n  lines: []\n};\ngraphInstance.addNodes(data.nodes);\ngraphInstance.addLines(data.lines);\ngraphInstance.stopAutoLayout();\n\nconst myLayout = new MyForceLayout(\n  { maxLayoutTimes: Number.MAX_SAFE_INTEGER, force_node_repulsion: 0.4, force_line_elastic: 0.1 },\n  graphInstance.getOptions(),\n  graphInstance\n);\ngraphInstance.setLayoutor(myLayout, true, true);\n```\n\nEach generated node carries both categorical metadata and visual defaults that the layout and slots reuse later.\n\n```tsx\nconst node: JsonNode = {\n  id: 'u-' + graphInstance.generateNewNodeId(),\n  text: userName,\n  x: Math.random() * 300,\n  y: Math.random() * 300,\n};\nnode.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};\nnode.color = randomGrade.color;\nnode.nodeShape = userGender === 'Male' ? RGNodeShape.rect : RGNodeShape.circle;\n```\n\nRegrouping is implemented as a metadata rewrite plus a layout restart, not as a full data reload.\n\n```tsx\ngraphInstance.stopAutoLayout();\ngraphInstance.getNodes().forEach(node => {\n  graphInstance.updateNodeData(node, {\n    currentGroupValue: node.data[currentGroupBy]\n  });\n});\nsetTimeout(async () => {\n  graphInstance.startAutoLayout();\n}, 200);\n```\n\nThe custom layout adds attraction only when two nodes share the currently selected grouping value.\n\n```ts\nthis.addGravityByNode(__node1, __node2);\n\nif (__node1.myGroupBy === __node2.myGroupBy) {\n  this.addElasticByLine(\n    __node1,\n    __node2,\n    1\n  );\n}\n```\n\nThe node slot turns one record into a compact badge with text, a major marker, and a region icon.\n\n```tsx\n\u003CRGSlotOnNode>\n  {({ node }) => (\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      \u003Cdiv className=\"absolute left-[-3px] top-[-3px] h-4 w-4 border border-gray-900 bg-gray-600 rounded text-white text-sm flex place-items-center justify-center\">\n        {node.data.userMajor.substring(0, 1).toUpperCase()}\n      \u003C/div>\n      \u003Cdiv className=\"absolute right-[-3px] bottom-[-3px] text-xl\">{node.data.userRegionIcon}\u003C/div>\n    \u003C/div>\n  )}\n\u003C/RGSlotOnNode>\n```\n\nThe shared settings panel shows how the example switches canvas behavior and exports the current graph image.\n\n```tsx\n\u003CSettingRow\n  label=\"Wheel Event:\"\n  value={wheelMode}\n  onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}\n/>\n\u003CSettingRow\n  label=\"Canvas Drag Event:\"\n  value={dragMode}\n  onChange={(newValue: string) => { graphInstance.setOptions({ dragEventAction: newValue }); }}\n/>\n\u003CSimpleUIButton onClick={downloadImage}>Download Image\u003C/SimpleUIButton>\n```\n\n## What Makes This Example Distinct\n\nThe prepared `comparison` file is empty for this example, so the safest distinctness claims come from `doc-context`. That context marks the following combination as rare, and in several cases very rare, within the example set: synthetic node generation, direct graph loading with `addNodes()` and `addLines()` instead of `setJsonData()`, a custom `MyForceLayout extends RGLayouts.ForceLayout`, runtime regrouping through `updateNodeData()`, and a view that stays edge-free while clustering by a selected attribute.\n\nThe `views` labels narrow the scene further: this example is explicitly categorized as an \"attribute clustering playground\" with \"attribute-rich people badges\", an \"edge-free clustered node field\", and a \"legend-guided light canvas\". That makes it more specialized than a general force-layout demo. The emphasis is not on relationship topology. It is on how one entity set can reorganize itself around different classification dimensions.\n\nThe nearest-example list also suggests a useful positioning. `force-classifier-pro` is the closest structural neighbor because it shares the custom force-layout playground theme, while examples such as `css-theme` and `node-drag-handle` overlap more on the reusable floating utility window and the general `RelationGraph` shell. So this example is the cleaner starting point when the goal is to study attribute-driven reclustering logic itself.\n\n## Where Else This Pattern Applies\n\nThis pattern transfers well to any exploratory view where entities need to regroup by one selected facet at a time: employees by office, team, or level; students by region, major, or year; customers by market, segment, or tier; or devices by site, type, and status.\n\nIt is also useful when explicit edges would add more noise than value. A lightweight force field plus a switchable grouping key can show distribution, density, and cluster boundaries while keeping the data model as a simple flat node list.\n",false,500,1782615401132]