From 0294db77fb793157d5f4f325194674ee800e6c36 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 7 Dec 2025 00:49:51 +0000 Subject: [PATCH] feat(plotnine): implement pie-basic Simplify pie chart implementation to follow KISS pattern: - Remove function wrapper, use sequential script format - Use matplotlib directly (plotnine lacks coord_polar) - Apply PyPlots.ai color palette - Use 16:9 aspect ratio (4800x2700 at 300 dpi) --- plots/plotnine/pie/pie-basic/default.py | 229 ++++++------------------ 1 file changed, 53 insertions(+), 176 deletions(-) diff --git a/plots/plotnine/pie/pie-basic/default.py b/plots/plotnine/pie/pie-basic/default.py index cf8942839e..7ef7381672 100644 --- a/plots/plotnine/pie/pie-basic/default.py +++ b/plots/plotnine/pie/pie-basic/default.py @@ -2,27 +2,22 @@ pie-basic: Basic Pie Chart Library: plotnine -A fundamental pie chart that visualizes proportions and percentages of categorical data -as slices of a circular chart. - -Note: plotnine (ggplot2 for Python) does not support coord_polar() as of version 0.15.x, -which is required for true pie charts in the grammar of graphics. This implementation -uses matplotlib directly (plotnine's underlying engine) to create the pie chart while -maintaining a compatible interface and following PyPlots.ai style guidelines. +Note: plotnine does not support coord_polar() required for pie charts. +This implementation uses matplotlib directly while following PyPlots.ai style. """ -from typing import TYPE_CHECKING - import matplotlib.pyplot as plt import pandas as pd -if TYPE_CHECKING: - from matplotlib.figure import Figure +# Data +data = pd.DataFrame( + {"category": ["Product A", "Product B", "Product C", "Product D", "Other"], "value": [35, 25, 20, 15, 5]} +) # PyPlots.ai color palette -PYPLOTS_COLORS = [ - "#306998", # Python Blue (Primary) +colors = [ + "#306998", # Python Blue "#FFD43B", # Python Yellow "#DC2626", # Signal Red "#059669", # Teal Green @@ -30,166 +25,48 @@ "#F97316", # Orange ] - -def create_plot( - data: pd.DataFrame, - category: str, - value: str, - figsize: tuple[float, float] = (10, 8), - title: str | None = None, - colors: list[str] | None = None, - startangle: float = 90, - autopct: str = "%1.1f%%", - explode: list[float] | None = None, - shadow: bool = False, - labels: list[str] | None = None, - legend: bool = True, - legend_loc: str = "best", - **kwargs, -) -> "Figure": - """ - Create a basic pie chart. - - Note: plotnine does not support polar coordinates (coord_polar), so this - implementation uses matplotlib directly while maintaining a compatible interface. - - Args: - data: Input DataFrame containing category and value columns - category: Column name for category names (slice labels) - value: Column name for numeric values (slice proportions) - figsize: Figure size as (width, height) - title: Plot title (optional) - colors: Custom color palette for slices (defaults to PyPlots palette) - startangle: Starting angle for first slice in degrees (default 90 = top) - autopct: Format string for percentage labels - explode: Offset distances for each slice (0-0.1 typical) - shadow: Add shadow effect for 3D appearance - labels: Custom labels (defaults to category names) - legend: Display legend - legend_loc: Legend location ('best', 'upper right', 'lower left', etc.) - **kwargs: Additional parameters passed to matplotlib pie() - - Returns: - matplotlib Figure object - - Raises: - ValueError: If data is empty or values contain negative numbers - KeyError: If required columns are not found in data - - Example: - >>> data = pd.DataFrame({ - ... 'category': ['A', 'B', 'C', 'D'], - ... 'value': [35, 25, 20, 20] - ... }) - >>> fig = create_plot(data, 'category', 'value', title='Distribution') - """ - # Input validation - if data.empty: - raise ValueError("Data cannot be empty") - - for col in [category, value]: - if col not in data.columns: - available = ", ".join(data.columns) - raise KeyError(f"Column '{col}' not found. Available: {available}") - - # Validate non-negative values - if (data[value] < 0).any(): - raise ValueError("Pie chart values must be non-negative") - - # Handle case where all values sum to zero - total = data[value].sum() - if total == 0: - raise ValueError("Total of values cannot be zero") - - # Prepare data - values = data[value].tolist() - category_labels = labels if labels is not None else data[category].astype(str).tolist() - - # Validate labels length if custom labels provided - if labels is not None and len(labels) != len(data): - raise ValueError(f"Labels length ({len(labels)}) must match data length ({len(data)})") - - # Set up colors - n_categories = len(data) - if colors is None: - # Extend palette if more categories than colors - plot_colors = (PYPLOTS_COLORS * ((n_categories // len(PYPLOTS_COLORS)) + 1))[:n_categories] - else: - if len(colors) < n_categories: - plot_colors = (colors * ((n_categories // len(colors)) + 1))[:n_categories] - else: - plot_colors = colors[:n_categories] - - # Create figure with white background - fig, ax = plt.subplots(figsize=figsize, facecolor="white") - ax.set_facecolor("white") - - # Configure explode - pie_explode = explode if explode is not None else None - if pie_explode is not None and len(pie_explode) != n_categories: - raise ValueError(f"Explode length ({len(pie_explode)}) must match data length ({n_categories})") - - # Configure text properties for percentage labels - textprops = {"fontsize": 14, "fontweight": "bold", "color": "white"} - - # Create pie chart - hide labels on the pie itself since we'll use legend - wedges, texts, autotexts = ax.pie( - values, - labels=None if legend else category_labels, # Labels on pie only if no legend - autopct=autopct if autopct else None, - startangle=startangle, - colors=plot_colors, - explode=pie_explode, - shadow=shadow, - textprops=textprops, - wedgeprops={"linewidth": 1, "edgecolor": "white"}, - **kwargs, - ) - - # Style the percentage labels with better contrast - for autotext in autotexts: - autotext.set_fontsize(14) - autotext.set_fontweight("bold") - # Use white for darker colors, dark for lighter colors - autotext.set_color("white") - - # Add legend if requested - if legend: - ax.legend( - wedges, - category_labels, - title=category, - loc=legend_loc, - fontsize=14, - title_fontsize=16, - frameon=True, - facecolor="white", - edgecolor="gray", - framealpha=1.0, - ) - - # Add title if provided - if title: - ax.set_title(title, fontsize=20, fontweight="semibold", pad=20) - - # Ensure equal aspect ratio for circular pie - ax.set_aspect("equal") - - # Adjust layout - fig.tight_layout() - - return fig - - -if __name__ == "__main__": - # Sample data for testing - sample_data = pd.DataFrame( - {"category": ["Product A", "Product B", "Product C", "Product D", "Other"], "value": [35, 25, 20, 15, 5]} - ) - - # Create plot - fig = create_plot(sample_data, "category", "value", title="Market Share Distribution") - - # Save - fig.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white") - print("Plot saved to plot.png") +# Create figure (16:9 aspect ratio for 4800x2700 at 300 dpi) +fig, ax = plt.subplots(figsize=(16, 9), facecolor="white") +ax.set_facecolor("white") + +# Create pie chart +wedges, texts, autotexts = ax.pie( + data["value"], + autopct="%1.1f%%", + startangle=90, + colors=colors[: len(data)], + textprops={"fontsize": 16, "fontweight": "bold", "color": "white"}, + wedgeprops={"linewidth": 2, "edgecolor": "white"}, +) + +# Style percentage labels +for autotext in autotexts: + autotext.set_fontsize(16) + autotext.set_fontweight("bold") + autotext.set_color("white") + +# Add legend +ax.legend( + wedges, + data["category"], + title="Category", + loc="center left", + bbox_to_anchor=(1.0, 0.5), + fontsize=16, + title_fontsize=20, + frameon=True, + facecolor="white", + edgecolor="gray", +) + +# Title +ax.set_title("Basic Pie Chart", fontsize=20, fontweight="semibold", pad=20) + +# Ensure circular shape +ax.set_aspect("equal") + +# Adjust layout +plt.tight_layout() + +# Save +plt.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white")