# World map
URL: /diagrammatic/docs/charts/world-map

A choropleth on real country outlines. Values keyed by ISO country code shade each country on one sequential ramp.

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

```tsx
"use client";

import { Root, Tooltip } from "diagrammatic/interactive";
import { WORLD, WorldMap } from "diagrammatic/world";

const WORKSPACES: Record<string, number> = {
  US: 412,
  CA: 88,
  MX: 41,
  BR: 96,
  AR: 22,
  CL: 14,
  CO: 18,
  PE: 9,
  GB: 160,
  IE: 31,
  FR: 122,
  DE: 198,
  NL: 74,
  BE: 22,
  ES: 64,
  PT: 19,
  IT: 58,
  CH: 37,
  AT: 18,
  SE: 44,
  NO: 21,
  DK: 23,
  FI: 17,
  PL: 49,
  CZ: 16,
  UA: 21,
  TR: 26,
  IL: 33,
  AE: 24,
  SA: 12,
  EG: 9,
  NG: 17,
  KE: 11,
  ZA: 19,
  IN: 231,
  PK: 14,
  BD: 9,
  CN: 174,
  JP: 141,
  KR: 92,
  TW: 38,
  SG: 47,
  ID: 39,
  VN: 29,
  TH: 21,
  PH: 18,
  MY: 16,
  AU: 72,
  NZ: 13,
};

const NAMES = new Map(WORLD.map((country) => [country.iso, country.name]));

export default function WorkspacesMap() {
  return (
    <Root highlightOnHover="series">
      <WorldMap
        title="Active workspaces by country"
        summary="The United States, India, Germany, and China have the most active workspaces."
        values={WORKSPACES}
      />
      <Tooltip side="top">
        {({ datum }) => {
          const iso = datum.series;
          if (!iso) return null;
          const value = WORKSPACES[iso];
          return (
            <div className="bg-popover text-popover-foreground w-max rounded-lg border px-2.5 py-1.5 text-xs shadow-md">
              <span className="font-medium">{NAMES.get(iso) ?? iso}</span>{" "}
              <span className="text-muted-foreground tabular-nums">
                {value === undefined ? "no workspaces" : `${value} workspaces`}
              </span>
            </div>
          );
        }}
      </Tooltip>
    </Root>
  );
}
```

## Usage

```
import { WorldMap } from "diagrammatic/world";

<WorldMap values={{ US: 412, DE: 198, IN: 231 }} />;
```

`WorldMap` ships in its own entry, `diagrammatic/world`, because 166 country outlines outweigh every other chart combined; only the pages that draw a map load them. The geometry is pre-projected (Miller) and simplified offline, so the component runs no geo code at render time and stays a server component.

## Examples

### Values by country

`values` is keyed by ISO 3166-1 alpha-2 code. Each country shades from 10% to 95% of the accent by its share of the largest value; a country without a value draws as an outline only.

```tsx
import { WorldMap } from "diagrammatic/world";

export default function RevenueMap() {
  return (
    <WorldMap
      title="Revenue by country, $k"
      values={{
        US: 920,
        GB: 310,
        DE: 280,
        FR: 190,
        JP: 240,
        IN: 120,
        BR: 90,
        AU: 110,
        CA: 170,
      }}
    />
  );
}
```

### One scale across maps

`max` fixes the top of the ramp, so two maps side by side shade the same value the same way. Without it, each map scales to its own largest value.

```tsx
import { WorldMap } from "diagrammatic/world";

const YEARS = [
  { year: 2024, values: { US: 280, GB: 90, DE: 110, IN: 60, JP: 70, BR: 30 } },
  {
    year: 2025,
    values: { US: 412, GB: 160, DE: 198, IN: 231, JP: 141, BR: 96 },
  },
];

export default function YearOverYearMaps() {
  return (
    <div className="grid gap-4 sm:grid-cols-2">
      {YEARS.map(({ year, values }) => (
        <figure key={year}>
          <figcaption className="mb-1 text-xs font-medium">{year}</figcaption>
          <WorldMap
            title={`Active workspaces, ${year}`}
            values={values}
            max={412}
            width={320}
          />
        </figure>
      ))}
    </div>
  );
}
```

### Color and hover style

The ramp is the first categorical token, `--dg-c1`. Every country is a mark with `data-series` set to its code, so CSS can style hover or single countries out.

