Skip to content

Replace matplotlib with Altair and add live charts to docs#305

Merged
chekos merged 5 commits intomainfrom
claude/zen-proskuriakova
Mar 10, 2026
Merged

Replace matplotlib with Altair and add live charts to docs#305
chekos merged 5 commits intomainfrom
claude/zen-proskuriakova

Conversation

@chekos
Copy link
Copy Markdown
Owner

@chekos chekos commented Mar 10, 2026

Summary

  • Replaced all matplotlib/pyplot code with Altair across 8 documentation files
  • Added mkdocs-charts-plugin to render interactive Vega-Lite charts directly in the docs (choropleth maps, line charts) so readers see live visualizations—not just code blocks
  • Updated the tidycensus migration comparison table to reference Altair instead of matplotlib

Changes

Infrastructure

  • pyproject.toml — Added mkdocs-charts-plugin to docs dependencies
  • mkdocs.yml — Added charts plugin, extra_javascript for Vega/Vega-Lite/Vega-Embed CDN, and vegalite custom fence

Documentation (8 files)

  • docs/guides/spatial.md — Replaced matplotlib choropleth & dot density code with Altair + live choropleth
  • docs/guides/multi-year.md — Replaced matplotlib line chart with Altair + live interactive line chart
  • docs/getting-started/quickstart.md — Replaced .plot() with Altair + live US choropleth
  • docs/guides/acs-data.md — Replaced .plot() with Altair mark_geoshape()
  • docs/guides/decennial-data.md — Replaced .plot() with Altair + live 2020 Census choropleth
  • docs/guides/population-estimates.md — Replaced .plot() with Altair + live population choropleth
  • docs/guides/migration-flows.md — Replaced .plot() with Altair
  • docs/migration/from-tidycensus.md — Updated comparison table and code example

Test plan

  • Run mkdocs build to verify no build errors with the new charts plugin
  • Run mkdocs serve and check that Vega-Lite charts render interactively on the spatial, multi-year, quickstart, decennial, and population estimates pages
  • Verify tooltips work on hover for all rendered charts
  • Confirm zero remaining matplotlib / plt. references in docs: grep -r "matplotlib\|plt\." docs/

🤖 Generated with Claude Code

…Lite charts

Switch all visualization examples from matplotlib to Altair for a more
declarative, interactive charting experience. Add mkdocs-charts-plugin to
render Vega-Lite specs directly in the docs so readers can see live,
interactive charts with tooltips—not just code blocks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Mar 10, 2026

Claude finished @chekos's task in 2m 40s —— View job


PR Review: Replace matplotlib with Altair and add live charts to docs

  • Read CLAUDE.md and gather context
  • Review infrastructure changes (pyproject.toml, mkdocs.yml)
  • Review documentation changes
  • Post detailed feedback

Summary

This is a well-motivated change — replacing matplotlib with Altair and embedding live Vega-Lite charts significantly improves the docs UX. The infrastructure setup (mkdocs-charts-plugin, CDN scripts, custom fence) is correct. However, there are several issues that need attention before merging.


Issues Found

🔴 Bug: Wide-format output + tidy-column field name mismatch (migration-flows.md)

In the complete example at the bottom of docs/guides/migration-flows.md (lines 295–320), the get_flows() call uses output="wide", which produces separate MOVEDIN, MOVEDOUT, and MOVEDNET columns. But the Altair encoding then references "estimate:Q":

ca_age_flows = get_flows(
    ...
    output="wide",   # produces MOVEDIN, MOVEDOUT, MOVEDNET columns
    ...
)
alt.Chart(young_workers).mark_geoshape(...).encode(
    color=alt.Color("estimate:Q", ...),  # ❌ "estimate" doesn't exist in wide format
    tooltip=["FULL1_NAME:N", alt.Tooltip("estimate:Q", ...)],
)

The field should be "MOVEDNET:Q" to match wide-format output. Fix this →


🟡 Misleading: Live charts don't match the surrounding code examples

In multiple files, the embedded vegalite chart renders data that is completely different from what the Python code just above it demonstrates:

  • docs/getting-started/quickstart.md (section 2): The code fetches tract-level poverty rates for LA County, but the rendered chart shows "Population by State (sample choropleth)" — a national state-level map with population data. A reader following along would be confused why their LA poverty map looks like a US population map.

  • docs/guides/decennial-data.md: Code fetches state="IL" county-level data, but chart shows national state-level 2020 Census data.

  • docs/guides/population-estimates.md: Code fetches state="WA" county-level estimates, but chart shows a national state-level choropleth.

