Coverage for src/CSET/operators/_colormaps.py: 98%
243 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-04 08:32 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-04 08:32 +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 # Update to always plot observations as first item with dimgray color
98 if any("OBS" in name.upper() for name in model_names):
99 colors = list(DEFAULT_DISCRETE_COLORS).copy()
100 colors.insert(0, mcolors.to_rgb("dimgray"))
101 ob_name = next(name for name in model_names if "OBS" in name.upper())
102 model_names.remove(ob_name)
103 model_names.insert(0, ob_name)
104 else:
105 colors = DEFAULT_DISCRETE_COLORS
107 color_list = itertools.cycle(colors)
108 return {mname: color for mname, color in zip(model_names, color_list, strict=False)}
111def colorbar_map_levels(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None):
112 """Get an appropriate colorbar for the given cube.
114 For the given variable the appropriate colorbar is looked up from a
115 combination of the built-in CSET colorbar definitions, and any user supplied
116 definitions. As well as varying on variables, these definitions may also
117 exist for specific pressure levels to account for variables with
118 significantly different ranges at different heights. The colorbars also exist
119 for masks and mask differences for considering variable presence diagnostics.
120 Specific variable ranges can be separately set in user-supplied definition
121 for x- or y-axis limits, or indicate where automated range preferred.
123 Parameters
124 ----------
125 cube: Cube
126 Cube of variable for which the colorbar information is desired.
127 axis: "x", "y", optional
128 Select the levels for just this axis of a line plot. The min and max
129 can be set by xmin/xmax or ymin/ymax respectively. For variables where
130 setting a universal range is not desirable (e.g. temperature), users
131 can set ymin/ymax values to "auto" in the colorbar definitions file.
132 Where no additional xmin/xmax or ymin/ymax values are provided, the
133 axis bounds default to use the vmin/vmax values provided.
135 Returns
136 -------
137 cmap:
138 Matplotlib colormap.
139 levels:
140 List of levels to use for plotting. For continuous plots the min and max
141 should be taken as the range.
142 norm:
143 BoundaryNorm information.
144 """
145 # Grab the colorbar file from the recipe global metadata.
146 user_colorbar_file = get_recipe_metadata().get("style_file_path", None)
147 colorbar = load_colorbar_map(user_colorbar_file)
148 cmap = None
150 try:
151 # We assume that pressure is a scalar coordinate here.
152 pressure_level_raw = cube.coord("pressure").points[0]
153 # Ensure pressure_level is a string, as it is used as a JSON key.
154 pressure_level = str(int(pressure_level_raw))
155 except iris.exceptions.CoordinateNotFoundError:
156 pressure_level = None
158 # First try long name, then standard name, then var name. This order is used
159 # as long name is the one we correct between models, so it most likely to be
160 # consistent.
161 varnames = list(filter(None, [cube.long_name, cube.standard_name, cube.var_name]))
162 # Treat observation-labelled var names consistently with model var names.
163 varnames = [varname.replace("observed_", "") for varname in varnames]
164 for varname in varnames:
165 # Get the colormap for this variable.
166 try:
167 var_colorbar = colorbar[varname]
168 cmap = plt.get_cmap(colorbar[varname]["cmap"], 51)
169 varname_key = varname
170 break
171 except KeyError:
172 logger.debug("Cube name %s has no colorbar definition.", varname)
174 # Get colormap if it is a mask.
175 if any("mask_for_" in name for name in varnames):
176 cmap, levels, norm = custom_colormap_mask(cube, axis=axis)
177 return cmap, levels, norm
178 # If winds on Beaufort Scale use custom colorbar and levels
179 if any("Beaufort_Scale" in name for name in varnames):
180 cmap, levels, norm = custom_beaufort_scale(cube, axis=axis)
181 return cmap, levels, norm
182 # If probability is plotted use custom colorbar and levels
183 if any("probability_of_" in name for name in varnames):
184 cmap, levels, norm = custom_colormap_probability(cube, axis=axis)
185 return cmap, levels, norm
186 # If aviation colour state use custom colorbar and levels
187 if any("aviation_colour_state" in name for name in varnames):
188 cmap, levels, norm = custom_colormap_aviation_colour_state(cube)
189 return cmap, levels, norm
190 # If verification scores use custom colorbar
191 if any("RMSE_" in name for name in varnames):
192 cmap, levels, norm = custom_colormap_scores(cube)
193 return cmap, levels, norm
194 # If feature tracking use custom colorbar and levels
195 if any("feature_" in name for name in varnames): 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true
196 cmap, levels, norm = custom_colormap_feature_tracking(cube)
197 return cmap, levels, norm
199 # If no valid colormap has been defined, use defaults and return.
200 if not cmap:
201 logger.warning("No colorbar definition exists for %s.", cube.name())
202 cmap, levels, norm = mpl.colormaps["viridis"], None, None
203 return cmap, levels, norm
205 # Test if pressure-level specific settings are provided for cube.
206 if pressure_level:
207 try:
208 var_colorbar = colorbar[varname_key]["pressure_levels"][pressure_level]
209 except KeyError:
210 logger.debug(
211 "%s has no colorbar definition for pressure level %s.",
212 varname,
213 pressure_level,
214 )
216 # Check for availability of x-axis or y-axis user-specific overrides
217 # for setting level bounds for line plot types and return just levels.
218 # Line plots do not need a colormap, and just use the data range.
219 if axis:
220 if axis == "x":
221 try:
222 vmin, vmax = var_colorbar["xmin"], var_colorbar["xmax"]
223 except KeyError:
224 vmin, vmax = var_colorbar["min"], var_colorbar["max"]
225 if axis == "y":
226 try:
227 vmin, vmax = var_colorbar["ymin"], var_colorbar["ymax"]
228 except KeyError:
229 vmin, vmax = var_colorbar["min"], var_colorbar["max"]
230 # Check if user-specified auto-scaling for this variable
231 if vmin == "auto" or vmax == "auto":
232 levels = None
233 else:
234 levels = [vmin, vmax]
235 return None, levels, None
236 # Get and use the colorbar levels for this variable if spatial or histogram.
237 else:
238 try:
239 levels = var_colorbar["levels"]
240 # Use discrete bins when levels are specified, rather
241 # than a smooth range.
242 norm = mpl.colors.BoundaryNorm(levels, ncolors=cmap.N)
243 logger.debug("Using levels for %s colorbar.", varname)
244 logger.info("Using levels: %s", levels)
245 except KeyError:
246 # Get the range for this variable.
247 vmin, vmax = var_colorbar["min"], var_colorbar["max"]
248 logger.debug("Using min and max for %s colorbar.", varname)
249 # Calculate levels from range.
250 if vmin == "auto" or vmax == "auto":
251 levels = None
252 else:
253 levels = np.linspace(vmin, vmax, 101)
254 norm = None
256 # Overwrite cmap, levels and norm for specific variables that
257 # require custom colorbar_map as these can not be defined in the
258 # JSON file.
259 cmap, levels, norm = custom_colormap_precipitation(cube, cmap, levels, norm)
260 cmap, levels, norm = custom_colourmap_nimrod_weights(cube, cmap, levels, norm)
261 cmap, levels, norm = custom_colormap_visibility_in_air(cube, cmap, levels, norm)
262 cmap, levels, norm = custom_colormap_celsius(cube, cmap, levels, norm)
263 return cmap, levels, norm
266def custom_colormap_mask(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None):
267 """Get colormap for mask.
269 If "mask_for_" appears anywhere in the name of a cube this function will be called
270 regardless of the name of the variable to ensure a consistent plot.
272 Parameters
273 ----------
274 cube: Cube
275 Cube of variable for which the colorbar information is desired.
276 axis: "x", "y", optional
277 Select the levels for just this axis of a line plot. The min and max
278 can be set by xmin/xmax or ymin/ymax respectively. For variables where
279 setting a universal range is not desirable (e.g. temperature), users
280 can set ymin/ymax values to "auto" in the colorbar definitions file.
281 Where no additional xmin/xmax or ymin/ymax values are provided, the
282 axis bounds default to use the vmin/vmax values provided.
284 Returns
285 -------
286 cmap:
287 Matplotlib colormap.
288 levels:
289 List of levels to use for plotting. For continuous plots the min and max
290 should be taken as the range.
291 norm:
292 BoundaryNorm information.
293 """
294 if "difference" not in cube.long_name:
295 if axis:
296 levels = [0, 1]
297 # Complete settings based on levels.
298 return None, levels, None
299 else:
300 # Define the levels and colors.
301 levels = [0, 1, 2]
302 colors = ["white", "dodgerblue"]
303 # Create a custom color map.
304 cmap = mcolors.ListedColormap(colors)
305 # Normalize the levels.
306 norm = mcolors.BoundaryNorm(levels, cmap.N)
307 logger.debug("Colormap for %s.", cube.long_name)
308 return cmap, levels, norm
309 else:
310 if axis:
311 levels = [-1, 1]
312 return None, levels, None
313 else:
314 # Search for if mask difference, set to +/- 0.5 as values plotted <
315 # not <=.
316 levels = [-2, -0.5, 0.5, 2]
317 colors = ["goldenrod", "white", "teal"]
318 cmap = mcolors.ListedColormap(colors)
319 norm = mcolors.BoundaryNorm(levels, cmap.N)
320 logger.debug("Colormap for %s.", cube.long_name)
321 return cmap, levels, norm
324def custom_beaufort_scale(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None):
325 """Get a custom colorbar for a cube in the Beaufort Scale.
327 Specific variable ranges can be separately set in user-supplied definition
328 for x- or y-axis limits, or indicate where automated range preferred.
330 Parameters
331 ----------
332 cube: Cube
333 Cube of variable with Beaufort Scale in name.
334 axis: "x", "y", optional
335 Select the levels for just this axis of a line plot. The min and max
336 can be set by xmin/xmax or ymin/ymax respectively. For variables where
337 setting a universal range is not desirable (e.g. temperature), users
338 can set ymin/ymax values to "auto" in the colorbar definitions file.
339 Where no additional xmin/xmax or ymin/ymax values are provided, the
340 axis bounds default to use the vmin/vmax values provided.
342 Returns
343 -------
344 cmap:
345 Matplotlib colormap.
346 levels:
347 List of levels to use for plotting. For continuous plots the min and max
348 should be taken as the range.
349 norm:
350 BoundaryNorm information.
351 """
352 if "difference" not in cube.long_name:
353 if axis:
354 levels = [0, 12]
355 return None, levels, None
356 else:
357 levels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
358 colors = [
359 "black",
360 (0, 0, 0.6),
361 "blue",
362 "cyan",
363 "green",
364 "yellow",
365 (1, 0.5, 0),
366 "red",
367 "pink",
368 "magenta",
369 "purple",
370 "maroon",
371 "white",
372 ]
373 cmap = mcolors.ListedColormap(colors)
374 norm = mcolors.BoundaryNorm(levels, cmap.N)
375 logger.info("change colormap for Beaufort Scale colorbar.")
376 return cmap, levels, norm
377 else:
378 if axis:
379 levels = [-4, 4]
380 return None, levels, None
381 else:
382 levels = [
383 -3.5,
384 -2.5,
385 -1.5,
386 -0.5,
387 0.5,
388 1.5,
389 2.5,
390 3.5,
391 ]
392 cmap = plt.get_cmap("bwr", 8)
393 norm = mcolors.BoundaryNorm(levels, cmap.N)
394 return cmap, levels, norm
397def custom_colormap_celsius(cube: iris.cube.Cube, cmap, levels, norm):
398 """Return altered colormap for temperature with change in units to Celsius.
400 If "Celsius" appears anywhere in the name of a cube this function will be called.
402 Parameters
403 ----------
404 cube: Cube
405 Cube of variable for which the colorbar information is desired.
406 cmap: Matplotlib colormap.
407 levels: List
408 List of levels to use for plotting. For continuous plots the min and max
409 should be taken as the range.
410 norm: BoundaryNorm.
412 Returns
413 -------
414 cmap: Matplotlib colormap.
415 levels: List
416 List of levels to use for plotting. For continuous plots the min and max
417 should be taken as the range.
418 norm: BoundaryNorm.
419 """
420 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
421 if any("temperature" in name for name in varnames) and "Celsius" == cube.units:
422 levels = np.array(levels)
423 levels -= 273
424 levels = levels.tolist()
425 return cmap, levels, norm
428def custom_colormap_probability(
429 cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None
430):
431 """Get a custom colorbar for a probability cube.
433 Specific variable ranges can be separately set in user-supplied definition
434 for x- or y-axis limits, or indicate where automated range preferred.
436 Parameters
437 ----------
438 cube: Cube
439 Cube of variable with probability in name.
440 axis: "x", "y", optional
441 Select the levels for just this axis of a line plot. The min and max
442 can be set by xmin/xmax or ymin/ymax respectively. For variables where
443 setting a universal range is not desirable (e.g. temperature), users
444 can set ymin/ymax values to "auto" in the colorbar definitions file.
445 Where no additional xmin/xmax or ymin/ymax values are provided, the
446 axis bounds default to use the vmin/vmax values provided.
448 Returns
449 -------
450 cmap:
451 Matplotlib colormap.
452 levels:
453 List of levels to use for plotting. For continuous plots the min and max
454 should be taken as the range.
455 norm:
456 BoundaryNorm information.
457 """
458 if axis:
459 levels = [0, 1]
460 return None, levels, None
461 else:
462 cmap = mcolors.ListedColormap(
463 [
464 "#FFFFFF",
465 "#636363",
466 "#e1dada",
467 "#B5CAFF",
468 "#8FB3FF",
469 "#7F97FF",
470 "#ABCF63",
471 "#E8F59E",
472 "#FFFA14",
473 "#FFD121",
474 "#FFA30A",
475 ]
476 )
477 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]
478 norm = mcolors.BoundaryNorm(levels, cmap.N)
479 return cmap, levels, norm
482def custom_colormap_precipitation(cube: iris.cube.Cube, cmap, levels, norm):
483 """Return a custom colormap for the current recipe."""
484 varnames_lower = [
485 n.lower() for n in (cube.long_name, cube.standard_name, cube.var_name) if n
486 ]
488 is_rainfall_var = any(
489 key in name
490 for name in varnames_lower
491 for key in (
492 "surface_microphysical",
493 "rainfall rate composite",
494 "nimrod5min",
495 "nimrod_5min",
496 "rain_accumulation",
497 "rain accumulation",
498 )
499 )
501 if is_rainfall_var:
502 logger.debug(
503 "Using custom precipitation colourmap due to varnames: %s", varnames_lower
504 )
505 levels = [0, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256]
506 colors = [
507 "w",
508 (0, 0, 0.6),
509 "b",
510 "c",
511 "g",
512 "y",
513 (1, 0.5, 0),
514 "r",
515 "pink",
516 "m",
517 "purple",
518 "maroon",
519 "gray",
520 ]
521 # Create a custom colormap
522 cmap = mcolors.ListedColormap(colors)
523 # Normalize the levels
524 norm = mcolors.BoundaryNorm(levels, cmap.N)
525 logger.info("Using custom rainfall colourmap.")
526 return cmap, levels, norm
529def custom_colourmap_nimrod_weights(cube: iris.cube.Cube, cmap, levels, norm):
530 """Return a custom colourmap for the current recipe."""
531 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
532 if (
533 any("wts" in name for name in varnames)
534 and "difference" not in cube.long_name
535 and "mask" not in cube.long_name
536 ):
537 # Define the levels and colors. Remember the Nimrod weights vary over
538 # the range [0,13] and should be integer values. Optimum value is 13.
539 levels = [
540 -0.5,
541 0.5,
542 1.5,
543 2.5,
544 3.5,
545 4.5,
546 5.5,
547 6.5,
548 7.5,
549 8.5,
550 9.5,
551 10.5,
552 11.5,
553 12.5,
554 13.5,
555 ]
556 norm = mcolors.BoundaryNorm(levels, cmap.N)
557 colours = [
558 "#d10000",
559 "purple",
560 "#8f00d6",
561 "#ff9700",
562 "pink",
563 "#ffff00",
564 "#00007f",
565 "#6c9ccd",
566 "#aae8ff",
567 "#37a648",
568 "#8edc64",
569 "#c5ffc5",
570 "#dcdcdc",
571 "#ffffff",
572 ]
573 # Create a custom colormap.
574 cmap = mcolors.ListedColormap(colours)
575 # Normalize the levels.
576 norm = mcolors.BoundaryNorm(levels, cmap.N)
577 logger.info("Change colormap for Nimrod weights colorbar.")
578 return cmap, levels, norm
581def custom_colormap_aviation_colour_state(cube: iris.cube.Cube):
582 """Return custom colormap for aviation colour state.
584 If "aviation_colour_state" appears anywhere in the name of a cube
585 this function will be called.
587 Parameters
588 ----------
589 cube: Cube
590 Cube of variable for which the colorbar information is desired.
592 Returns
593 -------
594 cmap: Matplotlib colormap.
595 levels: List
596 List of levels to use for plotting. For continuous plots the min and max
597 should be taken as the range.
598 norm: BoundaryNorm.
599 """
600 levels = [-0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5]
601 colors = [
602 "#87ceeb",
603 "#ffffff",
604 "#8ced69",
605 "#ffff00",
606 "#ffd700",
607 "#ffa500",
608 "#fe3620",
609 ]
610 # Create a custom colormap
611 cmap = mcolors.ListedColormap(colors)
612 # Normalise the levels
613 norm = mcolors.BoundaryNorm(levels, cmap.N)
614 return cmap, levels, norm
617def custom_colormap_visibility_in_air(cube: iris.cube.Cube, cmap, levels, norm):
618 """Return a custom colormap for the current recipe."""
619 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
620 if (
621 any("visibility_in_air" in name for name in varnames)
622 and "difference" not in cube.long_name
623 and "mask" not in cube.long_name
624 ):
625 # Define the levels and colors (in km)
626 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]
627 norm = mcolors.BoundaryNorm(levels, cmap.N)
628 colours = [
629 "#8f00d6",
630 "#d10000",
631 "#ff9700",
632 "#ffff00",
633 "#00007f",
634 "#6c9ccd",
635 "#aae8ff",
636 "#37a648",
637 "#8edc64",
638 "#c5ffc5",
639 "#dcdcdc",
640 "#ffffff",
641 ]
642 # Create a custom colormap
643 cmap = mcolors.ListedColormap(colours)
644 # Normalize the levels
645 norm = mcolors.BoundaryNorm(levels, cmap.N)
646 logger.info("change colormap for visibility_in_air variable colorbar.")
647 return cmap, levels, norm
650def custom_colormap_scores(cube: iris.cube.Cube):
651 """Return altered colormap for statistical metrics.
653 Parameters
654 ----------
655 cube: Cube
656 Cube of variable for which the colorbar information is desired.
658 Returns
659 -------
660 cmap: Matplotlib colormap.
661 levels: List
662 List of levels to use for plotting. For continuous plots the min and max
663 should be taken as the range.
664 norm: BoundaryNorm.
665 """
666 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name])
667 cmap, levels, norm = None, None, None
668 if any("RMSE_" in name for name in varnames): 668 ↛ 670line 668 didn't jump to line 670 because the condition on line 668 was always true
669 cmap = plt.get_cmap("PuRd", 51)
670 return cmap, levels, norm
673def custom_colormap_feature_tracking(cube: iris.cube.Cube):
674 """Return altered colormap for feature tracking.
676 Parameters
677 ----------
678 cube: Cube
679 Cube of variable for which the colorbar information is desired.
681 Returns
682 -------
683 cmap: Matplotlib colormap.
684 levels: List
685 List of levels to use for plotting. For continuous plots the min and max
686 should be taken as the range.
687 norm: BoundaryNorm.
688 """
689 varnames = list(filter(None, [cube.long_name, cube.standard_name, cube.var_name]))
691 if any("feature_id" in name for name in varnames):
692 # Get max lifetime from cube attributes if available, otherwise use max of data
693 max_id = cube.attributes.get("max_value", np.ma.max(cube.data))
694 levels = None
695 cmap = plt.get_cmap("viridis")
696 norm = mcolors.Normalize(vmin=1, vmax=max_id, clip=False)
697 logger.info("change colormap for feature id variable colorbar.")
699 elif any("feature_lifetime" in name for name in varnames):
700 # Get max lifetime from cube attributes if available, otherwise use max of data
701 max_lifetime = cube.attributes.get("max_value", np.ma.max(cube.data))
702 levels = None
703 cmap = plt.get_cmap("YlGnBu")
704 norm = mcolors.Normalize(vmin=1, vmax=max_lifetime, clip=False)
705 logger.info("change colormap for feature lifetime variable colorbar.")
707 elif any("feature_init" in name for name in varnames): 707 ↛ 715line 707 didn't jump to line 715 because the condition on line 707 was always true
708 # Define the levels and colors
709 levels = np.array([0.5, 1])
710 cmap = plt.get_cmap("Blues")
711 norm = mcolors.BoundaryNorm(levels, cmap.N)
712 logger.info("change colormap for feature init variable colorbar.")
714 # Set all non-feature data to white.
715 cmap = cmap.with_extremes(under="white")
717 return cmap, levels, norm