Skip to main content

Traffic by interface

The same six hours as the mirrored traffic chart, asked a different question: of everything crossing this router, which interface is carrying it? A stacked area answers composition — the total is still the top edge, but now every slab under it has a name.

The chart

Hover to read the running totals; the gap between two adjacent numbers is one interface's contribution at that moment.

The data

The CERN border-router capture described in full on the network traffic pagecern773-cr6, real measured telemetry, top 7 SAPs (99.87% of all bytes) at a 1-minute cadence in Gbps.

This chart shows inbound by default, because that's where the composition question has an interesting answer: four interfaces carry 10% or more of it. Outbound is 82.5% a single SAP (111-lag-3-522), so its stack is one slab and six slivers — a true picture, and a poor demonstration of stacking. Pass direction="out" to see it anyway; being able to see that one interface is the site's egress is a real operational finding.

The two directions don't even share an ordering. 111-lag-3-522 leads outbound with 82.5% of the mean but carries only 18.0% of the inbound, where 7061-lag-3-3525 leads.

Build it

Stacking is a data operation, not a chart prop. An area chart fills from a baseline to a column, so a stack is drawn from cumulative columns: stack0 is the busiest interface alone, stack1 is that plus the second, and the last column is the site total.

import { stackedTraffic, stackColumn, stackOrder } from './lib/cern-traffic';

const stacked = stackedTraffic('in'); // columns stack0 … stack6, each a running total
const names = stackOrder('in'); // interface ids, biggest first

Then draw them back to front — largest running total first, because it's the tallest and therefore sets the axis, with each smaller one painted over it. The strip left visible between two adjacent edges is one interface:

const order = names.map((_, i) => i).reverse();

<Layers>
{order.map((i) => (
<AreaChart
key={names[i]}
series={stacked}
column={stackColumn(i)}
as={roles[i]}
axis="gbps"
baseline={0}
legend={names[i]}
/>
))}
</Layers>;

That only works because the fills are opaque. A graded or translucent fill would let the slabs behind show through their neighbours and the whole composition would turn to mud — so the seq* area roles set flatFill, which exists for exactly this shape.

Seven series is well past the point where distinct hues help, so the colours step tonally through one brand hue rather than introducing seven competing ones:

import { rampRoles } from './lib/ramp-roles';

// → seq1, seq2, seq3, seq5, seq6, seq7, seq8 — spread across the ramp, not
// crowded into its dark end.
const roles = rampRoles(names.length);

rampRoles spreads n names evenly across the eight-step ramp rather than taking the first n. Taking the first seven would crowd every slab into the dark seven-eighths and drop the lightest step entirely, leaving the top two bands sitting almost on top of each other.

Options to try

OptionWhat it doesReach for it when
direction="out"Stacks egress instead of ingressYou want to see what one-interface dominance looks like
legend + <Legend placement>Names each slab; layers supply their own legend labelMore than three slabs — tonal steps aren't self-identifying
rampRoles(n) vs useSequentialRampRole names vs colour stringsRoles for as-styled layers; strings for <BarChart colors> and friends
Drop to the top 3–4 and an "other"Fewer, fatter slabsThe ramp runs out — eight steps is the ramp, by construction
cursor="crosshair"Reads every running total at onceYou're reading values, not shape

See also

  • Network traffic — the same data, in/out rather than by-interface, plus the linked interface table
  • <AreaChart>baseline, flatFill, and the seq* roles
  • Styling and theming — roles, the sequential ramp, and why no example holds a hex literal
  • <Legend> — placement and the headless variant
  • Storybook — the systematic knob walk