Source code for vampire.anno.pl._setting

import plotly.io as pio
import plotly.express as px
import plotly.graph_objects as go

import logging

logger = logging.getLogger(__name__)

# Module-level colormap constants
_RAINBOW_COLORMAP: list[str] = [
    "#f94144", "#f8961e", "#f9c74f", "#90be6d", "#43aa8b", "#4d908e", "#577590", "#277da1", "#5983f2", "#898bf1",
    "#8945dc"
]
_GLASBEY_COLORMAP: list[str] = [
    "#5983f2", "#87db96", "#eef248", "#f25b76", "#f29dd3", "#38abbd", "#f2b668", "#a570f2", "#f2b668", "#288126",
    "#3e3ed1", "#48f0d1", "#987b74", "#c5c2f2", "#96ab83", "#b639b1", "#63586e", "#722222", "#428acd", "#f1eba8",
    "#154715", "#877928", "#f2948c", "#e2bebe", "#627767", "#afe0f2", "#e04392", "#a386af", "#6a2e9b", "#3bbc38",
    "#965e2d", "#945361", "#c0bc39", "#768190", "#31a68d", "#9fa3f2", "#583f41", "#361010", "#f0c8f2", "#c0dbc1",
    "#d0863f", "#96aead", "#e07ff2", "#445c7b", "#ba7b97", "#545d31", "#bba183", "#297c80", "#adf248", "#7e4288",
    "#91c0f2", "#7c6af2", "#879a2f", "#f14acf", "#b9a6bb", "#bb7266", "#705c4a", "#1e6249", "#3ccbca", "#d9ddf2",
    "#661e53", "#7877a7", "#b99c37", "#d5c8a7", "#d9acf2", "#c44af0", "#8a6080", "#982d7a", "#669364", "#c8999b",
    "#38412d", "#9ba5b9", "#868667", "#f27748", "#cdf2ee", "#bc91f2", "#84c5aa", "#c7f2a5", "#8f6aa8", "#714632",
    "#f2b399", "#a03044", "#cc3d3d", "#5a2632", "#6f8f8d", "#f287ab", "#53656e", "#719bc1", "#a17b51", "#49d2f1",
    "#37ba7f", "#5c3e59", "#9cc95b", "#bbca8f", "#645c99", "#4a2d1e", "#755f23", "#f2d175", "#923cc4", "#7bf0f2",
    "#82274f", "#745960", "#2c7995", "#a18795", "#7e7387", "#898bf1", "#b95a92", "#c25569", "#f2afc1", "#b975b3",
    "#33621e", "#28866b", "#66793b", "#9190b5", "#a4d1cc", "#c38f75", "#c7b578", "#5f1f68", "#916358", "#b3c2d0",
    "#e9f2d4", "#43e044", "#c293b8", "#6638bb", "#7d9581", "#6c6b4f", "#aaf2c4", "#376eb7", "#b6a5e0", "#589b3d",
    "#634b79", "#154837", "#8eb6c8", "#90472b", "#456861", "#ae3470", "#f29d71", "#f2d4e3", "#794460", "#24310e",
    "#986c77", "#68aba8", "#97975b", "#45754b", "#b86237", "#c6dc6b", "#8945dc", "#84b26a", "#a5bba8", "#cf7587",
    "#cd9c62", "#ef9bf2", "#4e5e4d", "#d1c4df", "#734146", "#618e9c", "#77a3f2", "#a437ba", "#deafd0", "#f2c598",
    "#434414", "#b4cdf2", "#a3aede", "#5e6f8b", "#db80b8", "#9b92a7", "#534e35", "#b17ac9", "#9988d5", "#b78082",
    "#d840d7", "#afae91", "#362e10", "#d794ad", "#a45851", "#a5b167", "#79ab8a", "#9ec79b", "#a78f60", "#266171",
    "#2b9086", "#55231a", "#607779", "#395332", "#d98468", "#5a4536", "#8266b2", "#505565", "#c490d1", "#944d8c",
    "#c9dde5", "#46b4df", "#9e7696", "#dbb641", "#88f28a", "#e5d844", "#714f22", "#5b3346", "#7f8ec0", "#829ba7",
    "#4bddae", "#afebd8", "#7d2574", "#867059", "#d76b60", "#af657c", "#77577f", "#f2d2c1", "#6b76bd", "#d6b7a1",
    "#f27be6", "#d2e3b2", "#b5764f", "#cca9b5", "#b38835", "#8d7ca9", "#f268b3", "#f2e3bd", "#3a534b", "#ae9185",
    "#6e805d", "#b89ec7", "#5ae1f1", "#985579", "#9c659f", "#732b90", "#826a78", "#636284", "#eca6a3", "#dbd094",
    "#6a6720", "#195354", "#5761f2", "#3dcd76", "#edb3f0", "#7b8e52", "#534019", "#92c9d1"
]
_DNA_BASE_COLORS: dict[str, str] = {
    "A": "#2ca02c",
    "C": "#1f77b4",
    "G": "#ff7f0e",
    "T": "#d62728",
    "-": "#403d39",
    "N": "#403d39",
}

_COLORMAP_OPTIONS: dict[str, list[str] | dict[str, str]] = {
    "rainbow": _RAINBOW_COLORMAP,
    "glasbey": _GLASBEY_COLORMAP,
    "dna": _DNA_BASE_COLORS,
}

