Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ coverage.xml
htmlcov

# gh-pages build script output
/gh-pages
/gh-pages

# OS Files
.DS_Store
Binary file removed assets/frappe/bar.png
Binary file not shown.
Binary file removed assets/frappe/boxplot.png
Binary file not shown.
Binary file removed assets/frappe/imshow.png
Binary file not shown.
Binary file removed assets/frappe/palette.png
Binary file not shown.
Binary file removed assets/frappe/patches.png
Binary file not shown.
Binary file removed assets/frappe/plot.png
Binary file not shown.
Binary file removed assets/frappe/scatter.png
Binary file not shown.
Binary file removed assets/latte/bar.png
Binary file not shown.
Binary file removed assets/latte/boxplot.png
Binary file not shown.
Binary file removed assets/latte/imshow.png
Binary file not shown.
Binary file removed assets/latte/palette.png
Binary file not shown.
Binary file removed assets/latte/patches.png
Binary file not shown.
Binary file removed assets/latte/plot.png
Binary file not shown.
Binary file removed assets/latte/scatter.png
Binary file not shown.
Binary file removed assets/macchiato/bar.png
Binary file not shown.
Binary file removed assets/macchiato/boxplot.png
Binary file not shown.
Binary file removed assets/macchiato/imshow.png
Binary file not shown.
Binary file removed assets/macchiato/palette.png
Binary file not shown.
Binary file removed assets/macchiato/patches.png
Binary file not shown.
Binary file removed assets/macchiato/plot.png
Binary file not shown.
Binary file removed assets/macchiato/scatter.png
Binary file not shown.
Binary file removed assets/mocha/bar.png
Binary file not shown.
Binary file removed assets/mocha/boxplot.png
Binary file not shown.
Binary file removed assets/mocha/imshow.png
Binary file not shown.
Binary file removed assets/mocha/palette.png
Binary file not shown.
Binary file removed assets/mocha/patches.png
Diff not rendered.
Binary file removed assets/mocha/plot.png
Diff not rendered.
Binary file removed assets/mocha/scatter.png
Diff not rendered.
68 changes: 23 additions & 45 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import json
import subprocess
from dataclasses import asdict
from importlib import reload
from pathlib import Path
from typing import Any, cast

import catppuccin
from catppuccin.models import HSL, RGB, Color, Flavor, FlavorColors, Palette
from example_plots import example_plots, plot_palette

HEADER = '''"""Catppuccin palette definition."""
from catppuccin.models import HSL, RGB, Color, Flavor, FlavorColors, Palette'''
Expand Down Expand Up @@ -54,7 +55,7 @@ def make_flavor(identifier: str, fields: dict[str, Any]) -> Flavor:
)


def codegen() -> str:
def palette_codegen() -> str:
"""Generate contents of `catppuccin/palette.py`."""
palette_json = load_palette_json()
palette = Palette(
Expand All @@ -72,26 +73,11 @@ def codegen() -> str:
return "\n".join(lines)


if __name__ == "__main__":
print("running codegen")
palette_path = Path.cwd() / "catppuccin" / "palette.py"
with palette_path.open("w", newline="\n") as f:
f.write(codegen())
print("formatting with ruff")
ruff_format = f"ruff format {palette_path}"
subprocess.run(ruff_format.split(), check=True, stdout=subprocess.DEVNULL)
print("palette.py generation complete")

# Generate the matplotlib styles
print("generating matplotlib styles")
from catppuccin.extras.matplotlib import CATPPUCCIN_STYLE_DIRECTORY
from catppuccin.palette import PALETTE
def generate_mpl_styles() -> None:
"""Generate the matplotlib .mplstyle files."""
template_text = (Path.cwd() / "matplotlib_template.txt").read_text()

template_text = (
CATPPUCCIN_STYLE_DIRECTORY / "_catppuccin_template.txt"
).read_text()

for key, palette in asdict(PALETTE).items():
for key, palette in asdict(catppuccin.PALETTE).items():
print(f"- {key}")
text = template_text
text = text.replace("<palette>", key)
Expand All @@ -100,34 +86,26 @@ def codegen() -> str:
f"<{color}>",
palette["colors"][color]["hex"].replace("#", ""),
)
style_path = CATPPUCCIN_STYLE_DIRECTORY / f"{key}.mplstyle"
style_path = Path(catppuccin.__file__).parent / f"{key}.mplstyle"
with style_path.open("w", newline="\n") as f:
f.write(text)
print("matplotlib styles generation complete")