```tsx
import type { CSSProperties } from "react";
import { WorldMap } from "diagrammatic/world";

const WORKSPACES: Record<string, number> = {
  US: 412,
  CA: 88,
  MX: 41,
  BR: 96,
  AR: 22,
  CL: 14,
  CO: 18,
  PE: 9,
  GB: 160,
  IE: 31,
  FR: 122,
  DE: 198,
  NL: 74,
  BE: 22,
  ES: 64,
  PT: 19,
  IT: 58,
  CH: 37,
  AT: 18,
  SE: 44,
  NO: 21,
  DK: 23,
  FI: 17,
  PL: 49,
  CZ: 16,
  UA: 21,
  TR: 26,
  IL: 33,
  AE: 24,
  SA: 12,
  EG: 9,
  NG: 17,
  KE: 11,
  ZA: 19,
  IN: 231,
  PK: 14,
  BD: 9,
  CN: 174,
  JP: 141,
  KR: 92,
  TW: 38,
  SG: 47,
  ID: 39,
  VN: 29,
  TH: 21,
  PH: 18,
  MY: 16,
  AU: 72,
  NZ: 13,
};

export default function ThemedMap() {
  return (
    <div
      className="[&_[data-part=mark]:hover]:[stroke-width:1.5]"
      style={{ "--dg-c1": "#059669" } as CSSProperties}
    >
      <WorldMap title="Active workspaces by country" values={WORKSPACES} />
    </div>
  );
}
```

## Interaction

Countries are addressed by code: a tooltip reads `datum.series`, and a highlight takes `{ series: "DE" }` or a list of them.

### Tooltip

The figure at the top of this page: `highlightOnHover="series"` plus a `Tooltip` that looks the country's name up in the exported `WORLD` list.

### Highlight a region

A controlled highlight with a list of queries lights up a set of countries and fades the rest.

```tsx
"use client";

import { useState } from "react";
import { Root, type MarkQuery } from "diagrammatic/interactive";
import { WorldMap } from "diagrammatic/world";

const WORKSPACES: Record<string, number> = {
  US: 412,
  CA: 88,
  MX: 41,
  BR: 96,
  AR: 22,
  CL: 14,
  CO: 18,
  PE: 9,
  GB: 160,
  IE: 31,
  FR: 122,
  DE: 198,
  NL: 74,
  BE: 22,
  ES: 64,
  PT: 19,
  IT: 58,
  CH: 37,
  AT: 18,
  SE: 44,
  NO: 21,
  DK: 23,
  FI: 17,
  PL: 49,
  CZ: 16,
  UA: 21,
  TR: 26,
  IL: 33,
  AE: 24,
  SA: 12,
  EG: 9,
  NG: 17,
  KE: 11,
  ZA: 19,
  IN: 231,
  PK: 14,
  BD: 9,
  CN: 174,
  JP: 141,
  KR: 92,
  TW: 38,
  SG: 47,
  ID: 39,
  VN: 29,
  TH: 21,
  PH: 18,
  MY: 16,
  AU: 72,
  NZ: 13,
};

const REGIONS: Record<string, string[]> = {
  Americas: ["US", "CA", "MX", "BR", "AR", "CL", "CO", "PE"],
  Europe: [
    "GB",
    "IE",
    "FR",
    "DE",
    "NL",
    "BE",
    "ES",
    "PT",
    "IT",
    "CH",
    "AT",
    "SE",
    "NO",
    "DK",
    "FI",
    "PL",
    "CZ",
    "UA",
  ],
  "Asia Pacific": [
    "IN",
    "CN",
    "JP",
    "KR",
    "TW",
    "SG",
    "ID",
    "VN",
    "TH",
    "PH",
    "MY",
    "AU",
    "NZ",
  ],
};

export default function RegionMap() {
  const [region, setRegion] = useState<string | null>("Europe");
  const highlight: MarkQuery[] | null = region
    ? REGIONS[region]!.map((iso) => ({ series: iso }))
    : null;
  const total = region
    ? REGIONS[region]!.reduce((sum, iso) => sum + (WORKSPACES[iso] ?? 0), 0)
    : Object.values(WORKSPACES).reduce((a, b) => a + b, 0);

  return (
    <div>
      <div className="mb-3 flex flex-wrap items-center gap-1.5">
        {[null, ...Object.keys(REGIONS)].map((name) => (
          <button
            key={name ?? "all"}
            type="button"
            aria-pressed={region === name}
            onClick={() => setRegion(name)}
            className="aria-pressed:bg-foreground aria-pressed:text-background rounded-md border px-2 py-1 text-xs"
          >
            {name ?? "World"}
          </button>
        ))}
        <span className="text-muted-foreground ml-auto text-xs tabular-nums">
          {total.toLocaleString("en-US")} workspaces
        </span>
      </div>
      <Root {...(highlight && { highlight })}>
        <WorldMap title="Active workspaces by region" values={WORKSPACES} />
      </Root>
    </div>
  );
}
```

### Pick a country

`onMarkClick` reports the clicked country's code.

