Skip to content

Commit 4f5429d

Browse files
feat(plotly): implement pie-basic
Simplified implementation following KISS pattern: - Removed function-based approach, using sequential code - Uses PyPlots color palette from style guide - Proper 16:9 aspect ratio (4800x2700px via scale=3) - Clean Plotly white template with legend
1 parent 27357ca commit 4f5429d

1 file changed

Lines changed: 28 additions & 201 deletions

File tree

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

Lines changed: 28 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -3,215 +3,42 @@
33
Library: plotly
44
"""
55

6-
from typing import TYPE_CHECKING
7-
86
import pandas as pd
97
import plotly.graph_objects as go
108

119

12-
if TYPE_CHECKING:
13-
pass
14-
15-
# PyPlots.ai color palette
16-
PYPLOTS_COLORS = [
17-
"#306998", # Python Blue
18-
"#FFD43B", # Python Yellow
19-
"#DC2626", # Signal Red
20-
"#059669", # Teal Green
21-
"#8B5CF6", # Violet
22-
"#F97316", # Orange
23-
]
24-
25-
26-
def create_plot(
27-
data: pd.DataFrame,
28-
category: str,
29-
value: str,
30-
figsize: tuple[int, int] = (1600, 900),
31-
title: str | None = None,
32-
colors: list[str] | None = None,
33-
startangle: float = 90,
34-
autopct: str = "%1.1f%%",
35-
explode: list[float] | None = None,
36-
shadow: bool = False,
37-
labels: list[str] | None = None,
38-
legend: bool = True,
39-
legend_loc: str = "best",
40-
**kwargs,
41-
) -> go.Figure:
42-
"""
43-
Create a basic pie chart for categorical data composition.
44-
45-
A fundamental pie chart that visualizes proportions and percentages of
46-
categorical data as slices of a circular chart. Each slice represents
47-
a category's share of the whole, making it ideal for showing composition
48-
and distribution across a small number of categories.
49-
50-
Args:
51-
data: Input DataFrame containing the data to plot.
52-
category: Column name for category names (slice labels).
53-
value: Column name for numeric values (slice sizes).
54-
figsize: Figure size as (width, height) in pixels. Defaults to (1600, 900).
55-
title: Plot title. Defaults to None.
56-
colors: Custom color palette for slices. Defaults to PyPlots palette.
57-
startangle: Starting angle for first slice in degrees. Defaults to 90.
58-
autopct: Format string for percentage labels. Defaults to '%1.1f%%'.
59-
explode: Offset distances for each slice (0-0.1 typical). Defaults to None.
60-
shadow: Add shadow effect (not fully supported in Plotly). Defaults to False.
61-
labels: Custom labels (defaults to category names). Defaults to None.
62-
legend: Display legend. Defaults to True.
63-
legend_loc: Legend location (Plotly uses different positioning). Defaults to 'best'.
64-
**kwargs: Additional parameters passed to go.Pie.
65-
66-
Returns:
67-
Plotly Figure object containing the pie chart.
68-
69-
Raises:
70-
ValueError: If data is empty or contains negative values.
71-
KeyError: If required columns are not found in data.
72-
73-
Example:
74-
>>> data = pd.DataFrame({
75-
... 'category': ['Product A', 'Product B', 'Product C', 'Product D'],
76-
... 'value': [35, 25, 20, 20]
77-
... })
78-
>>> fig = create_plot(data, 'category', 'value', title='Market Share')
79-
"""
80-
# Input validation
81-
if data.empty:
82-
raise ValueError("Data cannot be empty")
83-
84-
for col in [category, value]:
85-
if col not in data.columns:
86-
available = ", ".join(data.columns)
87-
raise KeyError(f"Column '{col}' not found. Available: {available}")
88-
89-
# Validate non-negative values
90-
if (data[value] < 0).any():
91-
raise ValueError("Pie chart values must be non-negative")
92-
93-
# Handle case where all values sum to zero
94-
if data[value].sum() == 0:
95-
raise ValueError("Pie chart values cannot all be zero")
96-
97-
# Get data values
98-
categories = labels if labels is not None else data[category].tolist()
99-
values = data[value].tolist()
10+
# Data
11+
data = pd.DataFrame(
12+
{"category": ["Product A", "Product B", "Product C", "Product D", "Other"], "value": [35, 25, 20, 15, 5]}
13+
)
10014

101-
# Set colors - use custom colors or default PyPlots palette
102-
color_sequence = colors if colors is not None else PYPLOTS_COLORS
103-
# Extend colors if needed
104-
while len(color_sequence) < len(values):
105-
color_sequence = color_sequence + PYPLOTS_COLORS
106-
slice_colors = color_sequence[: len(values)]
15+
# Color palette from style guide
16+
colors = ["#306998", "#FFD43B", "#DC2626", "#059669", "#8B5CF6"]
10717

108-
# Handle explode/pull parameter
109-
pull_values = explode if explode is not None else [0] * len(values)
110-
# Ensure pull_values has correct length
111-
if len(pull_values) < len(values):
112-
pull_values = list(pull_values) + [0] * (len(values) - len(pull_values))
113-
114-
# Create figure
115-
fig = go.Figure()
116-
117-
# Build texttemplate from autopct format
118-
# Convert matplotlib format to plotly format
119-
if "%%" in autopct:
120-
# Parse the format string - e.g., '%1.1f%%' -> '%{percent:.1%}'
121-
try:
122-
# Extract precision from format like '%1.1f%%'
123-
import re
124-
125-
match = re.search(r"%(\d+)\.(\d+)f%%", autopct)
126-
if match:
127-
precision = int(match.group(2))
128-
text_template = f"%{{percent:.{precision}%}}"
129-
else:
130-
text_template = "%{percent:.1%}"
131-
except Exception:
132-
text_template = "%{percent:.1%}"
133-
else:
134-
text_template = "%{percent:.1%}"
135-
136-
# Add pie trace
137-
fig.add_trace(
18+
# Create plot
19+
fig = go.Figure(
20+
data=[
13821
go.Pie(
139-
labels=categories,
140-
values=values,
141-
marker={"colors": slice_colors, "line": {"color": "white", "width": 2}},
142-
textinfo="percent",
143-
texttemplate=text_template,
144-
textfont={"size": 14, "family": "Inter, DejaVu Sans, Arial, sans-serif"},
22+
labels=data["category"],
23+
values=data["value"],
24+
marker={"colors": colors, "line": {"color": "white", "width": 2}},
25+
textinfo="label+percent",
26+
textfont={"size": 16},
14527
textposition="inside",
14628
insidetextorientation="horizontal",
147-
pull=pull_values,
148-
rotation=startangle,
149-
showlegend=legend,
15029
hovertemplate="<b>%{label}</b><br>Value: %{value}<br>Percentage: %{percent}<extra></extra>",
151-
**kwargs,
30+
rotation=90,
15231
)
153-
)
154-
155-
# Configure legend position based on legend_loc
156-
legend_config = {
157-
"font": {"size": 16, "family": "Inter, DejaVu Sans, Arial, sans-serif"},
158-
"bgcolor": "rgba(255, 255, 255, 1)",
159-
"bordercolor": "rgba(0, 0, 0, 0.3)",
160-
"borderwidth": 1,
161-
}
162-
163-
# Map matplotlib legend locations to Plotly positions
164-
if legend_loc in ["right", "center right"]:
165-
legend_config.update({"x": 1.02, "y": 0.5, "xanchor": "left", "yanchor": "middle"})
166-
elif legend_loc in ["left", "center left"]:
167-
legend_config.update({"x": -0.15, "y": 0.5, "xanchor": "right", "yanchor": "middle"})
168-
elif legend_loc in ["upper right"]:
169-
legend_config.update({"x": 1.02, "y": 1, "xanchor": "left", "yanchor": "top"})
170-
elif legend_loc in ["upper left"]:
171-
legend_config.update({"x": -0.15, "y": 1, "xanchor": "right", "yanchor": "top"})
172-
elif legend_loc in ["lower right"]:
173-
legend_config.update({"x": 1.02, "y": 0, "xanchor": "left", "yanchor": "bottom"})
174-
elif legend_loc in ["lower left"]:
175-
legend_config.update({"x": -0.15, "y": 0, "xanchor": "right", "yanchor": "bottom"})
176-
else:
177-
# Default 'best' - place on the right
178-
legend_config.update({"x": 1.02, "y": 0.5, "xanchor": "left", "yanchor": "middle"})
179-
180-
# Update layout with styling
181-
fig.update_layout(
182-
title={
183-
"text": title,
184-
"x": 0.5,
185-
"xanchor": "center",
186-
"font": {"size": 20, "family": "Inter, DejaVu Sans, Arial, sans-serif", "weight": 600},
187-
}
188-
if title
189-
else None,
190-
template="plotly_white",
191-
width=figsize[0],
192-
height=figsize[1],
193-
showlegend=legend,
194-
legend=legend_config if legend else None,
195-
margin={"l": 40, "r": 150 if legend else 40, "t": 80 if title else 40, "b": 40},
196-
paper_bgcolor="white",
197-
plot_bgcolor="white",
198-
)
199-
200-
# Ensure pie chart is circular (equal aspect ratio)
201-
fig.update_layout(yaxis={"scaleanchor": "x", "scaleratio": 1})
202-
203-
return fig
204-
205-
206-
if __name__ == "__main__":
207-
# Sample data for testing
208-
sample_data = pd.DataFrame(
209-
{"category": ["Product A", "Product B", "Product C", "Product D", "Other"], "value": [35, 25, 20, 15, 5]}
210-
)
211-
212-
# Create plot
213-
fig = create_plot(sample_data, "category", "value", title="Market Share Distribution")
214-
215-
# Save
216-
fig.write_image("plot.png", width=1600, height=900, scale=2)
217-
print("Plot saved to plot.png")
32+
]
33+
)
34+
35+
# Layout
36+
fig.update_layout(
37+
title={"text": "Basic Pie Chart", "font": {"size": 20}, "x": 0.5, "xanchor": "center"},
38+
legend={"font": {"size": 16}, "orientation": "v", "yanchor": "middle", "y": 0.5, "xanchor": "left", "x": 1.02},
39+
template="plotly_white",
40+
margin={"l": 50, "r": 150, "t": 80, "b": 50},
41+
)
42+
43+
# Save (4800 x 2700 px using scale=3 with 1600x900 base)
44+
fig.write_image("plot.png", width=1600, height=900, scale=3)

0 commit comments

Comments
 (0)