198 lines
5.6 KiB
Python
198 lines
5.6 KiB
Python
"""
|
|
Colorblind-Friendly Color Palettes for Scientific Visualization
|
|
|
|
This module provides carefully curated color palettes optimized for
|
|
scientific publications and accessibility.
|
|
|
|
Usage:
|
|
from color_palettes import OKABE_ITO, apply_palette
|
|
import matplotlib.pyplot as plt
|
|
|
|
apply_palette('okabe_ito')
|
|
plt.plot([1, 2, 3], [1, 4, 9])
|
|
"""
|
|
|
|
# Okabe-Ito Palette (2008)
|
|
# The most widely recommended colorblind-friendly palette
|
|
OKABE_ITO = {
|
|
'orange': '#E69F00',
|
|
'sky_blue': '#56B4E9',
|
|
'bluish_green': '#009E73',
|
|
'yellow': '#F0E442',
|
|
'blue': '#0072B2',
|
|
'vermillion': '#D55E00',
|
|
'reddish_purple': '#CC79A7',
|
|
'black': '#000000'
|
|
}
|
|
|
|
OKABE_ITO_LIST = ['#E69F00', '#56B4E9', '#009E73', '#F0E442',
|
|
'#0072B2', '#D55E00', '#CC79A7', '#000000']
|
|
|
|
# Wong Palette (Nature Methods)
|
|
WONG = ['#000000', '#E69F00', '#56B4E9', '#009E73',
|
|
'#F0E442', '#0072B2', '#D55E00', '#CC79A7']
|
|
|
|
# Paul Tol Palettes (https://personal.sron.nl/~pault/)
|
|
TOL_BRIGHT = ['#4477AA', '#EE6677', '#228833', '#CCBB44',
|
|
'#66CCEE', '#AA3377', '#BBBBBB']
|
|
|
|
TOL_MUTED = ['#332288', '#88CCEE', '#44AA99', '#117733',
|
|
'#999933', '#DDCC77', '#CC6677', '#882255', '#AA4499']
|
|
|
|
TOL_LIGHT = ['#77AADD', '#EE8866', '#EEDD88', '#FFAABB',
|
|
'#99DDFF', '#44BB99', '#BBCC33', '#AAAA00', '#DDDDDD']
|
|
|
|
TOL_HIGH_CONTRAST = ['#004488', '#DDAA33', '#BB5566']
|
|
|
|
# Sequential colormaps (for continuous data)
|
|
SEQUENTIAL_COLORMAPS = [
|
|
'viridis', # Default, perceptually uniform
|
|
'plasma', # Perceptually uniform
|
|
'inferno', # Perceptually uniform
|
|
'magma', # Perceptually uniform
|
|
'cividis', # Optimized for colorblind viewers
|
|
'YlOrRd', # Yellow-Orange-Red
|
|
'YlGnBu', # Yellow-Green-Blue
|
|
'Blues', # Single hue
|
|
'Greens', # Single hue
|
|
'Purples', # Single hue
|
|
]
|
|
|
|
# Diverging colormaps (for data with meaningful center)
|
|
DIVERGING_COLORMAPS_SAFE = [
|
|
'RdYlBu', # Red-Yellow-Blue (reversed is common)
|
|
'RdBu', # Red-Blue
|
|
'PuOr', # Purple-Orange (excellent for colorblind)
|
|
'BrBG', # Brown-Blue-Green (good for colorblind)
|
|
'PRGn', # Purple-Green (use with caution)
|
|
'PiYG', # Pink-Yellow-Green (use with caution)
|
|
]
|
|
|
|
# Diverging colormaps to AVOID (red-green combinations)
|
|
DIVERGING_COLORMAPS_AVOID = [
|
|
'RdGn', # Red-Green (problematic!)
|
|
'RdYlGn', # Red-Yellow-Green (problematic!)
|
|
]
|
|
|
|
# Fluorophore colors (traditional - use with caution)
|
|
FLUOROPHORES_TRADITIONAL = {
|
|
'DAPI': '#0000FF', # Blue
|
|
'GFP': '#00FF00', # Green (problematic for colorblind)
|
|
'RFP': '#FF0000', # Red
|
|
'Cy5': '#FF00FF', # Magenta
|
|
'YFP': '#FFFF00', # Yellow
|
|
}
|
|
|
|
# Fluorophore colors (colorblind-friendly alternatives)
|
|
FLUOROPHORES_ACCESSIBLE = {
|
|
'Channel1': '#0072B2', # Blue
|
|
'Channel2': '#E69F00', # Orange (instead of green)
|
|
'Channel3': '#D55E00', # Vermillion (instead of red)
|
|
'Channel4': '#CC79A7', # Magenta
|
|
'Channel5': '#F0E442', # Yellow
|
|
}
|
|
|
|
# Genomics/Bioinformatics
|
|
DNA_BASES = {
|
|
'A': '#00CC00', # Green
|
|
'C': '#0000CC', # Blue
|
|
'G': '#FFB300', # Orange
|
|
'T': '#CC0000', # Red
|
|
}
|
|
|
|
DNA_BASES_ACCESSIBLE = {
|
|
'A': '#009E73', # Bluish Green
|
|
'C': '#0072B2', # Blue
|
|
'G': '#E69F00', # Orange
|
|
'T': '#D55E00', # Vermillion
|
|
}
|
|
|
|
|
|
def apply_palette(palette_name='okabe_ito'):
|
|
"""
|
|
Apply a color palette to matplotlib's default color cycle.
|
|
|
|
Parameters
|
|
----------
|
|
palette_name : str
|
|
Name of the palette to apply. Options:
|
|
'okabe_ito', 'wong', 'tol_bright', 'tol_muted',
|
|
'tol_light', 'tol_high_contrast'
|
|
|
|
Returns
|
|
-------
|
|
list
|
|
List of colors in the palette
|
|
|
|
Examples
|
|
--------
|
|
>>> apply_palette('okabe_ito')
|
|
>>> plt.plot([1, 2, 3], [1, 4, 9]) # Uses Okabe-Ito colors
|
|
"""
|
|
try:
|
|
import matplotlib.pyplot as plt
|
|
except ImportError:
|
|
print("matplotlib not installed")
|
|
return None
|
|
|
|
palettes = {
|
|
'okabe_ito': OKABE_ITO_LIST,
|
|
'wong': WONG,
|
|
'tol_bright': TOL_BRIGHT,
|
|
'tol_muted': TOL_MUTED,
|
|
'tol_light': TOL_LIGHT,
|
|
'tol_high_contrast': TOL_HIGH_CONTRAST,
|
|
}
|
|
|
|
if palette_name not in palettes:
|
|
available = ', '.join(palettes.keys())
|
|
raise ValueError(f"Palette '{palette_name}' not found. Available: {available}")
|
|
|
|
colors = palettes[palette_name]
|
|
plt.rcParams['axes.prop_cycle'] = plt.cycler(color=colors)
|
|
return colors
|
|
|
|
|
|
def get_palette(palette_name='okabe_ito'):
|
|
"""
|
|
Get a color palette as a list.
|
|
|
|
Parameters
|
|
----------
|
|
palette_name : str
|
|
Name of the palette
|
|
|
|
Returns
|
|
-------
|
|
list
|
|
List of color hex codes
|
|
"""
|
|
palettes = {
|
|
'okabe_ito': OKABE_ITO_LIST,
|
|
'wong': WONG,
|
|
'tol_bright': TOL_BRIGHT,
|
|
'tol_muted': TOL_MUTED,
|
|
'tol_light': TOL_LIGHT,
|
|
'tol_high_contrast': TOL_HIGH_CONTRAST,
|
|
}
|
|
|
|
if palette_name not in palettes:
|
|
available = ', '.join(palettes.keys())
|
|
raise ValueError(f"Palette '{palette_name}' not found. Available: {available}")
|
|
|
|
return palettes[palette_name]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("Available colorblind-friendly palettes:")
|
|
print(f" - Okabe-Ito: {len(OKABE_ITO_LIST)} colors")
|
|
print(f" - Wong: {len(WONG)} colors")
|
|
print(f" - Tol Bright: {len(TOL_BRIGHT)} colors")
|
|
print(f" - Tol Muted: {len(TOL_MUTED)} colors")
|
|
print(f" - Tol Light: {len(TOL_LIGHT)} colors")
|
|
print(f" - Tol High Contrast: {len(TOL_HIGH_CONTRAST)} colors")
|
|
|
|
print("\nOkabe-Ito palette (most recommended):")
|
|
for name, color in OKABE_ITO.items():
|
|
print(f" {name:15s}: {color}")
|