Skip to content

Add choropleth map implementation - #500

Open
chesselingfm wants to merge 3 commits into
chekos:mainfrom
chesselingfm:main
Open

Add choropleth map implementation#500
chesselingfm wants to merge 3 commits into
chekos:mainfrom
chesselingfm:main

Conversation

@chesselingfm

Copy link
Copy Markdown

Added implementation for Datawrapper's choropleth map API

  • Define columns for keys and values
  • Choose your basemap - at this point you have to know Datawrapper's internal name for the basemap
  • Projections
  • Select Key and Value columns from your df (and define ist key column contains leading zeros)
  • define color mode, palettes and colors
  • Map zoom
  • Internal borders
  • Hide empty regions

@palewire

Copy link
Copy Markdown
Collaborator

Great start!

Here's one thing. If you look in the tests/samples folder you will see that we've packaged some JSON configuration files and data uploads drawn from the real-world charts that Datawrapper includes in its documentation site.

Those are then integrated into tests that verify the code is working. One sample is pulled out and featured in our documentation site with a Pythonic implementation as an example.

Do you think you'd be able to try add those pieces yet?

@palewire
palewire requested a review from Copilot October 31, 2025 13:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for choropleth maps to the datawrapper library, enabling users to create geographic data visualizations where regions are colored based on data values.

Key changes:

  • New ChoroplethMap class with comprehensive configuration options for data mapping, basemaps, colors, and tooltips
  • Three new enum classes (ColorScale, ColorMode, BasemapProjection) for type-safe configuration
  • Integration with existing library architecture through proper imports and exports

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
datawrapper/charts/enums/color_scale.py Defines ColorScale enum with scale types (linear, log, sqrt, quantile, jenks)
datawrapper/charts/enums/color_mode.py Defines ColorMode enum for gradient vs buckets coloring
datawrapper/charts/enums/basemap_projection.py Defines BasemapProjection enum with map projection types
datawrapper/charts/enums/init.py Exports new enum classes for external use
datawrapper/charts/choropleth_map.py Main implementation with ChoroplethMap class and Tooltip configuration
datawrapper/charts/base.py Adds "d3-maps-choropleth" to supported chart types
datawrapper/charts/init.py Exports ChoroplethMap and related classes
datawrapper/init.py Re-exports ChoroplethMap and enums at package level

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

map_key_attr: str = Field(
default="",
alias="map-key-attr",
description="The attribute in the basemap to match against your keys column (e.g., 'iso-a3', 'fips', 'ags)",

Copilot AI Oct 31, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing single quote in the description string. The string should end with 'ags')" instead of 'ags)".

Suggested change
description="The attribute in the basemap to match against your keys column (e.g., 'iso-a3', 'fips', 'ags)",
description="The attribute in the basemap to match against your keys column (e.g., 'iso-a3', 'fips', 'ags')",

Copilot uses AI. Check for mistakes.
Comment on lines +296 to +298
if self.color_scale == "quantile":
interpolation = "equidistant"
elif self.color_scale == "jenks":

Copilot AI Oct 31, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 295 initializes interpolation to 'equidistant', then line 297 redundantly sets it to the same value when color_scale == 'quantile'. The if-condition on line 296-297 has no effect and should be removed.

Suggested change
if self.color_scale == "quantile":
interpolation = "equidistant"
elif self.color_scale == "jenks":
if self.color_scale == "jenks":

Copilot uses AI. Check for mistakes.
@chesselingfm

chesselingfm commented Oct 31, 2025 via email

Copy link
Copy Markdown
Author

@palewire

Copy link
Copy Markdown
Collaborator

Yes, that's right. We use this tool to snatch the JSON from the official examples. https://palewi.re/docs/datawrapper-json-bookmarklet/

