Coverage for src/CSET/operators/_colormaps.py: 98%
249 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-07 15:12 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-07 15:12 +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.")
538 return cmap, levels, norm
541def custom_colourmap_nimrod_weights(cube: iris.cube.Cube, cmap, levels, norm):
542 """Return a custom colourmap for the current recipe."""
543 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
544 if (
545 any("wts" in name for name in varnames)
546 and "difference" not in cube.long_name
547 and "mask" not in cube.long_name
548 ):
549 # Define the levels and colors. Remember the Nimrod weights vary over
550 # the range [0,13] and should be integer values. Optimum value is 13.
551 levels = [
552 -0.5,
553 0.5,
554 1.5,
555 2.5,
556 3.5,
557 4.5,
558 5.5,
559 6.5,
560 7.5,
561 8.5,
562 9.5,
563 10.5,
564 11.5,
565 12.5,
566 13.5,
567 ]
568 norm = mcolors.BoundaryNorm(levels, cmap.N)
569 colours = [
570 "#d10000",
571 "purple",
572 "#8f00d6",
573 "#ff9700",
574 "pink",
575 "#ffff00",
576 "#00007f",
577 "#6c9ccd",
578 "#aae8ff",
579 "#37a648",
580 "#8edc64",
581 "#c5ffc5",
582 "#dcdcdc",
583 "#ffffff",
584 ]
585 # Create a custom colormap.
586 cmap = mcolors.ListedColormap(colours)
587 # Normalize the levels.
588 norm = mcolors.BoundaryNorm(levels, cmap.N)
589 logger.info("Change colormap for Nimrod weights colorbar.")
590 return cmap, levels, norm
593def custom_colormap_aviation_colour_state(cube: iris.cube.Cube):
594 """Return custom colormap for aviation colour state.
596 If "aviation_colour_state" appears anywhere in the name of a cube
597 this function will be called.
599 Parameters
600 ----------
601 cube: Cube
602 Cube of variable for which the colorbar information is desired.
604 Returns
605 -------
606 cmap: Matplotlib colormap.
607 levels: List
608 List of levels to use for plotting. For continuous plots the min and max
609 should be taken as the range.
610 norm: BoundaryNorm.
611 """
612 levels = [-0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5]
613 colors = [
614 "#87ceeb",
615 "#ffffff",
616 "#8ced69",
617 "#ffff00",
618 "#ffd700",
619 "#ffa500",
620 "#fe3620",
621 ]
622 # Create a custom colormap
623 cmap = mcolors.ListedColormap(colors)
624 # Normalise the levels
625 norm = mcolors.BoundaryNorm(levels, cmap.N)
626 return cmap, levels, norm
629def custom_colormap_visibility_in_air(cube: iris.cube.Cube, cmap, levels, norm):
630 """Return a custom colormap for the current recipe."""
631 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
632 if (
633 any("visibility_in_air" in name for name in varnames)
634 and "difference" not in cube.long_name
635 and "mask" not in cube.long_name
636 ):
637 # Define the levels and colors (in km)
638 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]
639 norm = mcolors.BoundaryNorm(levels, cmap.N)
640 colours = [
641 "#8f00d6",
642 "#d10000",
643 "#ff9700",
644 "#ffff00",
645 "#00007f",
646 "#6c9ccd",
647 "#aae8ff",
648 "#37a648",
649 "#8edc64",
650 "#c5ffc5",
651 "#dcdcdc",
652 "#ffffff",
653 ]
654 # Create a custom colormap
655 cmap = mcolors.ListedColormap(colours)
656 # Normalize the levels
657 norm = mcolors.BoundaryNorm(levels, cmap.N)
658 logger.info("change colormap for visibility_in_air variable colorbar.")
659 return cmap, levels, norm
662def custom_colormap_scores(cube: iris.cube.Cube):
663 """Return altered colormap for statistical metrics.
665 Parameters
666 ----------
667 cube: Cube
668 Cube of variable for which the colorbar information is desired.
670 Returns
671 -------
672 cmap: Matplotlib colormap.
673 levels: List
674 List of levels to use for plotting. For continuous plots the min and max
675 should be taken as the range.
676 norm: BoundaryNorm.
677 """
678 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
679 cmap, levels, norm = None, None, None
680 if any("RMSE_" in name for name in varnames): 680 ↛ 682line 680 didn't jump to line 682 because the condition on line 680 was always true
681 cmap = plt.get_cmap("PuRd", 51)
682 return cmap, levels, norm
685def custom_colormap_feature_tracking(cube: iris.cube.Cube):
686 """Return altered colormap for feature tracking.
688 Parameters
689 ----------
690 cube: Cube
691 Cube of variable for which the colorbar information is desired.
693 Returns
694 -------
695 cmap: Matplotlib colormap.
696 levels: List
697 List of levels to use for plotting. For continuous plots the min and max
698 should be taken as the range.
699 norm: BoundaryNorm.
700 """
701 varnames = list(filter(None, [cube.long_name, cube.standard_name, cube.var_name]))
703 if any("feature_id" in name for name in varnames):
704 # Get max lifetime from cube attributes if available, otherwise use max of data
705 max_id = cube.attributes.get("max_value", np.ma.max(cube.data))
706 levels = None
707 cmap = plt.get_cmap("viridis")
708 norm = mcolors.Normalize(vmin=1, vmax=max_id, clip=False)
709 logger.info("change colormap for feature id variable colorbar.")
711 elif any("feature_lifetime" in name for name in varnames):
712 # Get max lifetime from cube attributes if available, otherwise use max of data
713 max_lifetime = cube.attributes.get("max_value", np.ma.max(cube.data))
714 levels = None
715 cmap = plt.get_cmap("YlGnBu")
716 norm = mcolors.Normalize(vmin=1, vmax=max_lifetime, clip=False)
717 logger.info("change colormap for feature lifetime variable colorbar.")
719 elif any("feature_init" in name for name in varnames): 719 ↛ 727line 719 didn't jump to line 727 because the condition on line 719 was always true
720 # Define the levels and colors
721 levels = np.array([0.5, 1])
722 cmap = plt.get_cmap("Blues")
723 norm = mcolors.BoundaryNorm(levels, cmap.N)
724 logger.info("change colormap for feature init variable colorbar.")
726 # Set all non-feature data to white.
727 cmap = cmap.with_extremes(under="white")
729 return cmap, levels, norm