Skip to content

U.S. State Choropleth

This example renders contiguous U.S. population density as polygon-set data.

Preview

Run And Adapt

Commands below assume a Datoviz source checkout and start at the repository root. Use your configured build environment; Python routes additionally require local bindings.

Route Availability Command or action
C Canonical native source just example-c showcases/choropleth (build and run), or rerun ./build/examples/c/showcases/choropleth
Python Available python3 -m examples.python.gallery.showcases.choropleth
Browser Live WebGPU route Open live example

Prepared data required

This example intentionally fails when its prepared input is absent; it does not substitute synthetic data. Expected input: data/examples/us_state_choropleth/prepared. Prepare it from the repository root with python tools/data/prepare_us_state_choropleth.py.

Use this example as capability or integration evidence, not as a minimal copy-paste template. Start from the nearest supported, copy-safe example and add this feature after verifying the linked API reference.

What To Look For

Prepared Census boundaries are loaded as flat point, ring, fill-color, stroke-color, stroke-width, and id arrays, then colored by log10 resident population density. Compare the state shapes with the vertical colorbar labeled in people per square kilometer; the topology is ring-based and interior holes are not preserved.

This workflow is useful for map-like scientific figures where real tabular values must be joined to prepared geometry before rendering. Run the preparation command if the promoted data bundle is not present.

Data: U.S. Census Bureau 2024 cartographic state boundaries and Vintage 2025 resident population estimates, prepared into flat polygon-set arrays.

Source: https://www2.census.gov/geo/tiger/GENZ2024/shp/cb_2024_us_state_20m.zip https://www2.census.gov/programs-surveys/popest/tables/2020-2025/state/totals/NST-EST2025-POP.xlsx

Terms: U.S. Census Bureau public data; cite the Census Bureau as source.

Prepare: python tools/data/prepare_us_state_choropleth.py

Promote: python tools/data/prepare_us_state_choropleth.py --output data/examples/us_state_choropleth

Source

#!/usr/bin/env python3
"""U.S. state choropleth from prepared Census polygon-set arrays."""

from __future__ import annotations

import ctypes
from dataclasses import dataclass
from pathlib import Path

import numpy as np

import datoviz as dvz

from examples.python.gallery import common as ex


DATA_BUNDLE = Path("data/examples/us_state_choropleth/prepared")
CACHE_BUNDLE = Path(".cache/datoviz/examples/us_state_choropleth/prepared")

METADATA_NAME = "metadata.tsv"
POINTS_NAME = "points_xy_f64.bin"
RINGS_NAME = "rings_u32.bin"
RING_FILL_NAME = "ring_fill_rgba8.bin"
RING_STROKE_NAME = "ring_stroke_rgba8.bin"
RING_WIDTH_NAME = "ring_width_f32.bin"
RING_ID_NAME = "ring_id_u64.bin"

CHOROPLETH_RAMP = (
    dvz.DvzColor(26, 35, 46, 235),
    dvz.DvzColor(33, 99, 126, 235),
    dvz.DvzColor(50, 160, 147, 235),
    dvz.DvzColor(237, 191, 94, 235),
    dvz.DvzColor(221, 96, 73, 235),
)


@dataclass
class ChoroplethBundle:
    path: Path
    region_count: int
    ring_count: int
    point_count: int
    xmin: float
    xmax: float
    ymin: float
    ymax: float
    value_min: float
    value_max: float
    density_min: float
    density_max: float
    rings: np.ndarray
    points: np.ndarray
    fill: np.ndarray
    stroke: np.ndarray
    widths: np.ndarray
    ids: np.ndarray


def _default_bundle_path() -> Path:
    for path in (DATA_BUNDLE, CACHE_BUNDLE):
        if (path / METADATA_NAME).is_file():
            return path
    raise RuntimeError(
        "choropleth: missing prepared bundle\n"
        "  python tools/data/prepare_us_state_choropleth.py"
    )


def _load_metadata(path: Path) -> dict[str, str]:
    metadata: dict[str, str] = {}
    with (path / METADATA_NAME).open("r", encoding="utf8") as f:
        for line in f:
            key, value = line.rstrip("\n").split("\t", 1)
            metadata[key] = value
    return metadata


def _metadata_u32(metadata: dict[str, str], key: str) -> int:
    value = int(metadata[key])
    if value <= 0 or value > np.iinfo(np.uint32).max:
        raise RuntimeError(f"choropleth: invalid metadata field {key}")
    return value


def _metadata_f64(metadata: dict[str, str], key: str) -> float:
    return float(metadata[key])


def _read_array(path: Path, name: str, dtype, count: int, shape: tuple[int, ...]) -> np.ndarray:
    array = np.fromfile(path / name, dtype=dtype)
    expected = int(np.prod(shape))
    if array.size != expected:
        raise RuntimeError(
            f"choropleth: {name} has {array.size} items, expected {expected}"
        )
    return np.ascontiguousarray(array.reshape(shape))


def _validate_rings(bundle: ChoroplethBundle) -> None:
    rings = bundle.rings
    if np.any(rings[:, 0] >= bundle.region_count):
        raise RuntimeError("choropleth: ring region index out of range")
    if np.any(rings[:, 2] < 3):
        raise RuntimeError("choropleth: ring with fewer than three points")
    ends = rings[:, 1].astype(np.uint64) + rings[:, 2].astype(np.uint64)
    if np.any(ends > bundle.point_count):
        raise RuntimeError("choropleth: ring point span out of range")


