# Country values
> Values per country as dot density: land dots switch on in an ordered-dither pattern, and a change rebuilds each country's print dot by dot.
- React: `import { GeoChoropleth } from "@/components/lumesec/geo-choropleth"`
- Collection: Location (https://elements.lumesec.ai/components/location)
- Data: takes your data (`data`); shows demo data until you pass it
- Registry item: https://elements.lumesec.ai/r/geo-choropleth.json
- Page: https://elements.lumesec.ai/components/location/choropleth



Live preview: https://elements.lumesec.ai/view/geo-choropleth

Demo source:

```tsx
"use client";

import * as React from "react";

import { GeoChoropleth, type CountryValue } from "@/components/lumesec/geo-choropleth";

/** Invented requests per day. */
const REQUESTS: readonly CountryValue[] = [
  { code: "US", value: 412000 },
  { code: "IN", value: 268000 },
  { code: "DE", value: 168000 },
  { code: "GB", value: 142000 },
  { code: "BR", value: 131000 },
  { code: "JP", value: 118000 },
  { code: "FR", value: 96000 },
  { code: "CA", value: 88000 },
  { code: "KR", value: 64000 },
  { code: "AU", value: 57000 },
  { code: "NL", value: 52000 },
  { code: "ID", value: 47000 },
  { code: "MX", value: 44000 },
  { code: "ES", value: 41000 },
  { code: "IT", value: 39000 },
  { code: "CN", value: 33000 },
  { code: "PL", value: 31000 },
  { code: "SE", value: 28000 },
  { code: "TR", value: 26000 },
  { code: "VN", value: 24000 },
  { code: "PH", value: 21000 },
  { code: "NG", value: 19000 },
  { code: "ZA", value: 17000 },
  { code: "AR", value: 16000 },
  { code: "SA", value: 15000 },
  { code: "AE", value: 14000 },
  { code: "EG", value: 12000 },
  { code: "CO", value: 11000 },
  { code: "TH", value: 10500 },
  { code: "UA", value: 9800 },
  { code: "IL", value: 9400 },
  { code: "NO", value: 8700 },
  { code: "FI", value: 7600 },
  { code: "CL", value: 7100 },
  { code: "PK", value: 6800 },
  { code: "KE", value: 5200 },
  { code: "MA", value: 4300 },
  { code: "PE", value: 3900 },
  { code: "KZ", value: 3100 },
  { code: "NZ", value: 2900 },
  { code: "RU", value: 2400 },
];

/** Invented sign-ups per week, for the same countries. */
const SIGN_UPS: readonly CountryValue[] = [
  { code: "IN", value: 9400 },
  { code: "US", value: 8200 },
  { code: "BR", value: 6100 },
  { code: "ID", value: 4800 },
  { code: "NG", value: 3900 },
  { code: "PH", value: 3100 },
  { code: "MX", value: 2900 },
  { code: "VN", value: 2600 },
  { code: "DE", value: 2300 },
  { code: "GB", value: 2100 },
  { code: "PK", value: 1900 },
  { code: "EG", value: 1800 },
  { code: "TR", value: 1600 },
  { code: "FR", value: 1500 },
  { code: "KE", value: 1350 },
  { code: "CO", value: 1250 },
  { code: "JP", value: 1200 },
  { code: "AR", value: 1100 },
  { code: "ZA", value: 1050 },
  { code: "TH", value: 980 },
  { code: "SA", value: 900 },
  { code: "ES", value: 860 },
  { code: "MA", value: 790 },
  { code: "PE", value: 720 },
  { code: "IT", value: 680 },
  { code: "PL", value: 610 },
  { code: "CA", value: 560 },
  { code: "UA", value: 540 },
  { code: "CL", value: 480 },
  { code: "KR", value: 430 },
  { code: "AU", value: 400 },
  { code: "KZ", value: 360 },
  { code: "AE", value: 330 },
  { code: "NL", value: 300 },
  { code: "CN", value: 280 },
  { code: "SE", value: 240 },
  { code: "IL", value: 210 },
  { code: "RU", value: 190 },
  { code: "NO", value: 150 },
  { code: "FI", value: 130 },
  { code: "NZ", value: 110 },
];

const METRICS = {
  requests: { name: "Requests", label: "Requests per day by country", unit: "requests", data: REQUESTS },
  signups: { name: "Sign-ups", label: "Sign-ups per week by country", unit: "sign-ups", data: SIGN_UPS },
} as const;

type Metric = keyof typeof METRICS;

export default function GeoChoroplethDemo() {
  const [metric, setMetric] = React.useState<Metric>("requests");
  const current = METRICS[metric];
  return (
    <div className="w-full">
      <div className="mx-auto grid w-full max-w-[660px] gap-2.5">
        <div className="flex items-center justify-between gap-3">
          <div role="group" aria-label="Metric" className="inline-flex h-[30px] items-center rounded-lg border border-border bg-card p-[2px]">
            {(Object.keys(METRICS) as Metric[]).map((key) => (
              <button
                key={key}
                type="button"
                aria-pressed={metric === key}
                onClick={() => setMetric(key)}
                className="h-[24px] cursor-pointer rounded-md px-2.5 font-[inherit] text-[12.5px] font-medium text-muted-foreground outline-none transition-colors duration-150 hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-solid focus-visible:outline-lumesec aria-pressed:bg-muted aria-pressed:text-foreground motion-reduce:transition-none"
              >
                {METRICS[key].name}
              </button>
            ))}
          </div>
          <span className="text-[12px] text-muted-foreground">Demo data, {current.data.length} countries</span>
        </div>
        <GeoChoropleth data={current.data} unit={current.unit} label={current.label} />
      </div>
    </div>
  );
}
```

> **Your data:** Pass your values per country as `data`. Without it the component shows demo values for 41 countries. See [Your data](/docs/data).

## Playground

Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.

## Installation

```bash
npx shadcn@latest add @lumesec/geo-choropleth
```

First time with the @lumesec registry? Register it once, or install by URL:

```bash
npx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.json
npx shadcn@latest add https://elements.lumesec.ai/r/geo-choropleth.json
```

## Usage

React:

```tsx
import { GeoChoropleth, type CountryValue } from "@/components/lumesec/geo-choropleth";

const requests: CountryValue[] = [
  { code: "US", value: 412000 },
  { code: "IN", value: 268000 },
  { code: "DE", value: 168000 },
  { code: "BR", value: 131000 },
  { code: "JP", value: 118000 },
];

export function Example() {
  return (
    <GeoChoropleth
      label="Requests by country"
      unit="requests"
      data={requests}
      onValueChange={(code) => console.log(code)}
    />
  );
}
```

## Behaviour

Values per country shown as dot density: each country's land dots switch on in an ordered-dither pattern, so a higher class reads as a denser print. Countries without data stay as the plain base.

When the data changes, each country builds up or thins out dot by dot in dither order, outward from its middle behind a bright front. Pointing at a country sends a short light around its border and rolls its value, and a ranked list beside the map stays in sync.

## API reference

### Props

Also accepts every prop of `<div>` (`React.ComponentProps<"div">`), spread onto the root element.

| Prop            | Type                                | Default                        | Description                                                                                                                                                                                                                                                                                                                                      |
| --------------- | ----------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data`          | `readonly CountryValue[]`           | `demo values for 41 countries` | `{ code, value }` records with ISO 3166-1 alpha-2 codes (any case; Kosovo is `XK`). Changing it rebuilds the print of every country whose class changes. Unknown codes are skipped with one development warning, non-finite values are skipped, and a code listed twice keeps its last value. An empty array shows the plain base and "No data". |
| `value`         | `string \| null`                    | —                              | Selected country code (controlled), or null. Use with `onValueChange`.                                                                                                                                                                                                                                                                           |
| `defaultValue`  | `string \| null`                    | `null`                         | Selected country when uncontrolled.                                                                                                                                                                                                                                                                                                              |
| `onValueChange` | `(value: string \| null) => void`   | —                              | Called with the upper-case code when a country is selected on the map or in the list, and with null when the selection is cleared.                                                                                                                                                                                                               |
| `classes`       | `number`                            | `5`                            | Number of density classes, 3 to 7. Class `k` of `n` lights `(k + 1) / n` of a country's dots.                                                                                                                                                                                                                                                    |
| `scale`         | `"quantile" \| "linear" \| "log"`   | `"quantile"`                   | How values are split into classes: the same number of countries per class, equal value steps, or equal steps of the logarithm (values of 0 or less fall into the lowest class).                                                                                                                                                                  |
| `format`        | `(value: number) => string`         | `formatCompact`                | Formats values in the list, the legend, the hover chip and the hidden table. The default gives `950`, `12.4K`, `3.1M`.                                                                                                                                                                                                                           |
| `unit`          | `string`                            | —                              | Unit after values in the chip and the hidden table, and above the ranked list.                                                                                                                                                                                                                                                                   |
| `listSize`      | `number`                            | `6`                            | Rows in the ranked list, 0 to 50. 0 hides the list and the map takes the full width.                                                                                                                                                                                                                                                             |
| `projection`    | `"equalEarth" \| "equirectangular"` | `"equalEarth"`                 | Map projection, cropped to latitudes 56° S to 78° N.                                                                                                                                                                                                                                                                                             |
| `label`         | `string`                            | `"Values by country"`          | Accessible name of the map, also the caption of the hidden table.                                                                                                                                                                                                                                                                                |

### Ref

`ref` points at the root `HTMLDivElement`.

## Keyboard

| Keys                                         | Action                                                                                                                                 |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| ArrowLeft / ArrowRight / ArrowUp / ArrowDown | On the map: move to the nearest country with data in that direction. The first press starts at the selected or the top-ranked country. |
| ArrowUp / ArrowDown, Home / End              | In the ranked list: move between rows.                                                                                                 |
| Enter / Space                                | Select the country in focus and run the border light again.                                                                            |
| Escape                                       | Clear the selection.                                                                                                                   |

## Accessibility

* The canvases are `aria-hidden`. A visually hidden table lists every country with data and its value, largest first, with `label` as its caption.
* The map is a focusable group with `aria-roledescription="map"`, `aria-label` set to `label` and a description of its keys. A polite live region announces the country in keyboard focus with its value and rank.
* The ranked list is a `listbox` with `aria-activedescendant`; each row is an `option` with `aria-selected` and an accessible name of the form "Germany, 168K requests, rank 3".
* Countries without data do not respond to the pointer or the keyboard. Hover works with a mouse or a pen; a tap selects.
* Reduced motion: the dots switch to their new levels at once, with no stagger and no bright front, the border light is a static outline, the chip moves without a spring, list rows do not slide and values change without rolling.

## Theming

Styled with Tailwind classes on your shadcn theme tokens, so light and dark follow your theme. The accent comes from `--lumesec`. See [Theming](/docs/theming).

This component reads `--lumesec`, `--lumesec-glint`, `--foreground`, `--muted-foreground`, `--muted`, `--card`, `--card-foreground`, `--popover`, `--popover-foreground` and `--border`.

## Notes

* A country's class sets how many of its dots are lit: a dot is lit when its level is above its threshold in a 4×4 Bayer matrix, so a rising level adds dots in a fixed, even order and a falling level removes them in reverse. Lit dots use `--lumesec`, more opaque in higher classes.
* When a level changes, the change travels outward from the country's label point with an uneven front, and dots glow in `--lumesec-glint` while they switch. Countries start 25 ms apart, the largest change first. Only countries that change are redrawn.
* Countries with data that get fewer than three dots on the lattice (small islands, city states, small countries on narrow maps) are drawn as a 2×2 cluster at their label point, moved up to two cells when it would cover another cluster or another country with data. Their border light and outline run around the cluster.
* Pointing at a country runs a short light once around its border and shows a chip with its name and rolling value. The selected country keeps a thin outline and its chip when nothing else is pointed at.
* The ranked list sits beside the map when the card is at least 560 px wide and below it otherwise. When the country in focus is not in the top rows it appears under them with its rank.
* The legend shows one dither tile per class with the class breaks under the joins: quantiles for `quantile`, equal steps otherwise.
* Codes come from the embedded country set of 175 countries (Natural Earth 110m). Places without an outline there, such as Singapore and Hong Kong, count as unknown codes.


