--- a +++ b/src/scpanel/palettes.py @@ -0,0 +1,231 @@ +"""Color palettes in addition to matplotlib's palettes.""" + +from typing import Mapping, Sequence + +from matplotlib import cm, colors + +tab10 = [ + "#1f77b4", + "#ff7f0e", + "#2ca02c", + "#d62728", + "#9467bd", + "#8c564b", + "#e377c2", + "#7f7f7f", + "#bcbd22", + "#17becf", +] +# Colorblindness adjusted vega_10 +# See https://github.com/scverse/scanpy/issues/387 +vega_10 = list(map(colors.to_hex, cm.tab10.colors)) +vega_10_scanpy = vega_10.copy() +vega_10_scanpy[2] = "#279e68" # green +vega_10_scanpy[4] = "#aa40fc" # purple +vega_10_scanpy[8] = "#b5bd61" # kakhi + +# default matplotlib 2.0 palette +# see 'category20' on https://github.com/vega/vega/wiki/Scales#scale-range-literals +vega_20 = list(map(colors.to_hex, cm.tab20.colors)) + +# reorderd, some removed, some added +vega_20_scanpy = [ + # dark without grey: + *vega_20[0:14:2], + *vega_20[16::2], + # light without grey: + *vega_20[1:15:2], + *vega_20[17::2], + # manual additions: + "#ad494a", + "#8c6d31", +] +vega_20_scanpy[2] = vega_10_scanpy[2] +vega_20_scanpy[4] = vega_10_scanpy[4] +vega_20_scanpy[7] = vega_10_scanpy[8] # kakhi shifted by missing grey +## ['#1f77b4','#aec7e8','#ff7f0e','#ffbb78','#2ca02c','#98df8a', +## '#d62728','#ff9896','#9467bd','#c5b0d5','#8c564b','#c49c94', +## '#e377c2','#f7b6d2','#7f7f7f','#c7c7c7','#bcbd22','#dbdb8d','#17becf','#9edae5'] +# TODO: also replace pale colors if necessary + +default_20 = vega_20_scanpy + +# https://graphicdesign.stackexchange.com/questions/3682/where-can-i-find-a-large-palette-set-of-contrasting-colors-for-coloring-many-d +# update 1 +# orig reference https://research.wu.ac.at/en/publications/escaping-rgbland-selecting-colors-for-statistical-graphics-26 +zeileis_28 = [ + "#023fa5", + "#7d87b9", + "#bec1d4", + "#d6bcc0", + "#bb7784", + "#8e063b", + "#4a6fe3", + "#8595e1", + "#b5bbe3", + "#e6afb9", + "#e07b91", + "#d33f6a", + "#11c638", + "#8dd593", + "#c6dec7", + "#ead3c6", + "#f0b98d", + "#ef9708", + "#0fcfc0", + "#9cded6", + "#d5eae7", + "#f3e1eb", + "#f6c4e1", + "#f79cd4", + # these last ones were added: + "#7f7f7f", + "#c7c7c7", + "#1CE6FF", + "#336600", +] + +default_28 = zeileis_28 + +# from https://godsnotwheregodsnot.blogspot.com/2012/09/color-distribution-methodology.html +godsnot_102 = [ + # "#000000", # remove the black, as often, we have black colored annotation + "#FFFF00", + "#1CE6FF", + "#FF34FF", + "#FF4A46", + "#008941", + "#006FA6", + "#A30059", + "#FFDBE5", + "#7A4900", + "#0000A6", + "#63FFAC", + "#B79762", + "#004D43", + "#8FB0FF", + "#997D87", + "#5A0007", + "#809693", + "#6A3A4C", + "#1B4400", + "#4FC601", + "#3B5DFF", + "#4A3B53", + "#FF2F80", + "#61615A", + "#BA0900", + "#6B7900", + "#00C2A0", + "#FFAA92", + "#FF90C9", + "#B903AA", + "#D16100", + "#DDEFFF", + "#000035", + "#7B4F4B", + "#A1C299", + "#300018", + "#0AA6D8", + "#013349", + "#00846F", + "#372101", + "#FFB500", + "#C2FFED", + "#A079BF", + "#CC0744", + "#C0B9B2", + "#C2FF99", + "#001E09", + "#00489C", + "#6F0062", + "#0CBD66", + "#EEC3FF", + "#456D75", + "#B77B68", + "#7A87A1", + "#788D66", + "#885578", + "#FAD09F", + "#FF8A9A", + "#D157A0", + "#BEC459", + "#456648", + "#0086ED", + "#886F4C", + "#34362D", + "#B4A8BD", + "#00A6AA", + "#452C2C", + "#636375", + "#A3C8C9", + "#FF913F", + "#938A81", + "#575329", + "#00FECF", + "#B05B6F", + "#8CD0FF", + "#3B9700", + "#04F757", + "#C8A1A1", + "#1E6E00", + "#7900D7", + "#A77500", + "#6367A9", + "#A05837", + "#6B002C", + "#772600", + "#D790FF", + "#9B9700", + "#549E79", + "#FFF69F", + "#201625", + "#72418F", + "#BC23FF", + "#99ADC0", + "#3A2465", + "#922329", + "#5B4534", + "#FDE8DC", + "#404E55", + "#0089A3", + "#CB7E98", + "#A4E804", + "#324E72", +] + +default_102 = godsnot_102 + + +def _plot_color_cycle(clists: Mapping[str, Sequence[str]]): + import matplotlib.pyplot as plt + import numpy as np + from matplotlib.colors import BoundaryNorm, ListedColormap + + fig, axes = plt.subplots(nrows=len(clists)) # type: plt.Figure, plt.Axes + fig.subplots_adjust(top=0.95, bottom=0.01, left=0.3, right=0.99) + axes[0].set_title("Color Maps/Cycles", fontsize=14) + + for ax, (name, clist) in zip(axes, clists.items()): + n = len(clist) + ax.imshow( + np.arange(n)[None, :].repeat(2, 0), + aspect="auto", + cmap=ListedColormap(clist), + norm=BoundaryNorm(np.arange(n + 1) - 0.5, n), + ) + pos = list(ax.get_position().bounds) + x_text = pos[0] - 0.01 + y_text = pos[1] + pos[3] / 2.0 + fig.text(x_text, y_text, name, va="center", ha="right", fontsize=10) + + # Turn off all ticks & spines + for ax in axes: + ax.set_axis_off() + fig.show() + + +if __name__ == "__main__": + _plot_color_cycle( + {name: colors for name, colors in globals().items() if isinstance(colors, list)} + )