def _load_bundle(path: Path | None = None) -> ChoroplethBundle:
    path = _default_bundle_path() if path is None else path
    metadata = _load_metadata(path)

    region_count = _metadata_u32(metadata, "region_count")
    ring_count = _metadata_u32(metadata, "ring_count")
    point_count = _metadata_u32(metadata, "point_count")
    xmin = _metadata_f64(metadata, "xmin")
    xmax = _metadata_f64(metadata, "xmax")
    ymin = _metadata_f64(metadata, "ymin")
    ymax = _metadata_f64(metadata, "ymax")
    value_min = _metadata_f64(metadata, "value_min")
    value_max = _metadata_f64(metadata, "value_max")
    density_min = _metadata_f64(metadata, "density_min")
    density_max = _metadata_f64(metadata, "density_max")
    if not (xmin < xmax and ymin < ymax and value_min < value_max):
        raise RuntimeError("choropleth: invalid metadata domains")

    bundle = ChoroplethBundle(
        path=path,
        region_count=region_count,
        ring_count=ring_count,
        point_count=point_count,
        xmin=xmin,
        xmax=xmax,
        ymin=ymin,
        ymax=ymax,
        value_min=value_min,
        value_max=value_max,
        density_min=density_min,
        density_max=density_max,
        rings=_read_array(path, RINGS_NAME, np.uint32, ring_count, (ring_count, 3)),
        points=_read_array(path, POINTS_NAME, np.float64, point_count, (point_count, 2)),
        fill=_read_array(path, RING_FILL_NAME, np.uint8, ring_count, (ring_count, 4)),
        stroke=_read_array(path, RING_STROKE_NAME, np.uint8, ring_count, (ring_count, 4)),
        widths=_read_array(path, RING_WIDTH_NAME, np.float32, ring_count, (ring_count,)),
        ids=_read_array(path, RING_ID_NAME, np.uint64, ring_count, (ring_count,)),
    )
    _validate_rings(bundle)
    return bundle


def _configure_panel(panel, bundle: ChoroplethBundle) -> None:
    padding = dvz.DvzPanelReserve()
    padding.left_px = 14.0
    padding.right_px = 42.0
    padding.bottom_px = 13.5
    padding.top_px = 110.0
    if dvz.dvz_panel_set_padding(panel, ctypes.byref(padding)) != 0:
        raise RuntimeError("dvz_panel_set_padding() failed")

    desc = dvz.dvz_panel_view2d_desc()
    desc.mode = dvz.DVZ_PANEL_VIEW2D_CONTAIN
    desc.aspect = dvz.DVZ_PANEL_VIEW2D_ASPECT_EQUAL
    desc.padding = 0.035
    desc.domain_x[:] = (bundle.xmin, bundle.xmax)
    desc.domain_y[:] = (bundle.ymin, bundle.ymax)
    desc.has_domain_x = True
    desc.has_domain_y = True
    if dvz.dvz_panel_set_view2d(panel, ctypes.byref(desc)) != 0:
        raise RuntimeError("dvz_panel_set_view2d() failed")


def _add_screen_text(panel, text: bytes, x: float, y: float, size: float) -> None:
    label = dvz.dvz_text(panel, 0)
    if not label:
        raise RuntimeError("dvz_text() failed")

    style = dvz.dvz_text_style()
    style.size_px = size
    style.renderer = dvz.DVZ_TEXT_RENDERER_MSDF_ATLAS
    style.color[:] = (ex.TEXT.r, ex.TEXT.g, ex.TEXT.b, ex.TEXT.a)
    if dvz.dvz_text_set_style(label, ctypes.byref(style)) != 0:
        raise RuntimeError("dvz_text_set_style() failed")

    placement = dvz.dvz_text_placement()
    placement.mode = dvz.DVZ_TEXT_PLACEMENT_SCREEN
    placement.anchor = dvz.DVZ_SCENE_ANCHOR_SCREEN
    placement.position[:] = (x, y, 0.0)
    placement.text_anchor[:] = (0.0, 0.5)
    placement.has_text_anchor = True
    placement.depth_test = False
    if dvz.dvz_text_set_placement(label, ctypes.byref(placement)) != 0:
        raise RuntimeError("dvz_text_set_placement() failed")
    if dvz.dvz_text_set_string(label, text) != 0:
        raise RuntimeError("dvz_text_set_string() failed")


def _add_scale(scene, bundle: ChoroplethBundle):
    desc = dvz.dvz_scale_desc()
    desc.kind = dvz.DVZ_SCALE_CONTINUOUS
    desc.label = b"log10 people/km2"
    scale = dvz.dvz_scale(scene, ctypes.byref(desc))
    if not scale:
        raise RuntimeError("dvz_scale() failed")

    fmt = dvz.dvz_format_desc()
    fmt.precision = 2
    fmt.trim_trailing_zeros = True
    if dvz.dvz_scale_set_format(scale, ctypes.byref(fmt)) != 0:
        raise RuntimeError("dvz_scale_set_format() failed")
    if dvz.dvz_scale_set_domain(scale, bundle.value_min, bundle.value_max) != 0:
        raise RuntimeError("dvz_scale_set_domain() failed")
    if dvz.dvz_scale_set_view_range(scale, bundle.value_min, bundle.value_max) != 0:
        raise RuntimeError("dvz_scale_set_view_range() failed")

    ramp = (dvz.DvzColor * len(CHOROPLETH_RAMP))(*CHOROPLETH_RAMP)
    colormap = dvz.dvz_colormap_custom(scene, b"us_state_density", ramp, len(ramp))
    if not colormap:
        raise RuntimeError("dvz_colormap_custom() failed")
    if dvz.dvz_scale_set_colormap(scale, colormap) != 0:
        raise RuntimeError("dvz_scale_set_colormap() failed")
    return scale


