Source code for dartwork_mpl.formatting

"""Enhanced formatting utilities for dartwork-mpl.

This module provides additional formatting functions for axes,
tick labels, and other matplotlib elements.
"""

from __future__ import annotations

from typing import Literal

import matplotlib.ticker as ticker
from matplotlib.axes import Axes


[docs] def format_axis_percent( ax: Axes, axis: Literal["x", "y", "both"] = "y", decimals: int = 0 ) -> None: """Format axis tick labels as percentages. Parameters ---------- ax : Axes Matplotlib axes axis : Literal["x", "y", "both"] Which axis to format decimals : int Number of decimal places Examples -------- >>> format_axis_percent(ax) # Format y-axis as percentages >>> format_axis_percent(ax, axis="both", decimals=1) """ formatter = ticker.PercentFormatter(1.0, decimals=decimals) if axis in ("y", "both"): ax.yaxis.set_major_formatter(formatter) if axis in ("x", "both"): ax.xaxis.set_major_formatter(formatter)
[docs] def format_axis_thousands( ax: Axes, axis: Literal["x", "y", "both"] = "y", sep: str = "," ) -> None: """Format axis tick labels with thousands separator. Parameters ---------- ax : Axes Matplotlib axes axis : Literal["x", "y", "both"] Which axis to format sep : str Thousands separator (default: comma) Examples -------- >>> format_axis_thousands(ax) # Format y-axis with commas """ formatter = ticker.FuncFormatter(lambda x, p: f"{x:,.0f}".replace(",", sep)) if axis in ("y", "both"): ax.yaxis.set_major_formatter(formatter) if axis in ("x", "both"): ax.xaxis.set_major_formatter(formatter)
[docs] def format_axis_millions( ax: Axes, axis: Literal["x", "y", "both"] = "y", suffix: str = "M", decimals: int = 1, ) -> None: """Format axis tick labels in millions. Parameters ---------- ax : Axes Matplotlib axes axis : Literal["x", "y", "both"] Which axis to format suffix : str Suffix to add (default: "M") decimals : int Number of decimal places Examples -------- >>> format_axis_millions(ax) # Show as 1.5M instead of 1500000 """ def millions_formatter(x, pos): """Internal formatter function for millions scale. Parameters ---------- x : float The tick value to format pos : int The tick position (unused but required by matplotlib) Returns ------- str Formatted string with millions suffix """ if x == 0: return "0" return f"{x / 1e6:.{decimals}f}{suffix}" formatter = ticker.FuncFormatter(millions_formatter) if axis in ("y", "both"): ax.yaxis.set_major_formatter(formatter) if axis in ("x", "both"): ax.xaxis.set_major_formatter(formatter)
[docs] def format_axis_billions( ax: Axes, axis: Literal["x", "y", "both"] = "y", suffix: str = "B", decimals: int = 1, ) -> None: """Format axis tick labels in billions. Parameters ---------- ax : Axes Matplotlib axes axis : Literal["x", "y", "both"] Which axis to format suffix : str Suffix to add (default: "B") decimals : int Number of decimal places Examples -------- >>> format_axis_billions(ax) # Show as 1.5B instead of 1500000000 """ def billions_formatter(x, pos): """Internal formatter function for billions scale. Parameters ---------- x : float The tick value to format pos : int The tick position (unused but required by matplotlib) Returns ------- str Formatted string with billions suffix """ if x == 0: return "0" return f"{x / 1e9:.{decimals}f}{suffix}" formatter = ticker.FuncFormatter(billions_formatter) if axis in ("y", "both"): ax.yaxis.set_major_formatter(formatter) if axis in ("x", "both"): ax.xaxis.set_major_formatter(formatter)
[docs] def format_axis_currency( ax: Axes, axis: Literal["x", "y", "both"] = "y", symbol: str = "$", position: Literal["prefix", "suffix"] = "prefix", decimals: int = 0, ) -> None: """Format axis tick labels as currency. Parameters ---------- ax : Axes Matplotlib axes axis : Literal["x", "y", "both"] Which axis to format symbol : str Currency symbol position : Literal["prefix", "suffix"], optional Position of currency symbol decimals : int Number of decimal places Examples -------- >>> format_axis_currency(ax) # Format as $1,000 >>> format_axis_currency(ax, symbol="€", position="suffix") # Format as 1,000€ """ def currency_formatter(x, pos): """Internal formatter function for currency values. Parameters ---------- x : float The tick value to format pos : int The tick position (unused but required by matplotlib) Returns ------- str Formatted string with currency symbol """ formatted = f"{x:,.{decimals}f}" if position == "prefix": return f"{symbol}{formatted}" else: return f"{formatted}{symbol}" formatter = ticker.FuncFormatter(currency_formatter) if axis in ("y", "both"): ax.yaxis.set_major_formatter(formatter) if axis in ("x", "both"): ax.xaxis.set_major_formatter(formatter)
[docs] def format_axis_si( ax: Axes, axis: Literal["x", "y", "both"] = "y", decimals: int = 1 ) -> None: """Format axis tick labels with SI prefixes (k, M, G, etc.). Parameters ---------- ax : Axes Matplotlib axes axis : Literal["x", "y", "both"] Which axis to format decimals : int Number of decimal places Examples -------- >>> format_axis_si(ax) # Show as 1.5k, 2.3M, etc. """ def si_formatter(x, pos): """Internal formatter function for SI prefix notation. Parameters ---------- x : float The tick value to format pos : int The tick position (unused but required by matplotlib) Returns ------- str Formatted string with SI prefix (k, M, G, T) """ if x == 0: return "0" abs_x = abs(x) sign = "-" if x < 0 else "" if abs_x >= 1e12: return f"{sign}{abs_x / 1e12:.{decimals}f}T" elif abs_x >= 1e9: return f"{sign}{abs_x / 1e9:.{decimals}f}G" elif abs_x >= 1e6: return f"{sign}{abs_x / 1e6:.{decimals}f}M" elif abs_x >= 1e3: return f"{sign}{abs_x / 1e3:.{decimals}f}k" else: return f"{x:.{decimals}f}" formatter = ticker.FuncFormatter(si_formatter) if axis in ("y", "both"): ax.yaxis.set_major_formatter(formatter) if axis in ("x", "both"): ax.xaxis.set_major_formatter(formatter)
[docs] def rotate_tick_labels( ax: Axes, axis: Literal["x", "y", "both"] = "x", rotation: float = 45, ha: str | None = None, ) -> None: """Rotate tick labels for better readability. Parameters ---------- ax : Axes Matplotlib axes axis : Literal["x", "y", "both"] Which axis to rotate rotation : float Rotation angle in degrees ha : str | None Horizontal alignment. If None, automatically set based on rotation Examples -------- >>> rotate_tick_labels(ax) # Rotate x-axis labels 45 degrees >>> rotate_tick_labels(ax, rotation=90, axis="both") """ if ha is None: # Auto-determine alignment based on rotation if rotation > 0: ha = "right" elif rotation < 0: ha = "left" else: ha = "center" if axis in ("x", "both"): ax.set_xticklabels(ax.get_xticklabels(), rotation=rotation, ha=ha) if axis in ("y", "both"): ax.set_yticklabels(ax.get_yticklabels(), rotation=rotation, ha=ha)