|
2 | 2 | pie-basic: Basic Pie Chart |
3 | 3 | Library: plotnine |
4 | 4 |
|
5 | | -A fundamental pie chart that visualizes proportions and percentages of categorical data |
6 | | -as slices of a circular chart. |
7 | | -
|
8 | | -Note: plotnine (ggplot2 for Python) does not support coord_polar() as of version 0.15.x, |
9 | | -which is required for true pie charts in the grammar of graphics. This implementation |
10 | | -uses matplotlib directly (plotnine's underlying engine) to create the pie chart while |
11 | | -maintaining a compatible interface and following PyPlots.ai style guidelines. |
| 5 | +Note: plotnine does not support coord_polar() required for pie charts. |
| 6 | +This implementation uses matplotlib directly while following PyPlots.ai style. |
12 | 7 | """ |
13 | 8 |
|
14 | | -from typing import TYPE_CHECKING |
15 | | - |
16 | 9 | import matplotlib.pyplot as plt |
17 | 10 | import pandas as pd |
18 | 11 |
|
19 | 12 |
|
20 | | -if TYPE_CHECKING: |
21 | | - from matplotlib.figure import Figure |
| 13 | +# Data |
| 14 | +data = pd.DataFrame( |
| 15 | + {"category": ["Product A", "Product B", "Product C", "Product D", "Other"], "value": [35, 25, 20, 15, 5]} |
| 16 | +) |
22 | 17 |
|
23 | 18 | # PyPlots.ai color palette |
24 | | -PYPLOTS_COLORS = [ |
25 | | - "#306998", # Python Blue (Primary) |
| 19 | +colors = [ |
| 20 | + "#306998", # Python Blue |
26 | 21 | "#FFD43B", # Python Yellow |
27 | 22 | "#DC2626", # Signal Red |
28 | 23 | "#059669", # Teal Green |
29 | 24 | "#8B5CF6", # Violet |
30 | 25 | "#F97316", # Orange |
31 | 26 | ] |
32 | 27 |
|
33 | | - |
34 | | -def create_plot( |
35 | | - data: pd.DataFrame, |
36 | | - category: str, |
37 | | - value: str, |
38 | | - figsize: tuple[float, float] = (10, 8), |
39 | | - title: str | None = None, |
40 | | - colors: list[str] | None = None, |
41 | | - startangle: float = 90, |
42 | | - autopct: str = "%1.1f%%", |
43 | | - explode: list[float] | None = None, |
44 | | - shadow: bool = False, |
45 | | - labels: list[str] | None = None, |
46 | | - legend: bool = True, |
47 | | - legend_loc: str = "best", |
48 | | - **kwargs, |
49 | | -) -> "Figure": |
50 | | - """ |
51 | | - Create a basic pie chart. |
52 | | -
|
53 | | - Note: plotnine does not support polar coordinates (coord_polar), so this |
54 | | - implementation uses matplotlib directly while maintaining a compatible interface. |
55 | | -
|
56 | | - Args: |
57 | | - data: Input DataFrame containing category and value columns |
58 | | - category: Column name for category names (slice labels) |
59 | | - value: Column name for numeric values (slice proportions) |
60 | | - figsize: Figure size as (width, height) |
61 | | - title: Plot title (optional) |
62 | | - colors: Custom color palette for slices (defaults to PyPlots palette) |
63 | | - startangle: Starting angle for first slice in degrees (default 90 = top) |
64 | | - autopct: Format string for percentage labels |
65 | | - explode: Offset distances for each slice (0-0.1 typical) |
66 | | - shadow: Add shadow effect for 3D appearance |
67 | | - labels: Custom labels (defaults to category names) |
68 | | - legend: Display legend |
69 | | - legend_loc: Legend location ('best', 'upper right', 'lower left', etc.) |
70 | | - **kwargs: Additional parameters passed to matplotlib pie() |
71 | | -
|
72 | | - Returns: |
73 | | - matplotlib Figure object |
74 | | -
|
75 | | - Raises: |
76 | | - ValueError: If data is empty or values contain negative numbers |
77 | | - KeyError: If required columns are not found in data |
78 | | -
|
79 | | - Example: |
80 | | - >>> data = pd.DataFrame({ |
81 | | - ... 'category': ['A', 'B', 'C', 'D'], |
82 | | - ... 'value': [35, 25, 20, 20] |
83 | | - ... }) |
84 | | - >>> fig = create_plot(data, 'category', 'value', title='Distribution') |
85 | | - """ |
86 | | - # Input validation |
87 | | - if data.empty: |
88 | | - raise ValueError("Data cannot be empty") |
89 | | - |
90 | | - for col in [category, value]: |
91 | | - if col not in data.columns: |
92 | | - available = ", ".join(data.columns) |
93 | | - raise KeyError(f"Column '{col}' not found. Available: {available}") |
94 | | - |
95 | | - # Validate non-negative values |
96 | | - if (data[value] < 0).any(): |
97 | | - raise ValueError("Pie chart values must be non-negative") |
98 | | - |
99 | | - # Handle case where all values sum to zero |
100 | | - total = data[value].sum() |
101 | | - if total == 0: |
102 | | - raise ValueError("Total of values cannot be zero") |
103 | | - |
104 | | - # Prepare data |
105 | | - values = data[value].tolist() |
106 | | - category_labels = labels if labels is not None else data[category].astype(str).tolist() |
107 | | - |
108 | | - # Validate labels length if custom labels provided |
109 | | - if labels is not None and len(labels) != len(data): |
110 | | - raise ValueError(f"Labels length ({len(labels)}) must match data length ({len(data)})") |
111 | | - |
112 | | - # Set up colors |
113 | | - n_categories = len(data) |
114 | | - if colors is None: |
115 | | - # Extend palette if more categories than colors |
116 | | - plot_colors = (PYPLOTS_COLORS * ((n_categories // len(PYPLOTS_COLORS)) + 1))[:n_categories] |
117 | | - else: |
118 | | - if len(colors) < n_categories: |
119 | | - plot_colors = (colors * ((n_categories // len(colors)) + 1))[:n_categories] |
120 | | - else: |
121 | | - plot_colors = colors[:n_categories] |
122 | | - |
123 | | - # Create figure with white background |
124 | | - fig, ax = plt.subplots(figsize=figsize, facecolor="white") |
125 | | - ax.set_facecolor("white") |
126 | | - |
127 | | - # Configure explode |
128 | | - pie_explode = explode if explode is not None else None |
129 | | - if pie_explode is not None and len(pie_explode) != n_categories: |
130 | | - raise ValueError(f"Explode length ({len(pie_explode)}) must match data length ({n_categories})") |
131 | | - |
132 | | - # Configure text properties for percentage labels |
133 | | - textprops = {"fontsize": 14, "fontweight": "bold", "color": "white"} |
134 | | - |
135 | | - # Create pie chart - hide labels on the pie itself since we'll use legend |
136 | | - wedges, texts, autotexts = ax.pie( |
137 | | - values, |
138 | | - labels=None if legend else category_labels, # Labels on pie only if no legend |
139 | | - autopct=autopct if autopct else None, |
140 | | - startangle=startangle, |
141 | | - colors=plot_colors, |
142 | | - explode=pie_explode, |
143 | | - shadow=shadow, |
144 | | - textprops=textprops, |
145 | | - wedgeprops={"linewidth": 1, "edgecolor": "white"}, |
146 | | - **kwargs, |
147 | | - ) |
148 | | - |
149 | | - # Style the percentage labels with better contrast |
150 | | - for autotext in autotexts: |
151 | | - autotext.set_fontsize(14) |
152 | | - autotext.set_fontweight("bold") |
153 | | - # Use white for darker colors, dark for lighter colors |
154 | | - autotext.set_color("white") |
155 | | - |
156 | | - # Add legend if requested |
157 | | - if legend: |
158 | | - ax.legend( |
159 | | - wedges, |
160 | | - category_labels, |
161 | | - title=category, |
162 | | - loc=legend_loc, |
163 | | - fontsize=14, |
164 | | - title_fontsize=16, |
165 | | - frameon=True, |
166 | | - facecolor="white", |
167 | | - edgecolor="gray", |
168 | | - framealpha=1.0, |
169 | | - ) |
170 | | - |
171 | | - # Add title if provided |
172 | | - if title: |
173 | | - ax.set_title(title, fontsize=20, fontweight="semibold", pad=20) |
174 | | - |
175 | | - # Ensure equal aspect ratio for circular pie |
176 | | - ax.set_aspect("equal") |
177 | | - |
178 | | - # Adjust layout |
179 | | - fig.tight_layout() |
180 | | - |
181 | | - return fig |
182 | | - |
183 | | - |
184 | | -if __name__ == "__main__": |
185 | | - # Sample data for testing |
186 | | - sample_data = pd.DataFrame( |
187 | | - {"category": ["Product A", "Product B", "Product C", "Product D", "Other"], "value": [35, 25, 20, 15, 5]} |
188 | | - ) |
189 | | - |
190 | | - # Create plot |
191 | | - fig = create_plot(sample_data, "category", "value", title="Market Share Distribution") |
192 | | - |
193 | | - # Save |
194 | | - fig.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white") |
195 | | - print("Plot saved to plot.png") |
| 28 | +# Create figure (16:9 aspect ratio for 4800x2700 at 300 dpi) |
| 29 | +fig, ax = plt.subplots(figsize=(16, 9), facecolor="white") |
| 30 | +ax.set_facecolor("white") |
| 31 | + |
| 32 | +# Create pie chart |
| 33 | +wedges, texts, autotexts = ax.pie( |
| 34 | + data["value"], |
| 35 | + autopct="%1.1f%%", |
| 36 | + startangle=90, |
| 37 | + colors=colors[: len(data)], |
| 38 | + textprops={"fontsize": 16, "fontweight": "bold", "color": "white"}, |
| 39 | + wedgeprops={"linewidth": 2, "edgecolor": "white"}, |
| 40 | +) |
| 41 | + |
| 42 | +# Style percentage labels |
| 43 | +for autotext in autotexts: |
| 44 | + autotext.set_fontsize(16) |
| 45 | + autotext.set_fontweight("bold") |
| 46 | + autotext.set_color("white") |
| 47 | + |
| 48 | +# Add legend |
| 49 | +ax.legend( |
| 50 | + wedges, |
| 51 | + data["category"], |
| 52 | + title="Category", |
| 53 | + loc="center left", |
| 54 | + bbox_to_anchor=(1.0, 0.5), |
| 55 | + fontsize=16, |
| 56 | + title_fontsize=20, |
| 57 | + frameon=True, |
| 58 | + facecolor="white", |
| 59 | + edgecolor="gray", |
| 60 | +) |
| 61 | + |
| 62 | +# Title |
| 63 | +ax.set_title("Basic Pie Chart", fontsize=20, fontweight="semibold", pad=20) |
| 64 | + |
| 65 | +# Ensure circular shape |
| 66 | +ax.set_aspect("equal") |
| 67 | + |
| 68 | +# Adjust layout |
| 69 | +plt.tight_layout() |
| 70 | + |
| 71 | +# Save |
| 72 | +plt.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white") |
0 commit comments