adesomics.plotting.style
Shared plotting style for adesomics.
All plots pull colours and labels from here, so a condition looks the same in a volcano plot and in a growth curve.
1"""Shared plotting style for adesomics. 2 3All plots pull colours and labels from here, so a condition looks the same 4in a volcano plot and in a growth curve. 5""" 6 7import matplotlib as mpl 8import matplotlib.pyplot as plt 9 10# Adesonica corporate palette 11PINK = "#e61f68" 12YELLOW = "#ffe11d" 13BLUE = "#5acdff" 14INK = "#1a1a2e" 15GREY = "#b0b0b8" 16 17PALETTE = [PINK, BLUE, YELLOW, INK, GREY] 18 19# distinguishable line styles, used for a second factor 20LINESTYLES = ["-", "--", ":", "-."] 21 22 23def use_style(base_size: int = 13) -> None: 24 """Readable defaults: larger type, no top/right spines, soft grid.""" 25 mpl.rcParams.update({ 26 "font.size": base_size, 27 "axes.titlesize": base_size + 4, 28 "axes.labelsize": base_size + 1, 29 "xtick.labelsize": base_size - 1, 30 "ytick.labelsize": base_size - 1, 31 "legend.fontsize": base_size - 1, 32 "axes.titleweight": "bold", 33 "axes.spines.top": False, 34 "axes.spines.right": False, 35 "axes.grid": True, 36 "grid.alpha": 0.25, 37 "grid.linewidth": 0.6, 38 "lines.linewidth": 2.2, 39 "figure.dpi": 110, 40 "savefig.bbox": "tight", 41 "axes.prop_cycle": mpl.cycler(color=PALETTE), 42 }) 43 44 45def group_colors(metadata, palette=None) -> dict: 46 """One colour per condition, stable across plots.""" 47 palette = palette or PALETTE 48 groups = sorted(metadata.groups.unique()) 49 return {g: palette[i % len(palette)] for i, g in enumerate(groups)} 50 51 52def factor_style(metadata, color_by="genotype", style_by="supplement") -> dict: 53 """Encode a two-factor design as colour x line style. 54 55 Returns per-sample styling plus the two lookup tables, so the legend can 56 be built per factor instead of per group. Falls back to plain per-group 57 colours when the structured factors are absent. 58 """ 59 tab = metadata.table 60 61 if color_by not in tab.columns: 62 lut = group_colors(metadata) 63 return { 64 "per_sample": {s: {"color": lut[g], "linestyle": "-"} 65 for s, g in metadata.groups.items()}, 66 "color_map": lut, 67 "style_map": {}, 68 "color_by": metadata.group_col, 69 "style_by": None, 70 } 71 72 c_levels = sorted(tab[color_by].unique()) 73 color_map = {lv: PALETTE[i % len(PALETTE)] for i, lv in enumerate(c_levels)} 74 75 if style_by in tab.columns: 76 s_levels = sorted(tab[style_by].unique()) 77 style_map = {lv: LINESTYLES[i % len(LINESTYLES)] 78 for i, lv in enumerate(s_levels)} 79 else: 80 style_by, style_map = None, {} 81 82 per_sample = { 83 s: { 84 "color": color_map[tab.loc[s, color_by]], 85 "linestyle": (style_map[tab.loc[s, style_by]] if style_by else "-"), 86 } 87 for s in metadata.samples 88 } 89 return {"per_sample": per_sample, "color_map": color_map, 90 "style_map": style_map, "color_by": color_by, "style_by": style_by} 91 92 93def factor_legend(ax, style: dict, loc="best") -> None: 94 """Two-part legend: colour = factor 1, line style = factor 2.""" 95 from matplotlib.lines import Line2D 96 97 handles = [Line2D([], [], color="none", label=f"{style['color_by']}:")] 98 handles += [Line2D([], [], color=c, lw=3, label=f" {lv}") 99 for lv, c in style["color_map"].items()] 100 101 if style["style_map"]: 102 handles += [Line2D([], [], color="none", label=" "), 103 Line2D([], [], color="none", label=f"{style['style_by']}:")] 104 handles += [Line2D([], [], color=INK, ls=ls, lw=2, label=f" {lv}") 105 for lv, ls in style["style_map"].items()] 106 107 ax.legend(handles=handles, loc=loc, frameon=False, 108 handlelength=2.4, labelspacing=0.35) 109 110 111def short_labels(metadata, factors=("genotype", "supplement")) -> list: 112 """Compact tick labels, e.g. 'del_atoC / LiCl_10mM'.""" 113 cols = [f for f in factors if f in metadata.table.columns] 114 if not cols: 115 return list(metadata.groups) 116 return metadata.table[cols].astype(str).agg("\n".join, axis=1).tolist() 117 118 119def annotate_study(ax, metadata) -> None: 120 """Footer with organism and project.""" 121 bits = [metadata.study.get(k) for k in ("Organism", "BioProject")] 122 text = " · ".join(b for b in bits if b) 123 if text: 124 ax.figure.text(0.99, 0.005, text, ha="right", fontsize=8, color=GREY)
PINK =
'#e61f68'
YELLOW =
'#ffe11d'
BLUE =
'#5acdff'
INK =
'#1a1a2e'
GREY =
'#b0b0b8'
PALETTE =
['#e61f68', '#5acdff', '#ffe11d', '#1a1a2e', '#b0b0b8']
LINESTYLES =
['-', '--', ':', '-.']
def
use_style(base_size: int = 13) -> None:
24def use_style(base_size: int = 13) -> None: 25 """Readable defaults: larger type, no top/right spines, soft grid.""" 26 mpl.rcParams.update({ 27 "font.size": base_size, 28 "axes.titlesize": base_size + 4, 29 "axes.labelsize": base_size + 1, 30 "xtick.labelsize": base_size - 1, 31 "ytick.labelsize": base_size - 1, 32 "legend.fontsize": base_size - 1, 33 "axes.titleweight": "bold", 34 "axes.spines.top": False, 35 "axes.spines.right": False, 36 "axes.grid": True, 37 "grid.alpha": 0.25, 38 "grid.linewidth": 0.6, 39 "lines.linewidth": 2.2, 40 "figure.dpi": 110, 41 "savefig.bbox": "tight", 42 "axes.prop_cycle": mpl.cycler(color=PALETTE), 43 })
Readable defaults: larger type, no top/right spines, soft grid.
def
group_colors(metadata, palette=None) -> dict:
46def group_colors(metadata, palette=None) -> dict: 47 """One colour per condition, stable across plots.""" 48 palette = palette or PALETTE 49 groups = sorted(metadata.groups.unique()) 50 return {g: palette[i % len(palette)] for i, g in enumerate(groups)}
One colour per condition, stable across plots.
def
factor_style(metadata, color_by='genotype', style_by='supplement') -> dict:
53def factor_style(metadata, color_by="genotype", style_by="supplement") -> dict: 54 """Encode a two-factor design as colour x line style. 55 56 Returns per-sample styling plus the two lookup tables, so the legend can 57 be built per factor instead of per group. Falls back to plain per-group 58 colours when the structured factors are absent. 59 """ 60 tab = metadata.table 61 62 if color_by not in tab.columns: 63 lut = group_colors(metadata) 64 return { 65 "per_sample": {s: {"color": lut[g], "linestyle": "-"} 66 for s, g in metadata.groups.items()}, 67 "color_map": lut, 68 "style_map": {}, 69 "color_by": metadata.group_col, 70 "style_by": None, 71 } 72 73 c_levels = sorted(tab[color_by].unique()) 74 color_map = {lv: PALETTE[i % len(PALETTE)] for i, lv in enumerate(c_levels)} 75 76 if style_by in tab.columns: 77 s_levels = sorted(tab[style_by].unique()) 78 style_map = {lv: LINESTYLES[i % len(LINESTYLES)] 79 for i, lv in enumerate(s_levels)} 80 else: 81 style_by, style_map = None, {} 82 83 per_sample = { 84 s: { 85 "color": color_map[tab.loc[s, color_by]], 86 "linestyle": (style_map[tab.loc[s, style_by]] if style_by else "-"), 87 } 88 for s in metadata.samples 89 } 90 return {"per_sample": per_sample, "color_map": color_map, 91 "style_map": style_map, "color_by": color_by, "style_by": style_by}
Encode a two-factor design as colour x line style.
Returns per-sample styling plus the two lookup tables, so the legend can be built per factor instead of per group. Falls back to plain per-group colours when the structured factors are absent.
def
factor_legend(ax, style: dict, loc='best') -> None:
94def factor_legend(ax, style: dict, loc="best") -> None: 95 """Two-part legend: colour = factor 1, line style = factor 2.""" 96 from matplotlib.lines import Line2D 97 98 handles = [Line2D([], [], color="none", label=f"{style['color_by']}:")] 99 handles += [Line2D([], [], color=c, lw=3, label=f" {lv}") 100 for lv, c in style["color_map"].items()] 101 102 if style["style_map"]: 103 handles += [Line2D([], [], color="none", label=" "), 104 Line2D([], [], color="none", label=f"{style['style_by']}:")] 105 handles += [Line2D([], [], color=INK, ls=ls, lw=2, label=f" {lv}") 106 for lv, ls in style["style_map"].items()] 107 108 ax.legend(handles=handles, loc=loc, frameon=False, 109 handlelength=2.4, labelspacing=0.35)
Two-part legend: colour = factor 1, line style = factor 2.
def
short_labels(metadata, factors=('genotype', 'supplement')) -> list:
112def short_labels(metadata, factors=("genotype", "supplement")) -> list: 113 """Compact tick labels, e.g. 'del_atoC / LiCl_10mM'.""" 114 cols = [f for f in factors if f in metadata.table.columns] 115 if not cols: 116 return list(metadata.groups) 117 return metadata.table[cols].astype(str).agg("\n".join, axis=1).tolist()
Compact tick labels, e.g. 'del_atoC / LiCl_10mM'.
def
annotate_study(ax, metadata) -> None:
120def annotate_study(ax, metadata) -> None: 121 """Footer with organism and project.""" 122 bits = [metadata.study.get(k) for k in ("Organism", "BioProject")] 123 text = " · ".join(b for b in bits if b) 124 if text: 125 ax.figure.text(0.99, 0.005, text, ha="right", fontsize=8, color=GREY)
Footer with organism and project.