Depth and z-order
Sigma v4 controls rendering order through two style properties that work together:
depth: a named bucket fromprimitives.depthLayers. Coarse, categorical.zIndex: a numeric sub-order within a bucket. Fine, continuous.
Everything renders into the same WebGL canvas. There is no separate node-layer and edge-layer pass: each named bucket is painted in turn, and within a bucket sigma paints sub-elements (edges, labels, nodes) in a fixed order.
The stack
The default depthLayers declaration is ["edges", "nodes", "topEdges", "topNodes"], painted back-to-front:
top of the stack ┌─ topNodes ── hovered nodes + their labels │ topEdges ── hovered / highlighted edges + their labels │ nodes ── regular nodes + their labelsbottom of stack └─ edges ── regular edges + their labelsYou can declare any number of buckets, in any order, with any names:
primitives: { depthLayers: ["edges", "nodes", "activeEdges", "activeNodes", "topNodes"],}A node or edge is placed into a bucket with the depth style property, which must reference a name from depthLayers:
styles: { nodes: [{ whenState: "isActive", then: { depth: "activeNodes" } }],}Sub-order within a bucket: zIndex
zIndex orders items inside a single bucket. Items in a later bucket always paint above items in an earlier one,
regardless of zIndex. Inside one bucket, higher zIndex paints on top.
zIndex is a continuous numeric sort key. Any finite value works (negative, fractional, large). Items in a bucket are
sorted by zIndex on the CPU during processing.
Use zIndex when you want a continuous ordering (e.g. “sort nodes by degree”). Use depth when you want a
categorical bucket that interactions can promote items into.
Paint order within a bucket
For each bucket, sigma paints in this fixed order:
- Custom WebGL layer bound to this bucket (see WebGL layers)
- Edges in this bucket
- Edge label backgrounds, then edge labels (
labelDepth === bucket) - Backdrops for nodes whose
depth === bucket - Nodes in this bucket
- Label attachments (
labelDepth === bucket) - Node label backgrounds, then node labels (
labelDepth === bucket)
Two consequences worth highlighting:
- Edge labels render below nodes of the same bucket. If you want a label on top of unrelated nodes, route the label
into a higher bucket with
labelDepth. - Nodes always render above edges of the same bucket. To put an edge above a node, the edge needs to live in a
higher bucket (this is the main reason
topEdgessits abovenodesin the default declaration).
Labels in a different bucket: labelDepth
Each item has two depth properties: depth for the geometry, labelDepth for the label. By default labelDepth
mirrors depth, so if a rule sets depth without labelDepth, the label follows.
Override labelDepth to detach the label from its parent:
styles: { nodes: [ // Nodes geometry stays in "nodes", but node labels paint above everything else: { labelDepth: "topNodes" }, ],}This is the v4 answer to the recurring v3 issue of “labels hidden under unrelated nodes”.
What sigma sets by default
The built-in DEFAULT_STYLES already does the most common interaction:
| State | Effect |
|---|---|
| Hovered node | depth: "topNodes", zIndex: 1 |
| Hovered or highlighted edge | depth: "topEdges", zIndex: 1 |
| Otherwise | depth: "nodes" / "edges", zIndex: 0 |
If you spread DEFAULT_STYLES.nodes into your rules, you keep this behavior for free. Add further rules to promote
items into your own buckets.
Cost model
- Each named bucket costs one node draw call and one edge draw call (skipped when the bucket is empty; occasionally split into a few contiguous fragments when items have recently moved between buckets).
- Changing an item’s
depthmoves it between buckets in place, without re-processing the rest of the graph. - Changing an item’s
zIndexre-sorts the bucket and triggers a full reprocess, so treatzIndexas a mostly-static order, not a per-frame interaction axis. - Adding buckets is cheap. Adding hundreds of buckets is not. Keep
depthLayersto a handful of named values, and usezIndexfor the fine-grained order.
Choosing between depth and zIndex
| Question | Answer |
|---|---|
| ”Bring some items on top during an interaction” | depth |
| ”Render some edges above some nodes” | depth (separate buckets) |
| “Render labels above nodes from other clusters” | labelDepth |
| ”Sort nodes by a numeric attribute (e.g. degree)“ | zIndex |
| ”Render type-A nodes always above type-B nodes” | depth (a bucket per type) |
| “I have one item I always want on top, regardless of state” | depth: "topNodes" |
Common recipes
Pin selected items on top
primitives: { depthLayers: ["edges", "nodes", "topEdges", "topNodes"],},styles: { nodes: [ DEFAULT_STYLES.nodes, { whenState: "isSelected", then: { depth: "topNodes" } }, ],}Sort nodes by degree
styles: { nodes: [ { zIndex: { attribute: "degree" }, }, ],}Any numeric attribute works directly as a zIndex — no range to bind, no clamping.
Labels above unrelated nodes
primitives: { depthLayers: ["edges", "nodes", "topNodes"],},styles: { nodes: [ // Nodes geometry stays in "nodes" so other nodes can still overlap: { depth: "nodes", labelDepth: "topNodes" }, ],}