Skip to content

palettes

The palettes module provides color palettes optimized for data visualization.

Module Overview

This module provides:

  • Four built-in MSU-branded palettes
  • MSUPalette class for palette management
  • Functions to access and list palettes
  • Format conversion for matplotlib, seaborn, and plotly

Classes

MSUPalette

MSU Color Palette class.

This class represents a color palette and provides methods to: - Get colors as hex strings - Get colors as RGB tuples - Generate matplotlib colormaps - Interpolate colors for continuous palettes

Source code in msuthemes/palettes.py
class MSUPalette:
    """MSU Color Palette class.

    This class represents a color palette and provides methods to:
    - Get colors as hex strings
    - Get colors as RGB tuples
    - Generate matplotlib colormaps
    - Interpolate colors for continuous palettes
    """

    def __init__(self, colors: List[str], palette_type: str, name: str = ""):
        """Initialize an MSU color palette.

        Args:
            colors: List of hex color codes
            palette_type: Type of palette ("seq", "div", or "qual")
            name: Name of the palette
        """
        self.colors = colors
        self.palette_type = palette_type
        self.name = name
        self._validate()

    def _validate(self):
        """Validate palette configuration."""
        valid_types = ["seq", "div", "qual", "core"]
        if self.palette_type not in valid_types:
            raise ValueError(
                f"Invalid palette type: {self.palette_type}. "
                f"Must be one of {valid_types}"
            )

        if not self.colors:
            raise ValueError("Palette must contain at least one color")

    def __repr__(self) -> str:
        """String representation of the palette."""
        return f"MSUPalette(name='{self.name}', type='{self.palette_type}', n_colors={len(self.colors)})"

    def __len__(self) -> int:
        """Return the number of colors in the palette."""
        return len(self.colors)

    def as_hex(self, n_colors: Optional[int] = None, reverse: bool = False) -> List[str]:
        """Get colors as hex strings.

        Args:
            n_colors: Number of colors to return. If None, returns all colors.
                     If greater than palette length, interpolates (continuous mode).
            reverse: If True, reverse the color order

        Returns:
            List of hex color strings

        Examples:
            >>> palette = msu_seq
            >>> palette.as_hex()  # All colors
            >>> palette.as_hex(n_colors=5)  # 5 colors
            >>> palette.as_hex(n_colors=5, reverse=True)  # 5 colors, reversed
        """
        if n_colors is None:
            colors = self.colors[::-1] if reverse else self.colors
            return colors

        if n_colors <= 0:
            raise ValueError("n_colors must be positive")

        # Get the colors without reversing first
        if n_colors <= len(self.colors):
            # For discrete selection, evenly space the colors
            if n_colors == len(self.colors):
                result = self.colors[:]
            else:
                indices = np.linspace(0, len(self.colors) - 1, n_colors).astype(int)
                result = [self.colors[i] for i in indices]
        else:
            # If requesting more colors than available, interpolate
            result = self._interpolate_colors(n_colors, reverse=False)

        # Apply reverse after selection
        return result[::-1] if reverse else result

    def as_rgb(self, n_colors: Optional[int] = None, reverse: bool = False) -> List[tuple]:
        """Get colors as RGB tuples (0-255 range).

        Args:
            n_colors: Number of colors to return. If None, returns all colors.
                     If greater than palette length, interpolates (continuous mode).
            reverse: If True, reverse the color order

        Returns:
            List of RGB tuples with values in 0-255 range

        Examples:
            >>> palette = msu_seq
            >>> palette.as_rgb()  # All colors as RGB
            >>> palette.as_rgb(n_colors=5)  # 5 colors as RGB
            >>> palette.as_rgb(n_colors=5, reverse=True)  # 5 colors, reversed
        """
        hex_colors = self.as_hex(n_colors=n_colors, reverse=reverse)

        rgb_colors = []
        for hex_color in hex_colors:
            hex_color = hex_color.lstrip('#')
            r = int(hex_color[0:2], 16)
            g = int(hex_color[2:4], 16)
            b = int(hex_color[4:6], 16)
            rgb_colors.append((r, g, b))

        return rgb_colors

    def _hex_to_rgb_normalized(self, hex_color: str) -> tuple:
        """Convert hex to normalized RGB (0-1)."""
        hex_color = hex_color.lstrip('#')
        r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
        return (r / 255.0, g / 255.0, b / 255.0)

    def _rgb_to_hex(self, rgb: tuple) -> str:
        """Convert normalized RGB (0-1) to hex."""
        r, g, b = int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255)
        return f'#{r:02X}{g:02X}{b:02X}'

    def _interpolate_colors(self, n_colors: int, reverse: bool = False) -> List[str]:
        """Interpolate colors to generate more colors than in palette.

        Args:
            n_colors: Number of colors to generate
            reverse: Whether to reverse the palette

        Returns:
            List of interpolated hex colors
        """
        colors = self.colors[::-1] if reverse else self.colors

        # Convert hex to RGB
        rgb_colors = [self._hex_to_rgb_normalized(c) for c in colors]

        # Create interpolation positions
        positions = np.linspace(0, 1, len(rgb_colors))
        new_positions = np.linspace(0, 1, n_colors)

        # Interpolate each channel
        r_interp = np.interp(new_positions, positions, [c[0] for c in rgb_colors])
        g_interp = np.interp(new_positions, positions, [c[1] for c in rgb_colors])
        b_interp = np.interp(new_positions, positions, [c[2] for c in rgb_colors])

        # Convert back to hex
        interpolated_colors = [
            self._rgb_to_hex((r, g, b))
            for r, g, b in zip(r_interp, g_interp, b_interp)
        ]

        return interpolated_colors

    def as_matplotlib_cmap(self, name: Optional[str] = None, n_colors: int = 256) -> Union[LinearSegmentedColormap, ListedColormap]:
        """Create a matplotlib colormap from the palette.

        Args:
            name: Name for the colormap (defaults to palette name)
            n_colors: Number of discrete colors for qualitative palettes

        Returns:
            matplotlib colormap object

        Examples:
            >>> import matplotlib.pyplot as plt
            >>> cmap = msu_seq.as_matplotlib_cmap()
            >>> plt.imshow([[1,2,3]], cmap=cmap)
        """
        cmap_name = name or self.name or "msu_palette"

        if self.palette_type in ["seq", "div"]:
            # Create continuous colormap with interpolation
            colors_rgb = [self._hex_to_rgb_normalized(c) for c in self.colors]
            return LinearSegmentedColormap.from_list(cmap_name, colors_rgb, N=256)
        else:
            # Create discrete colormap for qualitative/core palettes
            colors_rgb = [self._hex_to_rgb_normalized(c) for c in self.colors]
            return ListedColormap(colors_rgb, name=cmap_name)

    def show(self, n_colors: Optional[int] = None):
        """Display the palette colors (requires matplotlib).

        Args:
            n_colors: Number of colors to display (default: all)
        """
        try:
            import matplotlib.pyplot as plt
            import matplotlib.patches as mpatches
        except ImportError:
            raise ImportError("matplotlib is required to display palettes")

        colors = self.as_hex(n_colors=n_colors)

        fig, ax = plt.subplots(figsize=(len(colors) * 0.5, 1))
        ax.set_xlim(0, len(colors))
        ax.set_ylim(0, 1)
        ax.axis('off')

        for i, color in enumerate(colors):
            rect = mpatches.Rectangle((i, 0), 1, 1, facecolor=color, edgecolor='black', linewidth=0.5)
            ax.add_patch(rect)
            ax.text(i + 0.5, 0.5, color, ha='center', va='center',
                   fontsize=7, rotation=90, color='white' if i % 2 == 0 else 'black')

        plt.title(f"{self.name} ({self.palette_type})")
        plt.tight_layout()
        plt.show()

Functions

__init__(colors, palette_type, name='')

Initialize an MSU color palette.

Args: colors: List of hex color codes palette_type: Type of palette ("seq", "div", or "qual") name: Name of the palette

Source code in msuthemes/palettes.py
def __init__(self, colors: List[str], palette_type: str, name: str = ""):
    """Initialize an MSU color palette.

    Args:
        colors: List of hex color codes
        palette_type: Type of palette ("seq", "div", or "qual")
        name: Name of the palette
    """
    self.colors = colors
    self.palette_type = palette_type
    self.name = name
    self._validate()

as_hex(n_colors=None, reverse=False)

Get colors as hex strings.

Args: n_colors: Number of colors to return. If None, returns all colors. If greater than palette length, interpolates (continuous mode). reverse: If True, reverse the color order

Returns: List of hex color strings

Examples: >>> palette = msu_seq >>> palette.as_hex() # All colors >>> palette.as_hex(n_colors=5) # 5 colors >>> palette.as_hex(n_colors=5, reverse=True) # 5 colors, reversed

Source code in msuthemes/palettes.py
def as_hex(self, n_colors: Optional[int] = None, reverse: bool = False) -> List[str]:
    """Get colors as hex strings.

    Args:
        n_colors: Number of colors to return. If None, returns all colors.
                 If greater than palette length, interpolates (continuous mode).
        reverse: If True, reverse the color order

    Returns:
        List of hex color strings

    Examples:
        >>> palette = msu_seq
        >>> palette.as_hex()  # All colors
        >>> palette.as_hex(n_colors=5)  # 5 colors
        >>> palette.as_hex(n_colors=5, reverse=True)  # 5 colors, reversed
    """
    if n_colors is None:
        colors = self.colors[::-1] if reverse else self.colors
        return colors

    if n_colors <= 0:
        raise ValueError("n_colors must be positive")

    # Get the colors without reversing first
    if n_colors <= len(self.colors):
        # For discrete selection, evenly space the colors
        if n_colors == len(self.colors):
            result = self.colors[:]
        else:
            indices = np.linspace(0, len(self.colors) - 1, n_colors).astype(int)
            result = [self.colors[i] for i in indices]
    else:
        # If requesting more colors than available, interpolate
        result = self._interpolate_colors(n_colors, reverse=False)

    # Apply reverse after selection
    return result[::-1] if reverse else result

