Style a graph from data
Use a theme for graph-wide defaults, ordered engine rules for data-driven appearance, and GRAPHORA_STYLE_ATTRIBUTE for explicit item overrides. Interaction styles provide consistent feedback for hover, focus, selection, highlight, dimming, and drag.
Open the live custom styles example or view its deployed source.
Set the base theme
Pass the theme when the engine is created or through the React Graph prop:
const theme = {
backgroundColor: '#f8fafc',
node: {
fill: '#2563eb',
stroke: '#1e3a8a',
strokeWidth: 2,
radius: 20
},
edge: {
stroke: '#64748b',
strokeWidth: 2,
arrow: { end: true, size: 8 }
},
label: {
fill: '#0f172a',
fontSize: 13,
fontWeight: '500'
}
}
const engine = createGraphEngine({ graph, renderer, layout, viewport, theme })GRAPHORA_LIGHT_THEME and GRAPHORA_DARK_THEME are immutable presets exported by @graphora/renderer. There is no runtime theme setter; replace the owning engine or React configuration when the theme changes.
Apply ordered rules without changing graph data
const engine = createGraphEngine<{ load: number }>({
graph,
renderer,
styleRules: {
nodes: [
{
id: 'load',
appearance: (node) => ({
node: { radius: 12 + (node.data?.load ?? 0) * 3 }
})
},
{
id: 'busy',
when: (node) => (node.data?.load ?? 0) > 4,
appearance: {
node: { fill: '#dc2626' },
label: { fontWeight: '700' }
}
}
],
edges: [{ id: 'connections', appearance: { edge: { strokeWidth: 2 } } }]
}
})Later rule properties win, followed by explicit item attributes and the existing interaction overlays. Each rule needs an ID unique within its node/edge array. appearance may be a constant or pure synchronous callback; null/undefined means no contribution. Node rules return node/label, edge rules edge/label. Nested arrow settings merge per property. Rules see records from the current view.
Use engine.setStyleRules(nextRules) to replace the complete configuration, engine.getStyleRules() to read its frozen snapshot, and engine.refreshStyles() when captured application state changes. Configuration and results are copied; functions retain their identity. Graph data and versions remain unchanged by rule installation or refresh. Update data through the store for automatic refresh.
Callbacks run once per graph/view snapshot and configuration, and are reused for camera, picking and interaction-only changes. Any graph/view revision may run all rules again. Do not mutate state, read resolved styles, return promises or capture camera/interaction state. Finite nonnegative dimensions, opacity in [0,1], valid built-in shapes and correctly typed tokens are required. Validate application values or return null when data is incomplete.
A failed setStyleRules/refreshStyles throws and keeps the previous accepted configuration. If later graph data makes a callback fail, rendering reports a GraphStyleEvaluationError through render:error and throws; picking returns no hit, and measurement fails. Errors identify ruleId, itemType and itemId. The last bitmap may remain visible. Replace invalid rules or fix data before rendering/exporting again. Low-level renderer bitmap export can still return its old bitmap and does not certify fresh engine state.
Rule-derived base sizes also feed createLayoutGeometry(). Paint-only updates preserve installed measurements/routes. Size, shape, stroke, visibility and font changes conservatively invalidate geometry-dependent work. Remeasure and request layout explicitly; transient interaction emphasis stays outside base measurements. Caller-supplied measurements and padding remain your responsibility.
Override individual nodes and edges
import { GRAPHORA_STYLE_ATTRIBUTE } from '@graphora/renderer'
const graph = {
nodes: [
{
id: 'gateway',
label: 'Gateway',
attributes: {
[GRAPHORA_STYLE_ATTRIBUTE]: {
shape: 'rounded-rect',
fill: '#0f766e',
radius: 26
}
}
},
{ id: 'orders', label: 'Orders' }
],
edges: [
{
id: 'gateway-orders',
source: 'gateway',
target: 'orders',
directed: true,
attributes: {
[GRAPHORA_STYLE_ATTRIBUTE]: {
shape: 'curve',
stroke: '#0f766e',
strokeWidth: 3,
arrow: { end: true, size: 10 },
label: { visible: true, fontWeight: '700' }
}
}
}
]
}Built-in node shapes are circle, square, diamond, and rounded-rect. Edges support line and curve. Node size is currently radius-based; arbitrary width and height, images, icons, and custom visual callbacks are outside the current renderer surface.
Make interaction feedback visible
const interactionStyles = {
hover: { node: { stroke: '#0284c7', strokeWidth: 4 } },
selection: {
node: { stroke: '#7c3aed', strokeWidth: 5 },
edge: { stroke: '#7c3aed', strokeWidth: 4 }
},
highlight: {
node: { opacity: 1 },
edge: { stroke: '#0f766e', strokeWidth: 4 }
},
dimmed: {
node: { opacity: 0.2 },
edge: { opacity: 0.12 }
}
}
const engine = createGraphEngine({
graph,
renderer,
layout,
viewport,
interactionStyles
})Resolution order is built-in defaults, theme overrides, item overrides, then interaction overlays. Within interaction overlays, later active states win in this order: dimmed, highlight, hover, focus, selection, drag. Shape and curve geometry remain stable while overlays are active.
The current model is a compact set of properties, not a selector or stylesheet engine. See RenderThemeInput, InteractionStyleInput, and the resolved style types in the renderer API.