Groups draw a frame behind (or around) blocks: swimlanes, clusters by type, or collapsible regions. This page is organized by what you want to do; implementation details appear where they help you wire the behavior.
| I want to… | Start here |
|---|---|
| Paint fixed zones (swimlanes) that don’t move | Fixed zones |
Wrap blocks that share a field (e.g. type, group) in a live-updating frame |
Automatic grouping |
| Drag the frame and move inner blocks together | Drag frame + blocks |
| Collapse a group to a compact header and keep connections sensible | Collapsible groups |
| Shift other nodes when collapse changes height/width | Reflow on collapse |
| Control where the collapsed header sits or its size | Custom collapse rect |
| Draw extra labels or chrome on the frame | Custom group component |
Goal: Background areas with borders (e.g. “Team A / Team B”), while blocks move freely inside.
Do this:
- Add
BlockGroupswithdraggable: false. - Call
setGroupswith explicitrectandstyle.
import { Graph, BlockGroups } from "@gravity-ui/graph";
const areas = graph.addLayer(BlockGroups, {
draggable: false,
});
areas.setGroups([
{
id: "area1",
rect: { x: 0, y: 0, width: 800, height: 400 },
style: {
background: "rgba(100, 149, 237, 0.1)",
border: "rgba(100, 149, 237, 0.3)",
},
},
]);Why: Manual rect means you own position and size. Automatic grouping (below) recomputes rects from blocks; for static lanes you usually want full control.
See also: Manual Groups story — fixed colored zones.
Goal: Whenever blocks move, the group border tight-wraps a set of blocks (with optional padding), e.g. all blocks with group: "cluster-1".
Do this:
- Call the static helper
BlockGroups.withBlockGrouping({ groupingFn, mapToGroups }). It returns a layer constructor (a new subclass ofBlockGroups); pass that constructor tograph.addLayer(...), notBlockGroupsitself. groupingFn(blocks)receives allBlockState[]from the store and must returnRecord<string, BlockState[]>— one entry per group id, values are the member blocks.- For each entry, the library computes
rectwithgetBlocksRectfrom the blocks in that bucket, then callsmapToGroups(key, { blocks, rect }). You usually passrectthrough (or inflate it with padding); useblockswhen your group metadata depends on membership.
import { BlockGroups, Group } from "@gravity-ui/graph";
import type { BlockState } from "@gravity-ui/graph";
const MyGroup = Group.define({
style: {
background: "rgba(0, 200, 200, 0.2)",
border: "rgba(200, 200, 0, 0.2)",
},
});
const AutoGroups = BlockGroups.withBlockGrouping({
groupingFn: (blocks: BlockState[]) => {
const byGroup: Record<string, BlockState[]> = {};
for (const block of blocks) {
const groupId = block.$state.value.group;
if (!groupId) {
continue;
}
if (!byGroup[groupId]) {
byGroup[groupId] = [];
}
byGroup[groupId].push(block);
}
return byGroup;
},
mapToGroups: (groupId, { rect }) => ({
id: groupId,
// `rect` is already the axis-aligned bounds of that bucket’s blocks (getBlocksRect in the layer).
// Add padding here if needed, e.g. { x: rect.x - 8, y: rect.y - 8, ... }.
rect,
component: MyGroup,
}),
});
graph.addLayer(AutoGroups, { draggable: true });Why: A computed signal runs mapToGroups whenever blocks change, then calls setGroups on the layer — the frame stays aligned with data.
See also: Basic Groups, Large graph.
Goal: User drags the group border; blocks inside move by the same delta (not only when dragging each block).
Do this:
- Use automatic grouping (
withBlockGrouping) so the layer knows which blocks belong to which group. - Enable both:
graph.addLayer(AutoGroups, {
draggable: true,
updateBlocksOnDrag: true,
});Why: updateBlocksOnDrag only applies when the layer can resolve group membership from the automatic grouping pipeline.
Goal: A group can collapse to a small header: inner blocks are hidden visually, but stay in the store; connections can attach to the group edges instead of hidden block ports (via port delegation — see Connection system).
Do this:
- Use
CollapsibleGroupas the groupcomponentinmapToGroups(or in manualsetGroups). - Put
group: "<groupId>"on eachTBlockthat belongs to that frame. - There is no built-in collapse button. Subscribe to an interaction (commonly
dblclickon the group) and callcollapse()/expand()on theCollapsibleGroupinstance.
import { CollapsibleGroup } from "@gravity-ui/graph";
// inside mapToGroups, or manual group object:
{
id: "my-group",
rect: { /* ... */ },
component: CollapsibleGroup,
collapseDirection: { x: "center", y: "center" },
}// React: toggle on double-click
useGraphEvent(graph, "dblclick", ({ target }) => {
if (target instanceof CollapsibleGroup) {
if (target.isCollapsed()) {
target.expand();
} else {
target.collapse();
}
}
});Why: Collapse state lives on the group; programmatic API keeps the same behavior whether you use a toolbar, keyboard shortcut, or canvas double-click.
See also: Collapsible Groups story.
Goal: When a group collapses or expands, something else on the canvas should move (e.g. nodes below shift up, or side panels reflow).
Do this:
Listen for the **group-collapse-change** event. It fires before the rect change is applied; you can **preventDefault()** to cancel the transition.
Payload includes groupId, collapsed, currentRect, and nextRect — use the delta between currentRect and nextRect to update other block positions or run your layout.
useGraphEvent(graph, "group-collapse-change", (detail, event) => {
const { groupId, currentRect, nextRect } = detail;
// Example: nudge other blocks by (nextRect.height - currentRect.height) on Y
});Why: The graph does not auto-layout unrelated nodes; you decide what “reflow” means for your app.
Goal: Control where the collapsed strip appears (e.g. centered title bar) or exact width/height.
Do this:
**collapseDirection** — pins the default 200×48 header tostart|center|endon each axis (seeTCollapsibleGroupin source).**getCollapseRect(group, expandedRect)** — full control: return anyTRectfor the collapsed hit-test and draw area.
Why: Default behavior is a small header; custom getCollapseRect is for branded headers or wide title bars.
Goal: Extra text, badges, or drawing beyond the default fill/stroke.
Do this:
- Extend
Group(orCollapsibleGroup) and overriderender()(aftersuper.render()if you keep base chrome). - Pass
groupComponent: YourGrouptograph.addLayer(BlockGroups, { groupComponent: YourGroup, ... }). - For typed extra fields, extend
TGroup/TCollapsibleGroupand pass those fields fromsetGroups/mapToGroups.
See also: Extended Groups story.
Example shape (abbreviated):
import { Graph, BlockGroups, Group } from "@gravity-ui/graph";
import type { TGroup } from "@gravity-ui/graph";
interface ExtendedTGroup extends TGroup {
description?: string;
}
class CustomGroup extends Group<ExtendedTGroup> {
protected override render(): void {
super.render();
const ctx = this.context.ctx;
const rect = this.getRect();
if (this.state.description) {
ctx.font = "12px Arial";
ctx.fillStyle = this.style.textColor;
ctx.fillText(this.state.description, rect.x + 10, rect.y + 25);
}
}
}
const groups = graph.addLayer(BlockGroups, {
draggable: false,
groupComponent: CustomGroup,
});
groups.setGroups([
{
id: "group1",
description: "Contains critical blocks",
rect: { x: 0, y: 0, width: 800, height: 400 },
},
]);Common fields on group data / Group.define:
// Visuals
{
background: "rgba(100, 100, 100, 0.1)",
border: "rgba(100, 100, 100, 0.3)",
borderWidth: 2,
selectedBackground: "rgba(100, 100, 100, 1)",
selectedBorder: "rgba(100, 100, 100, 1)",
textColor: "rgba(0, 0, 0, 1)",
}
// Padding inside the frame (top, right, bottom, left)
{
padding: [20, 20, 20, 20],
}Layer props:
{
draggable: true, // user can drag the group frame
updateBlocksOnDrag: true, // move member blocks with the frame (automatic grouping)
}// Replace all groups
groups.setGroups(groups: TGroup[]): void;
// Patch by id
groups.updateGroups(groups: Partial<TGroup> & { id: string }[]): void;TGroup always includes id and rect; optional selected, style, and component-specific fields (e.g. collapsed on TCollapsibleGroup).
| Story | What it demonstrates |
|---|---|
| default | Automatic groups from block properties |
| large | Many blocks / groups |
| manual | Fixed non-draggable zones |
| extended | Custom group class + extra fields |
| collapsible | CollapsibleGroup, dblclick, group-collapse-change |
- Connection system — ports, and port delegation when groups collapse.