Skip to content

Commit 2f3d087

Browse files
Improved handling of colormaps
1 parent 6c72ed3 commit 2f3d087

File tree

1 file changed

+233
-11
lines changed

1 file changed

+233
-11
lines changed

src/python/spatialize/viz.py

Lines changed: 233 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
PALETTES = {
1313
'alges': ['#070f15', '#0f212d', '#19384d', '#275474', '#326c94', '#4b87af', '#78abce', '#a7c9e1', '#cfe2ef'],
1414
'alges_muted': ['#142b3b','#1e4058', '#285676', '#326c94', '#5a89a9', '#84a6be', '#c1d2de'],
15+
'navia': ['#0b1627','#16345c','#19598c','#29738e','#398285','#4b9379','#66aa6a','#98ca6e','#d9e5a6','#fcf5d9'],
1516
'navia_r': ['#011a5a','#104261','#205f61','#4c734d','#828434','#c09036','#f2a069','#fcb3b3','#fee5e5'],
1617
'precision': ['#51648a','#796982','#9d6c79','#c47673','#e98c76','#edb18e','#edd3b3'],
1718
'precision_dark': ['#0b1425','#133859','#48587a','#6c5e74','#90606c','#bc6461','#e67960','#eb937f','#f2bcaf'],
@@ -20,26 +21,205 @@
2021
'batlow': ['#fbcdfa','#fcb3b3','#f2a069','#c09036','#828434','#4c734d','#205f61','#104261','#011a5a'],
2122
'glasgow': ['#371437','#4e1921','#6a2810','#74471c','#716328','#697c47','#61917c','#74a8af','#a8bed7','#dcd1e8'],
2223
'lipari': ['#0b1425','#133859','#48587a','#6c5e74','#90606c','#bc6461','#e67960','#e9a278','#e8c79e','#fef5db'],
23-
'navia': ['#0b1627','#16345c','#19598c','#29738e','#398285','#4b9379','#66aa6a','#98ca6e','#d9e5a6','#fcf5d9'],
2424
'nuuk': ['#11598c','#2c6384','#4b7182','#70868c','#939b96','#acad95','#bbb98b','#c7c581','#e1e08c','#fbf7b3'],
2525
'bamako': ['#073a46','#12433f','#214f33','#385d2c','#547032','#738437','#978e33','#bfa830','#e2c66b','#fee4ab'],
2626
'tokio': ['#1f1032','#4b1f42','#6a404e','#715651','#746651','#767a54','#7d9857','#8ec26d','#c3e0a7','#eff5db'],
2727
'bilbao': ['#471010' ,'#752329', '#94454b', '#a16157', '#a6775a', '#ac8c5f', '#b6a672', '#c2bca1', '#d4d1cd', '#ffffff']}
2828