def _add_choropleth_polygons(scene, panel, bundle: ChoroplethBundle) -> None:
    polygons = dvz.dvz_polygons(scene, 0)
    if not polygons:
        raise RuntimeError("dvz_polygons() failed")

    for i, (_region_index, point_first, point_count) in enumerate(bundle.rings):
        desc = dvz.dvz_polygon_desc()
        points = bundle.points[point_first : point_first + point_count]
        desc.outer.xy = ctypes.c_void_p(points.ctypes.data)
        desc.outer.count = int(point_count)
        index = dvz.dvz_polygons_add_region(polygons, ctypes.byref(desc))
        if index == 0xFFFFFFFF or index != i:
            raise RuntimeError("dvz_polygons_add_region() failed")

    ids = bundle.ids.ctypes.data_as(ctypes.POINTER(ctypes.c_uint64))
    fill = bundle.fill.ctypes.data_as(ctypes.POINTER(dvz.DvzColor))
    stroke = bundle.stroke.ctypes.data_as(ctypes.POINTER(dvz.DvzColor))
    widths = bundle.widths.ctypes.data_as(ctypes.POINTER(ctypes.c_float))

    if dvz.dvz_polygons_set_region_ids(polygons, 0, bundle.ring_count, ids) != 0:
        raise RuntimeError("dvz_polygons_set_region_ids() failed")
    if dvz.dvz_polygons_set_region_fill_colors(polygons, 0, bundle.ring_count, fill) != 0:
        raise RuntimeError("dvz_polygons_set_region_fill_colors() failed")
    if dvz.dvz_polygons_set_region_stroke_colors(polygons, 0, bundle.ring_count, stroke) != 0:
        raise RuntimeError("dvz_polygons_set_region_stroke_colors() failed")
    if dvz.dvz_polygons_set_region_stroke_widths_px(polygons, 0, bundle.ring_count, widths) != 0:
        raise RuntimeError("dvz_polygons_set_region_stroke_widths_px() failed")
    if dvz.dvz_polygons_set_stroke_join(polygons, dvz.DVZ_PATH_JOIN_ROUND, 3.0) != 0:
        raise RuntimeError("dvz_polygons_set_stroke_join() failed")

    attach = dvz.dvz_visual_attach_desc()
    attach.coord_space = dvz.DVZ_VISUAL_COORD_DATA
    attach.z_layer = 0
    composite = dvz.dvz_polygons_composite(polygons, 0)
    if not composite:
        raise RuntimeError("dvz_polygons_composite() failed")
    if dvz.dvz_panel_add_composite(panel, composite, ctypes.byref(attach)) != 0:
        raise RuntimeError("dvz_panel_add_composite() failed")


def _add_annotations(panel, scale) -> None:
    _add_screen_text(panel, b"Contiguous U.S. state population density", 32.0, 38.0, 34.0)
    _add_screen_text(
        panel,
        b"Census 2024 boundaries + Vintage 2025 population estimates",
        32.0,
        76.0,
        20.0,
    )

    desc = dvz.dvz_colorbar_desc()
    desc.orientation = dvz.DVZ_COLORBAR_ORIENTATION_VERTICAL
    desc.anchor = dvz.DVZ_SCENE_ANCHOR_PANEL_RIGHT
    desc.title = b"log10 people/km2"
    desc.reserve_px = 120.0
    desc.ramp_width_px = 28.0
    desc.plot_gap_px = 14.0
    desc.tick_length_px = 6.0
    desc.label_gap_px = 7.0
    colorbar = dvz.dvz_colorbar(panel, scale, ctypes.byref(desc))
    if not colorbar:
        raise RuntimeError("dvz_colorbar() failed")

    fmt = dvz.dvz_format_desc()
    fmt.precision = 2
    fmt.trim_trailing_zeros = True
    if dvz.dvz_colorbar_set_format(colorbar, ctypes.byref(fmt)) != 0:
        raise RuntimeError("dvz_colorbar_set_format() failed")


def _build_scene(path: Path | None = None):
    bundle = _load_bundle(path)
    scene, figure, panel = ex.scene_panel()
    _configure_panel(panel, bundle)
    scale = _add_scale(scene, bundle)
    _add_choropleth_polygons(scene, panel, bundle)
    _add_annotations(panel, scale)
    return scene, figure, panel, bundle


def _configure_view(view, scene, panel) -> None:
    desc = dvz.dvz_panzoom_desc()
    desc.controller_flags = dvz.DVZ_PANZOOM_FLAGS_KEEP_ASPECT
    controller = dvz.dvz_panzoom(scene, ctypes.byref(desc))
    if not controller:
        raise RuntimeError("dvz_panzoom() failed")
    if dvz.dvz_view_bind_controller(view, panel, controller, dvz.DVZ_DIM_MASK_XY) != 0:
        raise RuntimeError("dvz_view_bind_controller() failed")


