Skip to content

Commit b63611f

Browse files
feat(plotnine): implement pie-basic (#370)
## Summary Implements `pie-basic` for **plotnine** library. **Parent Issue:** #206 **Sub-Issue:** #270 **Base Branch:** `plot/pie-basic` **Attempt:** 1/3 ## Implementation - `plots/plotnine/pie/pie-basic/default.py` ## Details - Simplified to KISS pattern (no functions, sequential script) - Uses matplotlib directly since plotnine lacks `coord_polar()` support - Applies PyPlots.ai color palette - Uses 16:9 aspect ratio (figsize 16x9 at 300 dpi) - Legend positioned outside pie for clarity - Percentage labels displayed on each slice Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
1 parent 9a3e634 commit b63611f

1 file changed

Lines changed: 53 additions & 176 deletions

File tree

plots/plotnine/pie/pie-basic/default.py

Lines changed: 53 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -2,194 +2,71 @@
22
pie-basic: Basic Pie Chart
33
Library: plotnine
44
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.
127
"""
138

14-
from typing import TYPE_CHECKING
15-
169
import matplotlib.pyplot as plt
1710
import pandas as pd
1811

1912

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+
)
2217

2318
# PyPlots.ai color palette
24-
PYPLOTS_COLORS = [
25-
"#306998", # Python Blue (Primary)
19+
colors = [
20+
"#306998", # Python Blue
2621
"#FFD43B", # Python Yellow
2722
"#DC2626", # Signal Red
2823
"#059669", # Teal Green
2924
"#8B5CF6", # Violet
3025
"#F97316", # Orange
3126
]
3227

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

Comments
 (0)