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
- ID:
showcases_choropleth - Category:
showcase - Lane:
showcases - Status:
supported - Source:
examples/c/showcases/choropleth.c - Approved adaptation starter:
no - Python source:
examples/python/gallery/showcases/choropleth.py - Python adaptation: Available
- Browser support: Live in browser
- WebGPU live route:
examples/webgpu/live.html?id=showcases_choropleth - Browser capability tags:
composite,polygon-set,colorbar,panzoom - Validation:
smoke+screenshot+manual
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 |