@palewire palewire left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding choropleth support — this is a worthwhile chart type to bring into the typed API, and the core shape is headed in the right direction. I reviewed the PR metadata, comments, full diff, implementation, exports, comparable chart classes/tests/docs, the bundled JSON/CSV sample, current CI/merge state, and ran focused local checks in a fresh worktree. My recommendation is request changes for now: the package imports and the existing test suite still passes, but the new chart type is not integrated deeply enough to be merge-ready, and realistic Academy-style parsing/serialization exposes several gaps.

Blocking findings

  1. The PR is currently not mergeable.
    GitHub reports CONFLICTING / DIRTY against main. The only visible status check is the Read the Docs build; I do not see the repository’s usual Ruff, mypy, pytest matrix, and build checks for this PR. It needs a rebase/update and full CI before merge.

  2. get_chart() cannot return a ChoroplethMap.
    The new class is exported at the top level, but datawrapper/chart_factory.py was not updated. A live/API choropleth returned by get_chart(chart_id=...) would still raise ValueError: Unsupported chart type: d3-maps-choropleth because chart_type_map lacks "d3-maps-choropleth": ChoroplethMap. This is inconsistent with the existing typed chart workflow and with comparable chart additions.

  3. There are no runnable choropleth tests.
    The PR adds datawrapper/charts/choropleth_map.py and a fixture, but no tests/integration/test_choropleth_map.py or unit tests exercise construction, imports, aliases, serialization, deserialization, colorscale handling, tooltips, custom basemap settings, annotations, or factory support. In the local run, datawrapper/charts/choropleth_map.py showed only 15.68% coverage because only imports/class definitions were touched by the existing suite.

  4. The bundled sample fixture is under the wrong path and is not wired into the localized testing approach.
    The files were added as:

    tests/samples/choropleth_map/tests/samples/choropleth_map/internet_usagecountry.csv
    tests/samples/choropleth_map/tests/samples/choropleth_map/internet_usagecountry.json
    

    Existing fixtures live directly under tests/samples/<chart_type>/..., and tests/integration/test_sample_charts.py only covers the bar sample. Because of the nested tests/samples/... path and the missing test file, this real choropleth fixture is never exercised.

  5. Realistic Academy-config round-tripping drops important map configuration.
    I parsed the bundled non-secret JSON fixture (Share of people using the internet, chart id 6Ue5p) with ChoroplethMap.deserialize_model() and serialized it back. The basics worked — title, type, axes, basemap, key attr, tooltip text, and simple color fields survived — but major visualization settings were missing or changed:

    • colorscale.stops changed from pretty to equidistant.
    • colorscale.stopCount changed from 5 to 7 (color-steps), and customStops, rangeMin, rangeMax, palette, map, categoryOrder, and categoryLabels were dropped.
    • tooltip.enabled, tooltip.sticky, and tooltip.migrated were dropped.
    • legends, map-type-set, mapView, zoom-button-pos, map-label-format, max-map-height, highlighted-series, highlighted-values, miniMap, patterns, and labels were all dropped.

    Some lossiness is acceptable if the public API intentionally starts small, but these are real Datawrapper choropleth fields from the fixture this PR adds. The tests should make the intended support boundary explicit and avoid silently changing settings that affect rendered output.

  6. Tooltip is too narrow for real Datawrapper tooltip objects.
    The sample tooltip includes enabled, sticky, and migrated, but the model only preserves body, title, and fields. Because Pydantic’s default extra="ignore" behavior is in effect on Tooltip, those extra fields are silently discarded during validation/deserialization. At minimum, tests should confirm whether this is intentional; ideally the model should preserve supported Datawrapper tooltip options that affect behavior.

  7. The public exports are inconsistent.
    datawrapper.__init__ exports ColorMode and ColorScale, but datawrapper.charts.__init__ imports only BasemapProjection; it does not import or include ColorMode and ColorScale in __all__. That means from datawrapper import ColorMode works, while from datawrapper.charts import ColorMode fails. Comparable enums are generally available through both import paths.

  8. There is no user-facing documentation page or docs navigation entry.
    The PR adds the class docstring and schema examples, but no docs/user-guide/charts/choropleth-maps.md and no docs/index.md toctree entry. Comparable chart types have a chart guide with a runnable example, and PR #500’s earlier discussion specifically asked for real examples/tests based on Academy chart JSON.

Test and validation notes

I ran:

uv sync --frozen --extra test --extra mypy
uv run python - <<'PY'
import datawrapper
from datawrapper import ChoroplethMap
print(ChoroplethMap().chart_type)
PY
uv run pytest tests/unit/test_enum_imports.py tests/unit/test_enum_backwards_compatibility.py tests/integration/test_sample_charts.py -q
uv run pytest -q
uv run python - <<'PY'
# loaded tests/samples/choropleth_map/tests/samples/choropleth_map/internet_usagecountry.json,
# ran ChoroplethMap.deserialize_model(...), instantiated the model, serialized it,
# and compared key metadata.visualize fields against the source fixture
PY
uv run mypy ./ --ignore-missing-imports

Results:

  • Importing datawrapper and ChoroplethMap works.
  • Focused existing tests passed: 71 passed.
  • Full test suite passed locally on Python 3.13: 1049 passed, 3 skipped.
  • The realistic choropleth fixture parse/serialize check confirmed the lossy fields listed above.
  • uv run mypy ./ --ignore-missing-imports still fails with 15 errors in tests/conftest.py; those appear pre-existing rather than caused by this PR, but CI should be reviewed after rebase because the workflow runs mypy.
  • Ruff was not available in the local uv environment because it is not declared in the project’s dev/test extras, so I could not run the exact local Ruff command without adding undeclared tooling.

Consistency with repository examples and documentation

The proposed class follows several repo conventions well: it subclasses BaseChart, uses aliases for Datawrapper hyphenated keys, adds the chart type to BaseChart.chart_type, and uses enums for a few higher-level choices. The missing parts are the same integration points other chart types rely on: factory mapping, chart-specific tests, fixture placement under tests/samples/<chart_type>/, docs page/toctree entry, and explicit assertions against real example JSON.

For the Academy/localized testing approach, I would expect at least one test module that loads the real choropleth JSON/CSV fixture, validates deserialization into ChoroplethMap, checks representative fields (axes, basemap, key attr, colorscale, tooltip, legend/display settings), serializes back, and documents which fields are intentionally unsupported. Additional constructor/API tests should cover a small Pythonic example using a DataFrame and enum values.

Suggestions / non-blocking API design notes

  • Consider introducing structured models for colorscale, legends, minimap, labels, and patterns rather than flattening only part of the choropleth API into top-level fields. If the first release intentionally supports only a smaller subset, document that clearly.
  • key_column_type writes into metadata.data.column-format, but there is no direct test proving it preserves existing column-format settings or only sets the intended column.
  • color_palette is accepted and serialized as color-palette, but the real sample’s palette-ish fields live inside colorscale; tests should clarify which Datawrapper color path is actually used for choropleths.
  • The sample added in this PR (6Ue5p) does not appear to be from the current public “Examples of Datawrapper choropleth maps” Academy article I checked; if it is from another Academy page or generated example, please note that in the fixture/test name or docs so future maintainers know its provenance.

Positive findings

  • The basic class import and construction path works.
  • The model correctly recognizes the d3-maps-choropleth chart type and serializes Datawrapper-style metadata.axes for keys/values.
  • Basemap, map key attribute, simple color fields, tooltip body/title, and annotations integration are a sensible starting slice.
  • Including a real JSON/CSV fixture is exactly the right direction; it just needs to be placed and tested consistently with the rest of the repository.

I’d be happy to re-review after the branch is rebased, factory support is added, the fixture path is fixed, real choropleth tests/docs are added, the chart exports are made consistent, and the lossy handling of real choropleth visualize fields is either fixed or clearly scoped and tested.

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.

3 participants