def main() -> None:
    scene, figure, panel, bundle = _build_scene()
    print(
        "choropleth:"
        f" {bundle.region_count} regions,"
        f" {bundle.ring_count} rings,"
        f" {bundle.point_count} points from {bundle.path}"
    )

    def configure(view) -> None:
        _configure_view(view, scene, panel)

    ex.run_with_view(scene, figure, "U.S. State Choropleth", configure)


if __name__ == "__main__":
    main()
/*
 * Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
 * Licensed under the MIT license. See LICENSE file in the project root for details.
 * SPDX-License-Identifier: MIT
 */

/* choropleth - This example renders contiguous U.S. population density as polygon-set data.
 *
 * What to look for: prepared Census boundaries are loaded as flat point, ring, fill-color,
 * stroke-color, stroke-width, and id arrays, then colored by log10 resident population density.
 * Compare the state shapes with the vertical colorbar labeled in people per square kilometer; the
 * topology is ring-based and interior holes are not preserved.
 *
 * This workflow is useful for map-like scientific figures where real tabular values must be joined
 * to prepared geometry before rendering. Run the preparation command if the promoted data bundle is
 * not present.
 *
 * Scenario: showcases_choropleth
 * Style: showcase scientific, polygon-set, 1280x720 window target
 *
 * Data:    U.S. Census Bureau 2024 cartographic state boundaries and Vintage 2025 resident
 *          population estimates, prepared into flat polygon-set arrays.
 * Source:  https://www2.census.gov/geo/tiger/GENZ2024/shp/cb_2024_us_state_20m.zip
 *          https://www2.census.gov/programs-surveys/popest/tables/2020-2025/state/totals/NST-EST2025-POP.xlsx
 * Terms:   U.S. Census Bureau public data; cite the Census Bureau as source.
 * Prepare: python tools/data/prepare_us_state_choropleth.py
 * Promote: python tools/data/prepare_us_state_choropleth.py --output data/examples/us_state_choropleth
 * Build:   just example-c showcases/choropleth
 * Run:     ./build/examples/c/showcases/choropleth --live
 * Smoke:   ./build/examples/c/showcases/choropleth --png
 */



/*************************************************************************************************/
/*  Includes                                                                                     */
/*************************************************************************************************/

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "_alloc.h"
#include "_assertions.h"
#include "_compat.h"
#include "datoviz/fileio/fileio.h"
#include "datoviz/scene.h"
#include "example_common.h"
#include "example_style.h"
#include "runner/scenario_runner.h"



/*************************************************************************************************/
/*  Constants                                                                                    */
/*************************************************************************************************/

#define WIDTH  EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT

#define DEFAULT_DATA_BUNDLE  "data/examples/us_state_choropleth/prepared"
#define DEFAULT_CACHE_BUNDLE ".cache/datoviz/examples/us_state_choropleth/prepared"

#define METADATA_NAME    "metadata.tsv"
#define POINTS_NAME      "points_xy_f64.bin"
#define RINGS_NAME       "rings_u32.bin"
#define RING_FILL_NAME   "ring_fill_rgba8.bin"
#define RING_STROKE_NAME "ring_stroke_rgba8.bin"
#define RING_WIDTH_NAME  "ring_width_f32.bin"
#define RING_ID_NAME     "ring_id_u64.bin"

static const DvzColor CHOROPLETH_RAMP[5] = {
    {26, 35, 46, 235},
    {33, 99, 126, 235},
    {50, 160, 147, 235},
    {237, 191, 94, 235},
    {221, 96, 73, 235},
};



/*************************************************************************************************/
/*  Structs                                                                                      */
/*************************************************************************************************/

typedef struct ChoroplethRing
{
    uint32_t region_index;
    uint32_t point_first;
    uint32_t point_count;
} ChoroplethRing;


typedef struct ChoroplethBundle
{
    char path[1024];
    uint32_t region_count;
    uint32_t ring_count;
    uint32_t point_count;
    double xmin;
    double xmax;
    double ymin;
    double ymax;
    double value_min;
    double value_max;
    double density_min;
    double density_max;
    ChoroplethRing* rings;
    dvec2* points;
    DvzColor* fill;
    DvzColor* stroke;
    float* widths;
    uint64_t* ids;
} ChoroplethBundle;


typedef struct ChoroplethState
{
    ChoroplethBundle bundle;
} ChoroplethState;



/*************************************************************************************************/
/*  Forward declarations                                                                         */
/*************************************************************************************************/

DvzScenarioSpec dvz_showcase_us_state_choropleth_scenario(void);



/*************************************************************************************************/
/*  Bundle loading                                                                               */
/*************************************************************************************************/

/**
 * Join a directory and child filename.
 *
 * @param dir parent directory
 * @param name child filename
 * @param out output path
 * @param out_size output path size
 * @return whether the result fits
 */
static bool _join_path(const char* dir, const char* name, char* out, size_t out_size)
{
    ANN(dir);
    ANN(name);
    ANN(out);
    int n = dvz_snprintf(out, out_size, "%s/%s", dir, name);
    return n > 0 && (size_t)n < out_size;
}



/**
 * Return whether a file can be opened for reading.
 *
 * @param path file path
 * @return true when the file exists and is readable
 */
static bool _file_readable(const char* path)
{
    ANN(path);
    FILE* fp = fopen(path, "rb");
    if (fp == NULL)
        return false;
    fclose(fp);
    return true;
}



/**
 * Destroy a loaded choropleth bundle.
 *
 * @param bundle bundle to clear
 */
