JavaScript is required

Attribute-Grouped Force Clustering

This example builds a live force-layout clustering canvas that groups synthetic user nodes by Region, Major, Grade, or Gender. A custom force layouter, timer-based node appends, slot-based node rendering, and shared canvas utilities make it a strong reference for metadata-driven segmentation dashboards.

Live Attribute-Grouped Force Clustering

What This Example Builds

This 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.

How the Data Is Organized

The 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.

Each 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(...).

In 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.

How relation-graph Is Used

The 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.

After 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.

Slots 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(...).

The 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.

Key Interactions

  • The Group By buttons switch clustering between Region, Major, Grade, and Gender by rewriting every node’s currentGroupValue and restarting auto layout.
  • 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.
  • The floating control window can be dragged, minimized, and switched into the shared settings overlay.
  • The settings overlay changes wheel behavior between scroll, zoom, and none, and canvas drag behavior between selection, move, and none.
  • The same settings overlay can export the current graph as an image.
  • 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.

Key Code Fragments

This fragment shows that the example keeps the force configuration explicit and positions the built-in toolbar as part of the workspace design.

        toolBarDirection: 'h',
        toolBarPositionH: 'right',
        toolBarPositionV: 'bottom',
        defaultLineShape: RGLineShape.StandardStraight, // 使用枚举值 1
        defaultJunctionPoint: RGJunctionPoint.border,
        layout: {
            layoutName: 'force',
            maxLayoutTimes: 500,
            force_node_repulsion: 0.4,
            force_line_elastic: 0.1
        }

This fragment shows how the custom layouter carries the active grouping value into its force-calculation state.

        this.visibleNodes.forEach((thisNode: RGNode) => {
            const calcNode = {
                rgNode: thisNode,
                Fx: 0,
                Fy: 0,
                x: thisNode.x,
                y: thisNode.y,
                // ...
                myGroupBy: thisNode.data.currentGroupValue // 记录颜色用于计算
            };

This fragment shows the core clustering rule: all visible nodes repel each other, but only same-group nodes receive extra attraction.

                    // 1. 计算斥力 (原有逻辑)
                    this.addGravityByNode(__node1, __node2);

                    // 2. 自定义逻辑:只有颜色相同时才增加弹性(类似引力)
                    if (__node1.myGroupBy === __node2.myGroupBy) {
                        this.addElasticByLine(
                            __node1,
                            __node2,
                            1 // 弹性系数
                        );
                    }

This fragment shows how each generated node is encoded with multiple visible attributes before it enters the graph.

            node.data = {
                userRegion: region.code,
                userRegionIcon: region.icon,
                userMajor: majors[Math.floor(Math.random() * majors.length)],
                userGrade: randomGrade.grade,
                userGender
            };
            node.color = randomGrade.color;
            node.nodeShape = userGender === 'Male' ? RGNodeShape.rect : RGNodeShape.circle;
            node.data.currentGroupValue = node.data[groupBy.current];

This fragment shows how regrouping updates the active clustering key for the existing population and then restarts layout.

        graphInstance.getNodes().forEach(node => {
            graphInstance.updateNodeData(node, {
                currentGroupValue: node.data[groupBy.current]
            });
        });
        setTimeout(async () => {
            graphInstance.startAutoLayout();
        }, 200);

What Makes This Example Distinct

This 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.

  • 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.
  • 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.
  • 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.
  • Compared with toys-galaxy, the stylized presentation still serves an analytical purpose: the motion explains metadata grouping, not orbital choreography.
  • 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.

Where Else This Pattern Applies

This 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.

It 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.