```tsx
"use client";

import { useState } from "react";
import { Root } from "diagrammatic/interactive";
import { WORLD, WorldMap } from "diagrammatic/world";

const WORKSPACES: Record<string, number> = {
  US: 412,
  CA: 88,
  MX: 41,
  BR: 96,
  AR: 22,
  CL: 14,
  CO: 18,
  PE: 9,
  GB: 160,
  IE: 31,
  FR: 122,
  DE: 198,
  NL: 74,
  BE: 22,
  ES: 64,
  PT: 19,
  IT: 58,
  CH: 37,
  AT: 18,
  SE: 44,
  NO: 21,
  DK: 23,
  FI: 17,
  PL: 49,
  CZ: 16,
  UA: 21,
  TR: 26,
  IL: 33,
  AE: 24,
  SA: 12,
  EG: 9,
  NG: 17,
  KE: 11,
  ZA: 19,
  IN: 231,
  PK: 14,
  BD: 9,
  CN: 174,
  JP: 141,
  KR: 92,
  TW: 38,
  SG: 47,
  ID: 39,
  VN: 29,
  TH: 21,
  PH: 18,
  MY: 16,
  AU: 72,
  NZ: 13,
};

const NAMES = new Map(WORLD.map((country) => [country.iso, country.name]));
const OPTIONS = WORLD.filter((country) => country.iso in WORKSPACES).sort(
  (a, b) => a.name.localeCompare(b.name),
);

export default function CountryPicker() {
  const [iso, setIso] = useState("JP");

  return (
    <div>
      <label className="mb-3 flex items-center gap-2 text-sm">
        Country
        <select
          value={iso}
          onChange={(event) => setIso(event.target.value)}
          className="rounded-md border bg-transparent px-2 py-1"
        >
          {OPTIONS.map((country) => (
            <option key={country.iso} value={country.iso}>
              {country.name}
            </option>
          ))}
        </select>
      </label>
      <Root
        highlight={{ series: iso }}
        onMarkClick={(datum) => {
          if (datum.series) setIso(datum.series);
        }}
      >
        <WorldMap title="Pick a country" values={WORKSPACES} />
      </Root>
      <p className="text-muted-foreground mt-3 text-sm">
        <span className="text-foreground font-medium">{NAMES.get(iso)}</span>
        {WORKSPACES[iso] !== undefined
          ? ` has ${WORKSPACES[iso]} active workspaces.`
          : " has no active workspaces yet."}{" "}
        Click a country or pick one from the list.
      </p>
    </div>
  );
}
```

### Zoom

With a function child, `Zoom` hands the map a `view` window and the map redraws through it, so borders keep their hairline width at any magnification. Scroll to zoom, drag to pan, double-click to reset.

```tsx
"use client";

import { Zoom } from "diagrammatic/interactive";
import { WorldMap } from "diagrammatic/world";

const WORKSPACES: Record<string, number> = {
  US: 412,
  CA: 88,
  MX: 41,
  BR: 96,
  AR: 22,
  CL: 14,
  CO: 18,
  PE: 9,
  GB: 160,
  IE: 31,
  FR: 122,
  DE: 198,
  NL: 74,
  BE: 22,
  ES: 64,
  PT: 19,
  IT: 58,
  CH: 37,
  AT: 18,
  SE: 44,
  NO: 21,
  DK: 23,
  FI: 17,
  PL: 49,
  CZ: 16,
  UA: 21,
  TR: 26,
  IL: 33,
  AE: 24,
  SA: 12,
  EG: 9,
  NG: 17,
  KE: 11,
  ZA: 19,
  IN: 231,
  PK: 14,
  BD: 9,
  CN: 174,
  JP: 141,
  KR: 92,
  TW: 38,
  SG: 47,
  ID: 39,
  VN: 29,
  TH: 21,
  PH: 18,
  MY: 16,
  AU: 72,
  NZ: 13,
};

export default function ZoomableMap() {
  return (
    <Zoom max={8} className="overflow-hidden rounded-md">
      {(view) => (
        <WorldMap
          title="Active workspaces by country"
          values={WORKSPACES}
          view={view}
        />
      )}
    </Zoom>
  );
}
```

## Related

- [Dot world map](/diagrammatic/docs/charts/dot-world-map) draws the same countries as halftone dots, with groups and flows.
- [Choropleth](/diagrammatic/docs/charts/choropleth), [Symbol map](/diagrammatic/docs/charts/symbol-map), [Dot map](/diagrammatic/docs/charts/dot-map), and [Flow map](/diagrammatic/docs/charts/flow-map) draw on a tile grid in the main entry, which gives every region the same area.

## Props

- `values?`: `Record<string, number>` — Values keyed by ISO 3166-1 alpha-2 code. Countries without a value draw as outlines.
- `max?`: `number` — The value that takes the darkest shade. Defaults to the largest value.
- `title?`: `string` — Names the map for assistive technology.
- `summary?`: `string` — A sentence read after the title.
- `width`: `number` (default `640`) — Rendered width in CSS pixels.
- `height?`: `number` — Rendered height in CSS pixels.
- `aspect?`: `number` — Width over height when \`height\` is not given.
- `view?`: `{ x: number; y: number; w: number; h: number }` — A window onto the map, from \`Zoom\`.

`WORLD` (every country's code, name, and path), `WORLD_HEIGHT`, and `CENTROIDS` are exported from `diagrammatic/world` for hosts drawing their own overlays.