static void _choropleth_bundle_destroy(ChoroplethBundle* bundle)
{
    if (bundle == NULL)
        return;
    dvz_free(bundle->ids);
    dvz_free(bundle->widths);
    dvz_free(bundle->stroke);
    dvz_free(bundle->fill);
    dvz_free(bundle->points);
    dvz_free(bundle->rings);
    dvz_memset(bundle, sizeof(ChoroplethBundle), 0, sizeof(ChoroplethBundle));
}



/**
 * Read one typed binary array with an exact expected byte size.
 *
 * @param dir prepared bundle directory
 * @param name array file name
 * @param count expected item count
 * @param item_size expected item byte size
 * @param out output pointer
 * @return whether loading succeeded
 */
static bool _load_array(
    const char* dir, const char* name, uint32_t count, DvzSize item_size, void** out)
{
    ANN(dir);
    ANN(name);
    ANN(out);

    char path[1024] = {0};
    if (!_join_path(dir, name, path, sizeof(path)))
        return false;

    DvzSize size = 0;
    void* data = dvz_read_file(path, &size);
    const DvzSize expected = (DvzSize)count * item_size;
    if (data == NULL || size != expected)
    {
        dvz_free(data);
        return false;
    }

    *out = data;
    return true;
}



/**
 * Parse an unsigned integer metadata field.
 *
 * @param text field text
 * @param out output value
 * @return whether parsing succeeded
 */
static bool _parse_u32(const char* text, uint32_t* out)
{
    ANN(text);
    ANN(out);
    char* end = NULL;
    unsigned long value = strtoul(text, &end, 10);
    if (end == text || (end != NULL && *end != '\0') || value > UINT32_MAX)
        return false;
    *out = (uint32_t)value;
    return true;
}



/**
 * Parse a floating-point metadata field.
 *
 * @param text field text
 * @param out output value
 * @return whether parsing succeeded
 */
static bool _parse_f64(const char* text, double* out)
{
    ANN(text);
    ANN(out);
    char* end = NULL;
    double value = strtod(text, &end);
    if (end == text || (end != NULL && *end != '\0'))
        return false;
    *out = value;
    return true;
}



/**
 * Apply one metadata key/value pair to a choropleth bundle.
 *
 * @param bundle target bundle
 * @param key metadata key
 * @param value metadata value
 * @return whether the key was known and the value valid
 */
static bool _metadata_apply(ChoroplethBundle* bundle, const char* key, const char* value)
{
    ANN(bundle);
    ANN(key);
    ANN(value);

    if (strcmp(key, "region_count") == 0)
        return _parse_u32(value, &bundle->region_count);
    if (strcmp(key, "ring_count") == 0)
        return _parse_u32(value, &bundle->ring_count);
    if (strcmp(key, "point_count") == 0)
        return _parse_u32(value, &bundle->point_count);
    if (strcmp(key, "xmin") == 0)
        return _parse_f64(value, &bundle->xmin);
    if (strcmp(key, "xmax") == 0)
        return _parse_f64(value, &bundle->xmax);
    if (strcmp(key, "ymin") == 0)
        return _parse_f64(value, &bundle->ymin);
    if (strcmp(key, "ymax") == 0)
        return _parse_f64(value, &bundle->ymax);
    if (strcmp(key, "value_min") == 0)
        return _parse_f64(value, &bundle->value_min);
    if (strcmp(key, "value_max") == 0)
        return _parse_f64(value, &bundle->value_max);
    if (strcmp(key, "density_min") == 0)
        return _parse_f64(value, &bundle->density_min);
    if (strcmp(key, "density_max") == 0)
        return _parse_f64(value, &bundle->density_max);
    return false;
}



/**
 * Load bundle metadata from a tiny key/value TSV sidecar.
 *
 * @param dir prepared bundle directory
 * @param out output bundle
 * @return whether loading succeeded
 */
static bool _load_metadata(const char* dir, ChoroplethBundle* out)
{
    ANN(dir);
    ANN(out);

    char path[1024] = {0};
    if (!_join_path(dir, METADATA_NAME, path, sizeof(path)))
        return false;

    FILE* fp = fopen(path, "r");
    if (fp == NULL)
        return false;

    bool ok = true;
    char line[256] = {0};
    while (fgets(line, sizeof(line), fp) != NULL)
    {
        char key[96] = {0};
        char value[128] = {0};
        if (sscanf(line, "%95[^\t]\t%127s", key, value) != 2)
        {
            ok = false;
            break;
        }
        if (!_metadata_apply(out, key, value))
        {
            ok = false;
            break;
        }
    }
    if (ferror(fp) != 0)
        ok = false;
    fclose(fp);

    return ok && out->region_count > 0 && out->ring_count > 0 && out->point_count > 0 &&
           out->xmin < out->xmax && out->ymin < out->ymax && out->value_min < out->value_max;
}



/**
 * Validate loaded ring spans against the point and region arrays.
 *
 * @param bundle loaded bundle
 * @return whether all ring spans are valid
 */
static bool _validate_rings(const ChoroplethBundle* bundle)
{
    ANN(bundle);
    ANN(bundle->rings);
    for (uint32_t i = 0; i < bundle->ring_count; i++)
    {
        const ChoroplethRing* ring = &bundle->rings[i];
        if (ring->region_index >= bundle->region_count || ring->point_count < 3)
            return false;
        if (ring->point_first > bundle->point_count ||
            ring->point_count > bundle->point_count - ring->point_first)
        {
            return false;
        }
    }
    return true;
}