# Generate matplotlib assets for the docs
import matplotlib as mpl
import matplotlib.pyplot as plt

import catppuccin # This loads the styles in matplotlib # noqa: F401

print("generating matplotlib asset images")
for palette_name in asdict(PALETTE):
print(f"- {palette_name}")
mpl.style.use(palette_name)
def main() -> None: # noqa: D103
print("running palette codegen")
palette_path = Path.cwd() / "catppuccin" / "palette.py"
with palette_path.open("w", newline="\n") as f:
f.write(palette_codegen())
print("formatting with ruff")
ruff_format = f"ruff format {palette_path}"
subprocess.run(ruff_format.split(), check=True, stdout=subprocess.DEVNULL)
print("palette.py generation complete")

palette_path = Path.cwd() / "assets" / palette_name
palette_path.mkdir(exist_ok=True, parents=True)
print("generating matplotlib styles")
reload(catppuccin) # Reload the palette
generate_mpl_styles()
print("matplotlib styles generation complete")

# Plot palette separately
print(" - palette")
fig = plot_palette(palette_name)
fig.savefig(palette_path / "palette.png", dpi=DPI)

# Plot examples
for filename, plot_function in example_plots.items():
print(f" - {filename}")
fig = plot_function()
fig.savefig(palette_path / f"{filename}.png", dpi=DPI)
plt.close()
print("matplotlib asset image generation complete")
if __name__ == "__main__":
main()
5 changes: 1 addition & 4 deletions catppuccin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,4 @@

# Attempt to register styles and colormaps if matplotlib is available
if importlib.util.find_spec("matplotlib") is not None:
from catppuccin.extras.matplotlib import _register_colormap_list, _register_styles

_register_styles()
_register_colormap_list()
import catppuccin.extras.matplotlib # noqa: F401
143 changes: 63 additions & 80 deletions catppuccin/extras/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,106 +9,89 @@

1. Load a style, using `mpl.style.use`

```python
import catppuccin
import matplotlib as mpl
import matplotlib.pyplot as plt
```python
import catppuccin
import matplotlib.pyplot as plt

mpl.style.use(catppuccin.PALETTE.mocha.identifier)
plt.plot([0,1,2,3], [1,2,3,4])
plt.show()
```
plt.style.use(catppuccin.PALETTE.mocha.matplotlib_style)
plt.plot([0,1,2,3], [1,2,3,4])
plt.show()
```

1. Mix it with different stylesheets!

```python
import catppuccin
import matplotlib as mpl
import matplotlib.pyplot as plt
```python
import catppuccin
import matplotlib.pyplot as plt

mpl.style.use(["ggplot", catppuccin.PALETTE.mocha.identifier])
plt.plot([0,1,2,3], [1,2,3,4])
plt.show()
```
plt.style.use(["ggplot", catppuccin.PALETTE.mocha.matplotlib_style])
plt.plot([0,1,2,3], [1,2,3,4])
plt.show()
```

1. Load individual colors

```python
import matplotlib.pyplot as plt
import catppuccin
from catppuccin.extras.matplotlib import load_color
```python
import matplotlib.pyplot as plt
import catppuccin

color = load_color(catppuccin.PALETTE.latte.identifier, "peach")
plt.plot([0,1,2,3], [1,2,3,4], color=color)
plt.show()
```
plt.plot([0,1,2,3], [1,2,3,4], color=catppuccin.PALETTE.latte.colors.peach.hex)
plt.show()
```

1. Define custom colormaps

