climplot 0.1.0__py3-none-any.whl

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/__init__.py ADDED
@@ -0,0 +1,118 @@
1
+ """
2
+ climplot - Publication-quality climate science plotting utilities.
3
+
4
+ A Python package for creating beautiful, publication-ready climate and weather
5
+ science figures with minimal effort. Designed for climate science beginners
6
+ and experts alike.
7
+
8
+ Quick Start
9
+ -----------
10
+ >>> import climplot
11
+ >>> climplot.publication() # Set up publication-quality defaults
12
+ >>> fig, ax = climplot.map_figure() # Create a map with Robinson projection
13
+
14
+ Style Modes
15
+ -----------
16
+ - climplot.publication(): 3.5" width, 8-11pt fonts, 300 DPI
17
+ - climplot.presentation(): 7" width, 12-16pt fonts, 150 DPI
18
+
19
+ See Also
20
+ --------
21
+ - Documentation: https://climplot.readthedocs.io
22
+ - Source: https://github.com/jkrasting/climplot
23
+ """
24
+
25
+ __version__ = "0.1.0"
26
+
27
+ # Style functions
28
+ from .style import (
29
+ publication,
30
+ presentation,
31
+ reset_style,
32
+ get_current_mode,
33
+ )
34
+
35
+ # Colormap functions
36
+ from .colormaps import (
37
+ anomaly_cmap,
38
+ sequential_cmap,
39
+ categorical_cmap,
40
+ discrete_cmap,
41
+ list_colormaps,
42
+ )
43
+
44
+ # Map functions
45
+ from .maps import (
46
+ map_figure,
47
+ add_land_overlay,
48
+ add_coastlines,
49
+ )
50
+
51
+ # Time series functions
52
+ from .timeseries import (
53
+ timeseries_figure,
54
+ )
55
+
56
+ # Panel/multi-figure functions
57
+ from .panels import (
58
+ panel_figure,
59
+ add_panel_labels,
60
+ add_colorbar,
61
+ bottom_colorbar,
62
+ save_figure,
63
+ )
64
+
65
+ # Metrics functions
66
+ from .metrics import (
67
+ area_weighted_mean,
68
+ area_weighted_bias,
69
+ area_weighted_rmse,
70
+ area_weighted_corr,
71
+ area_weighted_std,
72
+ timeseries_bias,
73
+ timeseries_rmse,
74
+ timeseries_corr,
75
+ timeseries_std,
76
+ metrics_summary,
77
+ print_metrics_summary,
78
+ )
79
+
80
+ __all__ = [
81
+ # Version
82
+ "__version__",
83
+ # Style
84
+ "publication",
85
+ "presentation",
86
+ "reset_style",
87
+ "get_current_mode",
88
+ # Colormaps
89
+ "anomaly_cmap",
90
+ "sequential_cmap",
91
+ "categorical_cmap",
92
+ "discrete_cmap",
93
+ "list_colormaps",
94
+ # Maps
95
+ "map_figure",
96
+ "add_land_overlay",
97
+ "add_coastlines",
98
+ # Time series
99
+ "timeseries_figure",
100
+ # Panels
101
+ "panel_figure",
102
+ "add_panel_labels",
103
+ "add_colorbar",
104
+ "bottom_colorbar",
105
+ "save_figure",
106
+ # Metrics
107
+ "area_weighted_mean",
108
+ "area_weighted_bias",
109
+ "area_weighted_rmse",
110
+ "area_weighted_corr",
111
+ "area_weighted_std",
112
+ "timeseries_bias",
113
+ "timeseries_rmse",
114
+ "timeseries_corr",
115
+ "timeseries_std",
116
+ "metrics_summary",
117
+ "print_metrics_summary",
118
+ ]
climplot/colormaps.py ADDED
@@ -0,0 +1,254 @@
1
+ """
2
+ Climate-specific colormap utilities.
3
+
4
+ This module provides colormaps optimized for climate data visualization,
5
+ including diverging colormaps for anomalies and sequential colormaps
6
+ for positive-only data.
7
+
8
+ Key Features
9
+ ------------
10
+ - Discrete levels with "round" intervals (0, 1, 2, or 5 endings)
11
+ - Center-on-white option for difference plots
12
+ - Pre-defined colormaps for common climate variables
13
+
14
+ Examples
15
+ --------
16
+ >>> import climplot
17
+ >>> cmap, norm, levels = climplot.anomaly_cmap(-0.3, 0.3, 0.05)
18
+ >>> ax.pcolormesh(lon, lat, data, cmap=cmap, norm=norm)
19
+ """
20
+
21
+ import matplotlib.pyplot as plt
22
+ import matplotlib.colors as mcolors
23
+ from matplotlib.colors import ListedColormap, BoundaryNorm
24
+ import numpy as np
25
+ from typing import Tuple, List, Optional
26
+
27
+ # Standard climate colormaps
28
+ CLIMATE_COLORMAPS = {
29
+ "anomaly": "RdBu_r", # Red-blue diverging (red=high, blue=low)
30
+ "temperature": "RdBu_r",
31
+ "precipitation": "BrBG", # Brown-teal diverging
32
+ "ssh": "RdBu_r", # Sea surface height
33
+ "wind": "PuOr_r", # Purple-orange diverging
34
+ "sequential": "viridis", # Sequential
35
+ "ice": "Blues_r", # Ice concentration
36
+ }
37
+
38
+
39
+ def anomaly_cmap(
40
+ vmin: float = -1.0,
41
+ vmax: float = 1.0,
42
+ interval: float = 0.1,
43
+ center_on_white: bool = False,
44
+ cmap_name: str = "RdBu_r",
45
+ ) -> Tuple[mcolors.Colormap, mcolors.BoundaryNorm, np.ndarray]:
46
+ """
47
+ Create a diverging colormap for anomaly fields.
48
+
49
+ Generates discrete color levels centered on zero with the specified
50
+ interval. By default uses RdBu_r where red=positive, blue=negative.
51
+
52
+ Parameters
53
+ ----------
54
+ vmin : float, optional
55
+ Minimum value for color scale. Default is -1.0.
56
+ vmax : float, optional
57
+ Maximum value for color scale. Default is 1.0.
58
+ interval : float, optional
59
+ Interval between color levels. Should end in 0, 1, 2, or 5.
60
+ Default is 0.1.
61
+ center_on_white : bool, optional
62
+ If True, creates a white band centered on zero.
63
+ White band width is ±interval/2. Default is False.
64
+ Recommended for difference plots.
65
+ cmap_name : str, optional
66
+ Base colormap name. Default is 'RdBu_r'.
67
+
68
+ Returns
69
+ -------
70
+ cmap : Colormap
71
+ The colormap object
72
+ norm : BoundaryNorm
73
+ Normalization for discrete levels
74
+ levels : ndarray
75
+ The discrete color levels
76
+
77
+ Examples
78
+ --------
79
+ >>> # Standard anomaly colormap
80
+ >>> cmap, norm, levels = climplot.anomaly_cmap(-0.3, 0.3, 0.05)
81
+
82
+ >>> # Difference plot with white band for near-zero values
83
+ >>> cmap, norm, levels = climplot.anomaly_cmap(-0.3, 0.3, 0.05, center_on_white=True)
84
+
85
+ Notes
86
+ -----
87
+ Common interval choices:
88
+ - 0.01, 0.02, 0.05, 0.10, 0.20, 0.50, 1.0, 2.0, 5.0
89
+ """
90
+ return discrete_cmap(
91
+ vmin, vmax, interval, cmap_name=cmap_name, center_on_white=center_on_white
92
+ )
93
+
94
+
95
+ def sequential_cmap(
96
+ vmin: float = 0.0,
97
+ vmax: float = 1.0,
98
+ interval: float = 0.1,
99
+ cmap_name: str = "viridis",
100
+ ) -> Tuple[mcolors.Colormap, mcolors.BoundaryNorm, np.ndarray]:
101
+ """
102
+ Create a sequential colormap for positive-only data.
103
+
104
+ Parameters
105
+ ----------
106
+ vmin : float, optional
107
+ Minimum value. Default is 0.0.
108
+ vmax : float, optional
109
+ Maximum value. Default is 1.0.
110
+ interval : float, optional
111
+ Interval between levels. Default is 0.1.
112
+ cmap_name : str, optional
113
+ Colormap name. Default is 'viridis'.
114
+
115
+ Returns
116
+ -------
117
+ cmap : Colormap
118
+ norm : BoundaryNorm
119
+ levels : ndarray
120
+
121
+ Examples
122
+ --------
123
+ >>> cmap, norm, levels = climplot.sequential_cmap(0, 100, 10)
124
+ """
125
+ return discrete_cmap(vmin, vmax, interval, cmap_name=cmap_name, extend="max")
126
+
127
+
128
+ def categorical_cmap(
129
+ n_categories: int,
130
+ cmap_name: str = "tab10",
131
+ ) -> ListedColormap:
132
+ """
133
+ Create a colormap for discrete categories.
134
+
135
+ Parameters
136
+ ----------
137
+ n_categories : int
138
+ Number of categories
139
+ cmap_name : str, optional
140
+ Base colormap. Default is 'tab10'.
141
+
142
+ Returns
143
+ -------
144
+ ListedColormap
145
+ Colormap with n_categories discrete colors
146
+
147
+ Examples
148
+ --------
149
+ >>> cmap = climplot.categorical_cmap(5)
150
+ """
151
+ base_cmap = plt.get_cmap(cmap_name)
152
+ colors = [base_cmap(i) for i in range(n_categories)]
153
+ return ListedColormap(colors)
154
+
155
+
156
+ def discrete_cmap(
157
+ vmin: float,
158
+ vmax: float,
159
+ interval: float,
160
+ cmap_name: str = "RdBu_r",
161
+ extend: str = "both",
162
+ center_on_white: bool = False,
163
+ ) -> Tuple[mcolors.Colormap, mcolors.BoundaryNorm, np.ndarray]:
164
+ """
165
+ Create a discrete colormap with BoundaryNorm.
166
+
167
+ Generates color levels with "round" intervals for clean, readable
168
+ colorbars. Optionally centers colormap on white for difference plots.
169
+
170
+ Parameters
171
+ ----------
172
+ vmin : float
173
+ Minimum value for color scale
174
+ vmax : float
175
+ Maximum value for color scale
176
+ interval : float
177
+ Interval between color levels. Should end in 0, 1, 2, or 5.
178
+ cmap_name : str, optional
179
+ Name of matplotlib colormap. Default is 'RdBu_r'.
180
+ extend : str, optional
181
+ Out-of-range handling: 'neither', 'min', 'max', or 'both'.
182
+ Default is 'both'.
183
+ center_on_white : bool, optional
184
+ If True, creates a white band centered on zero.
185
+ White band width is ±interval/2. Default is False.
186
+
187
+ Returns
188
+ -------
189
+ cmap : Colormap
190
+ The colormap object
191
+ norm : BoundaryNorm
192
+ Normalization for discrete levels
193
+ levels : ndarray
194
+ The discrete color levels
195
+
196
+ Examples
197
+ --------
198
+ >>> # Standard discrete colormap
199
+ >>> cmap, norm, levels = discrete_cmap(-0.3, 0.3, 0.05)
200
+
201
+ >>> # With white band at center
202
+ >>> cmap, norm, levels = discrete_cmap(-0.3, 0.3, 0.05, center_on_white=True)
203
+
204
+ Notes
205
+ -----
206
+ When center_on_white=True:
207
+ - White band width is ±interval/2
208
+ - For interval=0.05, white covers -0.025 to +0.025
209
+ - Best for difference plots where near-zero values are ambiguous
210
+ """
211
+ if center_on_white:
212
+ # Create levels with white band centered on zero
213
+ neg_levels = np.arange(vmin, -interval / 10, interval)
214
+ white_band = np.array([-interval / 2, interval / 2])
215
+ pos_levels = np.arange(interval, vmax + interval / 2, interval)
216
+ levels = np.sort(np.concatenate([neg_levels, white_band, pos_levels]))
217
+ else:
218
+ # Standard levels
219
+ levels = np.arange(vmin, vmax + interval / 2, interval)
220
+
221
+ # Get base colormap
222
+ base_cmap = plt.get_cmap(cmap_name)
223
+
224
+ # Modify for center-on-white
225
+ if center_on_white:
226
+ n_colors = 256
227
+ colors = base_cmap(np.linspace(0, 1, n_colors))
228
+ center_idx = n_colors // 2
229
+ colors[center_idx] = [1.0, 1.0, 1.0, 1.0] # Pure white
230
+ cmap = ListedColormap(colors, name=f"{cmap_name}_white_center")
231
+ else:
232
+ cmap = base_cmap
233
+
234
+ # Create discrete normalization
235
+ norm = BoundaryNorm(levels, cmap.N, extend=extend)
236
+
237
+ return cmap, norm, levels
238
+
239
+
240
+ def list_colormaps() -> dict:
241
+ """
242
+ List recommended colormaps for climate variables.
243
+
244
+ Returns
245
+ -------
246
+ dict
247
+ Mapping of variable types to recommended colormap names
248
+
249
+ Examples
250
+ --------
251
+ >>> climplot.list_colormaps()
252
+ {'anomaly': 'RdBu_r', 'temperature': 'RdBu_r', ...}
253
+ """
254
+ return CLIMATE_COLORMAPS.copy()
@@ -0,0 +1,37 @@
1
+ # climplot presentation style
2
+ # For slides/posters: 7" width, 12-16pt fonts, 150 DPI
3
+ # Usage: plt.style.use('climplot/data/presentation.mplstyle')
4
+
5
+ # Font sizes (larger for readability)
6
+ font.size: 14
7
+ axes.labelsize: 14
8
+ axes.titlesize: 16
9
+ xtick.labelsize: 14
10
+ ytick.labelsize: 14
11
+ legend.fontsize: 12
12
+
13
+ # Figure settings
14
+ figure.figsize: 7.0, 4.2
15
+ figure.dpi: 150
16
+ savefig.dpi: 150
17
+ savefig.bbox: tight
18
+ savefig.facecolor: white
19
+
20
+ # Line widths (thicker for visibility)
21
+ axes.linewidth: 1.2
22
+ grid.linewidth: 0.5
23
+ lines.linewidth: 2.5
24
+
25
+ # Grid
26
+ axes.grid: False
27
+ grid.alpha: 0.3
28
+
29
+ # Legend
30
+ legend.frameon: False
31
+ legend.loc: best
32
+
33
+ # PDF output
34
+ pdf.fonttype: 42
35
+
36
+ # Colors
37
+ axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf'])
@@ -0,0 +1,37 @@
1
+ # climplot publication style
2
+ # For journal figures: 3.5" width, 8-11pt fonts, 300 DPI
3
+ # Usage: plt.style.use('climplot/data/publication.mplstyle')
4
+
5
+ # Font sizes
6
+ font.size: 10
7
+ axes.labelsize: 10
8
+ axes.titlesize: 11
9
+ xtick.labelsize: 8
10
+ ytick.labelsize: 8
11
+ legend.fontsize: 8
12
+
13
+ # Figure settings
14
+ figure.figsize: 3.5, 2.625
15
+ figure.dpi: 300
16
+ savefig.dpi: 300
17
+ savefig.bbox: tight
18
+ savefig.facecolor: white
19
+
20
+ # Line widths
21
+ axes.linewidth: 0.8
22
+ grid.linewidth: 0.3
23
+ lines.linewidth: 1.5
24
+
25
+ # Grid
26
+ axes.grid: False
27
+ grid.alpha: 0.3
28
+
29
+ # Legend
30
+ legend.frameon: False
31
+ legend.loc: best
32
+
33
+ # PDF output
34
+ pdf.fonttype: 42
35
+
36
+ # Colors (use colorblind-friendly defaults)
37
+ axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf'])
climplot/maps.py ADDED
@@ -0,0 +1,230 @@
1
+ """
2
+ Map plotting utilities with Cartopy projections.
3
+
4
+ This module provides functions for creating publication-quality maps
5
+ with appropriate projections, coastlines, and land overlays.
6
+
7
+ Examples
8
+ --------
9
+ >>> import climplot
10
+ >>> climplot.publication()
11
+ >>> fig, ax = climplot.map_figure()
12
+ >>> cs = ax.pcolormesh(lon, lat, data, transform=ccrs.PlateCarree())
13
+ """
14
+
15
+ import matplotlib.pyplot as plt
16
+ import numpy as np
17
+ from typing import Tuple, Optional
18
+
19
+ # Cartopy imports (required dependency)
20
+ import cartopy.crs as ccrs
21
+ import cartopy.feature as cfeature
22
+
23
+
24
+ def map_figure(
25
+ projection: str = "robinson",
26
+ central_longitude: float = 180,
27
+ figsize: Optional[Tuple[float, float]] = None,
28
+ **kwargs,
29
+ ) -> Tuple[plt.Figure, plt.Axes]:
30
+ """
31
+ Create a map figure with specified projection.
32
+
33
+ Parameters
34
+ ----------
35
+ projection : str, optional
36
+ Map projection name. Options:
37
+ - 'robinson' (default): Good for global maps
38
+ - 'platecarree': Equirectangular, simple lat/lon
39
+ - 'mollweide': Equal-area, good for global
40
+ - 'orthographic': Globe view
41
+ - 'mercator': Standard navigation projection
42
+ - 'northpolarstereo': Arctic view
43
+ - 'southpolarstereo': Antarctic view
44
+ central_longitude : float, optional
45
+ Central longitude for projection. Default is 180 (Pacific-centered).
46
+ figsize : tuple, optional
47
+ Figure size (width, height) in inches. If None, uses matplotlib defaults.
48
+ **kwargs
49
+ Additional arguments passed to plt.figure()
50
+
51
+ Returns
52
+ -------
53
+ fig : Figure
54
+ The figure object
55
+ ax : GeoAxes
56
+ The map axes with specified projection
57
+
58
+ Examples
59
+ --------
60
+ >>> # Global Robinson projection (Pacific-centered)
61
+ >>> fig, ax = climplot.map_figure()
62
+
63
+ >>> # Atlantic-centered
64
+ >>> fig, ax = climplot.map_figure(central_longitude=0)
65
+
66
+ >>> # Plate Carree for regional maps
67
+ >>> fig, ax = climplot.map_figure(projection='platecarree')
68
+ """
69
+ # Get projection
70
+ proj = _get_projection(projection, central_longitude)
71
+
72
+ # Create figure
73
+ if figsize is not None:
74
+ kwargs["figsize"] = figsize
75
+ fig = plt.figure(**kwargs)
76
+ ax = fig.add_subplot(111, projection=proj)
77
+
78
+ return fig, ax
79
+
80
+
81
+ def _get_projection(name: str, central_longitude: float = 180):
82
+ """Get a cartopy projection by name."""
83
+ projections = {
84
+ "robinson": ccrs.Robinson(central_longitude=central_longitude),
85
+ "platecarree": ccrs.PlateCarree(central_longitude=central_longitude),
86
+ "mollweide": ccrs.Mollweide(central_longitude=central_longitude),
87
+ "orthographic": ccrs.Orthographic(
88
+ central_longitude=central_longitude, central_latitude=0
89
+ ),
90
+ "mercator": ccrs.Mercator(central_longitude=central_longitude),
91
+ "northpolarstereo": ccrs.NorthPolarStereo(),
92
+ "southpolarstereo": ccrs.SouthPolarStereo(),
93
+ }
94
+
95
+ name_lower = name.lower().replace("_", "").replace("-", "")
96
+ if name_lower not in projections:
97
+ raise ValueError(
98
+ f"Unknown projection: {name}. "
99
+ f"Available: {list(projections.keys())}"
100
+ )
101
+
102
+ return projections[name_lower]
103
+
104
+
105
+ def add_land_overlay(
106
+ ax: plt.Axes,
107
+ lon,
108
+ lat,
109
+ wet_mask,
110
+ land_color: str = "#808080",
111
+ zorder: int = 10,
112
+ ):
113
+ """
114
+ Add gray overlay to render land areas.
115
+
116
+ Draws a solid color layer over land points (wet=0), ensuring
117
+ continents appear uniformly regardless of underlying data values.
118
+
119
+ Parameters
120
+ ----------
121
+ ax : GeoAxes
122
+ Map axes to add overlay to
123
+ lon : array-like
124
+ 2D longitude array
125
+ lat : array-like
126
+ 2D latitude array
127
+ wet_mask : array-like
128
+ Wet mask where 1 = ocean, 0 = land
129
+ land_color : str, optional
130
+ Color for land. Default is '#808080' (medium gray).
131
+ zorder : int, optional
132
+ Drawing order. Default is 10 (on top of most elements).
133
+
134
+ Examples
135
+ --------
136
+ >>> fig, ax = climplot.map_figure()
137
+ >>> cs = ax.pcolormesh(lon, lat, data, transform=ccrs.PlateCarree())
138
+ >>> climplot.add_land_overlay(ax, lon, lat, wet_mask)
139
+ """
140
+ import xarray as xr
141
+ from matplotlib.colors import ListedColormap
142
+
143
+ # Create land overlay: 1 where land (wet=0), NaN where ocean
144
+ land_overlay = xr.where(wet_mask == 0, 1.0, np.nan)
145
+
146
+ # Single-color colormap
147
+ gray_cmap = ListedColormap([land_color])
148
+
149
+ # Draw over land
150
+ ax.pcolormesh(
151
+ lon,
152
+ lat,
153
+ land_overlay,
154
+ transform=ccrs.PlateCarree(),
155
+ cmap=gray_cmap,
156
+ vmin=0,
157
+ vmax=1,
158
+ zorder=zorder,
159
+ )
160
+
161
+
162
+ def add_coastlines(
163
+ ax: plt.Axes,
164
+ resolution: str = "110m",
165
+ linewidth: float = 0.5,
166
+ color: str = "black",
167
+ **kwargs,
168
+ ):
169
+ """
170
+ Add coastlines to a map.
171
+
172
+ Parameters
173
+ ----------
174
+ ax : GeoAxes
175
+ Map axes
176
+ resolution : str, optional
177
+ Resolution: '110m' (default), '50m', or '10m'.
178
+ Higher resolution = more detail but slower.
179
+ linewidth : float, optional
180
+ Line width. Default is 0.5.
181
+ color : str, optional
182
+ Line color. Default is 'black'.
183
+ **kwargs
184
+ Additional arguments passed to ax.coastlines()
185
+
186
+ Examples
187
+ --------
188
+ >>> fig, ax = climplot.map_figure()
189
+ >>> climplot.add_coastlines(ax)
190
+
191
+ Notes
192
+ -----
193
+ For model data on native grids, consider using add_land_overlay()
194
+ instead of coastlines, as coastlines may not align with the model grid.
195
+ """
196
+ ax.coastlines(resolution=resolution, linewidth=linewidth, color=color, **kwargs)
197
+
198
+
199
+ def add_land_feature(
200
+ ax: plt.Axes,
201
+ resolution: str = "110m",
202
+ facecolor: str = "#808080",
203
+ edgecolor: str = "none",
204
+ **kwargs,
205
+ ):
206
+ """
207
+ Add land feature (filled continents) to a map.
208
+
209
+ Parameters
210
+ ----------
211
+ ax : GeoAxes
212
+ Map axes
213
+ resolution : str, optional
214
+ Resolution: '110m' (default), '50m', or '10m'.
215
+ facecolor : str, optional
216
+ Fill color. Default is '#808080' (gray).
217
+ edgecolor : str, optional
218
+ Edge color. Default is 'none'.
219
+ **kwargs
220
+ Additional arguments passed to ax.add_feature()
221
+
222
+ Examples
223
+ --------
224
+ >>> fig, ax = climplot.map_figure()
225
+ >>> climplot.add_land_feature(ax)
226
+ """
227
+ land = cfeature.NaturalEarthFeature(
228
+ "physical", "land", resolution, facecolor=facecolor, edgecolor=edgecolor
229
+ )
230
+ ax.add_feature(land, **kwargs)