/**
 * Load a prepared choropleth typed-array bundle from disk.
 *
 * @param dir prepared bundle directory
 * @param out loaded bundle
 * @return whether loading succeeded
 */
static bool _choropleth_bundle_load(const char* dir, ChoroplethBundle* out)
{
    ANN(dir);
    ANN(out);

    dvz_memset(out, sizeof(ChoroplethBundle), 0, sizeof(ChoroplethBundle));
    if (dvz_snprintf(out->path, sizeof(out->path), "%s", dir) <= 0)
        return false;
    if (!_load_metadata(dir, out))
        goto fail;
    if (!_load_array(
            dir, RINGS_NAME, out->ring_count, sizeof(ChoroplethRing), (void**)&out->rings))
    {
        goto fail;
    }
    if (!_load_array(dir, POINTS_NAME, out->point_count, sizeof(dvec2), (void**)&out->points))
        goto fail;
    if (!_load_array(dir, RING_FILL_NAME, out->ring_count, sizeof(DvzColor), (void**)&out->fill))
        goto fail;
    if (!_load_array(
            dir, RING_STROKE_NAME, out->ring_count, sizeof(DvzColor), (void**)&out->stroke))
    {
        goto fail;
    }
    if (!_load_array(dir, RING_WIDTH_NAME, out->ring_count, sizeof(float), (void**)&out->widths))
        goto fail;
    if (!_load_array(dir, RING_ID_NAME, out->ring_count, sizeof(uint64_t), (void**)&out->ids))
        goto fail;
    if (!_validate_rings(out))
        goto fail;
    return true;

fail:
    _choropleth_bundle_destroy(out);
    return false;
}



/**
 * Return a default prepared bundle path.
 *
 * @param out output path
 * @param out_size output path size
 * @return whether a readable default bundle was found
 */
static bool _default_bundle_path(char* out, size_t out_size)
{
    ANN(out);
    char metadata_path[1200] = {0};
    if (_join_path(DEFAULT_DATA_BUNDLE, METADATA_NAME, metadata_path, sizeof(metadata_path)) &&
        _file_readable(metadata_path))
    {
        return dvz_snprintf(out, out_size, "%s", DEFAULT_DATA_BUNDLE) > 0;
    }
    if (_join_path(DEFAULT_CACHE_BUNDLE, METADATA_NAME, metadata_path, sizeof(metadata_path)) &&
        _file_readable(metadata_path))
    {
        return dvz_snprintf(out, out_size, "%s", DEFAULT_CACHE_BUNDLE) > 0;
    }
    return false;
}



/**
 * Print instructions when no prepared bundle is available.
 */
static void _print_missing_data(void)
{
    dvz_fprintf(stderr, "choropleth: missing prepared bundle\n");
    dvz_fprintf(stderr, "  python tools/data/prepare_us_state_choropleth.py\n");
}



/*************************************************************************************************/
/*  Scene helpers                                                                                */
/*************************************************************************************************/

/**
 * Configure the panel for the map view.
 *
 * @param panel target panel
 * @param bundle loaded map bundle
 * @return whether setup succeeded
 */
static bool _configure_panel(DvzPanel* panel, const ChoroplethBundle* bundle)
{
    ANN(panel);
    ANN(bundle);

    if (dvz_panel_set_padding(
            panel, &(DvzPanelReserve){
                       .left_px = 14.0f, .right_px = 42.0f, .bottom_px = 13.5f,
                       .top_px = 110.0f}) != DVZ_OK)
        return false;
    return example_configure_equal_aspect_panel(
        panel, (DvzDataDomain){.min = bundle->xmin, .max = bundle->xmax},
        (DvzDataDomain){.min = bundle->ymin, .max = bundle->ymax}, 0.035);
}



/**
 * Add screen-space text.
 *
 * @param panel target panel
 * @param text string to draw
 * @param x screen X in logical pixels
 * @param y screen Y in logical pixels
 * @param size text size in pixels
 * @param role graphite-cyan color role
 * @return whether setup succeeded
 */
static bool _add_screen_text(
    DvzPanel* panel, const char* text, float x, float y, float size, ExampleStyleColorRole role)
{
    ANN(panel);
    ANN(text);

    DvzText* label = dvz_text(panel, 0);
    if (label == NULL)
        return false;

    DvzColor color = example_graphite_cyan_color(role);
    DvzTextStyle style = dvz_text_style();
    style.size_px = size;
    style.renderer = DVZ_TEXT_RENDERER_MSDF_ATLAS;
    style.color[0] = color.r;
    style.color[1] = color.g;
    style.color[2] = color.b;
    style.color[3] = color.a;
    if (dvz_text_set_style(label, &style) != 0)
        return false;

    DvzTextPlacement placement = dvz_text_placement();
    placement.mode = DVZ_TEXT_PLACEMENT_SCREEN;
    placement.anchor = DVZ_SCENE_ANCHOR_SCREEN;
    placement.position[0] = x;
    placement.position[1] = y;
    placement.position[2] = 0.0;
    placement.text_anchor[0] = 0.0f;
    placement.text_anchor[1] = 0.5f;
    placement.has_text_anchor = true;
    dvz_text_set_placement(label, &placement);
    dvz_text_set_string(label, text);
    return true;
}



