Infra dependency map
The clustering payoff at scale. A service architecture is grouped into tiers, each a collapsible cluster — so the map opens as five boxes and the high-level dependencies between them instead of a hairball. A link between two services in different tiers folds into a single arrow between their boxes; expand a tier to drill into its services and watch those edges re-target as the boxes open. Directed edges show which service calls which (thickness tracks traffic, dashed amber marks async queue links), and colour tracks the tier.
Click a cluster's + / − badge to toggle it, or use the buttons.
js
// import { ColorPaletteMapper } from 'pivotick'
// Colourblind-safe palette, one hue per tier (a tier box and its children share it).
const palette = new ColorPaletteMapper('okabe-ito')
const options = {
render: {
// Tiers are expandable out of the box: a +/- badge appears on each box and a
// click toggles it (see the Expand/collapse tab for the programmatic path).
enableNodeExpansion: true,
defaultNodeStyle: {
size: 22,
strokeColor: '#ffffff',
textColor: '#1e293b',
text: (node) => node.getData()?.label,
textVerticalShift: -1.8,
color: (node) => palette.getColor(node.getData()?.tier)
},
defaultEdgeStyle: {
curveStyle: 'straight',
markerEnd: 'arrow',
// Async links (to the queue / telemetry) flow as dashed amber.
strokeColor: (edge) => (edge.getData()?.async ? '#E69F00' : '#94a3b8'),
strokeWidth: (edge) => 1 + (edge.getData()?.weight ?? 1) * 0.6,
dashed: (edge) => !!edge.getData()?.async,
animateDash: true,
opacity: 0.85
}
},
simulation: {
// Gentle repulsion so the tier boxes spread a little without the
// weakly-linked ones drifting off.
d3ManyBodyStrength: -140,
d3LinkDistance: 55
}
}js
// Expand or collapse every tier at once. toggleExpandNode takes a Node instance,
// so resolve the id first; node.expanded tells you the current state.
const tiers = ['ingress', 'app', 'core', 'datastores', 'observability']
function setAllExpanded(graph, expanded) {
tiers.forEach((id) => {
const node = graph.getMutableNode(id)
if (node && !!node.expanded !== expanded) graph.toggleExpandNode(node)
})
}js
// A service dependency map where *every tier is a cluster* — a node with
// `children`. Collapsed, the map is just the tiers and the dependencies between
// them (box → box); a link between two services in different tiers folds into a
// single arrow between their boxes. Expand a tier to drill into its services and
// see their real edges re-target as the boxes open. All edges are declared once,
// at the top level, between the real services — the boxes are derived.
const data = {
nodes: [
{
id: 'ingress', data: { label: 'Ingress', tier: 'Ingress' }, expanded: false,
children: [
{ id: 'cdn', data: { label: 'CDN', tier: 'Ingress' } },
{ id: 'lb', data: { label: 'Load balancer', tier: 'Ingress' } }
]
},
{
id: 'app', data: { label: 'App tier', tier: 'App' }, expanded: false,
children: [
{ id: 'web', data: { label: 'Web app', tier: 'App' } },
{ id: 'gateway', data: { label: 'API gateway', tier: 'App' } },
{ id: 'admin', data: { label: 'Admin', tier: 'App' } }
]
},
{
id: 'core', data: { label: 'Core services', tier: 'Core' }, expanded: false,
children: [
{ id: 'auth', data: { label: 'Auth', tier: 'Core' } },
{ id: 'orders', data: { label: 'Orders', tier: 'Core' } },
{ id: 'payments', data: { label: 'Payments', tier: 'Core' } }
]
},
{
id: 'datastores', data: { label: 'Data stores', tier: 'Data' }, expanded: false,
children: [
{ id: 'postgres', data: { label: 'Postgres', tier: 'Data' } },
{ id: 'redis', data: { label: 'Redis', tier: 'Data' } },
{ id: 'kafka', data: { label: 'Kafka', tier: 'Data' } },
{ id: 'blob', data: { label: 'Blob store', tier: 'Data' } }
]
},
{
id: 'observability', data: { label: 'Observability', tier: 'Observability' }, expanded: false,
children: [
{ id: 'metrics', data: { label: 'Metrics', tier: 'Observability' } },
{ id: 'logs', data: { label: 'Logs', tier: 'Observability' } },
{ id: 'traces', data: { label: 'Traces', tier: 'Observability' } }
]
}
],
edges: [
// Ingress → App. `weight` thickens the link once the tiers are open.
{ from: 'cdn', to: 'web', data: { weight: 3 } },
{ from: 'lb', to: 'web', data: { weight: 3 } },
{ from: 'lb', to: 'gateway', data: { weight: 2 } },
// App → Core.
{ from: 'web', to: 'auth', data: { weight: 2 } },
{ from: 'web', to: 'orders', data: { weight: 3 } },
{ from: 'admin', to: 'auth', data: { weight: 1 } },
{ from: 'gateway', to: 'orders', data: { weight: 2 } },
{ from: 'gateway', to: 'payments', data: { weight: 2 } },
// Inside Core (only visible once the Core box is open).
{ from: 'orders', to: 'payments', data: { weight: 3 } },
// Core → Data stores.
{ from: 'orders', to: 'postgres', data: { weight: 3 } },
{ from: 'payments', to: 'postgres', data: { weight: 2 } },
{ from: 'auth', to: 'redis', data: { weight: 2 } },
{ from: 'web', to: 'blob', data: { weight: 1 } }, // App → Data stores
{ from: 'payments', to: 'kafka', data: { weight: 2, async: true } },
{ from: 'orders', to: 'kafka', data: { weight: 1, async: true } },
// → Observability (async telemetry).
{ from: 'orders', to: 'metrics', data: { weight: 1, async: true } },
{ from: 'payments', to: 'logs', data: { weight: 1, async: true } },
{ from: 'gateway', to: 'traces', data: { weight: 1, async: true } }
]
}