Event spikes
Counts per location as dot spikes on a globe; new data grows them in a wave from the biggest increase, led by a ring of light.
Your data
Pass your counts per location as data. Without it the component shows demo counts for 24 cities. See Your data.
Playground
Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.
<GeoSpikes scale="log" maxHeight={0.5} unit="events" />Installation
pnpm dlx shadcn@latest add @lumesec/geo-spikesFirst 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-spikes.jsonUsage
import { GeoSpikes, type SpikeDatum } from "@/components/lumesec/geo-spikes";
const signIns: SpikeDatum[] = [
{ id: "lon", label: "London", lat: 51.5072, lon: -0.1276, value: 18400 },
{ id: "nyc", label: "New York", lat: 40.7128, lon: -74.006, value: 15200 },
{ id: "tyo", label: "Tokyo", lat: 35.6762, lon: 139.6503, value: 9800 },
{ id: "sao", label: "São Paulo", lat: -23.5505, lon: -46.6333, value: 4100 },
];
export function Example() {
return <GeoSpikes data={signIns} unit="sign-ins" label="Sign-ins by city" />;
}Behaviour
Counts per location as spikes of stacked dots standing on a dotted globe. Heights use a log scale by default, so one busy city does not flatten the rest.
When the data changes, spikes grow on springs in a wave that starts at the location with the biggest increase and spreads around the globe, with a ring of light on the land marking the front. Point at a spike, or Tab to it, to roll its count.
API reference
Props
Also accepts every prop of <div> (React.ComponentProps<"div">), spread onto the root element.
dataTypereadonly SpikeDatum[]Default24 demo citiesCounts per location, { id, label, lat, lon, value }. A new array is matched to the previous one by id: spikes grow or shrink from their previous height, new ids grow from zero and missing ids shrink away. Up to 64 locations are drawn, the largest first.
valueTypestring | nullSelected spike id (controlled), or null for none. Use with onValueChange.
defaultValueTypestring | nullDefaultnullSelected spike when uncontrolled.
onValueChangeType(value: string | null) => voidCalled when a spike is selected or the selection is cleared.
scaleType"log" | "linear"Default"log"How values map to heights. log spreads the three decades below the largest value evenly over the height, so one busy place does not flatten the rest. linear makes height proportional to the value.
maxHeightTypenumberDefault0.5Height of the tallest spike as a share of the globe radius, 0.1 to 0.6.
formatType(value: number) => stringDefaultformatCompactFormats counts in the chip, the legend and the accessible names. The default gives 950, 12.4K, 3.1M.
unitTypestringDefault"events"Unit after each count.
defaultCenterTypeLatLonWhere the globe faces on mount, { lat, lon }. Without it the globe faces the value-weighted centre of the data, tilted 30° south so the spikes stand up instead of pointing at the viewer.
labelTypestringDefault"Events by location"Accessible name of the globe and of the location list.
Ref
ref points at the root HTMLDivElement.
Keyboard
| Keys | Action |
|---|---|
| Tab | Moves from the globe to the spikes, which share one tab stop. |
| Arrow keys (globe focused) | Turn the globe 10°; the view moves in the arrow's direction. |
| Arrow keys (spike focused) | Move to the nearest visible spike in that direction on screen. |
| Home / End | Move to the largest or the smallest spike. |
| Enter / Space | Select the focused spike, or clear it when it is already selected. |
| Escape | Clear the selection. |
Accessibility
- The globe is a focusable
role="group"witharia-roledescription="globe", named bylabeland described by a visually hidden hint. - Every spike is a native button with the label and the count as its name and
aria-pressedfor the selection. The buttons follow their tips and share one roving tab stop; focusing a spike on the far side turns the globe to it. - A visually hidden list mirrors every location and value. With more than 64 locations it says that the globe shows the 64 largest.
- The canvas, the chip and the legend are
aria-hidden; the buttons and the list carry the same content. - Reduced motion: the spikes take their heights at once with no wave, ring, flicker or sparks, the globe neither coasts nor flies toward an update (focusing a far-side spike turns it at once), and counts and the chip swap without rolling or gliding.
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 --card, --card-foreground, --foreground, --muted-foreground, --border, --popover, --popover-foreground, --lumesec, --lumesec-soft and --lumesec-shine.
Notes
- On first view every spike grows from zero in a wave from the largest value. After that, each new
dataarray starts a wave at the location with the biggest increase (or the largest value when nothing grew): a ring of light runs across the land, each spike starts growing as the ring reaches it, and the counts in the chip and the legend roll at the same moment. - When the epicentre is on the far side, nothing is selected and the globe has not been dragged for 4 seconds, the globe first turns toward it and the wave starts as it arrives.
- Places whose count rose by half or more throw a few sparks from the tip at the top of the overshoot. The sparks are drawn on a layer that reaches 32 px past the globe.
- Zero or negative values draw only the three-dot footprint. Entries with a missing id, a repeated id or a non-finite number are skipped.
- The legend shows the three largest locations. Pointing at a legend entry lights its spike. The chip sits beyond the tip, on the side the spike leans to, so it never covers the column.
- The globe stays still between updates: the animation loop stops once the springs, the ring and the globe have settled, and pauses off screen.
Related
Was this page helpful?