climplot 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {climplot-0.2.0 → climplot-0.4.0}/.gitignore +0 -2
- climplot-0.4.0/CLAUDE.md +178 -0
- {climplot-0.2.0/src/climplot.egg-info → climplot-0.4.0}/PKG-INFO +64 -1
- {climplot-0.2.0 → climplot-0.4.0}/README.md +63 -0
- climplot-0.4.0/demo_atmosphere.py +89 -0
- climplot-0.4.0/demo_native_grid.py +90 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/plotting-guide.md +213 -26
- {climplot-0.2.0 → climplot-0.4.0}/docs/source/api.rst +36 -1
- {climplot-0.2.0 → climplot-0.4.0}/docs/source/quickstart.rst +30 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot/__init__.py +16 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot/colormaps.py +179 -7
- climplot-0.4.0/src/climplot/maps.py +610 -0
- climplot-0.4.0/src/climplot/panels.py +541 -0
- {climplot-0.2.0 → climplot-0.4.0/src/climplot.egg-info}/PKG-INFO +64 -1
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot.egg-info/SOURCES.txt +5 -0
- climplot-0.4.0/tests/test_colormaps.py +204 -0
- climplot-0.4.0/tests/test_maps.py +501 -0
- climplot-0.4.0/tests/test_panels.py +249 -0
- climplot-0.2.0/src/climplot/maps.py +0 -230
- climplot-0.2.0/src/climplot/panels.py +0 -336
- climplot-0.2.0/tests/test_colormaps.py +0 -65
- {climplot-0.2.0 → climplot-0.4.0}/.github/workflows/publish.yml +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/LICENSE +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/Makefile +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/images/anomaly_cmap.png +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/images/center_on_white.png +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/images/dimensions.png +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/images/good_vs_bad_intervals.png +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/images/map_example.png +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/images/multipanel_example.png +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/images/pitfall_jet_vs_rdbu.png +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/images/style_comparison.png +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/images/timeseries_example.png +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/source/conf.py +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/source/index.rst +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/source/metrics.rst +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/docs/source/style-guide.rst +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/examples/generate_guide_figures.py +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/pyproject.toml +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/setup.cfg +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot/data/presentation.mplstyle +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot/data/publication.mplstyle +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot/metrics.py +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot/style.py +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot/timeseries.py +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot.egg-info/dependency_links.txt +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot.egg-info/requires.txt +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/src/climplot.egg-info/top_level.txt +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/tests/__init__.py +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/tests/test_metrics.py +0 -0
- {climplot-0.2.0 → climplot-0.4.0}/tests/test_style.py +0 -0
climplot-0.4.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# climplot — AI Agent Guide
|
|
2
|
+
|
|
3
|
+
## Land-Rendering Decision Rule
|
|
4
|
+
|
|
5
|
+
| Grid type | How to render land | Functions |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| **Atmosphere / observations** (reanalysis, CMIP atmos, regular lat-lon) | Cartopy Natural Earth land (bundled) | `climplot.plot_atmos_field()` |
|
|
8
|
+
| **Native ocean-model grid** (tripolar, MOM6) | Gray background + NaN masking | `climplot.plot_ocean_field()` |
|
|
9
|
+
| **Regular / regridded ocean grid** (obs, 1°×1°) | Cartopy Natural Earth land (manual) | `climplot.add_land_feature()` + `climplot.add_coastlines()` |
|
|
10
|
+
|
|
11
|
+
> **Do NOT** use `add_land_feature` or `add_coastlines` on native model grids — Cartopy coastlines will not align with the model's land/sea boundary.
|
|
12
|
+
|
|
13
|
+
## Workflow A — Native Ocean-Model Grids
|
|
14
|
+
|
|
15
|
+
Use `plot_ocean_field()`. It bundles three steps: gray background, land masking, and plotting.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import climplot
|
|
19
|
+
import cartopy.crs as ccrs
|
|
20
|
+
|
|
21
|
+
climplot.publication()
|
|
22
|
+
fig, ax = climplot.map_figure()
|
|
23
|
+
|
|
24
|
+
# geolon_c / geolat_c are CORNER coordinates for pcolormesh
|
|
25
|
+
cs = climplot.plot_ocean_field(
|
|
26
|
+
ax, geolon_c, geolat_c, sst,
|
|
27
|
+
wet_mask=wet, # 1=ocean, 0=land
|
|
28
|
+
method="pcolormesh", # default
|
|
29
|
+
cmap=cmap, norm=norm,
|
|
30
|
+
)
|
|
31
|
+
climplot.add_colorbar(cs, ax, "SST (°C)")
|
|
32
|
+
climplot.save_figure("native_grid_sst.png")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Corner vs Center Coordinates
|
|
36
|
+
|
|
37
|
+
| Plot method | Coordinate type | Typical MOM6 variable |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `pcolormesh` | **Corner** (cell boundaries) | `geolon_c`, `geolat_c` |
|
|
40
|
+
| `contourf` | **Center** (cell midpoints) | `geolon`, `geolat` |
|
|
41
|
+
| `contour` | **Center** (cell midpoints) | `geolon`, `geolat` |
|
|
42
|
+
|
|
43
|
+
## Workflow B — Regular / Regridded Grids
|
|
44
|
+
|
|
45
|
+
Use `add_land_feature()` (filled gray continents) and optionally `add_coastlines()`.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import climplot
|
|
49
|
+
import cartopy.crs as ccrs
|
|
50
|
+
|
|
51
|
+
climplot.publication()
|
|
52
|
+
fig, ax = climplot.map_figure()
|
|
53
|
+
|
|
54
|
+
cs = ax.pcolormesh(
|
|
55
|
+
lon, lat, data,
|
|
56
|
+
cmap=cmap, norm=norm,
|
|
57
|
+
transform=ccrs.PlateCarree(),
|
|
58
|
+
)
|
|
59
|
+
climplot.add_land_feature(ax) # filled gray continents
|
|
60
|
+
climplot.add_coastlines(ax) # optional coastline outlines
|
|
61
|
+
climplot.add_colorbar(cs, ax, "SSH Anomaly (m)")
|
|
62
|
+
climplot.save_figure("regridded_ssh.png")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Workflow C — Atmosphere / Observations
|
|
66
|
+
|
|
67
|
+
Use `plot_atmos_field()`. It draws light-gray land underneath, plots data
|
|
68
|
+
with slight transparency on top, and adds thin coastlines. Default method
|
|
69
|
+
is `contourf` with `extend="both"` (prevents white holes from
|
|
70
|
+
out-of-range values). Optionally add `add_gridlines()`.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import climplot
|
|
74
|
+
|
|
75
|
+
climplot.publication()
|
|
76
|
+
fig, ax = climplot.map_figure()
|
|
77
|
+
|
|
78
|
+
cmap, norm, levels = climplot.anomaly_cmap(vmin=-2, vmax=2, interval=0.5)
|
|
79
|
+
cs = climplot.plot_atmos_field(
|
|
80
|
+
ax, lon, lat, temperature,
|
|
81
|
+
method="contourf", # default
|
|
82
|
+
land=True, # default: light-gray Natural Earth land underneath
|
|
83
|
+
coastlines=True, # default: thin coastline outlines on top
|
|
84
|
+
alpha=0.85, # default: slight transparency so land shows through
|
|
85
|
+
coastline_linewidth=0.3, # default: thin coastlines
|
|
86
|
+
cmap=cmap, norm=norm, levels=levels,
|
|
87
|
+
)
|
|
88
|
+
climplot.add_gridlines(ax, x_spacing=60, y_spacing=30)
|
|
89
|
+
climplot.add_colorbar(cs, ax, "Temperature Anomaly (K)")
|
|
90
|
+
climplot.save_figure("atmos_temperature.png")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Map Function Reference
|
|
94
|
+
|
|
95
|
+
| Function | When to use | Notes |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `climplot.map_figure()` | Always — creates figure + GeoAxes | Default: Robinson, central_longitude=180 |
|
|
98
|
+
| `climplot.plot_atmos_field()` | Atmosphere / regular grids | Land underneath → data (alpha=0.85) → coastlines; default `contourf`, `extend="both"` |
|
|
99
|
+
| `climplot.plot_ocean_field()` | Native model grids | Bundles background + mask + plot; default `pcolormesh` |
|
|
100
|
+
| `climplot.add_gridlines()` | Atmosphere / regular grids | Subtle lat/lon gridlines; returns Gridliner |
|
|
101
|
+
| `climplot.set_land_background()` | Manual native-grid workflow | Sets ax facecolor so NaN → gray |
|
|
102
|
+
| `climplot.mask_land()` | Manual native-grid workflow | Sets land points to NaN using wet_mask |
|
|
103
|
+
| `climplot.add_land_feature()` | Regular/regridded grids | Draws Natural Earth filled continents |
|
|
104
|
+
| `climplot.add_coastlines()` | Regular/regridded grids | Draws Natural Earth coastline outlines |
|
|
105
|
+
| `climplot.add_land_overlay()` | Legacy — center-coord pcolormesh only | Paints gray over land using wet_mask with pcolormesh |
|
|
106
|
+
|
|
107
|
+
## Common Mistakes
|
|
108
|
+
|
|
109
|
+
1. **Using `add_coastlines()` on native grids** — coastlines won't align with model land mask. Use `plot_ocean_field()` instead.
|
|
110
|
+
2. **Passing center coords to pcolormesh** — pcolormesh expects corner coordinates (`geolon_c`/`geolat_c`). Center coords cause a half-cell shift.
|
|
111
|
+
3. **Forgetting `transform=ccrs.PlateCarree()`** — data in lon/lat coordinates needs this transform. `plot_ocean_field` sets it automatically.
|
|
112
|
+
4. **Using `add_land_feature()` on native grids** — Natural Earth polygons won't match the model's coastline. Gray background + NaN masking is the correct approach.
|
|
113
|
+
5. **Using `add_land_overlay()` with corner-coord pcolormesh** — `add_land_overlay` uses center coordinates internally, so shapes won't match. Use `plot_ocean_field()` instead.
|
|
114
|
+
6. **Using `plot_ocean_field()` for atmosphere data** — atmosphere data doesn't have a wet mask or need gray-background land rendering. Use `plot_atmos_field()` instead.
|
|
115
|
+
7. **Using `draw_labels=True` on Robinson/Mollweide** — Cartopy gridline labels only work reliably on PlateCarree and Mercator projections. Leave `draw_labels=False` (the default) for other projections.
|
|
116
|
+
|
|
117
|
+
## Package Conventions
|
|
118
|
+
|
|
119
|
+
- **Public API**: All functions are exported from `climplot` directly (e.g., `climplot.add_land_feature()`, not `from climplot.maps import add_land_feature`).
|
|
120
|
+
- **wet_mask convention**: 1 = ocean, 0 = land.
|
|
121
|
+
- **Default land color**: `#808080` (medium gray) for ocean workflows; `#e0e0e0` (light gray) for `plot_atmos_field()`.
|
|
122
|
+
- **Default projection**: Robinson, centered on 180° (Pacific-centered).
|
|
123
|
+
- **Colormap intervals**: Use round numbers ending in 0, 1, 2, or 5.
|
|
124
|
+
|
|
125
|
+
## Colormap & Colorbar Reference
|
|
126
|
+
|
|
127
|
+
### Colormap Construction Functions
|
|
128
|
+
|
|
129
|
+
| Data type | Function | Example use case |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| Anomaly (centered on zero) | `anomaly_cmap` | Temperature bias, SSH anomaly |
|
|
132
|
+
| Positive-only | `sequential_cmap` | Precipitation, wind speed, ice concentration |
|
|
133
|
+
| General discrete | `discrete_cmap` | SLP (non-zero-centered ranges) |
|
|
134
|
+
| Auto-picked interval | `auto_levels` + `discrete_cmap` | Unknown data range |
|
|
135
|
+
| Log-spaced | `log_cmap` | Concentration spanning decades |
|
|
136
|
+
| Categorical | `categorical_cmap` | Land-use types |
|
|
137
|
+
|
|
138
|
+
### `center_on_white`
|
|
139
|
+
|
|
140
|
+
Use `center_on_white=True` on difference/bias plots where near-zero values should be visually neutral. It adds a white band at ±interval/2 around zero by building a `ListedColormap` with one color per interval. Pass it to `anomaly_cmap` or `discrete_cmap`:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
cmap, norm, levels = climplot.anomaly_cmap(-0.3, 0.3, 0.05, center_on_white=True)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `auto_levels`
|
|
147
|
+
|
|
148
|
+
When the data range isn't known in advance, `auto_levels` picks a "nice" interval (significant digit of 1, 2, or 5) and snaps the range to clean boundaries:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
interval, levels = climplot.auto_levels(data.min(), data.max(), n_levels=10)
|
|
152
|
+
cmap, norm, _ = climplot.discrete_cmap(levels[0], levels[-1], interval)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `log_cmap`
|
|
156
|
+
|
|
157
|
+
For data spanning orders of magnitude. `per_decade` controls boundary density: 1 = powers of 10, 2 = half-decades, 3 = 1-2-5 sequence. `vmin` must be > 0.
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
cmap, norm, levels = climplot.log_cmap(0.01, 100, per_decade=3)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Colorbar Behavior
|
|
164
|
+
|
|
165
|
+
`add_colorbar` automatically:
|
|
166
|
+
- Thins ticks to ≤9 (configurable via `max_ticks`), preferring round values
|
|
167
|
+
- Ensures symmetric colormaps get symmetric tick labels
|
|
168
|
+
- Suppresses minor ticks
|
|
169
|
+
- Formats labels cleanly (integers without decimals, minimal decimal precision)
|
|
170
|
+
- Respects `publication()`/`presentation()` font sizes
|
|
171
|
+
|
|
172
|
+
Overridable kwargs: `max_ticks` (default 9), `min_ticks` (default 5), `width` (colorbar thickness as fraction of axes), `label_fontsize`, `tick_fontsize`.
|
|
173
|
+
|
|
174
|
+
### Common Colormap Mistakes
|
|
175
|
+
|
|
176
|
+
8. **Using awkward intervals** (e.g., 0.07 instead of 0.05) — always pick intervals ending in 0, 1, 2, or 5.
|
|
177
|
+
9. **Forgetting `center_on_white` for bias plots** — without it, near-zero differences blend with the gradient and are hard to distinguish.
|
|
178
|
+
10. **Using `log_cmap` with `vmin <= 0`** — logarithmic scales require strictly positive values; `log_cmap` raises `ValueError` if `vmin <= 0`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: climplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Publication-quality climate science plotting utilities
|
|
5
5
|
Author-email: John Krasting <john.krasting@noaa.gov>
|
|
6
6
|
License: MIT
|
|
@@ -132,8 +132,32 @@ For difference plots where near-zero values should appear neutral:
|
|
|
132
132
|
cmap, norm, levels = climplot.anomaly_cmap(-0.3, 0.3, 0.05, center_on_white=True)
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
### Auto-Levels
|
|
136
|
+
When you don't know the data range in advance, `auto_levels` picks a nice interval automatically:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
interval, levels = climplot.auto_levels(-2.7, 2.7, n_levels=10)
|
|
140
|
+
cmap, norm, _ = climplot.discrete_cmap(levels[0], levels[-1], interval)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Log-Scale Colormap
|
|
144
|
+
For data spanning orders of magnitude (e.g., tracer concentrations):
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
cmap, norm, levels = climplot.log_cmap(0.01, 100, per_decade=3)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Colorbar Customization
|
|
151
|
+
`add_colorbar` supports keyword arguments for tick density and sizing:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
climplot.add_colorbar(cs, ax, 'Precip (mm/day)', max_ticks=7, width=0.03)
|
|
155
|
+
```
|
|
156
|
+
|
|
135
157
|
## Maps
|
|
136
158
|
|
|
159
|
+
### Projections
|
|
160
|
+
|
|
137
161
|
```python
|
|
138
162
|
# Robinson projection (Pacific-centered)
|
|
139
163
|
fig, ax = climplot.map_figure()
|
|
@@ -145,6 +169,45 @@ fig, ax = climplot.map_figure(central_longitude=0)
|
|
|
145
169
|
fig, ax = climplot.map_figure(projection='mollweide')
|
|
146
170
|
```
|
|
147
171
|
|
|
172
|
+
### Rendering Land: Three Workflows
|
|
173
|
+
|
|
174
|
+
**Atmosphere / lat-lon grids** (reanalysis, CMIP atmos, observations):
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
fig, ax = climplot.map_figure()
|
|
178
|
+
cmap, norm, levels = climplot.anomaly_cmap(-2, 2, 0.5)
|
|
179
|
+
cs = climplot.plot_atmos_field(
|
|
180
|
+
ax, lon, lat, temperature,
|
|
181
|
+
cmap=cmap, norm=norm, levels=levels,
|
|
182
|
+
)
|
|
183
|
+
climplot.add_gridlines(ax, x_spacing=30, y_spacing=30) # optional
|
|
184
|
+
climplot.add_colorbar(cs, ax, 'Temperature Anomaly (K)')
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`plot_atmos_field` draws light-gray land underneath, plots data with slight transparency on top, and adds thin coastlines — all in one call.
|
|
188
|
+
|
|
189
|
+
**Regular / regridded grids** (observations, reanalysis on 1°×1°, etc.):
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
fig, ax = climplot.map_figure()
|
|
193
|
+
cs = ax.pcolormesh(lon, lat, data, cmap=cmap, norm=norm,
|
|
194
|
+
transform=ccrs.PlateCarree())
|
|
195
|
+
climplot.add_land_feature(ax) # filled gray continents from Natural Earth
|
|
196
|
+
climplot.add_coastlines(ax) # optional coastline outlines
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Native ocean-model grids** (tripolar, MOM6 — Cartopy coastlines won't align):
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
fig, ax = climplot.map_figure()
|
|
203
|
+
cs = climplot.plot_ocean_field(
|
|
204
|
+
ax, geolon_c, geolat_c, sst,
|
|
205
|
+
wet_mask=wet, # 1=ocean, 0=land
|
|
206
|
+
)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`plot_ocean_field` sets a gray background so NaN over land renders as the model's coastline, optionally masks land, and plots the data in one call. See the [Plotting Guide](docs/plotting-guide.md) for full details.
|
|
210
|
+
|
|
148
211
|
## Multi-panel Figures
|
|
149
212
|
|
|
150
213
|
```python
|
|
@@ -91,8 +91,32 @@ For difference plots where near-zero values should appear neutral:
|
|
|
91
91
|
cmap, norm, levels = climplot.anomaly_cmap(-0.3, 0.3, 0.05, center_on_white=True)
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
### Auto-Levels
|
|
95
|
+
When you don't know the data range in advance, `auto_levels` picks a nice interval automatically:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
interval, levels = climplot.auto_levels(-2.7, 2.7, n_levels=10)
|
|
99
|
+
cmap, norm, _ = climplot.discrete_cmap(levels[0], levels[-1], interval)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Log-Scale Colormap
|
|
103
|
+
For data spanning orders of magnitude (e.g., tracer concentrations):
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
cmap, norm, levels = climplot.log_cmap(0.01, 100, per_decade=3)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Colorbar Customization
|
|
110
|
+
`add_colorbar` supports keyword arguments for tick density and sizing:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
climplot.add_colorbar(cs, ax, 'Precip (mm/day)', max_ticks=7, width=0.03)
|
|
114
|
+
```
|
|
115
|
+
|
|
94
116
|
## Maps
|
|
95
117
|
|
|
118
|
+
### Projections
|
|
119
|
+
|
|
96
120
|
```python
|
|
97
121
|
# Robinson projection (Pacific-centered)
|
|
98
122
|
fig, ax = climplot.map_figure()
|
|
@@ -104,6 +128,45 @@ fig, ax = climplot.map_figure(central_longitude=0)
|
|
|
104
128
|
fig, ax = climplot.map_figure(projection='mollweide')
|
|
105
129
|
```
|
|
106
130
|
|
|
131
|
+
### Rendering Land: Three Workflows
|
|
132
|
+
|
|
133
|
+
**Atmosphere / lat-lon grids** (reanalysis, CMIP atmos, observations):
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
fig, ax = climplot.map_figure()
|
|
137
|
+
cmap, norm, levels = climplot.anomaly_cmap(-2, 2, 0.5)
|
|
138
|
+
cs = climplot.plot_atmos_field(
|
|
139
|
+
ax, lon, lat, temperature,
|
|
140
|
+
cmap=cmap, norm=norm, levels=levels,
|
|
141
|
+
)
|
|
142
|
+
climplot.add_gridlines(ax, x_spacing=30, y_spacing=30) # optional
|
|
143
|
+
climplot.add_colorbar(cs, ax, 'Temperature Anomaly (K)')
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`plot_atmos_field` draws light-gray land underneath, plots data with slight transparency on top, and adds thin coastlines — all in one call.
|
|
147
|
+
|
|
148
|
+
**Regular / regridded grids** (observations, reanalysis on 1°×1°, etc.):
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
fig, ax = climplot.map_figure()
|
|
152
|
+
cs = ax.pcolormesh(lon, lat, data, cmap=cmap, norm=norm,
|
|
153
|
+
transform=ccrs.PlateCarree())
|
|
154
|
+
climplot.add_land_feature(ax) # filled gray continents from Natural Earth
|
|
155
|
+
climplot.add_coastlines(ax) # optional coastline outlines
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Native ocean-model grids** (tripolar, MOM6 — Cartopy coastlines won't align):
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
fig, ax = climplot.map_figure()
|
|
162
|
+
cs = climplot.plot_ocean_field(
|
|
163
|
+
ax, geolon_c, geolat_c, sst,
|
|
164
|
+
wet_mask=wet, # 1=ocean, 0=land
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`plot_ocean_field` sets a gray background so NaN over land renders as the model's coastline, optionally masks land, and plots the data in one call. See the [Plotting Guide](docs/plotting-guide.md) for full details.
|
|
169
|
+
|
|
107
170
|
## Multi-panel Figures
|
|
108
171
|
|
|
109
172
|
```python
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Visual demo of atmosphere data plotting functions.
|
|
2
|
+
|
|
3
|
+
Creates a 4-panel figure showing plot_atmos_field with different
|
|
4
|
+
variables and methods, plus an add_gridlines demo. Uses synthetic
|
|
5
|
+
data only (no external dependencies beyond climplot).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import cartopy.crs as ccrs
|
|
10
|
+
|
|
11
|
+
import climplot
|
|
12
|
+
|
|
13
|
+
# ---------- style ----------------------------------------------------------
|
|
14
|
+
climplot.publication()
|
|
15
|
+
|
|
16
|
+
# ---------- synthetic data -------------------------------------------------
|
|
17
|
+
lon = np.arange(0.5, 360.5, 2.0)
|
|
18
|
+
lat = np.arange(-89.5, 90.5, 2.0)
|
|
19
|
+
LON, LAT = np.meshgrid(lon, lat)
|
|
20
|
+
|
|
21
|
+
# (a) Temperature anomaly: dipole pattern
|
|
22
|
+
temperature = 2.0 * np.sin(np.deg2rad(LAT * 3)) * np.cos(np.deg2rad(LON * 2))
|
|
23
|
+
|
|
24
|
+
# (b) Precipitation: ITCZ-like tropical band (positive only)
|
|
25
|
+
precip = 8.0 * np.exp(-((LAT / 15.0) ** 2)) * (1 + 0.5 * np.cos(np.deg2rad(LON * 3)))
|
|
26
|
+
|
|
27
|
+
# (c) Sea level pressure: wavy zonal pattern
|
|
28
|
+
slp = 1013.0 + 15.0 * np.sin(np.deg2rad(LAT * 2)) + 5.0 * np.cos(np.deg2rad(LON))
|
|
29
|
+
|
|
30
|
+
# (d) Wind speed: jet stream pattern (positive only)
|
|
31
|
+
wind = 12.0 * np.exp(-(((LAT - 45) / 10.0) ** 2)) + 8.0 * np.exp(
|
|
32
|
+
-(((LAT + 45) / 10.0) ** 2)
|
|
33
|
+
)
|
|
34
|
+
wind = wind * (1 + 0.3 * np.cos(np.deg2rad(LON * 4)))
|
|
35
|
+
|
|
36
|
+
# ---------- colormaps ------------------------------------------------------
|
|
37
|
+
cmap_t, norm_t, levels_t = climplot.anomaly_cmap(vmin=-2, vmax=2, interval=0.5)
|
|
38
|
+
cmap_p, norm_p, levels_p = climplot.sequential_cmap(vmin=0, vmax=12, interval=1)
|
|
39
|
+
cmap_s, norm_s, levels_s = climplot.sequential_cmap(vmin=990, vmax=1030, interval=5)
|
|
40
|
+
cmap_w, norm_w, levels_w = climplot.sequential_cmap(
|
|
41
|
+
vmin=0, vmax=20, interval=2, cmap_name="inferno"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# ---------- figure ---------------------------------------------------------
|
|
45
|
+
fig, axes = climplot.panel_figure(
|
|
46
|
+
2, 2, projection=ccrs.Robinson(central_longitude=180)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# (a) Temperature anomaly -- contourf (default)
|
|
50
|
+
cs_a = climplot.plot_atmos_field(
|
|
51
|
+
axes[0, 0], lon, lat, temperature,
|
|
52
|
+
cmap=cmap_t, norm=norm_t, levels=levels_t,
|
|
53
|
+
)
|
|
54
|
+
climplot.add_colorbar(cs_a, axes[0, 0], "Temperature Anomaly (K)")
|
|
55
|
+
axes[0, 0].set_title("contourf — Temperature Anomaly", fontsize=7)
|
|
56
|
+
|
|
57
|
+
# (b) Precipitation -- contourf with sequential colormap
|
|
58
|
+
cs_b = climplot.plot_atmos_field(
|
|
59
|
+
axes[0, 1], lon, lat, precip,
|
|
60
|
+
cmap=cmap_p, norm=norm_p, levels=levels_p,
|
|
61
|
+
)
|
|
62
|
+
climplot.add_colorbar(cs_b, axes[0, 1], "Precipitation (mm/day)")
|
|
63
|
+
axes[0, 1].set_title("contourf — Precipitation", fontsize=7)
|
|
64
|
+
|
|
65
|
+
# (c) Sea level pressure -- contourf
|
|
66
|
+
cs_c = climplot.plot_atmos_field(
|
|
67
|
+
axes[1, 0], lon, lat, slp,
|
|
68
|
+
cmap=cmap_s, norm=norm_s, levels=levels_s,
|
|
69
|
+
)
|
|
70
|
+
climplot.add_colorbar(cs_c, axes[1, 0], "SLP (hPa)")
|
|
71
|
+
axes[1, 0].set_title("contourf — SLP", fontsize=7)
|
|
72
|
+
|
|
73
|
+
# (d) Wind speed -- pcolormesh
|
|
74
|
+
cs_d = climplot.plot_atmos_field(
|
|
75
|
+
axes[1, 1], lon, lat, wind,
|
|
76
|
+
method="pcolormesh",
|
|
77
|
+
cmap=cmap_w, norm=norm_w,
|
|
78
|
+
)
|
|
79
|
+
climplot.add_colorbar(cs_d, axes[1, 1], "Wind Speed (m/s)")
|
|
80
|
+
axes[1, 1].set_title("pcolormesh — Wind Speed", fontsize=7)
|
|
81
|
+
|
|
82
|
+
# Gridlines on all panels
|
|
83
|
+
for ax in axes.flat:
|
|
84
|
+
climplot.add_gridlines(ax, x_spacing=30, y_spacing=30)
|
|
85
|
+
|
|
86
|
+
# ---------- finishing touches ----------------------------------------------
|
|
87
|
+
climplot.add_panel_labels(axes)
|
|
88
|
+
path = climplot.save_figure("demo_atmosphere.png")
|
|
89
|
+
print(f"Saved to {path}")
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Visual demo of native-grid plotting functions.
|
|
2
|
+
|
|
3
|
+
Creates a 4-panel figure showing plot_ocean_field on tripolar and
|
|
4
|
+
lat-lon grids plus a traditional Cartopy-based panel for comparison.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import cartopy.crs as ccrs
|
|
9
|
+
import momgrid
|
|
10
|
+
|
|
11
|
+
import climplot
|
|
12
|
+
|
|
13
|
+
# ---------- style ----------------------------------------------------------
|
|
14
|
+
climplot.publication()
|
|
15
|
+
|
|
16
|
+
# ---------- colormap -------------------------------------------------------
|
|
17
|
+
cmap, norm, levels = climplot.anomaly_cmap(
|
|
18
|
+
vmin=-15, vmax=15, interval=2.5, cmap_name="RdBu_r"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# ---------- tripolar grid (ESM4) ------------------------------------------
|
|
22
|
+
grd = momgrid.MOMgrid("esm4_sym")
|
|
23
|
+
|
|
24
|
+
geolon = np.asarray(grd.geolon)
|
|
25
|
+
geolat = np.asarray(grd.geolat)
|
|
26
|
+
geolon_c = np.asarray(grd.geolon_c)
|
|
27
|
+
geolat_c = np.asarray(grd.geolat_c)
|
|
28
|
+
wet = np.asarray(grd.wet)
|
|
29
|
+
|
|
30
|
+
sst_tri = 15.0 * np.cos(np.deg2rad(geolat))
|
|
31
|
+
|
|
32
|
+
# ---------- regular lat-lon grid (WOA 1°) ---------------------------------
|
|
33
|
+
woa = momgrid.external.woa18_grid(1.0)
|
|
34
|
+
|
|
35
|
+
lon_1d = np.asarray(woa.lon)
|
|
36
|
+
lat_1d = np.asarray(woa.lat)
|
|
37
|
+
lon_b_1d = np.asarray(woa.lon_b)
|
|
38
|
+
lat_b_1d = np.asarray(woa.lat_b)
|
|
39
|
+
mask_woa = np.asarray(woa.mask)
|
|
40
|
+
|
|
41
|
+
lon_2d, lat_2d = np.meshgrid(lon_1d, lat_1d)
|
|
42
|
+
lon_b_2d, lat_b_2d = np.meshgrid(lon_b_1d, lat_b_1d)
|
|
43
|
+
|
|
44
|
+
sst_ll = 15.0 * np.cos(np.deg2rad(lat_2d))
|
|
45
|
+
|
|
46
|
+
# ---------- figure ---------------------------------------------------------
|
|
47
|
+
fig, axes = climplot.panel_figure(
|
|
48
|
+
2, 2, projection=ccrs.Robinson(central_longitude=180)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# (a) pcolormesh on tripolar with corner coords
|
|
52
|
+
cs_a = climplot.plot_ocean_field(
|
|
53
|
+
axes[0, 0], geolon_c, geolat_c, sst_tri,
|
|
54
|
+
wet_mask=wet, method="pcolormesh",
|
|
55
|
+
cmap=cmap, norm=norm,
|
|
56
|
+
)
|
|
57
|
+
axes[0, 0].set_title("plot_ocean_field — pcolormesh (tripolar)", fontsize=7)
|
|
58
|
+
|
|
59
|
+
# (b) contourf on tripolar with center coords
|
|
60
|
+
cs_b = climplot.plot_ocean_field(
|
|
61
|
+
axes[0, 1], geolon, geolat, sst_tri,
|
|
62
|
+
wet_mask=wet, method="contourf",
|
|
63
|
+
cmap=cmap, norm=norm, levels=levels,
|
|
64
|
+
)
|
|
65
|
+
axes[0, 1].set_title("plot_ocean_field — contourf (tripolar)", fontsize=7)
|
|
66
|
+
|
|
67
|
+
# (c) pcolormesh on regular lat-lon via plot_ocean_field
|
|
68
|
+
cs_c = climplot.plot_ocean_field(
|
|
69
|
+
axes[1, 0], lon_b_2d, lat_b_2d, sst_ll,
|
|
70
|
+
wet_mask=mask_woa, method="pcolormesh",
|
|
71
|
+
cmap=cmap, norm=norm,
|
|
72
|
+
)
|
|
73
|
+
axes[1, 0].set_title("plot_ocean_field — pcolormesh (lat-lon 1°)", fontsize=7)
|
|
74
|
+
|
|
75
|
+
# (d) traditional workflow: pcolormesh + add_land_feature + add_coastlines
|
|
76
|
+
ax_d = axes[1, 1]
|
|
77
|
+
cs_d = ax_d.pcolormesh(
|
|
78
|
+
lon_b_2d, lat_b_2d, sst_ll,
|
|
79
|
+
transform=ccrs.PlateCarree(),
|
|
80
|
+
cmap=cmap, norm=norm,
|
|
81
|
+
)
|
|
82
|
+
climplot.add_land_feature(ax_d, resolution="110m")
|
|
83
|
+
climplot.add_coastlines(ax_d, resolution="110m")
|
|
84
|
+
ax_d.set_title("Traditional: pcolormesh + land + coastlines", fontsize=7)
|
|
85
|
+
|
|
86
|
+
# ---------- finishing touches ----------------------------------------------
|
|
87
|
+
climplot.add_panel_labels(axes)
|
|
88
|
+
climplot.bottom_colorbar(cs_a, fig, axes, "Synthetic SST (°C)")
|
|
89
|
+
path = climplot.save_figure("demo_native_grid.png")
|
|
90
|
+
print(f"Saved to {path}")
|