```python
import matplotlib.pyplot as plt
import numpy as np
import catppuccin
from catppuccin.extras.matplotlib import get_colormap_from_list

cmap = get_colormap_from_list(
catppuccin.PALETTE.frappe.identifier,
["red", "peach", "yellow", "green"],
)
rng = np.random.default_rng()
data = rng.integers(2, size=(30, 30))

plt.imshow(data, cmap=cmap)
plt.show()
```
"""

from __future__ import annotations

from dataclasses import asdict
from pathlib import Path
from typing import TYPE_CHECKING, cast

import matplotlib as mpl
import matplotlib.colors
import matplotlib.style
1. Just use the colormaps in your plots

from catppuccin.palette import PALETTE
```python
import matplotlib.pyplot as plt
import numpy as np
import catppuccin

if TYPE_CHECKING:
from collections.abc import Iterable
rng = np.random.default_rng()
data = rng.integers(10, size=(30, 30))

CATPPUCCIN_STYLE_DIRECTORY = Path(__file__).parent / "matplotlib_styles"
DEFAULT_COLORMAP_COLORS = ("base", "blue")
plt.imshow(data, cmap=catppuccin.PALETTE.mocha.cmap)
plt.show()
```

1. Define custom colormaps

def _register_styles() -> None:
"""Register the included stylesheets in the mpl style library."""
catppuccin_stylesheets = mpl.style.core.read_style_directory( # type: ignore [attr-defined]
CATPPUCCIN_STYLE_DIRECTORY
```python
import matplotlib.pyplot as plt
import numpy as np
import catppuccin
from matplotlib.colors import LinearSegmentedColormap

cmap = LinearSegmentedColormap.from_list(
"my_custom_palette",
[
color.hex for color in catppuccin.PALETTE.frappe.colors
if color.identifier in ["red", "peach", "yellow", "green"]
],
)
mpl.style.core.update_nested_dict(mpl.style.library, catppuccin_stylesheets) # type: ignore [attr-defined]
rng = np.random.default_rng()
data = rng.integers(10, size=(30, 30))

plt.imshow(data, cmap=cmap)
plt.show()
```
"""

def _register_colormap_list() -> None:
"""Register the included color maps in the `matplotlib` colormap library."""
for palette_name in asdict(PALETTE):
cmap = get_colormap_from_list(palette_name, DEFAULT_COLORMAP_COLORS)
mpl.colormaps.register(cmap=cmap, name=palette_name, force=True)
import matplotlib as mpl
import matplotlib.colors

from catppuccin.palette import PALETTE

def get_colormap_from_list(
palette_name: str,
color_names: Iterable[str],
) -> matplotlib.colors.LinearSegmentedColormap:
"""Get a `matplotlib` colormap from a list of colors for a specific palette."""
colors = [load_color(palette_name, color_name) for color_name in color_names]
return matplotlib.colors.LinearSegmentedColormap.from_list(palette_name, colors)
DEFAULT_COLORMAP_COLORS = ("blue", "teal", "yellow", "peach", "red")


def load_color(palette_name: str, color_name: str) -> str:
"""Load the color for a given palette and color name."""
return cast(
str,
PALETTE.__getattribute__(palette_name).colors.__getattribute__(color_name).hex,
# Register the included color maps in the `matplotlib` colormap library.
for _palette in PALETTE:
_cmap = matplotlib.colors.LinearSegmentedColormap.from_list(
_palette.identifier,
[
color.hex
for color in PALETTE.frappe.colors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be _palette.colors instead?

Suggested change
for color in PALETTE.frappe.colors
for color in _palette.colors

if color.identifier in ("blue", "teal", "yellow", "peach", "red")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like it should reference DEFAULT_COLORMAP_COLORS

Suggested change
if color.identifier in ("blue", "teal", "yellow", "peach", "red")
if color.identifier in DEFAULT_COLORMAP_COLORS

],
)
mpl.colormaps.register(cmap=_cmap, name=_palette.identifier, force=True)
File renamed without changes.
File renamed without changes.
10 changes: 10 additions & 0 deletions catppuccin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ class Flavor:
colors: FlavorColors
"""@public"""

@property
def matplotlib_style(self) -> str:
"""Name of the matplotlib style associated with the flavor."""
return f"catppuccin.{self.identifier}"

@property
def cmap(self) -> str:
"""Name of the matplotlib colormap associated with the flavor."""
return self.identifier

Comment on lines +170 to +179

@backwardspy backwardspy Sep 13, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would prefer these to live inside the matplotlib extra if possible.

i think there's a case for just removing cmap; relying on identifier directly does introduce a little bit of coupling, but having this here just couples it the other way around (i.e. now the Flavor model has to know about matplotlib concepts.)

as for matplotlib_style, i'm not convinced it's that useful, but there's no harm keeping a helper for it. we could go with a style_name function inside catppuccin/extras/matplotlib.py along the lines of the following:

def style_name(flavor: Flavor) -> str:
    return f"catppuccin.{flavor.identifier}"

these are arguable stylistic choices, albeit ones motivated by keeping the main module clear of optional extras, but i'm open to debate on them. please let me know if you think changing them is a bad idea or you have a better way to approach it.


@dataclass(frozen=True)
class Palette:
Expand Down
Loading