/**
 * Create the choropleth color scale.
 *
 * @param scene owning scene
 * @param bundle loaded map bundle
 * @return created scale or NULL
 */
static DvzScale* _add_scale(DvzScene* scene, const ChoroplethBundle* bundle)
{
    ANN(scene);
    ANN(bundle);

    DvzScale* scale = dvz_scale(
        scene, &(DvzScaleDesc){DVZ_STRUCT_INIT_FIELDS(DvzScaleDesc),
                   .kind = DVZ_SCALE_CONTINUOUS,
                   .label = "log10 people/km2",
               });
    if (scale == NULL)
        return NULL;
    dvz_scale_set_format(
        scale, &(DvzFormatDesc){DVZ_STRUCT_INIT_FIELDS(DvzFormatDesc),
                   .precision = 2,
                   .trim_trailing_zeros = true});
    dvz_scale_set_domain(scale, bundle->value_min, bundle->value_max);
    dvz_scale_set_view_range(scale, bundle->value_min, bundle->value_max);

    DvzColormap* colormap =
        dvz_colormap_custom(scene, "us_state_density", CHOROPLETH_RAMP, DVZ_ARRAY_COUNT(CHOROPLETH_RAMP));
    if (colormap == NULL)
        return NULL;
    dvz_scale_set_colormap(scale, colormap);
    return scale;
}



/**
 * Add the filled state polygons and strokes.
 *
 * @param scene owning scene
 * @param panel target panel
 * @param bundle loaded map bundle
 * @return whether setup succeeded
 */
static bool _add_choropleth_polygons(
    DvzScene* scene, DvzPanel* panel, const ChoroplethBundle* bundle)
{
    ANN(scene);
    ANN(panel);
    ANN(bundle);

    DvzPolygons* set = dvz_polygons(scene, 0);
    if (set == NULL)
        return false;

    bool ok = true;
    for (uint32_t i = 0; i < bundle->ring_count; i++)
    {
        const ChoroplethRing* ring = &bundle->rings[i];
        const dvec2* xy = (const dvec2*)&bundle->points[ring->point_first];
        const uint32_t index = dvz_polygons_add_region(
            set, &(DvzPolygonDesc){DVZ_STRUCT_INIT_FIELDS(DvzPolygonDesc),
                   .outer = {.xy = xy, .count = ring->point_count}});
        if (index == UINT32_MAX || index != i)
        {
            ok = false;
            break;
        }
    }

    if (ok && dvz_polygons_set_region_ids(set, 0, bundle->ring_count, bundle->ids) != 0)
        ok = false;
    if (ok && dvz_polygons_set_region_fill_colors(set, 0, bundle->ring_count, bundle->fill) != 0)
        ok = false;
    if (ok && dvz_polygons_set_region_stroke_colors(set, 0, bundle->ring_count, bundle->stroke) != 0)
        ok = false;
    if (ok && dvz_polygons_set_region_stroke_widths_px(set, 0, bundle->ring_count, bundle->widths) != 0)
        ok = false;
    if (ok && dvz_polygons_set_stroke_join(set, DVZ_PATH_JOIN_ROUND, 3.0f) != 0)
        ok = false;

    if (ok)
    {
        DvzComposite* composite = dvz_polygons_composite(set, 0);
        ok = composite != NULL &&
             dvz_panel_add_composite(
                 panel, composite,
                 &(DvzVisualAttachDesc){DVZ_STRUCT_INIT_FIELDS(DvzVisualAttachDesc),
                                        .z_layer = 0,
                                        .coord_space = DVZ_VISUAL_COORD_DATA}) == 0;
    }

    return ok;
}



/**
 * Add title, source note, and scalar colorbar.
 *
 * @param panel target panel
 * @param scale color scale
 * @return whether setup succeeded
 */
static bool _add_annotations(DvzPanel* panel, DvzScale* scale)
{
    ANN(panel);
    ANN(scale);

    if (!_add_screen_text(
            panel, "Contiguous U.S. state population density", 32.0f, 38.0f, 34.0f,
            EXAMPLE_STYLE_COLOR_TEXT))
    {
        return false;
    }
    if (!_add_screen_text(
            panel, "Census 2024 boundaries + Vintage 2025 population estimates", 32.0f, 76.0f,
            20.0f, EXAMPLE_STYLE_COLOR_TEXT))
    {
        return false;
    }

    DvzColorbar* colorbar = dvz_colorbar(
        panel, scale,
        &(DvzColorbarDesc){DVZ_STRUCT_INIT_FIELDS(DvzColorbarDesc),
            .orientation = DVZ_COLORBAR_ORIENTATION_VERTICAL,
            .anchor = DVZ_SCENE_ANCHOR_PANEL_RIGHT,
            .title = "log10 people/km2",
            .reserve_px = 120.0f,
            .ramp_width_px = 28.0f,
            .plot_gap_px = 14.0f,
            .tick_length_px = 6.0f,
            .label_gap_px = 7.0f,
        });
    if (colorbar == NULL)
        return false;
    dvz_colorbar_set_format(
        colorbar, &(DvzFormatDesc){DVZ_STRUCT_INIT_FIELDS(DvzFormatDesc),
                      .precision = 2,
                      .trim_trailing_zeros = true});
    return true;
}



/*************************************************************************************************/
/*  Scenario callbacks                                                                           */
/*************************************************************************************************/

/**
 * Initialize the choropleth scenario.
 *
 * @param ctx scenario context
 * @param out_user scenario state output
 * @return true on success
 */
