Skip to content

Models

ColorModel

Bases: Enum

Enumeration for different color models.

Attributes:

Name Type Description
rgba

Red, green, blue, alpha.

rgb

Red, green, blue.

hsv

Hue, saturation, value.

hex

Hexcode.

Source code in tastymap/models.py
class ColorModel(Enum):
    """Enumeration for different color models.

    Attributes:
        rgba: Red, green, blue, alpha.
        rgb: Red, green, blue.
        hsv: Hue, saturation, value.
        hex: Hexcode.
    """

    RGBA = "rgba"
    RGB = "rgb"
    HSV = "hsv"
    HEX = "hex"

HoloViewsTastyBar

Bases: TastyBar

Source code in tastymap/models.py
class HoloViewsTastyBar(TastyBar):
    def __init__(
        self,
        tmap: TastyMap,
        bounds: slice | Sequence[float],
        labels: list[str] | None = None,
        uniform_spacing: bool = True,
    ):
        """Initializes a HoloViewsTastyBar instance.

        Args:
            tmap: A TastyMap instance.
            bounds: Bounds for the colorbar.
            labels: Labels for the colorbar. Defaults to None.
            uniform_spacing: Whether to use uniform spacing for the colorbar.
                Defaults to True.
        """
        super().__init__(tmap, bounds, labels, uniform_spacing)

        from bokeh import models  # type: ignore

        self._models = models
        self.palette = self.tmap.to_model("hex").tolist()

        self.factors = None
        self.major_label_overrides = None

        num_colors = len(self.tmap)
        is_slice = isinstance(self.bounds, slice)
        if is_slice:
            vmin = self.bounds.start  # type: ignore
            vmax = self.bounds.stop  # type: ignore
            step = self.bounds.step  #  type: ignore
            if step is None:
                num_ticks = min(num_colors - 1, 11)
                ticks = np.linspace(vmin, vmax, num_ticks)
            else:
                ticks = np.arange(vmin, vmax + step, step)
        else:
            ticks = np.array(self.bounds)
            num_ticks = len(ticks)
        self.ticks = ticks.tolist()

        num_labels = len(labels) if labels else 0
        if uniform_spacing:
            if labels is None:
                self.factors = [
                    f"{self.ticks[i]} - {self.ticks[i + 1]}"
                    for i in range(len(self.ticks) - 1)
                ]
            else:
                self.factors = labels
                if not num_labels == num_ticks - 1:
                    raise ValueError(
                        f"Number of labels must be one less than the number of ticks; "
                        f"received {num_labels} labels and {num_ticks} ticks."
                    )
        elif not uniform_spacing and labels:
            if not num_labels == num_ticks:
                raise ValueError(
                    f"Number of labels must be equal to the number of ticks; "
                    f"received {num_labels} labels and {num_ticks} ticks."
                )
            self.major_label_overrides = dict(zip(self.ticks, labels))

    def _hook(self, hv_plot, _):
        plot = hv_plot.handles["plot"]
        mapper = self._models.CategoricalColorMapper(
            palette=self.palette,
            factors=self.factors,
        )
        color_bar = self._models.ColorBar(color_mapper=mapper)
        plot.right[0] = color_bar

    @property
    def opts_settings(self):
        """Keyword arguments for opts."""
        opts_kwargs = dict(
            cmap=self.palette,
            color_levels=self.ticks,
            clim=(self.ticks[0], self.ticks[-1]),
            colorbar=True,
        )
        colorbar_opts = dict(ticker=self._models.FixedTicker(ticks=self.ticks))
        if self.uniform_spacing:
            opts_kwargs["hooks"] = [self._hook]
        elif self.major_label_overrides:
            colorbar_opts["major_label_overrides"] = self.major_label_overrides

        if colorbar_opts:
            opts_kwargs["colorbar_opts"] = colorbar_opts
        return opts_kwargs

    def add_to(self, plot):
        """Adds a colorbar to a plot.

        Args:
            plot: A HoloViews plot.
        """
        return plot.opts(**self.opts_settings)

opts_settings property

Keyword arguments for opts.

__init__(tmap, bounds, labels=None, uniform_spacing=True)

Initializes a HoloViewsTastyBar instance.

Parameters:

Name Type Description Default
tmap TastyMap

A TastyMap instance.

required
bounds slice | Sequence[float]

Bounds for the colorbar.

required
labels list[str] | None

Labels for the colorbar. Defaults to None.

None
uniform_spacing bool

Whether to use uniform spacing for the colorbar. Defaults to True.

True
Source code in tastymap/models.py
def __init__(
    self,
    tmap: TastyMap,
    bounds: slice | Sequence[float],
    labels: list[str] | None = None,
    uniform_spacing: bool = True,
):
    """Initializes a HoloViewsTastyBar instance.

    Args:
        tmap: A TastyMap instance.
        bounds: Bounds for the colorbar.
        labels: Labels for the colorbar. Defaults to None.
        uniform_spacing: Whether to use uniform spacing for the colorbar.
            Defaults to True.
    """
    super().__init__(tmap, bounds, labels, uniform_spacing)

    from bokeh import models  # type: ignore

    self._models = models
    self.palette = self.tmap.to_model("hex").tolist()

    self.factors = None
    self.major_label_overrides = None

    num_colors = len(self.tmap)
    is_slice = isinstance(self.bounds, slice)
    if is_slice:
        vmin = self.bounds.start  # type: ignore
        vmax = self.bounds.stop  # type: ignore
        step = self.bounds.step  #  type: ignore
        if step is None:
            num_ticks = min(num_colors - 1, 11)
            ticks = np.linspace(vmin, vmax, num_ticks)
        else:
            ticks = np.arange(vmin, vmax + step, step)
    else:
        ticks = np.array(self.bounds)
        num_ticks = len(ticks)
    self.ticks = ticks.tolist()

    num_labels = len(labels) if labels else 0
    if uniform_spacing:
        if labels is None:
            self.factors = [
                f"{self.ticks[i]} - {self.ticks[i + 1]}"
                for i in range(len(self.ticks) - 1)
            ]
        else:
            self.factors = labels
            if not num_labels == num_ticks - 1:
                raise ValueError(
                    f"Number of labels must be one less than the number of ticks; "
                    f"received {num_labels} labels and {num_ticks} ticks."
                )
    elif not uniform_spacing and labels:
        if not num_labels == num_ticks:
            raise ValueError(
                f"Number of labels must be equal to the number of ticks; "
                f"received {num_labels} labels and {num_ticks} ticks."
            )
        self.major_label_overrides = dict(zip(self.ticks, labels))

add_to(plot)

Adds a colorbar to a plot.

Parameters:

Name Type Description Default
plot

A HoloViews plot.

required
Source code in tastymap/models.py
def add_to(self, plot):
    """Adds a colorbar to a plot.

    Args:
        plot: A HoloViews plot.
    """
    return plot.opts(**self.opts_settings)

MatplotlibTastyBar

Bases: TastyBar

Source code in tastymap/models.py
class MatplotlibTastyBar(TastyBar):
    def __init__(
        self,
        tmap: TastyMap,
        bounds: slice | Sequence[float],
        labels: list[str] | None = None,
        uniform_spacing: bool = True,
        center: bool | None = None,
        extend: Literal["both", "neither", "min", "max"] = "both",
        clip: bool | None = None,
        **colorbar_kwargs: dict[str, Any],
    ):
        """Initializes a MatplotlibTastyBar instance.

        Args:
            tmap: A TastyMap instance.
            bounds: Bounds for the colorbar.
            labels: Labels for the colorbar. Defaults to None.
            uniform_spacing: Whether to use uniform spacing for the colorbar.
                Defaults to True.
            center: Whether to center the colorbar. Defaults to None.
            extend: Whether to extend the colorbar. Defaults to "both".
            clip: Whether to clip the colorbar. Defaults to None.
            **colorbar_kwargs: Keyword arguments for the colorbar.
        """
        super().__init__(tmap, bounds, labels, uniform_spacing)
        self.extend = extend
        self.clip = clip
        self.colorbar_kwargs = colorbar_kwargs
        self.spacing = "uniform" if uniform_spacing else "proportional"

        num_colors = len(self.tmap)
        is_slice = isinstance(self.bounds, slice)
        if is_slice:
            vmin = self.bounds.start  # type: ignore
            vmax = self.bounds.stop  # type: ignore
            step = self.bounds.step  #  type: ignore
            if step is None:
                num_ticks = min(num_colors - 1, 11)
                ticks = np.linspace(vmin, vmax, num_ticks)
            else:
                ticks = np.arange(vmin, vmax + step, step)
        else:
            ticks = np.array(self.bounds)
            ticks.sort()
            vmin, vmax = ticks[0], ticks[-1]

        if center is None and not is_slice:
            center = False

        if clip is None:
            clip = self.extend == "neither"

        norm = None
        if center is None:
            norm = Normalize(vmin=vmin, vmax=vmax, clip=clip)
            if is_slice:
                ticks = None  # let matplotlib decide
        elif center:
            norm_bins = ticks + 0.5
            norm_bins = np.insert(norm_bins, 0, norm_bins[0] - 1)
            norm = BoundaryNorm(norm_bins, num_colors, clip=clip, extend=self.extend)
            if labels is None:
                labels = ticks.copy()
            ticks = norm_bins[:-1] + np.diff(norm_bins) / 2
        else:
            norm = BoundaryNorm(ticks, num_colors, clip=clip, extend=self.extend)

        format = None
        if labels is not None:
            format = FuncFormatter(
                lambda _, index: labels[index]  # type: ignore
                if index < len(labels)  # type: ignore
                else ""
            )

        self.norm = norm
        self.ticks = ticks
        self.format = format

    @property
    def plot_settings(self):
        """Keyword arguments for the plot."""
        return dict(cmap=self.tmap.cmap, norm=self.norm)

    @property
    def colorbar_settings(self):
        """Keyword arguments for the colorbar."""
        colorbar_kwargs = dict(
            cmap=self.tmap.cmap,
            norm=self.norm,
            ticks=self.ticks,
            format=self.format,
            spacing=self.spacing,
        )
        colorbar_kwargs.update(self.colorbar_kwargs)
        return colorbar_kwargs

    def add_to(self, plot: ScalarMappable) -> ScalarMappable:
        """Adds a colorbar to a plot.

        Args:
            plot: A matplotlib ax.
        """
        plot_settings = self.plot_settings
        plot.cmap = plot_settings["cmap"]
        plot.norm = plot_settings["norm"]
        plt.colorbar(plot, **self.colorbar_settings)
        return plot

colorbar_settings property

Keyword arguments for the colorbar.

plot_settings property

Keyword arguments for the plot.

__init__(tmap, bounds, labels=None, uniform_spacing=True, center=None, extend='both', clip=None, **colorbar_kwargs)

Initializes a MatplotlibTastyBar instance.

Parameters:

Name Type Description Default
tmap TastyMap

A TastyMap instance.

required
bounds slice | Sequence[float]

Bounds for the colorbar.

required
labels list[str] | None

Labels for the colorbar. Defaults to None.

None
uniform_spacing bool

Whether to use uniform spacing for the colorbar. Defaults to True.

True
center bool | None

Whether to center the colorbar. Defaults to None.

None
extend Literal['both', 'neither', 'min', 'max']

Whether to extend the colorbar. Defaults to "both".

'both'
clip bool | None

Whether to clip the colorbar. Defaults to None.

None
**colorbar_kwargs dict[str, Any]

Keyword arguments for the colorbar.

{}
Source code in tastymap/models.py
def __init__(
    self,
    tmap: TastyMap,
    bounds: slice | Sequence[float],
    labels: list[str] | None = None,
    uniform_spacing: bool = True,
    center: bool | None = None,
    extend: Literal["both", "neither", "min", "max"] = "both",
    clip: bool | None = None,
    **colorbar_kwargs: dict[str, Any],
):
    """Initializes a MatplotlibTastyBar instance.

    Args:
        tmap: A TastyMap instance.
        bounds: Bounds for the colorbar.
        labels: Labels for the colorbar. Defaults to None.
        uniform_spacing: Whether to use uniform spacing for the colorbar.
            Defaults to True.
        center: Whether to center the colorbar. Defaults to None.
        extend: Whether to extend the colorbar. Defaults to "both".
        clip: Whether to clip the colorbar. Defaults to None.
        **colorbar_kwargs: Keyword arguments for the colorbar.
    """
    super().__init__(tmap, bounds, labels, uniform_spacing)
    self.extend = extend
    self.clip = clip
    self.colorbar_kwargs = colorbar_kwargs
    self.spacing = "uniform" if uniform_spacing else "proportional"

    num_colors = len(self.tmap)
    is_slice = isinstance(self.bounds, slice)
    if is_slice:
        vmin = self.bounds.start  # type: ignore
        vmax = self.bounds.stop  # type: ignore
        step = self.bounds.step  #  type: ignore
        if step is None:
            num_ticks = min(num_colors - 1, 11)
            ticks = np.linspace(vmin, vmax, num_ticks)
        else:
            ticks = np.arange(vmin, vmax + step, step)
    else:
        ticks = np.array(self.bounds)
        ticks.sort()
        vmin, vmax = ticks[0], ticks[-1]

    if center is None and not is_slice:
        center = False

    if clip is None:
        clip = self.extend == "neither"

    norm = None
    if center is None:
        norm = Normalize(vmin=vmin, vmax=vmax, clip=clip)
        if is_slice:
            ticks = None  # let matplotlib decide
    elif center:
        norm_bins = ticks + 0.5
        norm_bins = np.insert(norm_bins, 0, norm_bins[0] - 1)
        norm = BoundaryNorm(norm_bins, num_colors, clip=clip, extend=self.extend)
        if labels is None:
            labels = ticks.copy()
        ticks = norm_bins[:-1] + np.diff(norm_bins) / 2
    else:
        norm = BoundaryNorm(ticks, num_colors, clip=clip, extend=self.extend)

    format = None
    if labels is not None:
        format = FuncFormatter(
            lambda _, index: labels[index]  # type: ignore
            if index < len(labels)  # type: ignore
            else ""
        )

    self.norm = norm
    self.ticks = ticks
    self.format = format

add_to(plot)

Adds a colorbar to a plot.

Parameters:

Name Type Description Default
plot ScalarMappable

A matplotlib ax.

required
Source code in tastymap/models.py
def add_to(self, plot: ScalarMappable) -> ScalarMappable:
    """Adds a colorbar to a plot.

    Args:
        plot: A matplotlib ax.
    """
    plot_settings = self.plot_settings
    plot.cmap = plot_settings["cmap"]
    plot.norm = plot_settings["norm"]
    plt.colorbar(plot, **self.colorbar_settings)
    return plot

TastyBar

Bases: ABC

Source code in tastymap/models.py
class TastyBar(ABC):
    def __init__(
        self,
        tmap: TastyMap,
        bounds: slice | Sequence[float],
        labels: list[str] | None = None,
        uniform_spacing: bool = True,
    ):
        self.tmap = tmap
        self.bounds = bounds
        self.labels = labels
        self.uniform_spacing = uniform_spacing

    @abstractmethod
    def add_to(self, plot: Any):
        """Adds a colorbar to a plot.

        Args:
            plot: A plot.
        """

add_to(plot) abstractmethod

Adds a colorbar to a plot.

Parameters:

Name Type Description Default
plot Any

A plot.

required
Source code in tastymap/models.py
@abstractmethod
def add_to(self, plot: Any):
    """Adds a colorbar to a plot.

    Args:
        plot: A plot.
    """

TastyMap

A class to represent and manipulate colormaps in a tasty manner.

Attributes:

Name Type Description
cmap Colormap

The colormap object.

cmap_array Colormap

RGB array representation of the colormap.

Source code in tastymap/models.py
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
class TastyMap:
    """A class to represent and manipulate colormaps in a tasty manner.

    Attributes:
        cmap: The colormap object.
        cmap_array: RGB array representation of the colormap.
    """

    def __init__(
        self,
        cmap: Colormap,
        name: str | None = None,
    ):
        """Initializes a TastyMap instance.

        Args:
            cmap: The colormap to be used.
            name: Name of the colormap. Defaults to the name of the provided colormap.
        """
        if not isinstance(cmap, LinearSegmentedColormap):
            raise TypeError(
                f"Expected LinearSegmentedColormap; received {type(cmap)!r}."
            )

        cmap = cmap.copy()
        cmap.name = name or cmap.name
        self.cmap: Colormap = cmap
        self._cmap_array = cmap_to_array(cmap)

    @classmethod
    def from_str(cls, string: str) -> TastyMap:
        """Creates a TastyMap instance from a string name.

        Args:
            string: Name of the colormap.

        Returns:
            TastyMap: A new TastyMap instance.
        """
        cmap = get_cmap(string)  # type: ignore
        cmap_array = cmap_to_array(cmap)

        new_name = string
        cmap = LinearSegmentedColormap.from_list(
            new_name, cmap_array, N=len(cmap_array)
        )
        return TastyMap(cmap, name=new_name)

    @classmethod
    def from_list(
        cls,
        colors: Sequence,
        name: str = "custom_tastymap",
        color_model: ColorModel | str = ColorModel.RGBA,
    ) -> TastyMap:
        """Creates a TastyMap instance from a list of colors.

        Args:
            colors: List of colors.
            name: Name of the colormap. Defaults to "custom_tastymap".

        Returns:
            TastyMap: A new TastyMap instance.
        """
        if not isinstance(colors, (Sequence, np.ndarray)):
            raise TypeError(f"Expected Sequence; received {type(colors)!r}.")
        if colors is None or len(colors) == 0:  # type: ignore
            raise ValueError("Must provide at least one color.")
        cmap_array = cmap_to_array(colors)

        if color_model in (ColorModel.HSV, ColorModel.HSV.value):
            cmap_array = hsv_to_rgb(cmap_array)

        cmap = LinearSegmentedColormap.from_list(name, cmap_array, N=len(cmap_array))
        return TastyMap(cmap)

    @classmethod
    def from_listed_colormap(
        cls,
        listed_colormap: ListedColormap,
        name: str = "custom_tastymap",
    ) -> TastyMap:
        """Creates a TastyMap instance from a ListedColormap.

        Args:
            listed_colormap: The colormap to be used.
            name: Name of the colormap. Defaults to "custom_tastymap".

        Returns:
            TastyMap: A new TastyMap instance.
        """
        return cls.from_list(listed_colormap.colors, name=name)  # type: ignore

    def _from_list_with_extremes(self, *args, **kwargs) -> LinearSegmentedColormap:
        """Creates a TastyMap instance from a list of colors with extreme values."""
        cmap = LinearSegmentedColormap.from_list(*args, **kwargs)
        cmap.set_extremes(
            bad=self.cmap.get_bad(),  # type: ignore
            under=self.cmap.get_under(),  # type: ignore
            over=self.cmap.get_over(),  # type: ignore
        )
        return cmap

    def resize(self, num_colors: int) -> TastyMap:
        """Resizes the colormap to a specified number of colors.

        Args:
            num_colors: Number of colors to resize to.

        Returns:
            TastyMap: A new TastyMap instance with the interpolated colormap.
        """
        cmap = self._from_list_with_extremes(
            self.cmap.name, self._cmap_array, N=num_colors
        )
        return TastyMap(cmap)

    def register(self, name: str | None = None, echo: bool = True) -> TastyMap:
        """Registers the colormap with matplotlib.

        Returns:
            TastyMap: A new TastyMap instance with the registered colormap.
        """
        tmap = self.rename(name) if name else self
        colormaps.register(self.cmap, name=tmap.cmap.name, force=True)
        if echo:
            print(
                f"Successfully registered the colormap; "
                f"to use, set `cmap={tmap.cmap.name!r}` in your plot."
            )
        return tmap

    def rename(self, name: str) -> TastyMap:
        """Renames the colormap.

        Args:
            name (str): New name for the colormap.

        Returns:
            TastyMap: A new TastyMap instance with the renamed colormap.
        """
        return TastyMap(self.cmap, name=name)

    def reverse(self) -> TastyMap:
        """Reverses the colormap.

        Returns:
            TastyMap: A new TastyMap instance with the reversed colormap.
        """
        return self[::-1]

    def to_model(self, color_model: ColorModel | str) -> np.ndarray:
        """Converts the colormap to a specified color model.

        Args:
            color_model: The color model to convert to.

        Returns:
            np.ndarray: Array representation of the colormap
                in the specified color model.
        """
        if isinstance(color_model, str):
            try:
                color_model = ColorModel(color_model.lower())
            except ValueError:
                raise ValueError(
                    f"Invalid color model: {color_model!r}; "
                    f"select from: {[cm.value for cm in ColorModel]}."
                )

        if color_model == ColorModel.RGBA:
            return self._cmap_array
        elif color_model == ColorModel.RGB:
            return self._cmap_array[:, :3]
        elif color_model == ColorModel.HSV:
            return rgb_to_hsv(self._cmap_array[:, :3])
        elif color_model == ColorModel.HEX:
            return np.apply_along_axis(
                rgb2hex, 1, self._cmap_array[:, :3]  # type: ignore
            )

    def set_extremes(
        self,
        bad: str | None = None,
        under: str | None = None,
        over: str | None = None,
    ) -> TastyMap:
        """Sets the colors for bad, underflow, and overflow values.

        Args:
            bad: Color for bad values. Defaults to None.
            under: Color for underflow values. Defaults to None.
            over: Color for overflow values. Defaults to None.

        Returns:
            TastyMap: A new TastyMap instance with the updated colormap.
        """
        cmap = self.cmap.copy()
        cmap.set_extremes(bad=bad, under=under, over=over)  # type: ignore
        return TastyMap(cmap)

    def tweak_hsv(
        self,
        hue: float | None = None,
        saturation: float | None = None,
        value: float | None = None,
        name: str | None = None,
    ) -> TastyMap:
        """Tweaks the hue, saturation, and value of the colormap.

        Args:
            hue: Hue factor (-255 to 255) to tweak by.
            saturation: Saturation factor (-10 to 10) to tweak by.
            value: Brightness value factor (0, 3) to tweak by.
            name: Name of the new colormap.

        Returns:
            TastyMap: A new TastyMap instance with the tweaked colormap.
        """
        cmap_array = self._cmap_array.copy()
        cmap_array[:, :3] = rgb_to_hsv(cmap_array[:, :3])
        if hue is not None:
            hue /= 255
            if abs(hue) > 1:
                raise ValueError("Hue must be between -255 and 255 (non-inclusive).")
            cmap_array[:, 0] = (cmap_array[:, 0] + hue) % 1
        if saturation is not None:
            if abs(saturation) > 10:
                raise ValueError("Saturation must be between -10 and 10.")
            cmap_array[:, 1] = cmap_array[:, 1] * saturation
        if value is not None:
            if value < 0 or value > 3:
                raise ValueError("Value must be between 0 and 3.")
            cmap_array[:, 2] = cmap_array[:, 2] * value
        cmap_array[:, :3] = hsv_to_rgb(np.clip(cmap_array[:, :3], 0, 1))
        cmap = self._from_list_with_extremes(
            name or self.cmap.name, cmap_array, N=len(cmap_array)
        )
        return TastyMap(cmap)

    def __iter__(self) -> Generator[np.ndarray, None, None]:
        """Iterates over the colormap.

        Yields:
            np.ndarray: An array of colors.
        """
        yield from self._cmap_array

    def __getitem__(self, indices: int | float | slice | Sequence) -> TastyMap:
        """Gets a subset of the colormap.

        Args:
            indices: Indices to subset the colormap.

        Returns:
            TastyMap: A new TastyMap instance with the subset colormap.
        """
        cmap = subset_cmap(self.cmap, indices)
        return TastyMap(cmap)

    def _repr_html_(self) -> str:
        """Returns an HTML representation of the colormap.

        Returns:
            str: HTML representation of the colormap.
        """
        return self.cmap._repr_html_()

    def __add__(self, hue: float) -> TastyMap:
        """Adds a hue factor to the colormap.

        Args:
            hue: Hue factor to add.

        Returns:
            TastyMap: A new TastyMap instance with the hue
                added to the colormap.
        """
        return self.tweak_hsv(hue=hue)

    def __sub__(self, hue: float) -> TastyMap:
        """Subtracts a hue factor to the colormap.

        Args:
            hue: Hue factor to subtract.

        Returns:
            TastyMap: A new TastyMap instance with the hue
                subtracted from the colormap.
        """
        return self.tweak_hsv(hue=-hue)

    def __mul__(self, saturation: float) -> TastyMap:
        """Multiplies a saturation factor to the colormap.

        Args:
            saturation: Saturation factor to multiply.

        Returns:
            TastyMap: A new TastyMap instance with the saturation
                multiplied to the colormap.
        """
        return self.tweak_hsv(saturation=saturation)

    def __truediv__(self, saturation: float) -> TastyMap:
        """Divides a saturation factor to the colormap.

        Args:
            saturation: Saturation factor to divide.

        Returns:
            TastyMap: A new TastyMap instance with the saturation
                divided from the colormap.
        """
        return self.tweak_hsv(saturation=1 / saturation)

    def __pow__(self, value: float) -> TastyMap:
        """Raises the brightness value factor to the colormap.

        Args:
            value: Brightness value factor to raise.

        Returns:
            TastyMap: A new TastyMap instance with the brightness value
                raised to the colormap.
        """
        return self.tweak_hsv(value=value)

    def __invert__(self) -> TastyMap:
        """Reverses the colormap.

        Returns:
            TastyMap: A new TastyMap instance with the reversed colormap.
        """
        return self.reverse()

    def __and__(self, tmap: TastyMap) -> TastyMap:
        """Combines two TastyMap instances.

        Args:
            tmap: Another TastyMap instance to combine with.

        Returns:
            TastyMap: A new TastyMap instance with the combined colormap.
        """
        if not isinstance(tmap, TastyMap):
            raise TypeError(
                f"Can only combine TastyMap instances; received {type(tmap)!r}."
            )
        name = self.cmap.name + "_" + tmap.cmap.name
        cmap_array = np.concatenate([self._cmap_array, cmap_to_array(tmap.cmap)])
        cmap = self._from_list_with_extremes(name, cmap_array, N=len(cmap_array))
        return TastyMap(cmap)

    def __or__(self, num_colors: int) -> TastyMap:
        """Interpolates the colormap to a specified number of colors.

        Args:
            num_colors: Number of colors to resize to.

        Returns:
            TastyMap: A new TastyMap instance with the interpolated colormap.
        """
        return self.resize(num_colors)

    def __lshift__(self, name: str) -> TastyMap:
        """Renames the colormap.

        Args:
            name: New name for the colormap.

        Returns:
            TastyMap: A new TastyMap instance with the renamed colormap.
        """
        return self.rename(name)

    def __rshift__(self, name: str) -> TastyMap:
        """Registers the colormap with matplotlib.

        Args:
            name: Name of the colormap.

        Returns:
            TastyMap: A new TastyMap instance with the registered colormap.
        """
        return self.register(name)

    def __mod__(self, color_model: str) -> np.ndarray:
        """Converts the colormap to a specified color model.

        Args:
            color_model: The color model to convert to.

        Returns:
            np.ndarray: Array representation of the colormap
                in the specified color model.
        """
        return self.to_model(color_model)

    def __len__(self) -> int:
        """Returns the number of colors in the colormap.

        Returns:
            int: Number of colors in the colormap.
        """
        return len(self._cmap_array)

    def __eq__(self, other: Any) -> bool:
        """Checks if two TastyMap instances are equal.

        Args:
            other: Another TastyMap instance to compare with.

        Returns:
            bool: True if the two TastyMap instances are equal; False otherwise.
        """
        if not isinstance(other, TastyMap):
            return False
        cmap_array = other._cmap_array
        return bool(np.all(self._cmap_array == cmap_array))

    def __str__(self) -> str:
        """Returns the name of the colormap.

        Returns:
            str: Name of the colormap.
        """
        return f"{self.cmap.name} ({len(self)} colors)"

    def __repr__(self) -> str:
        """Returns a string representation of the TastyMap instance.

        Returns:
            str: String representation of the TastyMap instance.
        """
        return f"TastyMap({self.cmap.name!r})"

__add__(hue)

Adds a hue factor to the colormap.

Parameters:

Name Type Description Default
hue float

Hue factor to add.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the hue added to the colormap.

Source code in tastymap/models.py
def __add__(self, hue: float) -> TastyMap:
    """Adds a hue factor to the colormap.

    Args:
        hue: Hue factor to add.

    Returns:
        TastyMap: A new TastyMap instance with the hue
            added to the colormap.
    """
    return self.tweak_hsv(hue=hue)

__and__(tmap)

Combines two TastyMap instances.

Parameters:

Name Type Description Default
tmap TastyMap

Another TastyMap instance to combine with.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the combined colormap.

Source code in tastymap/models.py
def __and__(self, tmap: TastyMap) -> TastyMap:
    """Combines two TastyMap instances.

    Args:
        tmap: Another TastyMap instance to combine with.

    Returns:
        TastyMap: A new TastyMap instance with the combined colormap.
    """
    if not isinstance(tmap, TastyMap):
        raise TypeError(
            f"Can only combine TastyMap instances; received {type(tmap)!r}."
        )
    name = self.cmap.name + "_" + tmap.cmap.name
    cmap_array = np.concatenate([self._cmap_array, cmap_to_array(tmap.cmap)])
    cmap = self._from_list_with_extremes(name, cmap_array, N=len(cmap_array))
    return TastyMap(cmap)

__eq__(other)

Checks if two TastyMap instances are equal.

Parameters:

Name Type Description Default
other Any

Another TastyMap instance to compare with.

required

Returns:

Name Type Description
bool bool

True if the two TastyMap instances are equal; False otherwise.

Source code in tastymap/models.py
def __eq__(self, other: Any) -> bool:
    """Checks if two TastyMap instances are equal.

    Args:
        other: Another TastyMap instance to compare with.

    Returns:
        bool: True if the two TastyMap instances are equal; False otherwise.
    """
    if not isinstance(other, TastyMap):
        return False
    cmap_array = other._cmap_array
    return bool(np.all(self._cmap_array == cmap_array))

__getitem__(indices)

Gets a subset of the colormap.

Parameters:

Name Type Description Default
indices int | float | slice | Sequence

Indices to subset the colormap.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the subset colormap.

Source code in tastymap/models.py
def __getitem__(self, indices: int | float | slice | Sequence) -> TastyMap:
    """Gets a subset of the colormap.

    Args:
        indices: Indices to subset the colormap.

    Returns:
        TastyMap: A new TastyMap instance with the subset colormap.
    """
    cmap = subset_cmap(self.cmap, indices)
    return TastyMap(cmap)

__init__(cmap, name=None)

Initializes a TastyMap instance.

Parameters:

Name Type Description Default
cmap Colormap

The colormap to be used.

required
name str | None

Name of the colormap. Defaults to the name of the provided colormap.

None
Source code in tastymap/models.py
def __init__(
    self,
    cmap: Colormap,
    name: str | None = None,
):
    """Initializes a TastyMap instance.

    Args:
        cmap: The colormap to be used.
        name: Name of the colormap. Defaults to the name of the provided colormap.
    """
    if not isinstance(cmap, LinearSegmentedColormap):
        raise TypeError(
            f"Expected LinearSegmentedColormap; received {type(cmap)!r}."
        )

    cmap = cmap.copy()
    cmap.name = name or cmap.name
    self.cmap: Colormap = cmap
    self._cmap_array = cmap_to_array(cmap)

__invert__()

Reverses the colormap.

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the reversed colormap.

Source code in tastymap/models.py
def __invert__(self) -> TastyMap:
    """Reverses the colormap.

    Returns:
        TastyMap: A new TastyMap instance with the reversed colormap.
    """
    return self.reverse()

__iter__()

Iterates over the colormap.

Yields:

Type Description
ndarray

np.ndarray: An array of colors.

Source code in tastymap/models.py
def __iter__(self) -> Generator[np.ndarray, None, None]:
    """Iterates over the colormap.

    Yields:
        np.ndarray: An array of colors.
    """
    yield from self._cmap_array

__len__()

Returns the number of colors in the colormap.

Returns:

Name Type Description
int int

Number of colors in the colormap.

Source code in tastymap/models.py
def __len__(self) -> int:
    """Returns the number of colors in the colormap.

    Returns:
        int: Number of colors in the colormap.
    """
    return len(self._cmap_array)

__lshift__(name)

Renames the colormap.

Parameters:

Name Type Description Default
name str

New name for the colormap.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the renamed colormap.

Source code in tastymap/models.py
def __lshift__(self, name: str) -> TastyMap:
    """Renames the colormap.

    Args:
        name: New name for the colormap.

    Returns:
        TastyMap: A new TastyMap instance with the renamed colormap.
    """
    return self.rename(name)

__mod__(color_model)

Converts the colormap to a specified color model.

Parameters:

Name Type Description Default
color_model str

The color model to convert to.

required

Returns:

Type Description
ndarray

np.ndarray: Array representation of the colormap in the specified color model.

Source code in tastymap/models.py
def __mod__(self, color_model: str) -> np.ndarray:
    """Converts the colormap to a specified color model.

    Args:
        color_model: The color model to convert to.

    Returns:
        np.ndarray: Array representation of the colormap
            in the specified color model.
    """
    return self.to_model(color_model)

__mul__(saturation)

Multiplies a saturation factor to the colormap.

Parameters:

Name Type Description Default
saturation float

Saturation factor to multiply.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the saturation multiplied to the colormap.

Source code in tastymap/models.py
def __mul__(self, saturation: float) -> TastyMap:
    """Multiplies a saturation factor to the colormap.

    Args:
        saturation: Saturation factor to multiply.

    Returns:
        TastyMap: A new TastyMap instance with the saturation
            multiplied to the colormap.
    """
    return self.tweak_hsv(saturation=saturation)

__or__(num_colors)

Interpolates the colormap to a specified number of colors.

Parameters:

Name Type Description Default
num_colors int

Number of colors to resize to.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the interpolated colormap.

Source code in tastymap/models.py
def __or__(self, num_colors: int) -> TastyMap:
    """Interpolates the colormap to a specified number of colors.

    Args:
        num_colors: Number of colors to resize to.

    Returns:
        TastyMap: A new TastyMap instance with the interpolated colormap.
    """
    return self.resize(num_colors)

__pow__(value)

Raises the brightness value factor to the colormap.

Parameters:

Name Type Description Default
value float

Brightness value factor to raise.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the brightness value raised to the colormap.

Source code in tastymap/models.py
def __pow__(self, value: float) -> TastyMap:
    """Raises the brightness value factor to the colormap.

    Args:
        value: Brightness value factor to raise.

    Returns:
        TastyMap: A new TastyMap instance with the brightness value
            raised to the colormap.
    """
    return self.tweak_hsv(value=value)

__repr__()

Returns a string representation of the TastyMap instance.

Returns:

Name Type Description
str str

String representation of the TastyMap instance.

Source code in tastymap/models.py
def __repr__(self) -> str:
    """Returns a string representation of the TastyMap instance.

    Returns:
        str: String representation of the TastyMap instance.
    """
    return f"TastyMap({self.cmap.name!r})"

__rshift__(name)

Registers the colormap with matplotlib.

Parameters:

Name Type Description Default
name str

Name of the colormap.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the registered colormap.

Source code in tastymap/models.py
def __rshift__(self, name: str) -> TastyMap:
    """Registers the colormap with matplotlib.

    Args:
        name: Name of the colormap.

    Returns:
        TastyMap: A new TastyMap instance with the registered colormap.
    """
    return self.register(name)

__str__()

Returns the name of the colormap.

Returns:

Name Type Description
str str

Name of the colormap.

Source code in tastymap/models.py
def __str__(self) -> str:
    """Returns the name of the colormap.

    Returns:
        str: Name of the colormap.
    """
    return f"{self.cmap.name} ({len(self)} colors)"

__sub__(hue)

Subtracts a hue factor to the colormap.

Parameters:

Name Type Description Default
hue float

Hue factor to subtract.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the hue subtracted from the colormap.

Source code in tastymap/models.py
def __sub__(self, hue: float) -> TastyMap:
    """Subtracts a hue factor to the colormap.

    Args:
        hue: Hue factor to subtract.

    Returns:
        TastyMap: A new TastyMap instance with the hue
            subtracted from the colormap.
    """
    return self.tweak_hsv(hue=-hue)

__truediv__(saturation)

Divides a saturation factor to the colormap.

Parameters:

Name Type Description Default
saturation float

Saturation factor to divide.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the saturation divided from the colormap.

Source code in tastymap/models.py
def __truediv__(self, saturation: float) -> TastyMap:
    """Divides a saturation factor to the colormap.

    Args:
        saturation: Saturation factor to divide.

    Returns:
        TastyMap: A new TastyMap instance with the saturation
            divided from the colormap.
    """
    return self.tweak_hsv(saturation=1 / saturation)

from_list(colors, name='custom_tastymap', color_model=ColorModel.RGBA) classmethod

Creates a TastyMap instance from a list of colors.

Parameters:

Name Type Description Default
colors Sequence

List of colors.

required
name str

Name of the colormap. Defaults to "custom_tastymap".

'custom_tastymap'

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance.

Source code in tastymap/models.py
@classmethod
def from_list(
    cls,
    colors: Sequence,
    name: str = "custom_tastymap",
    color_model: ColorModel | str = ColorModel.RGBA,
) -> TastyMap:
    """Creates a TastyMap instance from a list of colors.

    Args:
        colors: List of colors.
        name: Name of the colormap. Defaults to "custom_tastymap".

    Returns:
        TastyMap: A new TastyMap instance.
    """
    if not isinstance(colors, (Sequence, np.ndarray)):
        raise TypeError(f"Expected Sequence; received {type(colors)!r}.")
    if colors is None or len(colors) == 0:  # type: ignore
        raise ValueError("Must provide at least one color.")
    cmap_array = cmap_to_array(colors)

    if color_model in (ColorModel.HSV, ColorModel.HSV.value):
        cmap_array = hsv_to_rgb(cmap_array)

    cmap = LinearSegmentedColormap.from_list(name, cmap_array, N=len(cmap_array))
    return TastyMap(cmap)

from_listed_colormap(listed_colormap, name='custom_tastymap') classmethod

Creates a TastyMap instance from a ListedColormap.

Parameters:

Name Type Description Default
listed_colormap ListedColormap

The colormap to be used.

required
name str

Name of the colormap. Defaults to "custom_tastymap".

'custom_tastymap'

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance.

Source code in tastymap/models.py
@classmethod
def from_listed_colormap(
    cls,
    listed_colormap: ListedColormap,
    name: str = "custom_tastymap",
) -> TastyMap:
    """Creates a TastyMap instance from a ListedColormap.

    Args:
        listed_colormap: The colormap to be used.
        name: Name of the colormap. Defaults to "custom_tastymap".

    Returns:
        TastyMap: A new TastyMap instance.
    """
    return cls.from_list(listed_colormap.colors, name=name)  # type: ignore

from_str(string) classmethod

Creates a TastyMap instance from a string name.

Parameters:

Name Type Description Default
string str

Name of the colormap.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance.

Source code in tastymap/models.py
@classmethod
def from_str(cls, string: str) -> TastyMap:
    """Creates a TastyMap instance from a string name.

    Args:
        string: Name of the colormap.

    Returns:
        TastyMap: A new TastyMap instance.
    """
    cmap = get_cmap(string)  # type: ignore
    cmap_array = cmap_to_array(cmap)

    new_name = string
    cmap = LinearSegmentedColormap.from_list(
        new_name, cmap_array, N=len(cmap_array)
    )
    return TastyMap(cmap, name=new_name)

register(name=None, echo=True)

Registers the colormap with matplotlib.

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the registered colormap.

Source code in tastymap/models.py
def register(self, name: str | None = None, echo: bool = True) -> TastyMap:
    """Registers the colormap with matplotlib.

    Returns:
        TastyMap: A new TastyMap instance with the registered colormap.
    """
    tmap = self.rename(name) if name else self
    colormaps.register(self.cmap, name=tmap.cmap.name, force=True)
    if echo:
        print(
            f"Successfully registered the colormap; "
            f"to use, set `cmap={tmap.cmap.name!r}` in your plot."
        )
    return tmap

rename(name)

Renames the colormap.

Parameters:

Name Type Description Default
name str

New name for the colormap.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the renamed colormap.

Source code in tastymap/models.py
def rename(self, name: str) -> TastyMap:
    """Renames the colormap.

    Args:
        name (str): New name for the colormap.

    Returns:
        TastyMap: A new TastyMap instance with the renamed colormap.
    """
    return TastyMap(self.cmap, name=name)

resize(num_colors)

Resizes the colormap to a specified number of colors.

Parameters:

Name Type Description Default
num_colors int

Number of colors to resize to.

required

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the interpolated colormap.

Source code in tastymap/models.py
def resize(self, num_colors: int) -> TastyMap:
    """Resizes the colormap to a specified number of colors.

    Args:
        num_colors: Number of colors to resize to.

    Returns:
        TastyMap: A new TastyMap instance with the interpolated colormap.
    """
    cmap = self._from_list_with_extremes(
        self.cmap.name, self._cmap_array, N=num_colors
    )
    return TastyMap(cmap)

reverse()

Reverses the colormap.

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the reversed colormap.

Source code in tastymap/models.py
def reverse(self) -> TastyMap:
    """Reverses the colormap.

    Returns:
        TastyMap: A new TastyMap instance with the reversed colormap.
    """
    return self[::-1]

set_extremes(bad=None, under=None, over=None)

Sets the colors for bad, underflow, and overflow values.

Parameters:

Name Type Description Default
bad str | None

Color for bad values. Defaults to None.

None
under str | None

Color for underflow values. Defaults to None.

None
over str | None

Color for overflow values. Defaults to None.

None

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the updated colormap.

Source code in tastymap/models.py
def set_extremes(
    self,
    bad: str | None = None,
    under: str | None = None,
    over: str | None = None,
) -> TastyMap:
    """Sets the colors for bad, underflow, and overflow values.

    Args:
        bad: Color for bad values. Defaults to None.
        under: Color for underflow values. Defaults to None.
        over: Color for overflow values. Defaults to None.

    Returns:
        TastyMap: A new TastyMap instance with the updated colormap.
    """
    cmap = self.cmap.copy()
    cmap.set_extremes(bad=bad, under=under, over=over)  # type: ignore
    return TastyMap(cmap)

to_model(color_model)

Converts the colormap to a specified color model.

Parameters:

Name Type Description Default
color_model ColorModel | str

The color model to convert to.

required

Returns:

Type Description
ndarray

np.ndarray: Array representation of the colormap in the specified color model.

Source code in tastymap/models.py
def to_model(self, color_model: ColorModel | str) -> np.ndarray:
    """Converts the colormap to a specified color model.

    Args:
        color_model: The color model to convert to.

    Returns:
        np.ndarray: Array representation of the colormap
            in the specified color model.
    """
    if isinstance(color_model, str):
        try:
            color_model = ColorModel(color_model.lower())
        except ValueError:
            raise ValueError(
                f"Invalid color model: {color_model!r}; "
                f"select from: {[cm.value for cm in ColorModel]}."
            )

    if color_model == ColorModel.RGBA:
        return self._cmap_array
    elif color_model == ColorModel.RGB:
        return self._cmap_array[:, :3]
    elif color_model == ColorModel.HSV:
        return rgb_to_hsv(self._cmap_array[:, :3])
    elif color_model == ColorModel.HEX:
        return np.apply_along_axis(
            rgb2hex, 1, self._cmap_array[:, :3]  # type: ignore
        )

tweak_hsv(hue=None, saturation=None, value=None, name=None)

Tweaks the hue, saturation, and value of the colormap.

Parameters:

Name Type Description Default
hue float | None

Hue factor (-255 to 255) to tweak by.

None
saturation float | None

Saturation factor (-10 to 10) to tweak by.

None
value float | None

Brightness value factor (0, 3) to tweak by.

None
name str | None

Name of the new colormap.

None

Returns:

Name Type Description
TastyMap TastyMap

A new TastyMap instance with the tweaked colormap.

Source code in tastymap/models.py
def tweak_hsv(
    self,
    hue: float | None = None,
    saturation: float | None = None,
    value: float | None = None,
    name: str | None = None,
) -> TastyMap:
    """Tweaks the hue, saturation, and value of the colormap.

    Args:
        hue: Hue factor (-255 to 255) to tweak by.
        saturation: Saturation factor (-10 to 10) to tweak by.
        value: Brightness value factor (0, 3) to tweak by.
        name: Name of the new colormap.

    Returns:
        TastyMap: A new TastyMap instance with the tweaked colormap.
    """
    cmap_array = self._cmap_array.copy()
    cmap_array[:, :3] = rgb_to_hsv(cmap_array[:, :3])
    if hue is not None:
        hue /= 255
        if abs(hue) > 1:
            raise ValueError("Hue must be between -255 and 255 (non-inclusive).")
        cmap_array[:, 0] = (cmap_array[:, 0] + hue) % 1
    if saturation is not None:
        if abs(saturation) > 10:
            raise ValueError("Saturation must be between -10 and 10.")
        cmap_array[:, 1] = cmap_array[:, 1] * saturation
    if value is not None:
        if value < 0 or value > 3:
            raise ValueError("Value must be between 0 and 3.")
        cmap_array[:, 2] = cmap_array[:, 2] * value
    cmap_array[:, :3] = hsv_to_rgb(np.clip(cmap_array[:, :3], 0, 1))
    cmap = self._from_list_with_extremes(
        name or self.cmap.name, cmap_array, N=len(cmap_array)
    )
    return TastyMap(cmap)