Coverage for src/CSET/operators/_colormaps.py: 94%
261 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-06 10:09 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-06 10:09 +0000
1# © Crown copyright, Met Office (2022-2026) and CSET contributors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Functions to support colormap settings for CSET plots."""
17import functools
18import importlib.resources
19import itertools
20import json
21import logging
22from typing import Literal
24import iris
25import iris.cube
26import matplotlib as mpl
27import matplotlib.colors as mcolors
28import matplotlib.pyplot as plt
29import numpy as np
31from CSET._common import (
32 combine_dicts,
33 get_recipe_metadata,
34 iter_maybe,
35)
37logger = logging.getLogger(__name__)
39DEFAULT_DISCRETE_COLORS = mpl.colormaps["tab10"].colors + mpl.colormaps["Accent"].colors
42@functools.cache
43def load_colorbar_map(user_colorbar_file: str | None = None) -> dict:
44 """Load the colorbar definitions from a file.
46 This is a separate function to make it cacheable.
47 """
48 colorbar_file = importlib.resources.files().joinpath("_colorbar_definition.json")
49 with open(colorbar_file, "rt", encoding="UTF-8") as fp:
50 colorbar = json.load(fp)
52 logger.debug("User colour bar file: %s", user_colorbar_file)
53 override_colorbar = {}
54 if user_colorbar_file:
55 try:
56 with open(user_colorbar_file, "rt", encoding="UTF-8") as fp:
57 override_colorbar = json.load(fp)
58 except FileNotFoundError:
59 logger.warning("Colorbar file does not exist. Using default values.")
61 # Overwrite values with the user supplied colorbar definition.
62 colorbar = combine_dicts(colorbar, override_colorbar)
63 return colorbar
66def get_model_colors_map(cubes: iris.cube.CubeList | iris.cube.Cube) -> dict:
67 """Get an appropriate colors for model lines in line plots.
69 For each model in the list of cubes colors either from user provided
70 color definition file (so-called style file) or from default colors are mapped
71 to model_name attribute.
73 Parameters
74 ----------
75 cubes: CubeList or Cube
76 Cubes with model_name attribute
78 Returns
79 -------
80 model_colors_map:
81 Dictionary mapping model_name attribute to colors
82 """
83 user_colorbar_file = get_recipe_metadata().get("style_file_path", None)
84 colorbar = load_colorbar_map(user_colorbar_file)
85 model_names = sorted(
86 filter(
87 lambda x: x is not None,
88 (cube.attributes.get("model_name", None) for cube in iter_maybe(cubes)),
89 )
90 )
91 if not model_names:
92 return {}
93 use_user_colors = all(mname in colorbar for mname in model_names)
94 if use_user_colors: 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true
95 return {mname: colorbar[mname] for mname in model_names}
97 # Supported analysis names
98 ANALYSIS_NAMES = {"ERA5", "UM_ANALYSIS"}
100 is_reference = lambda name: "OBS" in name.upper() or name.upper() in ANALYSIS_NAMES
102 ref_models = [name for name in model_names if is_reference(name)]
104 if ref_models:
105 colors = list(DEFAULT_DISCRETE_COLORS).copy()
107 for name in reversed(ref_models):
108 model_names.remove(name)
109 model_names.insert(0, name)
111 for name in reversed(ref_models):
112 if "OBS" in name.upper():
113 colors.insert(0, mcolors.to_rgb("dimgray"))
114 else: # ERA5 or UM_ANALYSIS
115 colors.insert(0, mcolors.to_rgb("black"))
116 else:
117 colors = DEFAULT_DISCRETE_COLORS
119 color_list = itertools.cycle(colors)
120 return {mname: color for mname, color in zip(model_names, color_list, strict=False)}
123def colorbar_map_levels(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None):
124 """Get an appropriate colorbar for the given cube.
126 For the given variable the appropriate colorbar is looked up from a
127 combination of the built-in CSET colorbar definitions, and any user supplied
128 definitions. As well as varying on variables, these definitions may also
129 exist for specific pressure levels to account for variables with
130 significantly different ranges at different heights. The colorbars also exist
131 for masks and mask differences for considering variable presence diagnostics.
132 Specific variable ranges can be separately set in user-supplied definition
133 for x- or y-axis limits, or indicate where automated range preferred.
135 Parameters
136 ----------
137 cube: Cube
138 Cube of variable for which the colorbar information is desired.
139 axis: "x", "y", optional
140 Select the levels for just this axis of a line plot. The min and max
141 can be set by xmin/xmax or ymin/ymax respectively. For variables where
142 setting a universal range is not desirable (e.g. temperature), users
143 can set ymin/ymax values to "auto" in the colorbar definitions file.
144 Where no additional xmin/xmax or ymin/ymax values are provided, the
145 axis bounds default to use the vmin/vmax values provided.
147 Returns
148 -------
149 cmap:
150 Matplotlib colormap.
151 levels:
152 List of levels to use for plotting. For continuous plots the min and max
153 should be taken as the range.
154 norm:
155 BoundaryNorm information.
156 """
157 # Grab the colorbar file from the recipe global metadata.
158 user_colorbar_file = get_recipe_metadata().get("style_file_path", None)
159 colorbar = load_colorbar_map(user_colorbar_file)
160 cmap = None
162 try:
163 # We assume that pressure is a scalar coordinate here.
164 pressure_level_raw = cube.coord("pressure").points[0]
165 # Ensure pressure_level is a string, as it is used as a JSON key.
166 pressure_level = str(int(pressure_level_raw))
167 except iris.exceptions.CoordinateNotFoundError:
168 pressure_level = None
170 # First try long name, then standard name, then var name. This order is used
171 # as long name is the one we correct between models, so it most likely to be
172 # consistent.
173 varnames = list(filter(None, [cube.long_name, cube.standard_name, cube.var_name]))
174 # Treat observation-labelled var names consistently with model var names.
175 varnames = [varname.replace("observed_", "") for varname in varnames]
176 for varname in varnames:
177 # Get the colormap for this variable.
178 try:
179 var_colorbar = colorbar[varname]
180 cmap = plt.get_cmap(colorbar[varname]["cmap"], 51)
181 varname_key = varname
182 break
183 except KeyError:
184 logger.debug("Cube name %s has no colorbar definition.", varname)
186 # Get colormap if it is a mask.
187 if any("mask_for_" in name for name in varnames):
188 cmap, levels, norm = custom_colormap_mask(cube, axis=axis)
189 return cmap, levels, norm
190 # If winds on Beaufort Scale use custom colorbar and levels
191 if any("Beaufort_Scale" in name for name in varnames):
192 cmap, levels, norm = custom_beaufort_scale(cube, axis=axis)
193 return cmap, levels, norm
194 # If probability is plotted use custom colorbar and levels
195 if any("probability_of_" in name for name in varnames):
196 cmap, levels, norm = custom_colormap_probability(cube, axis=axis)
197 return cmap, levels, norm
198 # If aviation colour state use custom colorbar and levels
199 if any("aviation_colour_state" in name for name in varnames):
200 cmap, levels, norm = custom_colormap_aviation_colour_state(cube)
201 return cmap, levels, norm
202 # If verification scores use custom colorbar
203 if any("RMSE_" in name for name in varnames):
204 cmap, levels, norm = custom_colormap_scores(cube)
205 return cmap, levels, norm
206 # If feature tracking use custom colorbar and levels
207 if any("feature_" in name for name in varnames): 207 ↛ 208line 207 didn't jump to line 208 because the condition on line 207 was never true
208 cmap, levels, norm = custom_colormap_feature_tracking(cube)
209 return cmap, levels, norm
210 if any("CURV_" in name for name in varnames): 210 ↛ 211line 210 didn't jump to line 211 because the condition on line 210 was never true
211 cmap, levels, norm = custom_colormap_curv(cube)
212 return cmap, levels, norm
214 # If no valid colormap has been defined, use defaults and return.
215 if not cmap:
216 logger.warning("No colorbar definition exists for %s.", cube.name())
217 cmap, levels, norm = mpl.colormaps["viridis"], None, None
218 return cmap, levels, norm
220 # Test if pressure-level specific settings are provided for cube.
221 if pressure_level:
222 try:
223 var_colorbar = colorbar[varname_key]["pressure_levels"][pressure_level]
224 except KeyError:
225 logger.debug(
226 "%s has no colorbar definition for pressure level %s.",
227 varname,
228 pressure_level,
229 )
231 # Check for availability of x-axis or y-axis user-specific overrides
232 # for setting level bounds for line plot types and return just levels.
233 # Line plots do not need a colormap, and just use the data range.
234 if axis:
235 if axis == "x":
236 try:
237 vmin, vmax = var_colorbar["xmin"], var_colorbar["xmax"]
238 except KeyError:
239 vmin, vmax = var_colorbar["min"], var_colorbar["max"]
240 if axis == "y":
241 try:
242 vmin, vmax = var_colorbar["ymin"], var_colorbar["ymax"]
243 except KeyError:
244 vmin, vmax = var_colorbar["min"], var_colorbar["max"]
245 # Check if user-specified auto-scaling for this variable
246 if vmin == "auto" or vmax == "auto":
247 levels = None
248 else:
249 levels = [vmin, vmax]
250 return None, levels, None
251 # Get and use the colorbar levels for this variable if spatial or histogram.
252 else:
253 try:
254 levels = var_colorbar["levels"]
255 # Use discrete bins when levels are specified, rather
256 # than a smooth range.
257 norm = mpl.colors.BoundaryNorm(levels, ncolors=cmap.N)
258 logger.debug("Using levels for %s colorbar.", varname)
259 logger.info("Using levels: %s", levels)
260 except KeyError:
261 # Get the range for this variable.
262 vmin, vmax = var_colorbar["min"], var_colorbar["max"]
263 logger.debug("Using min and max for %s colorbar.", varname)
264 # Calculate levels from range.
265 if vmin == "auto" or vmax == "auto":
266 levels = None
267 else:
268 levels = np.linspace(vmin, vmax, 101)
269 norm = None
271 # Overwrite cmap, levels and norm for specific variables that
272 # require custom colorbar_map as these can not be defined in the
273 # JSON file.
274 cmap, levels, norm = custom_colormap_precipitation(cube, cmap, levels, norm)
275 cmap, levels, norm = custom_colourmap_nimrod_weights(cube, cmap, levels, norm)
276 cmap, levels, norm = custom_colormap_visibility_in_air(cube, cmap, levels, norm)
277 cmap, levels, norm = custom_colormap_celsius(cube, cmap, levels, norm)
278 return cmap, levels, norm
281def custom_colormap_mask(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None):
282 """Get colormap for mask.
284 If "mask_for_" appears anywhere in the name of a cube this function will be called
285 regardless of the name of the variable to ensure a consistent plot.
287 Parameters
288 ----------
289 cube: Cube
290 Cube of variable for which the colorbar information is desired.
291 axis: "x", "y", optional
292 Select the levels for just this axis of a line plot. The min and max
293 can be set by xmin/xmax or ymin/ymax respectively. For variables where
294 setting a universal range is not desirable (e.g. temperature), users
295 can set ymin/ymax values to "auto" in the colorbar definitions file.
296 Where no additional xmin/xmax or ymin/ymax values are provided, the
297 axis bounds default to use the vmin/vmax values provided.
299 Returns
300 -------
301 cmap:
302 Matplotlib colormap.
303 levels:
304 List of levels to use for plotting. For continuous plots the min and max
305 should be taken as the range.
306 norm:
307 BoundaryNorm information.
308 """
309 if "difference" not in cube.long_name:
310 if axis:
311 levels = [0, 1]
312 # Complete settings based on levels.
313 return None, levels, None
314 else:
315 # Define the levels and colors.
316 levels = [0, 1, 2]
317 colors = ["white", "dodgerblue"]
318 # Create a custom color map.
319 cmap = mcolors.ListedColormap(colors)
320 # Normalize the levels.
321 norm = mcolors.BoundaryNorm(levels, cmap.N)
322 logger.debug("Colormap for %s.", cube.long_name)
323 return cmap, levels, norm
324 else:
325 if axis:
326 levels = [-1, 1]
327 return None, levels, None
328 else:
329 # Search for if mask difference, set to +/- 0.5 as values plotted <
330 # not <=.
331 levels = [-2, -0.5, 0.5, 2]
332 colors = ["goldenrod", "white", "teal"]
333 cmap = mcolors.ListedColormap(colors)
334 norm = mcolors.BoundaryNorm(levels, cmap.N)
335 logger.debug("Colormap for %s.", cube.long_name)
336 return cmap, levels, norm
339def custom_beaufort_scale(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None):
340 """Get a custom colorbar for a cube in the Beaufort Scale.
342 Specific variable ranges can be separately set in user-supplied definition
343 for x- or y-axis limits, or indicate where automated range preferred.
345 Parameters
346 ----------
347 cube: Cube
348 Cube of variable with Beaufort Scale in name.
349 axis: "x", "y", optional
350 Select the levels for just this axis of a line plot. The min and max
351 can be set by xmin/xmax or ymin/ymax respectively. For variables where
352 setting a universal range is not desirable (e.g. temperature), users
353 can set ymin/ymax values to "auto" in the colorbar definitions file.
354 Where no additional xmin/xmax or ymin/ymax values are provided, the
355 axis bounds default to use the vmin/vmax values provided.
357 Returns
358 -------
359 cmap:
360 Matplotlib colormap.
361 levels:
362 List of levels to use for plotting. For continuous plots the min and max
363 should be taken as the range.
364 norm:
365 BoundaryNorm information.
366 """
367 if "difference" not in cube.long_name:
368 if axis:
369 levels = [0, 12]
370 return None, levels, None
371 else:
372 levels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
373 colors = [
374 "black",
375 (0, 0, 0.6),
376 "blue",
377 "cyan",
378 "green",
379 "yellow",
380 (1, 0.5, 0),
381 "red",
382 "pink",
383 "magenta",
384 "purple",
385 "maroon",
386 "white",
387 ]
388 cmap = mcolors.ListedColormap(colors)
389 norm = mcolors.BoundaryNorm(levels, cmap.N)
390 logger.info("change colormap for Beaufort Scale colorbar.")
391 return cmap, levels, norm
392 else:
393 if axis:
394 levels = [-4, 4]
395 return None, levels, None
396 else:
397 levels = [
398 -3.5,
399 -2.5,
400 -1.5,
401 -0.5,
402 0.5,
403 1.5,
404 2.5,
405 3.5,
406 ]
407 cmap = plt.get_cmap("bwr", 8)
408 norm = mcolors.BoundaryNorm(levels, cmap.N)
409 return cmap, levels, norm
412def custom_colormap_celsius(cube: iris.cube.Cube, cmap, levels, norm):
413 """Return altered colormap for temperature with change in units to Celsius.
415 If "Celsius" appears anywhere in the name of a cube this function will be called.
417 Parameters
418 ----------
419 cube: Cube
420 Cube of variable for which the colorbar information is desired.
421 cmap: Matplotlib colormap.
422 levels: List
423 List of levels to use for plotting. For continuous plots the min and max
424 should be taken as the range.
425 norm: BoundaryNorm.
427 Returns
428 -------
429 cmap: Matplotlib colormap.
430 levels: List
431 List of levels to use for plotting. For continuous plots the min and max
432 should be taken as the range.
433 norm: BoundaryNorm.
434 """
435 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
436 if any("temperature" in name for name in varnames) and "Celsius" == cube.units:
437 levels = np.array(levels)
438 levels -= 273
439 levels = levels.tolist()
440 return cmap, levels, norm
443def custom_colormap_probability(
444 cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None
445):
446 """Get a custom colorbar for a probability cube.
448 Specific variable ranges can be separately set in user-supplied definition
449 for x- or y-axis limits, or indicate where automated range preferred.
451 Parameters
452 ----------
453 cube: Cube
454 Cube of variable with probability in name.
455 axis: "x", "y", optional
456 Select the levels for just this axis of a line plot. The min and max
457 can be set by xmin/xmax or ymin/ymax respectively. For variables where
458 setting a universal range is not desirable (e.g. temperature), users
459 can set ymin/ymax values to "auto" in the colorbar definitions file.
460 Where no additional xmin/xmax or ymin/ymax values are provided, the
461 axis bounds default to use the vmin/vmax values provided.
463 Returns
464 -------
465 cmap:
466 Matplotlib colormap.
467 levels:
468 List of levels to use for plotting. For continuous plots the min and max
469 should be taken as the range.
470 norm:
471 BoundaryNorm information.
472 """
473 if axis:
474 levels = [0, 1]
475 return None, levels, None
476 else:
477 cmap = mcolors.ListedColormap(
478 [
479 "#FFFFFF",
480 "#636363",
481 "#e1dada",
482 "#B5CAFF",
483 "#8FB3FF",
484 "#7F97FF",
485 "#ABCF63",
486 "#E8F59E",
487 "#FFFA14",
488 "#FFD121",
489 "#FFA30A",
490 ]
491 )
492 levels = [0.0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
493 norm = mcolors.BoundaryNorm(levels, cmap.N)
494 return cmap, levels, norm
497def custom_colormap_precipitation(cube: iris.cube.Cube, cmap, levels, norm):
498 """Return a custom colormap for the current recipe."""
499 varnames_lower = [
500 n.lower() for n in (cube.long_name, cube.standard_name, cube.var_name) if n
501 ]
503 is_rainfall_var = any(
504 key in name
505 for name in varnames_lower
506 for key in (
507 "surface_microphysical",
508 "rainfall rate composite",
509 "nimrod5min",
510 "nimrod_5min",
511 "rain_accumulation",
512 "rain accumulation",
513 )
514 )
516 if is_rainfall_var:
517 logger.debug(
518 "Using custom precipitation colourmap due to varnames: %s", varnames_lower
519 )
520 levels = [0, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256]
521 colors = [
522 "w",
523 (0, 0, 0.6),
524 "b",
525 "c",
526 "g",
527 "y",
528 (1, 0.5, 0),
529 "r",
530 "pink",
531 "m",
532 "purple",
533 "maroon",
534 "gray",
535 ]
536 # Create a custom colormap
537 cmap = mcolors.ListedColormap(colors)
538 # Normalize the levels
539 norm = mcolors.BoundaryNorm(levels, cmap.N)
540 logger.info("Using custom rainfall colourmap.")
541 return cmap, levels, norm
544def custom_colourmap_nimrod_weights(cube: iris.cube.Cube, cmap, levels, norm):
545 """Return a custom colourmap for the current recipe."""
546 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
547 if (
548 any("wts" in name for name in varnames)
549 and "difference" not in cube.long_name
550 and "mask" not in cube.long_name
551 ):
552 # Define the levels and colors. Remember the Nimrod weights vary over
553 # the range [0,13] and should be integer values. Optimum value is 13.
554 levels = [
555 -0.5,
556 0.5,
557 1.5,
558 2.5,
559 3.5,
560 4.5,
561 5.5,
562 6.5,
563 7.5,
564 8.5,
565 9.5,
566 10.5,
567 11.5,
568 12.5,
569 13.5,
570 ]
571 norm = mcolors.BoundaryNorm(levels, cmap.N)
572 colours = [
573 "#d10000",
574 "purple",
575 "#8f00d6",
576 "#ff9700",
577 "pink",
578 "#ffff00",
579 "#00007f",
580 "#6c9ccd",
581 "#aae8ff",
582 "#37a648",
583 "#8edc64",
584 "#c5ffc5",
585 "#dcdcdc",
586 "#ffffff",
587 ]
588 # Create a custom colormap.
589 cmap = mcolors.ListedColormap(colours)
590 # Normalize the levels.
591 norm = mcolors.BoundaryNorm(levels, cmap.N)
592 logger.info("Change colormap for Nimrod weights colorbar.")
593 return cmap, levels, norm
596def custom_colormap_aviation_colour_state(cube: iris.cube.Cube):
597 """Return custom colormap for aviation colour state.
599 If "aviation_colour_state" appears anywhere in the name of a cube
600 this function will be called.
602 Parameters
603 ----------
604 cube: Cube
605 Cube of variable for which the colorbar information is desired.
607 Returns
608 -------
609 cmap: Matplotlib colormap.
610 levels: List
611 List of levels to use for plotting. For continuous plots the min and max
612 should be taken as the range.
613 norm: BoundaryNorm.
614 """
615 levels = [-0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5]
616 colors = [
617 "#87ceeb",
618 "#ffffff",
619 "#8ced69",
620 "#ffff00",
621 "#ffd700",
622 "#ffa500",
623 "#fe3620",
624 ]
625 # Create a custom colormap
626 cmap = mcolors.ListedColormap(colors)
627 # Normalise the levels
628 norm = mcolors.BoundaryNorm(levels, cmap.N)
629 return cmap, levels, norm
632def custom_colormap_visibility_in_air(cube: iris.cube.Cube, cmap, levels, norm):
633 """Return a custom colormap for the current recipe."""
634 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
635 if (
636 any("visibility_in_air" in name for name in varnames)
637 and "difference" not in cube.long_name
638 and "mask" not in cube.long_name
639 ):
640 # Define the levels and colors (in km)
641 levels = [0, 0.05, 0.1, 0.2, 1.0, 2.0, 5.0, 10.0, 20.0, 30.0, 50.0, 70.0, 100.0]
642 norm = mcolors.BoundaryNorm(levels, cmap.N)
643 colours = [
644 "#8f00d6",
645 "#d10000",
646 "#ff9700",
647 "#ffff00",
648 "#00007f",
649 "#6c9ccd",
650 "#aae8ff",
651 "#37a648",
652 "#8edc64",
653 "#c5ffc5",
654 "#dcdcdc",
655 "#ffffff",
656 ]
657 # Create a custom colormap
658 cmap = mcolors.ListedColormap(colours)
659 # Normalize the levels
660 norm = mcolors.BoundaryNorm(levels, cmap.N)
661 logger.info("change colormap for visibility_in_air variable colorbar.")
662 return cmap, levels, norm
665def custom_colormap_scores(cube: iris.cube.Cube):
666 """Return altered colormap for statistical metrics.
668 Parameters
669 ----------
670 cube: Cube
671 Cube of variable for which the colorbar information is desired.
673 Returns
674 -------
675 cmap: Matplotlib colormap.
676 levels: List
677 List of levels to use for plotting. For continuous plots the min and max
678 should be taken as the range.
679 norm: BoundaryNorm.
680 """
681 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
682 cmap, levels, norm = None, None, None
683 if any("RMSE_" in name for name in varnames): 683 ↛ 685line 683 didn't jump to line 685 because the condition on line 683 was always true
684 cmap = plt.get_cmap("PuRd", 51)
685 return cmap, levels, norm
688def custom_colormap_feature_tracking(cube: iris.cube.Cube):
689 """Return altered colormap for feature tracking.
691 Parameters
692 ----------
693 cube: Cube
694 Cube of variable for which the colorbar information is desired.
696 Returns
697 -------
698 cmap: Matplotlib colormap.
699 levels: List
700 List of levels to use for plotting. For continuous plots the min and max
701 should be taken as the range.
702 norm: BoundaryNorm.
703 """
704 varnames = list(filter(None, [cube.long_name, cube.standard_name, cube.var_name]))
706 if any("feature_id" in name for name in varnames):
707 # Get max lifetime from cube attributes if available, otherwise use max of data
708 max_id = cube.attributes.get("max_value", np.ma.max(cube.data))
709 levels = None
710 cmap = plt.get_cmap("viridis")
711 norm = mcolors.Normalize(vmin=1, vmax=max_id, clip=False)
712 logger.info("change colormap for feature id variable colorbar.")
714 elif any("feature_lifetime" in name for name in varnames):
715 # Get max lifetime from cube attributes if available, otherwise use max of data
716 max_lifetime = cube.attributes.get("max_value", np.ma.max(cube.data))
717 levels = None
718 cmap = plt.get_cmap("YlGnBu")
719 norm = mcolors.Normalize(vmin=1, vmax=max_lifetime, clip=False)
720 logger.info("change colormap for feature lifetime variable colorbar.")
722 elif any("feature_init" in name for name in varnames): 722 ↛ 730line 722 didn't jump to line 730 because the condition on line 722 was always true
723 # Define the levels and colors
724 levels = np.array([0.5, 1])
725 cmap = plt.get_cmap("Blues")
726 norm = mcolors.BoundaryNorm(levels, cmap.N)
727 logger.info("change colormap for feature init variable colorbar.")
729 # Set all non-feature data to white.
730 cmap = cmap.with_extremes(under="white")
732 return cmap, levels, norm
735def custom_colormap_curv(cube: iris.cube.Cube):
736 """Return custom colourmap for curv.
738 If "CURV_" appears anywhere in the name of a cube
739 this function will be called.
741 Parameters
742 ----------
743 cube: Cube
744 Cube of variable for which the colorbar information is desired.
746 Returns
747 -------
748 cmap: Matplotlib colormap.
749 levels: List
750 List of levels to use for plotting. For continuous plots the min and max
751 should be taken as the range.
752 norm: BoundaryNorm.
753 """
754 if "16" in cube.long_name:
755 levels = [-17, -15, -13, -11, -9, -7, -5, -3, -1, 1, 3, 5, 7, 9, 11, 13, 15, 17]
756 colors = [
757 "#01153e",
758 "#030764",
759 "#00008b",
760 "#0000ff",
761 "#0323df",
762 "#069af3",
763 "#00ffff",
764 "#7fffd4",
765 "#ffffff",
766 "#ffd700",
767 "#fac205",
768 "#ffa500",
769 "#f97306",
770 "#ff4500",
771 "#ff0000",
772 "#dc143c",
773 "#a52a2a",
774 ]
775 else:
776 levels = [-9, -7, -5, -3, -1, 1, 3, 5, 7, 9]
777 colors = [
778 "#01153e",
779 "#00008b",
780 "#0323df",
781 "#00ffff",
782 "#ffffff",
783 "#fac205",
784 "#f97306",
785 "#ff0000",
786 "#a52a2a",
787 ]
788 # Create a custom colormap
789 cmap = mcolors.ListedColormap(colors)
790 # Normalise the levels
791 norm = mcolors.BoundaryNorm(levels, cmap.N)
792 return cmap, levels, norm