Consider either: (a) labeling the vegalite blocks more explicitly with an admonition like !!! note "Sample chart (state-level data)" to set expectations, or (b) aligning the sample data more closely with the code example (e.g., county-level data for the state shown in the code).


🟡 Missing altair in docs dependencies (pyproject.toml)

The docs examples all use import altair as alt, but altair is not listed in [project.optional-dependencies] docs:

# pyproject.toml line 35-43
docs = [
    "mkdocs",
    "mkdocs-material",
    ...
    "mkdocs-charts-plugin",
    # altair is missing here
]

Anyone running the code examples (or building the docs with doctest) would hit ModuleNotFoundError: No module named 'altair'. Fix this →


🟡 Missing DC (id: 11) in some choropleth data sets

The Vega-Lite chart in docs/guides/spatial.md correctly includes District of Columbia ({"id": 11, ...}), but the charts in docs/getting-started/quickstart.md and docs/guides/decennial-data.md omit it. This leaves DC as a grey/missing region on those maps, which looks like a data error.


🟡 No version pin for mkdocs-charts-plugin

"mkdocs-charts-plugin",   # no version constraint

The other docs dependencies similarly lack pins, but since this plugin governs how charts render, a major version bump could silently break all live charts. Consider adding "mkdocs-charts-plugin>=0.0" or a tighter constraint.


🟢 Minor: CDN URLs use major-version-only pins

# mkdocs.yml lines 82-84
- https://cdn.jsdelivr.net/npm/vega@5
- https://cdn.jsdelivr.net/npm/vega-lite@5
- https://cdn.jsdelivr.net/npm/vega-embed@6

These will always resolve to the latest minor/patch within vega@5, vega-lite@5, and vega-embed@6. This is standard practice for Vega (no known breaking minor releases within a major version), but for reproducibility you could pin to a specific version like vega@5.30.0. Low priority.


What's Working Well

  • The Altair code patterns themselves are clean and idiomatic throughout.
  • The vegalite custom fence integration with mkdocs-charts-plugin is set up correctly.
  • The CDN approach for topojson data (vega-datasets@v2.7.0) is version-pinned and reliable.
  • All matplotlib/plt. references have been fully removed — confirmed with a full docs grep.
  • The multi-year.md line chart is the cleanest example: the rendered data matches the Python code above it perfectly.
  • Adding mkdocs-charts-plugin to docs deps is the right approach.

Key Items to Address

Priority File Issue
🔴 Bug migration-flows.md "estimate:Q""MOVEDNET:Q" in wide-format example
🟡 Confusing Multiple files Live charts show different data than the code above them
🟡 Missing dep pyproject.toml altair not in docs dependencies
🟡 Incomplete data quickstart.md, decennial-data.md DC missing from choropleth data

- Add `altair` to docs optional dependencies in pyproject.toml
- Add DC (id: 11) to choropleth datasets in quickstart, decennial, and
  population-estimates pages that were missing it
- Add `!!! example` admonitions above all rendered vegalite charts to
  clarify they show state-level sample data, not the exact output of the
  code above them
- Update chart titles to remove "(sample data)" suffix now that
  admonitions explain the context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Mar 10, 2026

Claude finished @chekos's task in 1m 20s —— View job


PR Review Update: Replace matplotlib with Altair and add live charts to docs

  • Read current file state and compare with previous review findings
  • Verify which issues from prior review have been addressed
  • Post updated review

All Key Issues Resolved ✅

This update addresses every item flagged in the previous review. Here's the status:

Priority File Issue Status
🔴 Bug migration-flows.md "estimate:Q""MOVEDNET:Q" in wide-format example ✅ Fixed (line 318)
🟡 Confusing Multiple files Live charts showed different data than surrounding code ✅ Fixed — admonitions added
🟡 Missing dep pyproject.toml altair not in docs dependencies ✅ Fixed (line 43)
🟡 Incomplete data quickstart.md, decennial-data.md DC missing from choropleth data ✅ Fixed — {"id": 11, ...} added to all charts
🟡 No version pin pyproject.toml mkdocs-charts-plugin unpinned Still open (minor)
🟢 Minor mkdocs.yml CDN URLs use major-version-only pins Still open (low priority)

