Latency matrix
Round-trip times between regions as dot blocks that fill with latency; a map beside the grid slides the measured route from pair to pair.
Your data
Pass your regions as regions and your round-trip times as measurements. Without either the component shows eight demo regions with demo latencies (the fibre minimum times a seeded stretch of 1.3 to 1.9); regions alone shows every pair as "no data". See Your data.
Playground
Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.
<GeoLatencyMatrix slowMs={250} symmetric label="Latency between regions" />Installation
pnpm dlx shadcn@latest add @lumesec/geo-latency-matrixFirst timeRegister the @lumesec registry once, or install by URL+
Adds @lumesec to your components.json:
pnpm dlx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.jsonOr skip the setup and install by URL:
npx shadcn@latest add https://elements.lumesec.ai/r/geo-latency-matrix.jsonUsage
"use client";
import * as React from "react";
import { GeoLatencyMatrix, type LatencyMeasurement, type LatencyPair, type LatencyRegion } from "@/components/lumesec/geo-latency-matrix";
const regions: LatencyRegion[] = [
{ id: "eu-central", city: "Frankfurt", lat: 50.11, lon: 8.68 },
{ id: "us-east", city: "Ashburn", lat: 39.04, lon: -77.49 },
{ id: "ap-southeast", city: "Singapore", lat: 1.35, lon: 103.82 },
];
export function Example({ measurements }: { measurements: LatencyMeasurement[] }) {
const [pair, setPair] = React.useState<LatencyPair | null>({ from: "eu-central", to: "ap-southeast" });
return <GeoLatencyMatrix regions={regions} measurements={measurements} value={pair} onValueChange={setPair} slowMs={200} />;
}Behaviour
Round-trip times between regions as a grid of dot blocks: the slower the pair, the more dots are lit. Pairs above a threshold get a warning mark.
Point at a cell, or move through the grid with the arrow keys, and the mini map beside it shows that pair: the measured route against a faint arc for the fibre minimum, with the milliseconds and the path stretch rolling. The route slides from pair to pair instead of redrawing.
API reference
Props
Also accepts every prop of <div> (React.ComponentProps<"div">), spread onto the root element.
regionsTypereadonly LatencyRegion[]Default8 demo regionsRows and columns, in order: { id, label?, city?, lat, lon }. label is the header text (default: the id), city the place name in the detail panel and the accessible names (default: the label). A GeoRegion from @/lib/lumesec/geo-data fits. Regions with a repeated id or a non-finite coordinate are skipped.
measurementsTypereadonly LatencyMeasurement[]Defaultdemo latenciesRound-trip times { from, to, ms } with region ids: from is the row, to the column. Entries with an unknown id or without a finite, non-negative ms are skipped; the last entry for a direction wins. Pairs without a measurement show "no data".
symmetricTypebooleanDefaulttrueUses a measurement for both directions when only one direction is given. A measurement given for a direction always wins over its mirror.
valueTypeLatencyPair | nullSelected pair { from, to } (controlled), or null for none. Use with onValueChange.
defaultValueTypeLatencyPair | nullDefaultfirst off-diagonal pairSelected pair when uncontrolled.
onValueChangeType(value: LatencyPair | null) => voidCalled with the new pair when the user selects a different cell: click, tap, Enter or Space.
slowMsTypenumberDefault250Pairs at or above this round-trip time in ms get a warning mark in their cell, a Slow badge in the detail panel and "slow" in their accessible name.
labelTypestringDefault"Latency between regions"Accessible name of the grid.
Ref
ref points at the root HTMLDivElement.
Keyboard
| Keys | Action |
|---|---|
| Tab | Moves into the grid, to the selected cell (one roving tab stop), and out again. |
| ArrowUp / ArrowDown / ArrowLeft / ArrowRight | Moves one cell. The detail panel and the map follow the focused cell. |
| Home / End | Moves to the first or last cell of the row. |
| Ctrl+Home / Ctrl+End | Moves to the first or last cell of the grid. |
| Enter / Space | Selects the focused pair. |
Accessibility
- The matrix is an ARIA grid (
role="grid"withrowgroup,row,columnheader,rowheaderandgridcell), named bylabel. The cells share one roving tab stop. - Each cell's
aria-labelreads like "Frankfurt to Singapore, 162 milliseconds, 61 % above fibre minimum", with "no data", "same region" or "slow" where they apply. The selected cell hasaria-selected="true". - The header above the row headers is named "From"; rows are the regions a measurement starts from, columns the regions it goes to.
- The detail panel is DOM text. The map has
role="img"and anaria-labelthat names the route; its canvases and endpoint labels arearia-hidden. - The focused or hovered cell is marked by an accent frame on the matrix canvas, and its row and column headers are highlighted.
- Reduced motion: the route jumps between pairs, the frame jumps between cells, numbers swap without rolling, the blocks show at once, and the cell twinkle, the looping packet, the ping and the sparks are off.
Theming
Styled with Tailwind classes on your shadcn theme tokens, so light and dark follow your theme. The accent comes from --lumesec. See Theming.
This component reads --lumesec, --lumesec-shine, --lumesec-warning, --foreground, --muted-foreground, --muted, --card and --border.
Notes
- Each cell is a 5 × 5 block of 2 px dots. Its level is log(ms / fastest) / log(slowest / fastest) over the pairs off the diagonal, quantised to 25 steps, so the fastest pair lights one dot and the slowest all 25. Dots light in a fixed dispersed order and lean from the land grey toward
--lumesecas the level rises. - The diagonal shows a single centre dot; pairs without a measurement show every other dot, faint, and read "no data". The legend under the grid gives the fastest and slowest time and shows the marks that are in use.
- The fibre minimum is the great-circle distance at 204 km per ms each way. Stretch is the measured time over that minimum, minus 1. The measured route on the map bends away from the great circle, by 0.12 times the ratio and at most 0.3.
- The route's two endpoints are springs in screen space, and the curves are rebuilt from them every frame, so moving across the grid slides and reshapes the route. Pairs that cross the 170° W edge take the shorter way and continue from the other edge.
- Selecting a pair rings the land dots around its destination and sends a few sparks past the map's edge. Hover and focus preview a pair; leaving the grid returns to the selected one.
- When
measurementschanges, every block springs to its new dot count, filling or thinning in rank order, and the numbers roll in the direction of change. - The map is equirectangular, 50° S to 70° N, with its central meridian at 10° E. At 560 px and wider the map sits beside the grid; below that it moves under it, and below 420 px the cells shrink from 24 to 20 px.
- The animation runs only while the component is on screen. Colours follow the theme and are re-read when it changes.
Related
Was this page helpful?