as_rgb(n_colors=None, reverse=False)

Get colors as RGB tuples (0-255 range).

Args: n_colors: Number of colors to return. If None, returns all colors. If greater than palette length, interpolates (continuous mode). reverse: If True, reverse the color order

Returns: List of RGB tuples with values in 0-255 range

Examples: >>> palette = msu_seq >>> palette.as_rgb() # All colors as RGB >>> palette.as_rgb(n_colors=5) # 5 colors as RGB >>> palette.as_rgb(n_colors=5, reverse=True) # 5 colors, reversed

Source code in msuthemes/palettes.py
def as_rgb(self, n_colors: Optional[int] = None, reverse: bool = False) -> List[tuple]:
    """Get colors as RGB tuples (0-255 range).

    Args:
        n_colors: Number of colors to return. If None, returns all colors.
                 If greater than palette length, interpolates (continuous mode).
        reverse: If True, reverse the color order

    Returns:
        List of RGB tuples with values in 0-255 range

    Examples:
        >>> palette = msu_seq
        >>> palette.as_rgb()  # All colors as RGB
        >>> palette.as_rgb(n_colors=5)  # 5 colors as RGB
        >>> palette.as_rgb(n_colors=5, reverse=True)  # 5 colors, reversed
    """
    hex_colors = self.as_hex(n_colors=n_colors, reverse=reverse)

    rgb_colors = []
    for hex_color in hex_colors:
        hex_color = hex_color.lstrip('#')
        r = int(hex_color[0:2], 16)
        g = int(hex_color[2:4], 16)
        b = int(hex_color[4:6], 16)
        rgb_colors.append((r, g, b))

    return rgb_colors

as_matplotlib_cmap(name=None, n_colors=256)

Create a matplotlib colormap from the palette.

Args: name: Name for the colormap (defaults to palette name) n_colors: Number of discrete colors for qualitative palettes

Returns: matplotlib colormap object

Examples: >>> import matplotlib.pyplot as plt >>> cmap = msu_seq.as_matplotlib_cmap() >>> plt.imshow([[1,2,3]], cmap=cmap)

Source code in msuthemes/palettes.py
def as_matplotlib_cmap(self, name: Optional[str] = None, n_colors: int = 256) -> Union[LinearSegmentedColormap, ListedColormap]:
    """Create a matplotlib colormap from the palette.

    Args:
        name: Name for the colormap (defaults to palette name)
        n_colors: Number of discrete colors for qualitative palettes

    Returns:
        matplotlib colormap object

    Examples:
        >>> import matplotlib.pyplot as plt
        >>> cmap = msu_seq.as_matplotlib_cmap()
        >>> plt.imshow([[1,2,3]], cmap=cmap)
    """
    cmap_name = name or self.name or "msu_palette"

    if self.palette_type in ["seq", "div"]:
        # Create continuous colormap with interpolation
        colors_rgb = [self._hex_to_rgb_normalized(c) for c in self.colors]
        return LinearSegmentedColormap.from_list(cmap_name, colors_rgb, N=256)
    else:
        # Create discrete colormap for qualitative/core palettes
        colors_rgb = [self._hex_to_rgb_normalized(c) for c in self.colors]
        return ListedColormap(colors_rgb, name=cmap_name)

Palette Objects

msu_seq = MSUPalette(colors=['#18453B', '#2F574E', '#466A62', '#5D7C75', '#748F89', '#8BA29D', '#A2B4B0', '#B9C7C4', '#D0D9D7', '#E7ECEB'], palette_type='seq', name='msu_seq') module-attribute

MSU Sequential Palette - Main (10 colors, green-based)

msu_div = MSUPalette(colors=['#E41B12', '#EB5751', '#F29490', '#F9D1CF', '#D4D4D4', '#CCEBF3', '#88D0E3', '#43B6D3', '#009CC4'], palette_type='div', name='msu_div') module-attribute

MSU Diverging Palette (9 colors, red to blue)

msu_qual1 = MSUPalette(colors=['#18453B', '#0DB14B', '#97A2A2', '#F08521', '#008183', '#909AB7', '#535054', '#D1DE3F', '#E8D9B5', '#C89A58', '#94AE4A', '#6E005F', '#CB5A28'], palette_type='qual', name='msu_qual1') module-attribute

MSU Qualitative Palette 1 (13 colors for categorical data)

msu_qual2 = MSUPalette(colors=['#18453B', '#F08521', '#6E005F', '#97A2A2'], palette_type='qual', name='msu_qual2') module-attribute

MSU Qualitative Palette 2 (4 colors for categorical data)

Functions

get_palette(name)

Get a palette by name.

Args: name: Name of the palette

Returns: MSUPalette object

Raises: ValueError: If palette name not found

Examples: >>> palette = get_palette("msu_seq") >>> colors = palette.as_hex(n_colors=5)

Source code in msuthemes/palettes.py
def get_palette(name: str) -> MSUPalette:
    """Get a palette by name.

    Args:
        name: Name of the palette

    Returns:
        MSUPalette object

    Raises:
        ValueError: If palette name not found

    Examples:
        >>> palette = get_palette("msu_seq")
        >>> colors = palette.as_hex(n_colors=5)
    """
    if name not in MSU_PALETTES:
        available = ", ".join(MSU_PALETTES.keys())
        raise ValueError(
            f"Palette '{name}' not found. Available palettes: {available}"
        )
    return MSU_PALETTES[name]

list_palettes()

List all available palette names.

Returns: List of palette names

Examples: >>> palettes = list_palettes() >>> print(palettes) ['msu_seq', 'msu_seq_red', ...]

Source code in msuthemes/palettes.py
def list_palettes() -> List[str]:
    """List all available palette names.

    Returns:
        List of palette names

    Examples:
        >>> palettes = list_palettes()
        >>> print(palettes)
        ['msu_seq', 'msu_seq_red', ...]
    """
    return list(MSU_PALETTES.keys())

Palette Dictionary

MSU_PALETTES = {'msu_seq': msu_seq, 'msu_seq2': msu_seq2, 'msu_seq_red': msu_seq_red, 'msu_seq_purple': msu_seq_purple, 'msu_seq_yellow': msu_seq_yellow, 'msu_seq_blue': msu_seq_blue, 'msu_seq_orange': msu_seq_orange, 'msu_seq_green': msu_seq_green, 'msu_div': msu_div, 'msu_div2': msu_div2, 'msu_core': msu_core, 'msu_qual1': msu_qual1, 'msu_qual2': msu_qual2, 'bigten_primary': bigten_primary, 'bigten_secondary': bigten_secondary} module-attribute

Dictionary of all MSU palettes for easy access by name

Usage Examples

Getting a Palette

from msuthemes import get_palette

# Get sequential palette
seq = get_palette('msu_seq')
colors = seq.as_hex()
print(colors)

Using with Matplotlib

from msuthemes import msu_seq
import matplotlib.pyplot as plt
import numpy as np

data = np.random.rand(10, 10)
plt.imshow(data, cmap=msu_seq.as_matplotlib_cmap())
plt.colorbar()
plt.show()

Using with Seaborn

from msuthemes import msu_qual1
import seaborn as sns

palette = msu_qual1.as_seaborn_palette()
sns.boxplot(data=data, palette=palette)
plt.show()

Creating Custom Palettes

from msuthemes.palettes import MSUPalette

# Create custom palette
custom = MSUPalette(
    name='my_palette',
    colors=['#18453b', '#ff6e1b', '#008183'],
    palette_type='qual'
)

hex_colors = custom.as_hex()
rgb_colors = custom.as_rgb()

See Also