[docs] def set_default_plotstyle( font_size: int = 14, font_family: str = "Arial", line_width: float = 1.5, width: int = 900, height: int = 400, showgrid: bool = False ): """ Set the plotly template for the vampire package. Parameters ---------- font_size: int, the font size font_family: str, the font family width: int, the width of the plot height: int, the height of the plot showgrid: bool, whether to show the grid Returns ------- None Examples -------- >>> import vampire as vp >>> vp.anno.pl.set_default_plotstyle(font_size=8, line_width=1) """ pio.templates["vampire"] = go.layout.Template( layout=dict( font=dict( size=font_size, family=font_family, ), width=width, height=height, plot_bgcolor="white", paper_bgcolor="white", xaxis=dict( showgrid=showgrid, zeroline=False, linewidth=line_width, tickwidth=line_width, linecolor="black", ticks="outside", ), yaxis=dict( showgrid=showgrid, zeroline=False, linewidth=line_width, tickwidth=line_width, linecolor="black", ticks="outside", ), legend=dict( borderwidth=0, ), margin=dict(l=80, r=80, t=80, b=80), ) ) pio.templates.default = "vampire"
def _get_categorical_colormap( labels: list[str], colormap: dict[str, str] | list[str] | str | None = None, ) -> tuple[list[str], dict[str, str]]: """ Generate a color mapping for categorical labels. This function assigns colors to each unique label and returns: 1. A list of colors corresponding to the input labels (in original order) 2. A dictionary mapping each unique label to its assigned color Parameters ---------- labels : array-like Sequence of categorical labels. colormap : dict[str, str] | list[str] | str | None, optional Color specification: - None: use default Glasbey-like color palette - dict[str, str]: explicit mapping from label to color - list[str]: list of colors cycled over unique labels - str: Plotly palette name (qualitative, sequential, or diverging) Returns ------- colors : list[str] Color assigned to each label in input order. mapping : dict[str, str] Mapping from unique label -> color. Notes ----- - Unrecognized labels in dict mode fall back to '#0c0c0c' - In list/str modes, colors are assigned in sorted label order - Sorting of labels is deterministic to ensure reproducibility """ # check label uniqueness and order if len(set(labels)) != len(labels): raise ValueError("Labels must be unique") # None -> Glasbey / fallback colormap if colormap is None: colors = _GLASBEY_COLORMAP # dict[str, str] -> direct mapping elif isinstance(colormap, dict): colors: list[str] = [] # check that all labels have a mapping, if not raise a error for l in labels: if l in colormap.keys(): colors.append(colormap[l]) else: logger.warning( "Label '%s' not found in colormap dict, it will be colored in #0c0c0c", l ) colors.append("#0c0c0c") # list[str] -> cycle colors elif isinstance(colormap, list): if len(colormap) == 0: raise ValueError("colormap list cannot be empty") colors = colormap # str -> plotly built-in palettes elif isinstance(colormap, str): if _COLORMAP_OPTIONS.get(colormap) is not None: colors = _COLORMAP_OPTIONS[colormap] elif hasattr(px.colors.qualitative, colormap): colors = getattr(px.colors.qualitative, colormap) elif hasattr(px.colors.sequential, colormap): colors = getattr(px.colors.sequential, colormap) elif hasattr(px.colors.diverging, colormap): colors = getattr(px.colors.diverging, colormap) else: raise ValueError( f"Unknown plotly colormap: {colormap}" ) else: raise TypeError( "colormap must be one of: dict[str, str], list[str], str, None" ) if len(labels) > len(colors): logger.warning( "Number of unique labels (%d) exceeds colormap size (%d), the overflow labels will be colored in #0c0c0c", len(labels), len(colors) ) mapping: dict[str, str] = {} for i, cat in enumerate(labels): if i < len(colors): mapping[cat] = colors[i] else: mapping[cat] = "#0c0c0c" return ( [mapping.get(l, "#0c0c0c") for l in labels], mapping, ) def _save_figure( fig: go.Figure, save: str | bool | None, default_name: str ) -> None: """ Save a Plotly figure following scanpy-style ``save`` semantics. Parameters ---------- fig: go.Figure Plotly figure to save. save: str | bool | None ``None`` or ``False`` — do nothing. ``True`` — save to ``<default_name>.pdf``. ``str`` — if it ends with ``.pdf``, ``.png``, ``.svg``, or ``.html``, use it as the full file path; otherwise prepend it to ``default_name`` (e.g. ``save="prefix_"`` → ``prefix_<default_name>.pdf``). default_name: str Base filename used when ``save`` is ``True`` or a prefix string. """ if save is None or save is False: return import pathlib if save is True: path = f"{default_name}.pdf" elif isinstance(save, str): save_lower = save.lower() if save_lower.endswith((".pdf", ".png", ".svg", ".html")): path = save else: path = f"{save}{default_name}.pdf" else: return ext = pathlib.Path(path).suffix.lstrip(".").lower() # HTML is handled separately via write_html if ext == "html": try: fig.write_html(path) except Exception as e: logger.warning("Failed to save figure to %s: %s", path, e) return fmt = ext if ext in ("pdf", "png", "svg", "jpeg", "jpg", "webp") else "pdf" if fmt == "jpg": fmt = "jpeg" write_kwargs = {} if fmt == "png": write_kwargs["scale"] = 4 try: fig.write_image(path, format=fmt, **write_kwargs) except Exception as e: logger.warning("Failed to save figure to %s: %s", path, e)