Coverage for src/CSET/operators/_colormaps.py: 98%
250 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-24 15:03 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-24 15:03 +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
211 # If no valid colormap has been defined, use defaults and return.
212 if not cmap:
213 logger.warning("No colorbar definition exists for %s.", cube.name())
214 cmap, levels, norm = mpl.colormaps["viridis"], None, None
215 return cmap, levels, norm
217 # Test if pressure-level specific settings are provided for cube.
218 if pressure_level:
219 try:
220 var_colorbar = colorbar[varname_key]["pressure_levels"][pressure_level]
221 except KeyError:
222 logger.debug(
223 "%s has no colorbar definition for pressure level %s.",
224 varname,
225 pressure_level,
226 )
228 # Check for availability of x-axis or y-axis user-specific overrides
229 # for setting level bounds for line plot types and return just levels.
230 # Line plots do not need a colormap, and just use the data range.
231 if axis:
232 if axis == "x":
233 try:
234 vmin, vmax = var_colorbar["xmin"], var_colorbar["xmax"]
235 except KeyError:
236 vmin, vmax = var_colorbar["min"], var_colorbar["max"]
237 if axis == "y":
238 try:
239 vmin, vmax = var_colorbar["ymin"], var_colorbar["ymax"]
240 except KeyError:
241 vmin, vmax = var_colorbar["min"], var_colorbar["max"]
242 # Check if user-specified auto-scaling for this variable
243 if vmin == "auto" or vmax == "auto":
244 levels = None
245 else:
246 levels = [vmin, vmax]
247 return None, levels, None
248 # Get and use the colorbar levels for this variable if spatial or histogram.
249 else:
250 try:
251 levels = var_colorbar["levels"]
252 # Use discrete bins when levels are specified, rather
253 # than a smooth range.
254 norm = mpl.colors.BoundaryNorm(levels, ncolors=cmap.N)
255 logger.debug("Using levels for %s colorbar.", varname)
256 logger.info("Using levels: %s", levels)
257 except KeyError:
258 # Get the range for this variable.
259 vmin, vmax = var_colorbar["min"], var_colorbar["max"]
260 logger.debug("Using min and max for %s colorbar.", varname)
261 # Calculate levels from range.
262 if vmin == "auto" or vmax == "auto":
263 levels = None
264 else:
265 levels = np.linspace(vmin, vmax, 101)
266 norm = None
268 # Overwrite cmap, levels and norm for specific variables that
269 # require custom colorbar_map as these can not be defined in the
270 # JSON file.
271 cmap, levels, norm = custom_colormap_precipitation(cube, cmap, levels, norm)
272 cmap, levels, norm = custom_colourmap_nimrod_weights(cube, cmap, levels, norm)
273 cmap, levels, norm = custom_colormap_visibility_in_air(cube, cmap, levels, norm)
274 cmap, levels, norm = custom_colormap_celsius(cube, cmap, levels, norm)
275 return cmap, levels, norm
278def custom_colormap_mask(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None):
279 """Get colormap for mask.
281 If "mask_for_" appears anywhere in the name of a cube this function will be called
282 regardless of the name of the variable to ensure a consistent plot.
284 Parameters
285 ----------
286 cube: Cube
287 Cube of variable for which the colorbar information is desired.
288 axis: "x", "y", optional
289 Select the levels for just this axis of a line plot. The min and max
290 can be set by xmin/xmax or ymin/ymax respectively. For variables where
291 setting a universal range is not desirable (e.g. temperature), users
292 can set ymin/ymax values to "auto" in the colorbar definitions file.
293 Where no additional xmin/xmax or ymin/ymax values are provided, the
294 axis bounds default to use the vmin/vmax values provided.
296 Returns
297 -------
298 cmap:
299 Matplotlib colormap.
300 levels:
301 List of levels to use for plotting. For continuous plots the min and max
302 should be taken as the range.
303 norm:
304 BoundaryNorm information.
305 """
306 if "difference" not in cube.long_name:
307 if axis:
308 levels = [0, 1]
309 # Complete settings based on levels.
310 return None, levels, None
311 else:
312 # Define the levels and colors.
313 levels = [0, 1, 2]
314 colors = ["white", "dodgerblue"]
315 # Create a custom color map.
316 cmap = mcolors.ListedColormap(colors)
317 # Normalize the levels.
318 norm = mcolors.BoundaryNorm(levels, cmap.N)
319 logger.debug("Colormap for %s.", cube.long_name)
320 return cmap, levels, norm
321 else:
322 if axis:
323 levels = [-1, 1]
324 return None, levels, None
325 else:
326 # Search for if mask difference, set to +/- 0.5 as values plotted <
327 # not <=.
328 levels = [-2, -0.5, 0.5, 2]
329 colors = ["goldenrod", "white", "teal"]
330 cmap = mcolors.ListedColormap(colors)
331 norm = mcolors.BoundaryNorm(levels, cmap.N)
332 logger.debug("Colormap for %s.", cube.long_name)
333 return cmap, levels, norm
336def custom_beaufort_scale(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None):
337 """Get a custom colorbar for a cube in the Beaufort Scale.
339 Specific variable ranges can be separately set in user-supplied definition
340 for x- or y-axis limits, or indicate where automated range preferred.
342 Parameters
343 ----------
344 cube: Cube
345 Cube of variable with Beaufort Scale in name.
346 axis: "x", "y", optional
347 Select the levels for just this axis of a line plot. The min and max
348 can be set by xmin/xmax or ymin/ymax respectively. For variables where
349 setting a universal range is not desirable (e.g. temperature), users
350 can set ymin/ymax values to "auto" in the colorbar definitions file.
351 Where no additional xmin/xmax or ymin/ymax values are provided, the
352 axis bounds default to use the vmin/vmax values provided.
354 Returns
355 -------
356 cmap:
357 Matplotlib colormap.
358 levels:
359 List of levels to use for plotting. For continuous plots the min and max
360 should be taken as the range.
361 norm:
362 BoundaryNorm information.
363 """
364 if "difference" not in cube.long_name:
365 if axis:
366 levels = [0, 12]
367 return None, levels, None
368 else:
369 levels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
370 colors = [
371 "black",
372 (0, 0, 0.6),
373 "blue",
374 "cyan",
375 "green",
376 "yellow",
377 (1, 0.5, 0),
378 "red",
379 "pink",
380 "magenta",
381 "purple",
382 "maroon",
383 "white",
384 ]
385 cmap = mcolors.ListedColormap(colors)
386 norm = mcolors.BoundaryNorm(levels, cmap.N)
387 logger.info("change colormap for Beaufort Scale colorbar.")
388 return cmap, levels, norm
389 else:
390 if axis:
391 levels = [-4, 4]
392 return None, levels, None
393 else:
394 levels = [
395 -3.5,
396 -2.5,
397 -1.5,
398 -0.5,
399 0.5,
400 1.5,
401 2.5,
402 3.5,
403 ]
404 cmap = plt.get_cmap("bwr", 8)
405 norm = mcolors.BoundaryNorm(levels, cmap.N)
406 return cmap, levels, norm
409def custom_colormap_celsius(cube: iris.cube.Cube, cmap, levels, norm):
410 """Return altered colormap for temperature with change in units to Celsius.
412 If "Celsius" appears anywhere in the name of a cube this function will be called.
414 Parameters
415 ----------
416 cube: Cube
417 Cube of variable for which the colorbar information is desired.
418 cmap: Matplotlib colormap.
419 levels: List
420 List of levels to use for plotting. For continuous plots the min and max
421 should be taken as the range.
422 norm: BoundaryNorm.
424 Returns
425 -------
426 cmap: Matplotlib colormap.
427 levels: List
428 List of levels to use for plotting. For continuous plots the min and max
429 should be taken as the range.
430 norm: BoundaryNorm.
431 """
432 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
433 if any("temperature" in name for name in varnames) and "Celsius" == cube.units:
434 levels = np.array(levels)
435 levels -= 273
436 levels = levels.tolist()
437 return cmap, levels, norm
440def custom_colormap_probability(
441 cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None
442):
443 """Get a custom colorbar for a probability cube.
445 Specific variable ranges can be separately set in user-supplied definition
446 for x- or y-axis limits, or indicate where automated range preferred.
448 Parameters
449 ----------
450 cube: Cube
451 Cube of variable with probability in name.
452 axis: "x", "y", optional
453 Select the levels for just this axis of a line plot. The min and max
454 can be set by xmin/xmax or ymin/ymax respectively. For variables where
455 setting a universal range is not desirable (e.g. temperature), users
456 can set ymin/ymax values to "auto" in the colorbar definitions file.
457 Where no additional xmin/xmax or ymin/ymax values are provided, the
458 axis bounds default to use the vmin/vmax values provided.
460 Returns
461 -------
462 cmap:
463 Matplotlib colormap.
464 levels:
465 List of levels to use for plotting. For continuous plots the min and max
466 should be taken as the range.
467 norm:
468 BoundaryNorm information.
469 """
470 if axis:
471 levels = [0, 1]
472 return None, levels, None
473 else:
474 cmap = mcolors.ListedColormap(
475 [
476 "#FFFFFF",
477 "#636363",
478 "#e1dada",
479 "#B5CAFF",
480 "#8FB3FF",
481 "#7F97FF",
482 "#ABCF63",
483 "#E8F59E",
484 "#FFFA14",
485 "#FFD121",
486 "#FFA30A",
487 ]
488 )
489 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]
490 norm = mcolors.BoundaryNorm(levels, cmap.N)
491 return cmap, levels, norm
494def custom_colormap_precipitation(cube: iris.cube.Cube, cmap, levels, norm):
495 """Return a custom colormap for the current recipe."""
496 varnames_lower = [
497 n.lower() for n in (cube.long_name, cube.standard_name, cube.var_name) if n
498 ]
500 is_rainfall_var = any(
501 key in name
502 for name in varnames_lower
503 for key in (
504 "surface_microphysical",
505 "rainfall rate composite",
506 "nimrod5min",
507 "nimrod_5min",
508 "rain_accumulation",
509 "rain accumulation",
510 )
511 )
513 if is_rainfall_var:
514 logger.debug(
515 "Using custom precipitation colourmap due to varnames: %s", varnames_lower
516 )
517 levels = [0, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256]
518 colors = [
519 "w",
520 (0, 0, 0.6),
521 "b",
522 "c",
523 "g",
524 "y",
525 (1, 0.5, 0),
526 "r",
527 "pink",
528 "m",
529 "purple",
530 "maroon",
531 "gray",
532 ]
533 # Create a custom colormap
534 cmap = mcolors.ListedColormap(colors)
535 # Normalize the levels
536 norm = mcolors.BoundaryNorm(levels, cmap.N)
537 logger.info("Using custom rainfall colourmap.")
539 # Set any Nan values to be plotted a light grey.
540 cmap.set_bad("#dcdcdc")
542 return cmap, levels, norm
545def custom_colourmap_nimrod_weights(cube: iris.cube.Cube, cmap, levels, norm):
546 """Return a custom colourmap for the current recipe."""
547 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
548 if (
549 any("wts" in name for name in varnames)
550 and "difference" not in cube.long_name
551 and "mask" not in cube.long_name
552 ):
553 # Define the levels and colors. Remember the Nimrod weights vary over
554 # the range [0,13] and should be integer values. Optimum value is 13.
555 levels = [
556 -0.5,
557 0.5,
558 1.5,
559 2.5,
560 3.5,
561 4.5,
562 5.5,
563 6.5,
564 7.5,
565 8.5,
566 9.5,
567 10.5,
568 11.5,
569 12.5,
570 13.5,
571 ]
572 norm = mcolors.BoundaryNorm(levels, cmap.N)
573 colours = [
574 "#dcdcdc",
575 "#d10000",
576 "purple",
577 "#8f00d6",
578 "#ff9700",
579 "pink",
580 "#ffff00",
581 "#00007f",
582 "#6c9ccd",
583 "#aae8ff",
584 "#37a648",
585 "#8edc64",
586 "#c5ffc5",
587 "#ffffff",
588 ]
589 # Create a custom colormap.
590 cmap = mcolors.ListedColormap(colours)
591 # Normalize the levels.
592 norm = mcolors.BoundaryNorm(levels, cmap.N)
593 logger.info("Change colormap for Nimrod weights colorbar.")
594 return cmap, levels, norm
597def custom_colormap_aviation_colour_state(cube: iris.cube.Cube):
598 """Return custom colormap for aviation colour state.
600 If "aviation_colour_state" appears anywhere in the name of a cube
601 this function will be called.
603 Parameters
604 ----------
605 cube: Cube
606 Cube of variable for which the colorbar information is desired.
608 Returns
609 -------
610 cmap: Matplotlib colormap.
611 levels: List
612 List of levels to use for plotting. For continuous plots the min and max
613 should be taken as the range.
614 norm: BoundaryNorm.
615 """
616 levels = [-0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5]
617 colors = [
618 "#87ceeb",
619 "#ffffff",
620 "#8ced69",
621 "#ffff00",
622 "#ffd700",
623 "#ffa500",
624 "#fe3620",
625 ]
626 # Create a custom colormap
627 cmap = mcolors.ListedColormap(colors)
628 # Normalise the levels
629 norm = mcolors.BoundaryNorm(levels, cmap.N)
630 return cmap, levels, norm
633def custom_colormap_visibility_in_air(cube: iris.cube.Cube, cmap, levels, norm):
634 """Return a custom colormap for the current recipe."""
635 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
636 if (
637 any("visibility_in_air" in name for name in varnames)
638 and "difference" not in cube.long_name
639 and "mask" not in cube.long_name
640 ):
641 # Define the levels and colors (in km)
642 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]
643 norm = mcolors.BoundaryNorm(levels, cmap.N)
644 colours = [
645 "#8f00d6",
646 "#d10000",
647 "#ff9700",
648 "#ffff00",
649 "#00007f",
650 "#6c9ccd",
651 "#aae8ff",
652 "#37a648",
653 "#8edc64",
654 "#c5ffc5",
655 "#dcdcdc",
656 "#ffffff",
657 ]
658 # Create a custom colormap
659 cmap = mcolors.ListedColormap(colours)
660 # Normalize the levels
661 norm = mcolors.BoundaryNorm(levels, cmap.N)
662 logger.info("change colormap for visibility_in_air variable colorbar.")
663 return cmap, levels, norm
666def custom_colormap_scores(cube: iris.cube.Cube):
667 """Return altered colormap for statistical metrics.
669 Parameters
670 ----------
671 cube: Cube
672 Cube of variable for which the colorbar information is desired.
674 Returns
675 -------
676 cmap: Matplotlib colormap.
677 levels: List
678 List of levels to use for plotting. For continuous plots the min and max
679 should be taken as the range.
680 norm: BoundaryNorm.
681 """
682 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
683 cmap, levels, norm = None, None, None
684 if any("RMSE_" in name for name in varnames): 684 ↛ 686line 684 didn't jump to line 686 because the condition on line 684 was always true
685 cmap = plt.get_cmap("PuRd", 51)
686 return cmap, levels, norm
689def custom_colormap_feature_tracking(cube: iris.cube.Cube):
690 """Return altered colormap for feature tracking.
692 Parameters
693 ----------
694 cube: Cube
695 Cube of variable for which the colorbar information is desired.
697 Returns
698 -------
699 cmap: Matplotlib colormap.
700 levels: List
701 List of levels to use for plotting. For continuous plots the min and max
702 should be taken as the range.
703 norm: BoundaryNorm.
704 """
705 varnames = list(filter(None, [cube.long_name, cube.standard_name, cube.var_name]))
707 if any("feature_id" in name for name in varnames):
708 # Get max lifetime from cube attributes if available, otherwise use max of data
709 max_id = cube.attributes.get("max_value", np.ma.max(cube.data))
710 levels = None
711 cmap = plt.get_cmap("viridis")
712 norm = mcolors.Normalize(vmin=1, vmax=max_id, clip=False)
713 logger.info("change colormap for feature id variable colorbar.")
715 elif any("feature_lifetime" in name for name in varnames):
716 # Get max lifetime from cube attributes if available, otherwise use max of data
717 max_lifetime = cube.attributes.get("max_value", np.ma.max(cube.data))
718 levels = None
719 cmap = plt.get_cmap("YlGnBu")
720 norm = mcolors.Normalize(vmin=1, vmax=max_lifetime, clip=False)
721 logger.info("change colormap for feature lifetime variable colorbar.")
723 elif any("feature_init" in name for name in varnames): 723 ↛ 731line 723 didn't jump to line 731 because the condition on line 723 was always true
724 # Define the levels and colors
725 levels = np.array([0.5, 1])
726 cmap = plt.get_cmap("Blues")
727 norm = mcolors.BoundaryNorm(levels, cmap.N)
728 logger.info("change colormap for feature init variable colorbar.")
730 # Set all non-feature data to white.
731 cmap = cmap.with_extremes(under="white")
733 return cmap, levels, norm