Details

migration-flows.md — The complete example now correctly uses "MOVEDNET:Q" in both the color and tooltip encodings, matching the output="wide" call above it.

Chart/code alignment — Each embedded vegalite chart now has a clear !!! example "Interactive preview — ..." admonition that explicitly explains the chart shows different geographic scope than the code. This effectively resolves the confusion.

altair dependencyaltair is now listed in [project.optional-dependencies].docs in pyproject.toml.

DC coverage — All three choropleth charts (quickstart.md, decennial-data.md, population-estimates.md) now include {"id": 11, ...} for the District of Columbia.

Remaining Minor Items

These two items remain but are low-priority and don't block merging:

  • mkdocs-charts-plugin has no version constraint in pyproject.toml. If a breaking release occurs, charts could silently break. Consider "mkdocs-charts-plugin>=0.0,<1" or similar.
  • Vega CDN URLs (vega@5, vega-lite@5, vega-embed@6) resolve to the latest patch within the major. This is standard practice and fine for now.

This PR is in good shape. The Altair patterns are clean and idiomatic, the live chart integration is set up correctly, and all the substantive issues from the prior review have been addressed.

The lockfile was out of sync with pyproject.toml after adding
mkdocs-charts-plugin and altair to the docs extras. ReadTheDocs
uses `uv sync --frozen` which requires the lockfile to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Mar 10, 2026

Claude finished @chekos's task in 3m 0s —— View job


PR Review: Replace matplotlib with Altair and add live charts to docs

  • Read CLAUDE.md and gather context
  • Review current state of all changed files
  • Post detailed feedback

Status: All Prior Issues Resolved — One New Issue Found

The two issues flagged as "still open" in the previous review remain unaddressed (version pinning), and one new bug has been identified that wasn't in prior reviews.


New Issue: geometry.x:Q / geometry.y:Q is not a valid Altair field reference (spatial.md)

In the dot density map example at docs/guides/spatial.md lines 403–405:

