Skip to content

Legend

Built-in canvas legend mapping node types to colors and shapes. Manual or auto-generated.

Manual Legend

TypeScript
const app = create(container, {
  nodes: [...],
  edges: [...],
  layout: 'force',
  theme: 'dark',
  legend: {
    title: 'Node Types',
    position: 'bottom-left',
    items: [
      { label: 'Server',   color: '#3b82f6', shape: 'rect' },
      { label: 'Database', color: '#8b5cf6', shape: 'circle' },
      { label: 'Client',   color: '#10b981', shape: 'diamond' },
      { label: 'Gateway',  color: '#f59e0b', shape: 'hexagon' },
    ],
  },
});

Auto-Generated Legend

TypeScript
const app = create(container, {
  nodes: [...],
  edges: [...],
  layout: 'force',
  legend: {
    groupBy: 'data.type',
    position: 'bottom-left',
    title: 'Categories',
  },
});

Automatically lists every unique value from the specified field with its assigned color.

Options

OptionTypeDefaultDescription
titlestring'Legend'Title text.
positionstring'top-left''top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
itemsLegendItem[][]Manual legend entries.
groupBystringundefinedDot-path or callback for auto-grouping nodes by category.
groupEdgesBystringundefinedDot-path or callback for auto-grouping edges. See Edge-type filter.
maxItemsnumber10Max entries (extra collapsed as "+ N more").
collapsedbooleanfalseStart collapsed.
onItemClick(item, groupKey) => voidundefinedClick handler for legend items. Always wins over clickToToggle.
clickToTogglebooleantrueWhen true and onItemClick is unset, clicking a row toggles visibility of all nodes in that group.
fadedNodeOpacitynumber0.25Opacity applied to nodes whose visible incident edge count drops to zero from edge-type filtering.

LegendItem

PropertyTypeDescription
labelstringDisplay text.
colorstringColor swatch.
shapestringShape indicator.
countnumberOptional count beside label.

Interactive Legend

TypeScript
const app = create(container, {
  nodes: [...],
  edges: [...],
  legend: {
    groupBy: 'data.type',
    position: 'bottom-left',
    onItemClick: (item) => {
      app.graph.highlightByPredicate(
        (node) => node.data.type === item.label,
        { dimOpacity: 0.1 }
      );
      setTimeout(() => app.graph.resetHighlight(), 3000);
    },
  },
});

Clickable Legend & Pinned Focus

By default (clickToToggle: true), clicking a legend row toggles visibility of every node in that group. Toggling a group off also hides every edge with either endpoint in that group — even cross-category edges. A second click restores the group; the legend tracks an internal legend-hidden set that is separate from any explicit visible: false the host code may have set, so toggling off-then-on never accidentally un-hides user-hidden nodes.

An explicit onItemClick handler always wins over the default toggle. Use it for spotlights or to drive your own focus behavior.

TypeScript
// Toggle a category programmatically
app.legend.toggleCategory('database');

// Pinned-focus events fire on click (pinned) and on second click / outside-click (released)
app.graph.on('focus:pin', ({ nodeId, hops }) => {
  console.log('Pinned focus on', nodeId, 'with', hops, 'hops of context');
});
app.graph.on('focus:release', () => {
  console.log('Focus released');
});

Edge-Type Filter

Set groupEdgesBy (or rely on auto-detect from type → category → role → kind → class) to grow a second legend section with one row per edge category. Clicking an edge category toggles all edges of that group between visible and faded — edges don't disappear, they fade to fadedNodeOpacity (default 0.25) so the graph keeps its spatial structure.

TypeScript
app.legend.toggleEdgeCategory('auth');

app.graph.on('focus:edge-filter', ({ hiddenTypes }) => {
  console.log('Hidden edge categories:', hiddenTypes);
});

See Filtering → Edge-type filter for the full behavior, including how it interacts with hideOrphans.

Minimap Clearance

When a Minimap is mounted on the same vertical edge as the legend (e.g. both bottom-left), the legend body's max-height self-shrinks to keep them from overlapping. The cap is floored at 80 px, so very short containers produce a scrollable legend rather than a clipped minimap. On opposite edges, the legend uses its CSS default (240 px, or 180 px in compact mode).

The sizing helper is also exported as a pure function for testing:

TypeScript
import { computeLegendBodyMaxHeight } from '@topokit/renderer-canvas';

const cap = computeLegendBodyMaxHeight({
  legendPosition: 'bottom-left',
  minimapPosition: 'bottom-left',
  containerHeight: 600,
  minimapHeight: 120,
  headerHeight: 28,
});
// → number (px) | null. Null when CSS default already prevents overlap.

Runtime Updates

TypeScript
// Update legend
app.setLegend({
  title: 'Updated Legend',
  items: [
    { label: 'Active',   color: '#10b981' },
    { label: 'Inactive', color: '#6b7280' },
  ],
});

// Remove legend
app.setLegend(null);

Next Steps