static bool _scenario_init(DvzScenarioContext* ctx, void** out_user)
{
    if (ctx == NULL || out_user == NULL)
        return false;
    *out_user = NULL;

    ChoroplethState* state = (ChoroplethState*)dvz_calloc(1, sizeof(ChoroplethState));
    if (state == NULL)
        return false;

    char bundle_path[1024] = {0};
    if (!_default_bundle_path(bundle_path, sizeof(bundle_path)))
    {
        _print_missing_data();
        dvz_free(state);
        return false;
    }
    if (!_choropleth_bundle_load(bundle_path, &state->bundle))
    {
        dvz_fprintf(stderr, "choropleth: failed to load prepared bundle %s\n", bundle_path);
        dvz_free(state);
        return false;
    }

    ctx->figure = dvz_figure(ctx->scene, ctx->width, ctx->height, 0);
    if (ctx->figure == NULL)
        goto fail;

    DvzPanel* panel = dvz_panel(ctx->figure, &(DvzPanelDesc){0.0f, 0.0f, 1.0f, 1.0f});
    if (panel == NULL)
        goto fail;
    if (!_configure_panel(panel, &state->bundle))
        goto fail;

    DvzScale* scale = _add_scale(ctx->scene, &state->bundle);
    if (scale == NULL)
        goto fail;
    if (!_add_choropleth_polygons(ctx->scene, panel, &state->bundle))
        goto fail;
    if (!_add_annotations(panel, scale))
        goto fail;

    DvzPanzoomDesc panzoom = dvz_panzoom_desc();
    panzoom.controller_flags = DVZ_PANZOOM_FLAGS_KEEP_ASPECT;
    if (dvz_scenario_panzoom(ctx, panel, &panzoom, DVZ_DIM_MASK_XY) == NULL)
        goto fail;

    dvz_fprintf(
        stderr, "choropleth: %u regions, %u rings, %u points from %s\n",
        state->bundle.region_count, state->bundle.ring_count, state->bundle.point_count,
        state->bundle.path);

    *out_user = state;
    return true;

fail:
    _choropleth_bundle_destroy(&state->bundle);
    dvz_free(state);
    return false;
}



/**
 * Destroy choropleth scenario state.
 *
 * @param ctx scenario context
 * @param user scenario state
 */
static void _scenario_destroy(DvzScenarioContext* ctx, void* user)
{
    (void)ctx;
    ChoroplethState* state = (ChoroplethState*)user;
    if (state == NULL)
        return;
    _choropleth_bundle_destroy(&state->bundle);
    dvz_free(state);
}



/**
 * Return the choropleth scenario specification.
 *
 * @return scenario specification
 */
DvzScenarioSpec dvz_showcase_us_state_choropleth_scenario(void)
{
    return (DvzScenarioSpec){
        .id = "showcases_choropleth",
        .title = "U.S. State Choropleth",
        .width = WIDTH,
        .height = HEIGHT,
        .fps = 60.0,
        .init = _scenario_init,
        .destroy = _scenario_destroy,
    };
}



/*************************************************************************************************/
/*  Functions                                                                                    */
/*************************************************************************************************/

/**
 * Run the U.S. state choropleth through the native scenario runner.
 *
 * @param argc command-line argument count
 * @param argv command-line argument vector
 * @return process exit code
 */
#ifndef DVZ_EXAMPLE_NO_MAIN
int main(int argc, char** argv)
{
    DvzScenarioSpec spec = dvz_showcase_us_state_choropleth_scenario();
    return dvz_scenario_run_native_cli(&spec, argc, argv) == 0 ? 0 : 1;
}
#endif
Example details

Tags

scientific, real-data, geo, composite, polygon-set, choropleth, colorbar, panzoom, capture

Data

Field Value
kind prepared

Dataset

Field Value
name Contiguous U.S. state population density
source U.S. Census Bureau 2024 cartographic state boundaries and Vintage 2025 state population estimates
source_url https://www2.census.gov/geo/tiger/GENZ2024/shp/cb_2024_us_state_20m.zip; https://www2.census.gov/programs-surveys/popest/tables/2020-2025/state/totals/NST-EST2025-POP.xlsx
boundary_source https://www2.census.gov/geo/tiger/GENZ2024/shp/cb_2024_us_state_20m.zip
population_source https://www2.census.gov/programs-surveys/popest/tables/2020-2025/state/totals/NST-EST2025-POP.xlsx
license U.S. Census Bureau public data; cite the Census Bureau as source.
citation U.S. Census Bureau, 2024 Cartographic Boundary File, States, 1:20m; U.S. Census Bureau, Vintage 2025 state resident population estimates.
fallback_prepared_path .cache/datoviz/examples/us_state_choropleth/prepared
promoted_prepared_path data/examples/us_state_choropleth/prepared
preprocessing python tools/data/prepare_us_state_choropleth.py
prepared_layout flat typed arrays plus metadata.tsv
provenance Prepared from Census boundary and population files by filtering to the 48 contiguous states, projecting rings with a spherical Albers equal-area transform, and encoding log10 resident population density from 2025 population estimates and Census ALAND values.

Encoding

Field Value
position Census boundary rings projected with a spherical Albers equal-area transform and normalized into scene space
color log10 of Vintage 2025 resident population per Census ALAND square kilometer
topology each shapefile ring is rendered as one polygon-set region; interior holes are not preserved