29+
def get_available_palettes():
30+
"""
31+
Get a list of all available palette names.
32+
33+
Returns
34+
-------
35+
list of str
36+
Names of available color palettes
37+
38+
Examples
39+
--------
40+
>>> palettes = get_available_palettes()
41+
>>> print(palettes)
42+
['alges', 'alges_muted', 'navia_r', 'precision', ...]
43+
"""
44+
return list(PALETTES.keys())
45+
46+
def get_palette(name):
47+
"""
48+
Get a color palette by name.
49+
50+
Parameters
51+
----------
52+
name : str
53+
Name of the palette. Use get_available_palettes() to see options.
54+
55+
Returns
56+
-------
57+
list of str
58+
List of hex color codes in the palette
59+
60+
Raises
61+
------
62+
ValueError
63+
If palette name is not found
64+
65+
Examples
66+
--------
67+
>>> colors = get_palette('alges')
68+
>>> print(colors[0])
69+
'#070f15'
70+
71+
>>> # Use palette colors directly
72+
>>> colors = get_palette('glasgow')
73+
>>> plt.scatter(x, y, c=colors[3])
74+
"""
75+
if name not in PALETTES:
76+
available = ', '.join(get_available_palettes())
77+
raise ValueError(f"Palette '{name}' not found. Available palettes: {available}")
78+
return PALETTES[name]
79+
80+
def get_palette_colormap(name, reverse=False):
81+
"""
82+
Create a matplotlib colormap from a named palette.
83+
84+
Parameters
85+
----------
86+
name : str
87+
Name of the palette. Use get_available_palettes() to see options.
88+
reverse : bool, optional
89+
If True, reverses the color order. Default is False.
90+
91+
Returns
92+
-------
93+
matplotlib.colors.LinearSegmentedColormap
94+
Matplotlib colormap object ready for use in plotting
95+
96+
Raises
97+
------
98+
ValueError
99+
If palette name is not found
100+
101+
Examples
102+
--------
103+
>>> cmap = get_palette_colormap('batlow')
104+
>>> plt.imshow(data, cmap=cmap)
105+
106+
>>> # Reverse the colormap
107+
>>> cmap_r = get_palette_colormap('navia', reverse=True)
108+
>>> plt.contourf(X, Y, Z, cmap=cmap_r)
109+
110+
>>> # Use in PlotStyle
111+
>>> style = PlotStyle(cmap=get_palette_colormap('lipari'))
112+
>>> plt.scatter(x, y, c=values, cmap=style.cmap)
113+
"""
114+
colors = get_palette(name)
115+
if reverse:
116+
colors = colors[::-1]
117+
return Colormap.from_list(f'{name}{"_r" if reverse else ""}', colors)
118+
119+
def show_palettes(names=None, figsize=None, n_colors=256, title="Available Color Palettes"):
120+
"""
121+
Display color palettes as horizontal gradient bars for visual comparison.
122+
123+
Parameters
124+
----------
125+
names : list of str, optional
126+
Specific palette names to display. If None, shows all available palettes.
127+
figsize : tuple, optional
128+
Figure size as (width, height) in inches. If None, auto-calculated based
129+
on the number of palettes (width=8, height=0.4*n_palettes).
130+
n_colors : int, optional
131+
Number of color samples to display for each palette gradient. Default is 256.
132+
title : str, optional
133+
Title for the figure. Default is "Available Color Palettes".
134+
135+
Returns
136+
-------
137+
matplotlib.figure.Figure
138+
The figure object containing the palette visualizations
139+
140+
Examples
141+
--------
142+
>>> # Show all palettes
143+
>>> show_palettes()
144+
145+
>>> # Show specific palettes
146+
>>> show_palettes(names=['alges', 'precision', 'navia'])
147+
148+
>>> # Customize figure size
149+
>>> show_palettes(figsize=(10, 8))
150+
151+
>>> # Show palettes without title
152+
>>> show_palettes(title=None)
153+
"""
154+
# Determine which palettes to show
155+
if names is None:
156+
names = get_available_palettes()
157+
else:
158+
# Validate provided names
159+
invalid = [n for n in names if n not in PALETTES]
160+
if invalid:
161+
raise ValueError(f"Invalid palette names: {invalid}. Use get_available_palettes() to see options.")
162+
163+
n_palettes = len(names)
164+
165+
# Auto-calculate figure size if not provided
166+
if figsize is None:
167+
figsize = (8, 0.4 * n_palettes + 0.5)
168+
169+
# Create figure and axes
170+
fig, axes = plt.subplots(n_palettes, 1, figsize=figsize)
171+
172+
# Handle single palette case (axes won't be an array)
173+
if n_palettes == 1:
174+
axes = [axes]
175+
176+
# Create gradient array for displaying colors
177+
gradient = np.linspace(0, 1, n_colors).reshape(1, -1)
178+
179+
# Plot each palette
180+
for ax, name in zip(axes, names):
181+
# Get colormap and display as gradient
182+
cmap = get_palette_colormap(name)
183+
ax.imshow(gradient, aspect='auto', cmap=cmap)
184+
185+
# Remove ticks and spines
186+
ax.set_xticks([])
187+
ax.set_yticks([])
188+
for spine in ax.spines.values():
189+
spine.set_visible(False)
190+
191+
# Add palette name as y-label
192+
ax.set_ylabel(name, rotation=0, ha='right', va='center', fontsize=10)
193+
194+
# Add title if provided
195+
if title:
196+
fig.suptitle(title, fontsize=12, fontweight='bold')
197+
198+
plt.tight_layout()
199+
200+
if not in_notebook():
201+
return fig
202+
203+
29204
class PlotStyle:
30205
"""
31206
Manage matplotlib plot styles with predefined themes.
32-
207+
33208
Parameters
34209
----------
35210
theme : str, optional
36-
Theme name. Available: 'darkgrid', 'whitegrid', 'dark', 'white',
211+
Theme name. Available: 'darkgrid', 'whitegrid', 'dark', 'white',
37212
'alges', 'minimal', 'publication'
38213
color : str, optional
39214
Primary color for plots
40-
cmap : str or colormap, optional
41-
Colormap for plots
42-
215+
cmap : str, list, or colormap, optional
216+
Colormap for plots. Can be:
217+
- str: Name from PALETTES (e.g., 'alges', 'batlow') or matplotlib colormap (e.g., 'viridis')
218+
- list: List of hex colors to create a custom colormap
219+
- Colormap object: Matplotlib colormap instance
220+
precision_cmap : str, list, or colormap, optional
221+
Colormap for precision/uncertainty plots. Same format as cmap.
222+
43223
Attributes
44224
----------
45225
theme : str
@@ -48,7 +228,9 @@ class PlotStyle:
48228
Primary plot color
49229
cmap : str or colormap
50230
Plot colormap
51-
231+
precision_cmap : str or colormap
232+
Precision/uncertainty colormap
233+
52234
Examples
53235
--------
54236
Basic usage with context manager::
@@ -57,6 +239,22 @@ class PlotStyle:
57239
plt.plot(x, y, color=style.color)
58240
plt.imshow(data, cmap=style.cmap)
59241
242+
Using custom palettes from PALETTES::
243+
244+
style = PlotStyle(cmap='batlow') # Uses palette from PALETTES
245+
plt.imshow(data, cmap=style.cmap)
246+
247+
Using custom color list::
248+
249+
custom_colors = ['#ff0000', '#00ff00', '#0000ff']
250+
style = PlotStyle(cmap=custom_colors)
251+
plt.scatter(x, y, c=values, cmap=style.cmap)
252+
253+
Using matplotlib colormaps::
254+
255+
style = PlotStyle(cmap='viridis') # Standard matplotlib colormap
256+
plt.contourf(X, Y, Z, cmap=style.cmap)
257+
60258
Simple usage without context manager::
61259
62260
style = PlotStyle(theme='alges')
@@ -222,7 +420,7 @@ def __init__(self,
222420
theme = None,
223421
color = None,
224422
cmap = None,
225-
precision_cmap = None,):
423+
precision_cmap = None):
226424

227425
if theme and theme not in self.THEMES:
228426
raise ValueError(f"Theme '{theme}' not found. Available: {list(self.THEMES.keys())}")
@@ -247,15 +445,39 @@ def _set_color(self, color):
247445

248446
def _set_cmap(self, cmap):
249447
if cmap:
250-
return cmap
448+
# Handle string input - check PALETTES first, then assume matplotlib colormap
449+
if isinstance(cmap, str):
450+
if cmap in PALETTES:
451+
return get_palette_colormap(cmap)
452+
else:
453+
# Return as-is, assuming it's a matplotlib colormap name
454+
return cmap
455+
# Handle list input - create colormap from color list
456+
elif isinstance(cmap, list):
457+
return Colormap.from_list('custom_cmap', cmap)
458+
# Otherwise return as-is (already a colormap object)
459+
else:
460+
return cmap
251461
elif self.theme:
252462
return self.THEMES[self.theme]['cmap']
253463
else:
254464
return self.DEFAULT_CMAP
255-
465+
256466
def _set_precision_cmap(self, cmap):
257467
if cmap:
258-
return cmap
468+
# Handle string input - check PALETTES first, then assume matplotlib colormap
469+
if isinstance(cmap, str):
470+
if cmap in PALETTES:
471+
return get_palette_colormap(cmap)
472+
else:
473+
# Return as-is, assuming it's a matplotlib colormap name
474+
return cmap
475+
# Handle list input - create colormap from color list
476+
elif isinstance(cmap, list):
477+
return Colormap.from_list('custom_precision_cmap', cmap)
478+
# Otherwise return as-is (already a colormap object)
479+
else:
480+
return cmap
259481
elif self.theme:
260482
return self.THEMES[self.theme]['precision_cmap']
261483
else:

0 commit comments

Comments
 (0)