alt.Chart(dots).mark_circle(size=1, opacity=0.6).encode(
    longitude="geometry.x:Q",   # ❌ Altair doesn't support nested attribute access
    latitude="geometry.y:Q",    # ❌ in field references
    ...

When alt.Chart(dots) serializes the GeoDataFrame, the geometry column is converted to GeoJSON features — not to {"x": ..., "y": ...} objects. Altair has no way to resolve geometry.x as a field reference, so the chart will produce no points.

The fix is to extract the coordinates into separate columns first:

dots = dots.copy()
dots["lon"] = dots.geometry.x
dots["lat"] = dots.geometry.y

alt.Chart(dots).mark_circle(size=1, opacity=0.6).encode(
    longitude="lon:Q",
    latitude="lat:Q",
    ...

Fix this →


Remaining Items from Prior Reviews (Still Open)

Priority File Issue
🟡 pyproject.toml mkdocs-charts-plugin has no version constraint — a breaking release could silently break all live charts. Consider "mkdocs-charts-plugin>=0.0,<1".
🟢 mkdocs.yml Vega CDN URLs (vega@5, vega-lite@5, vega-embed@6) pin major only. Fine for standard practice, but worth noting for reproducibility.

What's Working Well

  • All critical issues from prior reviews have been resolved: "MOVEDNET:Q" fix in migration-flows.md, altair added to docs deps, DC (id: 11) added to all choropleth datasets, and admonitions added to clarify chart/code scope mismatches.
  • The Vega-Lite inline data values are accurate (cross-checked against real Census figures).
  • The multi-year.md line chart remains the cleanest example — data matches the code perfectly.
  • Zero remaining matplotlib/plt. references confirmed.
  • Infrastructure (mkdocs-charts-plugin, custom vegalite fence, CDN scripts) is set up correctly.

| Branch

Documents project structure, common commands, dependency
management (including the uv.lock gotcha with ReadTheDocs),
docs conventions, CI pipeline, and test markers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Mar 10, 2026

Claude finished @chekos's task in 1m 16s —— View job


PR Review: Replace matplotlib with Altair and add live charts to docs

  • Read CLAUDE.md and gather context
  • Check if geometry.x:Q / geometry.y:Q issue in spatial.md was fixed
  • Review any other changes since last review
  • Post updated feedback

Status: One Bug Still Open

The PR was synchronized by adding CLAUDE.md (project context for Claude Code sessions), which is a good addition. However, the bug flagged in the previous review remains unaddressed.


Still Open: geometry.x:Q / geometry.y:Q is not a valid Altair field reference (spatial.md)

In the dot density map example at docs/guides/spatial.md lines 403–405:

alt.Chart(dots).mark_circle(size=1, opacity=0.6).encode(
    longitude="geometry.x:Q",   # ❌ Altair doesn't support nested attribute access
    latitude="geometry.y:Q",    # ❌ in field references
    ...

When alt.Chart(dots) serializes the GeoDataFrame, the geometry column is converted to GeoJSON features — not to {"x": ..., "y": ...} objects. Altair cannot resolve geometry.x as a field reference, so the chart will produce no points.

Fix: Extract the coordinates into separate columns first:

dots = dots.copy()
dots["lon"] = dots.geometry.x
dots["lat"] = dots.geometry.y

alt.Chart(dots).mark_circle(size=1, opacity=0.6).encode(
    longitude="lon:Q",
    latitude="lat:Q",
    ...

Fix this →


Low-Priority Items (Unchanged)

Priority File Issue
🟡 pyproject.toml mkdocs-charts-plugin has no version constraint — a breaking release could silently break all live charts. Consider "mkdocs-charts-plugin>=0.0,<1".
🟢 mkdocs.yml Vega CDN URLs (vega@5, vega-lite@5, vega-embed@6) pin major only — standard practice, but worth noting for reproducibility.

What's Working Well

  • CLAUDE.md is a nice addition with clear project setup and conventions.
  • All critical issues from the first two reviews are resolved: "MOVEDNET:Q" fix, altair in docs deps, DC (id: 11) in all choropleth datasets, admonitions for chart/code scope differences.
  • Zero remaining matplotlib/plt. references.
  • Infrastructure (mkdocs-charts-plugin, vegalite fence, CDN scripts) is correct.
    | Branch

Altair cannot resolve nested attribute access like geometry.x
in field references. Extract lon/lat into separate columns
before passing to alt.Chart().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Mar 10, 2026

Claude finished @chekos's task in 54s —— View job


PR Review: Replace matplotlib with Altair and add live charts to docs

  • Read CLAUDE.md and gather context
  • Check current state of spatial.md for the geometry.x:Q bug
  • Post updated review

All Issues Resolved ✅

The geometry.x:Q / geometry.y:Q bug flagged in previous reviews has been fixed. The dot density map now correctly extracts coordinates before building the Altair chart:

# Extract point coordinates for Altair.
dots = dots.copy()
dots["lon"] = dots.geometry.x
dots["lat"] = dots.geometry.y

alt.Chart(dots).mark_circle(size=1, opacity=0.6).encode(
    longitude="lon:Q",   # ✅ plain column reference
    latitude="lat:Q",    # ✅ plain column reference
    ...

Full Issue Tracker

Priority File Issue Status
🔴 Bug migration-flows.md "estimate:Q""MOVEDNET:Q" in wide-format example ✅ Fixed
🔴 Bug spatial.md geometry.x:Q / geometry.y:Q invalid Altair field refs ✅ Fixed
🟡 Confusing Multiple files Live charts showed different data than surrounding code ✅ Fixed — admonitions added
🟡 Missing dep pyproject.toml altair not in docs dependencies ✅ Fixed
🟡 Incomplete data quickstart.md, decennial-data.md DC missing from choropleth data ✅ Fixed
🟡 No version pin pyproject.toml mkdocs-charts-plugin unpinned Still open (minor)
🟢 Minor mkdocs.yml CDN URLs pin major only Still open (low priority)

Remaining Low-Priority Items

These two items don't block merging:

  • mkdocs-charts-plugin version constraint — No upper bound means a future breaking release could silently break all live charts. Consider "mkdocs-charts-plugin>=0.0,<1".
  • Vega CDN major-only pins (vega@5, vega-lite@5, vega-embed@6) — Standard practice; fine as-is.

Summary

This PR is in great shape. Zero remaining matplotlib/plt. references in docs (confirmed). The Altair patterns are clean and idiomatic, the live vegalite fence integration works correctly, all chart/code scope mismatches are clearly labeled with admonitions, and all substantive bugs have been fixed.
| Branch

@chekos chekos merged commit d2f5dd6 into main Mar 10, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant