climate-canvas
Python package and command line interface (CLI) for plotting climate impact study response surfaces and other climate change scenario visualizations.
Example: The figure above is created by running the uv run climate-canvas response examples\complex_surface.csv --interp climate-canvas CLI command on the complex_surface.csv data distributed with the climate-canvas program.
Installation Instructions
System Requirements
climate-canvas requires python 3.12+. It aims to be multi-platform and has been run on Windows 11 and MacOS 14 and 15.
Clone or Fork climate-canvas from GitHub
The climate-canvas source code can be found here: https://github.com/JohnRushKucharski/climate-canvas is available under the GNU Version 3 General Public License.
It can be cloned or forked by following the normal cloning or forking instructions, which are available here: https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository and here: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo.
Installation with uv
climate-canvas is developed with uv, which can be used to simplify the installation process.
To install uv, follow the instructions here: https://docs.astral.sh/uv/getting-started/installation/.
Once uv is installed, use your favorite shell to go to the location of the local climate-canvas repository, e.g.
cd <PATH_TO_LOCAL>\climate-canvas
Next run:
uv sync --group test
This will create a python virtual environment (.venv) containing all the required climate-canvas dependencies, without affecting your system's global python environment.
The climate-canvas program should be ready for use as either a python package or command line utility. To test the command line interface (CLI) type the following command into your shell:
uv run climate-canvas --help
This should return help instructions for the climate-canvas CLI.
Basic Usage
The climate-canvas program can be extended as a python package or run through a command line interface (CLI).
The current version (0.1.0) only supports 2D climate response surface style plots. These are two-dimensional contour plots that show the impact of two variables plotted on the x and y axes on a response (i.e. z axis) variable whose values are plotted using contour lines and a colorbar. An example is shown below.
Command Line Interface
The example above is created response command. From a shell run:
uv run climate-canvas response --help
To view help for the response command, i.e.:
As the response help document describes the response command requires that a path to a .csv file containing plotting data be specified. Two example, data files are provided in the repository's examples/ directory. The following command, using on of these example files, reproduces the figure above:
uv run climate-canvas response examples\scenario_data.csv
The --interp flag can be used to bi-linearly interpolate between z-axis values. For example, running the following command (using the same data from the figure above) with the --interp flag produces the figure below:
uv run climate-canvas response examples\scenario_data.csv --interp
The --fillin flag extends --interp to fill in grid cells that bilinear interpolation leaves blank because one or more of the cell's four corners is missing. It has no effect unless --interp is also set. For example:
uv run climate-canvas response examples\great_lakes\kingston_shoal_low_water_grid.csv --interp --fillin
The math behind --interp and --fillin
This section works through the exact arithmetic behind --interp (bilinear interpolation) and
--fillin (Delaunay-linear fallback for missing data), using the smallest possible data sets —
2x2 grids with z-values between 0 and 1 — so every number can be checked by hand.
1. Bilinear interpolation on a complete grid — examples/simple.csv
This file defines a full 2x2 grid (no missing values):
| z | x=0 | x=1 |
|---|---|---|
| y=0 | 0 | 1 |
| y=1 | 1 | 1 |
Bilinear interpolation first interpolates along x at each of the two known y-rows, then
interpolates the result along y. For a query point (x, y), with px = x and py = y (since the
grid spans exactly 0 to 1 here, the interpolation weight equals the coordinate):
z_x,y0 = z00*(1-px) + z01*px # interpolate along x at y=0
z_x,y1 = z10*(1-px) + z11*px # interpolate along x at y=1
z_x,y = z_x,y0*(1-py) + z_x,y1*py # interpolate the two results along y
At the midpoint (0.5, 0.5):
z_x,y0 = 0*(1-0.5) + 1*0.5 = 0.5
z_x,y1 = 1*(1-0.5) + 1*0.5 = 1.0
z_x,y = 0.5*(1-0.5) + 1.0*0.5 = 0.75
Running uv run climate-canvas response examples\simple.csv --interp reproduces this: every
point in the unit square is filled with the plane implied by these four corners (no blank cells,
since the one cell in this grid has all four corners known — --fillin has no effect here).
Without --interp, only the 4 known grid points are plotted (as 4 flat-colored quarter-cells);
no interpolation is performed between them.
2. A missing corner — examples/simple_3points.csv
This file is identical, except the top-right corner is missing:
| z | x=0 | x=1 |
|---|---|---|
| y=0 | 0 | 1 |
| y=1 | 1 | NaN |
There is only one cell in this grid, and one of its four corners is unknown, so:
-
Without
--interp: the 3 known points are plotted as flat-colored quarter-cells; the(1, 1)corner's quarter-cell is blank. No interpolation happens. -
With
--interponly (--interp, no--fillin): bilinear interpolation strictly requires all four corners (seeinterpolate_2dindata_utilities.py) — with one corner missing, the entire cell returnsNaN. The plot is blank everywhere except the 3 exact known grid points (which are always re-inserted into the resampled grid). Seeexamples/simple_3points_interp.png. -
With
--interp --fillin: the missing cell falls back todelaunay_fill(), which triangulates only the 3 known points —(0, 0, z=0),(1, 0, z=1),(0, 1, z=1)— into a single triangle and fits the planez = a + bx + cythrough them:(0,0): a = 0 -> a = 0 (1,0): a + b = 1 -> b = 1 (0,1): a + c = 1 -> c = 1 z = x + yThis plane is only used inside the triangle's convex hull, i.e. where
x + y <= 1:(0.25, 0.25)->z = 0.25 + 0.25 = 0.5(0.5, 0.5)(on the hull's edge, between the two knownz=1points) ->z = 0.5 + 0.5 = 1.0(matches both known neighbors, as expected on their connecting edge)(0.75, 0.75)->x + y = 1.5 > 1, outside the hull, so this staysNaN(blank) — the same as the exact missing corner(1, 1)itself, which hasx + y = 2 > 1.
So
--fillinfills the lower-left triangle (x + y <= 1, nearest the 3 known points) with the plane above, while the upper-right triangle (nearest the missing corner) stays blank. Seeexamples/simple_3points_interp_fillin.pngnext toexamples/simple_3points_interp.pngfor the before/after.
Because the bilinear surface (example 1) and the Delaunay plane (example 2) are fit independently,
a visible seam can appear where a --fillin triangle borders a complete bilinear cell (the two
surfaces aren't generally coplanar there). See
docs/adr/0002-delaunay-linear-fillin-for-missing-grid-points.md
for the full rationale and this trade-off.
As the help documentation shows titles for the figure, x, y, and z axes can be added as optional arguments.
The --threshold option sets the z-value that becomes the colormap's center (yellow) color, splitting the color range asymmetrically around it (and adjusting the contour levels to match). If omitted, it defaults to the midpoint of the data's z-value range. Use --color-map to change the matplotlib colormap (defaults to RdBu), and --color-map-ticks to set explicit colorbar tick values. If --threshold is given but --color-map-ticks is not, the colorbar ticks default to the same contour levels used for the black contour lines, since matplotlib's default tick locator is linear in data value and can otherwise cluster ticks into one visually-compressed half of the colorbar when threshold is far from the z-range's midpoint. E.g.:
uv run climate-canvas response examples\scenario_data.csv --threshold 0.2 --color-map RdYlBu
Multiple Response Surfaces (response-surfaces-grid Command)
The response-surfaces-grid command plots several response surfaces as subplots of one figure, arranged in a grid. Every subplot is rendered through the exact same pathway as the response command, so a subplot always matches what a standalone response call would draw for the same data/options.
Unlike response, which takes a .csv file, response-surfaces-grid takes a single Excel (.xlsx) workbook containing:
- a
main_configsheet: figure-wide options (key in column A, value in column B) --suptitle,sharex,sharey,shared_colorbar,color_map,threshold,color_map_ticks,output_directory. - a
main_layoutsheet: column numbers across row 1 (B1:K1), row numbers down column A (A2:A11, max 10x10), and a subplot id in each interior cell. The same id repeated in adjacent cells makes that subplot span that rectangular block of grid cells. - one
<id>_configsheet per subplot: the same options as response (interpolate,fillin,xlabel,ylabel,zlabel,title,threshold,color_map,color_map_ticks), same key-in-column-A/value-in-column-B shape asmain_config. - one
<id>_gridsheet per subplot: that subplot's x/y/z data, in the same grid shape as the CSV format response reads.
shared_colorbar (default false) controls whether every subplot uses its own
color_map/threshold/color_map_ticks and its own colorbar (the default, matching a
standalone response call exactly for each subplot), or all subplots share one color scale
(main_config's own color_map/threshold/color_map_ticks) and one colorbar for the whole
figure. output_directory, if set, saves the figure as a PNG named after the workbook's
filename in that directory.
A runnable, pre-filled template workbook is provided at
examples/response_surfaces_grid_template.xlsx (built by
examples/build_response_surfaces_grid_template.py). Run it with:
uv run climate-canvas response-surfaces-grid examples\response_surfaces_grid_template.xlsx
See
docs/adr/0003-excel-driven-response-surfaces-grid-command.md
and CONTEXT.md for the full schema/rationale.
Python API
plot_response_surface (in climate_canvas.plots_utilities) can also be called directly as a
library function, e.g. from another package's CLI. It accepts several optional parameters not
exposed by the response CLI command:
save_path(Path | None, defaultNone): when provided, saves the figure to this path.show(bool, defaultTrue): whenFalse, skips the interactiveplt.show()window (useful for batch/headless plotting, e.g. saving one plot per component in a loop). The figure is always closed after the call to avoid leaking matplotlib figures.threshold(float | None, defaultNone): z-value that becomes the colormap's center color. Defaults to the midpoint of the z-value range ifNoneor outside that range.color_map(str, default'RdBu'): matplotlib colormap name.color_map_ticks(list[float] | None, defaultNone): explicit colorbar tick values. IfNoneandthresholdis given, defaults to the contour levels (5 below/above threshold plus threshold itself) instead of matplotlib's automatic tick locator.
The third element of the labels tuple (z label) is rendered as the colorbar's label, in
addition to labels[0]/labels[1] being used as the x/y axis labels.
from climate_canvas.plots_utilities import plot_response_surface
plot_response_surface(xs, ys, zs, labels=('Precip Delta (%)', 'Temp Delta (C)', 'portion'),
save_path='surface.png', show=False, threshold=0.2)
plot_response_surfaces_grid (also in climate_canvas.plots_utilities) reads a workbook path
and renders the multi-subplot figure described above:
from climate_canvas.plots_utilities import plot_response_surfaces_grid
plot_response_surfaces_grid('examples/response_surfaces_grid_template.xlsx', show=False)
Metadata
Release files for climate-canvas 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| climate_canvas-0.1.0.tar.gz | 1.0 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| climate_canvas-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.1 MB
Release files / climate_canvas-0.1.0.tar.gz
| Download URL | climate_canvas-0.1.0.tar.gz |
|---|---|
| Size | 1.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
87f03f65934a1d9de5ed007422d52e78c173e7c03831a17724815e29ec7b09d3
|
|
BLAKE2b-256 checksum How to use checksums |
3392563560b2b02e9e2701cc38ab9d8ffb66ad0b4febcd216917ac33fa0e2c50
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency logRelease files / climate_canvas-0.1.0-py3-none-any.whl
| Download URL | climate_canvas-0.1.0-py3-none-any.whl |
|---|---|
| Size | 34.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
179061af7ba6c0599796efd5b5dfe7ccdaccb38a5b548f7bcd6eb6c5c4e8b811
|
|
BLAKE2b-256 checksum How to use checksums |
e3b8845fc12e09d9ac5fc574ac5b3ca6d0ac104d1031abf45c199859808e61bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency log