Source code for dmslogo.colorschemes

"""
============
colorschemes
============

Color schemes.
"""


import matplotlib.colors
import matplotlib.pyplot as plt

import numpy


#: color-blind safe palette with gray, from
#: http://bconnelly.net/2013/10/creating-colorblind-friendly-figures
CBPALETTE = [
    "#999999",
    "#E69F00",
    "#56B4E9",
    "#009E73",
    "#F0E442",
    "#0072B2",
    "#D55E00",
    "#CC79A7",
]

#:  color-blind safe palette with black, from
#: http://bconnelly.net/2013/10/creating-colorblind-friendly-figures
CBBPALETTE = [
    "#000000",
    "#E69F00",
    "#56B4E9",
    "#009E73",
    "#F0E442",
    "#0072B2",
    "#D55E00",
    "#CC79A7",
]

#: color amino acids by functional group
AA_FUNCTIONAL_GROUP = {
    "G": "#f76ab4",
    "A": "#f76ab4",
    "S": "#ff7f00",
    "T": "#ff7f00",
    "C": "#ff7f00",
    "V": "#12ab0d",
    "L": "#12ab0d",
    "I": "#12ab0d",
    "M": "#12ab0d",
    "P": "#12ab0d",
    "F": "#84380b",
    "Y": "#84380b",
    "W": "#84380b",
    "D": "#e41a1c",
    "E": "#e41a1c",
    "H": "#3c58e5",
    "K": "#3c58e5",
    "R": "#3c58e5",
    "N": "#972aa8",
    "Q": "#972aa8",
}

#: color amino acids by charge
AA_CHARGE = {
    "A": "#000000",
    "R": "#FF0000",
    "N": "#000000",
    "D": "#0000FF",
    "C": "#000000",
    "Q": "#000000",
    "E": "#0000FF",
    "G": "#000000",
    "H": "#FF0000",
    "I": "#000000",
    "L": "#000000",
    "K": "#FF0000",
    "M": "#000000",
    "F": "#000000",
    "P": "#000000",
    "S": "#000000",
    "T": "#000000",
    "W": "#000000",
    "Y": "#000000",
    "V": "#000000",
}


[docs] class ValueToColorMap: """Map numerical values to color gradient. Args: `minvalue` (float) Color map starts at this value. `maxvalue` (float) Color map ends at this value. `cmap` (str or matplotlib.colors.Colormap) Name of `matplotlib colormap`_, or an actual `Colormap` object. You can also use the wider set of color maps from palettable_, such as by providing `palettable.cmocean.sequential.Dense_20.mpl_colormap`. Attributes: `cmap` (matplotlib.colors.Colormap) Color map. `minvalue` (float) Color map starts at this value. `maxvalue` (float) Color map ends at this value. Make a data frame with some values, and two color maps (one with default 'viridis' and another with 'cividis') covering value range in data frame: .. plot:: :context: >>> import pandas as pd >>> from dmslogo.colorschemes import ValueToColorMap >>> >>> df = pd.DataFrame({'value': [0, 1, 2, 1, 3, 0]}) >>> map1 = ValueToColorMap(df['value'].min(), ... df['value'].max()) >>> map2 = ValueToColorMap(df['value'].min(), ... df['value'].max(), ... cmap='cividis') Map values to colors using :meth:`ValueToColorMap.val_to_color`: .. plot:: :context: >>> df = (df ... .assign(color=lambda x: x['value'].map(map1.val_to_color), ... color2=lambda x: x['value'].map(map2.val_to_color), ... ) ... ) >>> df value color color2 0 0 #440154 #00224d 1 1 #30678d #575d6d 2 2 #35b778 #a59b73 3 1 #30678d #575d6d 4 3 #fde724 #fde737 5 0 #440154 #00224d Draw scale bars: .. plot:: :context: close-figs >>> fig1, ax1 = map1.scale_bar(orientation='horizontal', ... label='viridis scale') .. plot:: :context: close-figs >>> fig2, ax2 = map2.scale_bar(orientation='vertical', ... label='cividis scale') .. plot:: :context: close-figs >>> fig2, ax2 = map2.scale_bar(orientation='vertical', ... label='cividis scale (alpha 0.3)', ... alpha=0.3) .. _matplotlib colormap: https://matplotlib.org/tutorials/colors/colormaps.html .. _palettable: https://jiffyclub.github.io/palettable/ """ def __init__( self, minvalue, maxvalue, cmap="viridis", ): """See main class docstring.""" if isinstance(cmap, matplotlib.colors.Colormap): self.cmap = cmap elif cmap in plt.colormaps(): self.cmap = plt.get_cmap(cmap) else: raise ValueError(f"`cmap` not `Colormap` or name of one: {cmap}") self.minvalue = float(minvalue) self.maxvalue = float(maxvalue) if self.maxvalue <= self.minvalue: raise ValueError("`maxvalue` must exceed `minvalue`")
[docs] def val_to_color( self, values, *, return_color_as="rgb_hex_code", ): """Map numerical values between `minvalue` and `maxvalue` to colors. Args: `values` (number or array-like of numbers) Values to map to colors `return_color_as` ({'rgb_hex_code', 'rgb_triple'}) Return color as RGB hex code (e.g., `'#FF0000'`) or triple of numbers (e.g., `[255, 0, 0]`). Returns: Either str or length-3 arrays depending on `return_color_as`. If `values` is single value, return single value; otherwise array. """ if isinstance(values, (int, float)): single_value = True values = numpy.array([values], dtype="float") else: single_value = False values = numpy.array(values, dtype="float") if any(values < self.minvalue) or any(values > self.maxvalue): raise ValueError("`values` not between `minvalue` and `maxvalue`") values = (values - self.minvalue) / (self.maxvalue - self.minvalue) assert all(values >= 0) and all(values <= 1) colors = self.cmap(values, bytes=True) if colors.shape != (len(values), 4): raise ValueError( "unexpected shape for `colors`, " "is `values` multi-dimensional?" ) if return_color_as == "rgb_triple": colors = colors[:, :3] assert colors.shape == (len(values), 3) elif return_color_as == "rgb_hex_code": color_list = [] for r, g, b, _a in colors: assert all(0 <= x < 256 for x in [r, g, b]) color_list.append(f"#{r:02x}{g:02x}{b:02x}") colors = numpy.array(color_list) else: raise ValueError(f"invalid `return_color_as` {return_color_as}") if single_value: return colors[0] else: return colors
[docs] def scale_bar( self, *, orientation="vertical", ax=None, label=None, axisfontscale=1, low_high_ticks_only=False, alpha=1, ): """Draw a scale bar for the value-to-color map. Args: `orientation` ({'horizontal', 'vertical'}) Direction that scale bar drawn is drawn. `ax` (None or matplotlib.axes.Axes) If specified, draw scale bar on this axis. Otherwise create new axes. `label` (None or str) Label for scale bar. `axisfontscale` (float) Scale font size by this much. `low_high_ticks_only` (bool) Rather than showing numerical ticks, indicate low and high. `alpha` (float) Transparency of scale bar colors. Returns: `(matplotlib.figure.Figure, matplotlib.axes.Axes)`, figure and axis on which the color bar is drawn. """ colors = self.val_to_color( numpy.linspace(self.minvalue, self.maxvalue, 256), return_color_as="rgb_triple", ) if orientation == "vertical": colors = numpy.expand_dims(colors, 1) extent = [0, 1, self.minvalue, self.maxvalue] figsize = (0.4, 3.5) elif orientation == "horizontal": colors = numpy.expand_dims(colors, 0) extent = [self.minvalue, self.maxvalue, 0, 1] figsize = (3.5, 0.4) else: raise ValueError(f"invalid `orientation` of {orientation}") if ax is None: _, ax = plt.subplots(figsize=figsize) ax.imshow( colors, aspect="auto", extent=extent, origin="lower", alpha=alpha, ) if label: if orientation == "vertical": ax.set_ylabel(label, fontsize=17 * axisfontscale) elif orientation == "horizontal": ax.set_xlabel(label, fontsize=17 * axisfontscale) if low_high_ticks_only: ax.tick_params( "both", top=False, bottom=False, left=False, right=False, labelbottom=(orientation == "horizontal"), labelleft=(orientation == "vertical"), ) axtype = {"vertical": "y", "horizontal": "x"}[orientation] axis = getattr(ax, axtype + "axis") dtick = (self.maxvalue - self.minvalue) * 0.1 axis.set_ticks([self.minvalue + dtick, self.maxvalue - dtick]) axis.set_ticklabels( ["low", "high"], rotation=orientation, verticalalignment={"vertical": "center", "horizontal": "top"}[ orientation ], ) ax.tick_params(axis=axtype, labelsize=12 * axisfontscale) else: ax.tick_params(axis="both", labelsize=12 * axisfontscale) ax.tick_params( axis={"vertical": "x", "horizontal": "y"}[orientation], left=False, right=False, bottom=False, top=False, labelbottom=False, labelleft=False, ) return ax.get_figure(), ax
if __name__ == "__main__": import doctest doctest.testmod()