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.
Files changed (51) hide show
  1. {climplot-0.2.0 → climplot-0.4.0}/.gitignore +0 -2
  2. climplot-0.4.0/CLAUDE.md +178 -0
  3. {climplot-0.2.0/src/climplot.egg-info → climplot-0.4.0}/PKG-INFO +64 -1
  4. {climplot-0.2.0 → climplot-0.4.0}/README.md +63 -0
  5. climplot-0.4.0/demo_atmosphere.py +89 -0
  6. climplot-0.4.0/demo_native_grid.py +90 -0
  7. {climplot-0.2.0 → climplot-0.4.0}/docs/plotting-guide.md +213 -26
  8. {climplot-0.2.0 → climplot-0.4.0}/docs/source/api.rst +36 -1
  9. {climplot-0.2.0 → climplot-0.4.0}/docs/source/quickstart.rst +30 -0
  10. {climplot-0.2.0 → climplot-0.4.0}/src/climplot/__init__.py +16 -0
  11. {climplot-0.2.0 → climplot-0.4.0}/src/climplot/colormaps.py +179 -7
  12. climplot-0.4.0/src/climplot/maps.py +610 -0
  13. climplot-0.4.0/src/climplot/panels.py +541 -0
  14. {climplot-0.2.0 → climplot-0.4.0/src/climplot.egg-info}/PKG-INFO +64 -1
  15. {climplot-0.2.0 → climplot-0.4.0}/src/climplot.egg-info/SOURCES.txt +5 -0
  16. climplot-0.4.0/tests/test_colormaps.py +204 -0
  17. climplot-0.4.0/tests/test_maps.py +501 -0
  18. climplot-0.4.0/tests/test_panels.py +249 -0
  19. climplot-0.2.0/src/climplot/maps.py +0 -230
  20. climplot-0.2.0/src/climplot/panels.py +0 -336
  21. climplot-0.2.0/tests/test_colormaps.py +0 -65
  22. {climplot-0.2.0 → climplot-0.4.0}/.github/workflows/publish.yml +0 -0
  23. {climplot-0.2.0 → climplot-0.4.0}/LICENSE +0 -0
  24. {climplot-0.2.0 → climplot-0.4.0}/docs/Makefile +0 -0
  25. {climplot-0.2.0 → climplot-0.4.0}/docs/images/anomaly_cmap.png +0 -0
  26. {climplot-0.2.0 → climplot-0.4.0}/docs/images/center_on_white.png +0 -0
  27. {climplot-0.2.0 → climplot-0.4.0}/docs/images/dimensions.png +0 -0
  28. {climplot-0.2.0 → climplot-0.4.0}/docs/images/good_vs_bad_intervals.png +0 -0
  29. {climplot-0.2.0 → climplot-0.4.0}/docs/images/map_example.png +0 -0
  30. {climplot-0.2.0 → climplot-0.4.0}/docs/images/multipanel_example.png +0 -0
  31. {climplot-0.2.0 → climplot-0.4.0}/docs/images/pitfall_jet_vs_rdbu.png +0 -0
  32. {climplot-0.2.0 → climplot-0.4.0}/docs/images/style_comparison.png +0 -0
  33. {climplot-0.2.0 → climplot-0.4.0}/docs/images/timeseries_example.png +0 -0
  34. {climplot-0.2.0 → climplot-0.4.0}/docs/source/conf.py +0 -0
  35. {climplot-0.2.0 → climplot-0.4.0}/docs/source/index.rst +0 -0
  36. {climplot-0.2.0 → climplot-0.4.0}/docs/source/metrics.rst +0 -0
  37. {climplot-0.2.0 → climplot-0.4.0}/docs/source/style-guide.rst +0 -0
  38. {climplot-0.2.0 → climplot-0.4.0}/examples/generate_guide_figures.py +0 -0
  39. {climplot-0.2.0 → climplot-0.4.0}/pyproject.toml +0 -0
  40. {climplot-0.2.0 → climplot-0.4.0}/setup.cfg +0 -0
  41. {climplot-0.2.0 → climplot-0.4.0}/src/climplot/data/presentation.mplstyle +0 -0
  42. {climplot-0.2.0 → climplot-0.4.0}/src/climplot/data/publication.mplstyle +0 -0
  43. {climplot-0.2.0 → climplot-0.4.0}/src/climplot/metrics.py +0 -0
  44. {climplot-0.2.0 → climplot-0.4.0}/src/climplot/style.py +0 -0
  45. {climplot-0.2.0 → climplot-0.4.0}/src/climplot/timeseries.py +0 -0
  46. {climplot-0.2.0 → climplot-0.4.0}/src/climplot.egg-info/dependency_links.txt +0 -0
  47. {climplot-0.2.0 → climplot-0.4.0}/src/climplot.egg-info/requires.txt +0 -0
  48. {climplot-0.2.0 → climplot-0.4.0}/src/climplot.egg-info/top_level.txt +0 -0
  49. {climplot-0.2.0 → climplot-0.4.0}/tests/__init__.py +0 -0
  50. {climplot-0.2.0 → climplot-0.4.0}/tests/test_metrics.py +0 -0
  51. {climplot-0.2.0 → climplot-0.4.0}/tests/test_style.py +0 -0
@@ -79,5 +79,3 @@ venv.bak/
79
79
  .DS_Store
80
80
  Thumbs.db
81
81
 
82
- # Claude Code context (local only)
83
- CLAUDE.md
@@ -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.2.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}")