Skip to content
194 changes: 48 additions & 146 deletions plots/altair/arc/pie-basic/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,152 +7,54 @@
import pandas as pd


# PyPlots.ai default color palette
PYPLOTS_COLORS = ["#306998", "#FFD43B", "#DC2626", "#059669", "#8B5CF6", "#F97316"]


def create_plot(
data: pd.DataFrame,
category: str,
value: str,
*,
title: str | None = None,
colors: list[str] | None = None,
startangle: float = 90,
show_labels: bool = True,
label_format: str = ".1%",
legend: bool = True,
legend_loc: str = "right",
inner_radius: float = 0,
outer_radius: float = 150,
**kwargs,
) -> alt.Chart:
"""
Create a basic pie chart visualizing proportions of categorical data.

A fundamental pie chart where each slice represents a category's share of the whole,
ideal for showing composition and distribution across a small number of categories.

Args:
data: Input DataFrame containing the data to plot.
category: Column name for category labels (slice names).
value: Column name for numeric values (slice sizes).
title: Plot title. Defaults to None.
colors: Custom color palette for slices. Defaults to PyPlots.ai palette.
startangle: Starting angle for first slice in degrees. Defaults to 90.
show_labels: Whether to show percentage labels on slices. Defaults to True.
label_format: Format string for percentage labels. Defaults to ".1%".
legend: Whether to display legend. Defaults to True.
legend_loc: Legend location ('right', 'left', 'top', 'bottom'). Defaults to 'right'.
inner_radius: Inner radius for donut style (0 for solid pie). Defaults to 0.
outer_radius: Outer radius of the pie. Defaults to 150.
**kwargs: Additional parameters.

Returns:
Altair Chart 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': ['Product A', 'Product B', 'Product C'],
... 'value': [35, 25, 40]
... })
>>> chart = create_plot(data, 'category', 'value', title='Market Share')
"""
# 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 are zero
total = data[value].sum()
if total == 0:
raise ValueError("Sum of values cannot be zero")

# Use custom colors or default palette
color_palette = colors if colors is not None else PYPLOTS_COLORS
# Data
data = pd.DataFrame(
{"category": ["Product A", "Product B", "Product C", "Product D", "Other"], "value": [35, 25, 20, 15, 5]}
)

# Calculate the starting angle in radians (Altair uses radians, offset from 12 o'clock)
# Altair's theta starts from 3 o'clock (0 degrees), so we need to adjust
# To start from 12 o'clock (90 degrees from 3 o'clock), we use theta2Offset
start_offset = (startangle - 90) * 3.14159 / 180
# Calculate percentages for labels
total = data["value"].sum()
data["percentage"] = data["value"] / total

# Create base chart with arc mark
base = alt.Chart(data).encode(
theta=alt.Theta(f"{value}:Q", stack=True),
color=alt.Color(
f"{category}:N",
scale=alt.Scale(range=color_palette),
legend=alt.Legend(title=category, orient=legend_loc, labelFontSize=16, titleFontSize=16)
if legend
else None,
),
tooltip=[alt.Tooltip(f"{category}:N", title="Category"), alt.Tooltip(f"{value}:Q", title="Value")],
)

# Create the pie/arc chart
pie = base.mark_arc(
innerRadius=inner_radius,
outerRadius=outer_radius,
stroke="#ffffff",
strokeWidth=2,
theta2Offset=start_offset,
thetaOffset=start_offset,
)

# Add percentage labels if requested
if show_labels:
# Calculate percentage for labels
data_with_pct = data.copy()
data_with_pct["_percentage"] = data_with_pct[value] / total

# Create text labels positioned at the middle of each arc
text = (
alt.Chart(data_with_pct)
.mark_text(radius=outer_radius * 0.7, fontSize=14, fontWeight="bold", color="#FFFFFF")
.encode(theta=alt.Theta(f"{value}:Q", stack=True), text=alt.Text("_percentage:Q", format=label_format))
.transform_calculate(theta2Offset=str(start_offset), thetaOffset=str(start_offset))
)

# Layer pie and text
chart = alt.layer(pie, text)
else:
chart = pie

# Set chart dimensions and title
chart = chart.properties(width=400, height=400)

if title is not None:
chart = chart.properties(title=alt.TitleParams(text=title, fontSize=20, anchor="middle", fontWeight=600))

# Configure chart appearance
chart = chart.configure_view(strokeWidth=0).configure_legend(
labelFontSize=16, titleFontSize=16, symbolSize=200, padding=10
)

return chart


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")
# PyPlots.ai default color palette
PYPLOTS_COLORS = ["#306998", "#FFD43B", "#DC2626", "#059669", "#8B5CF6", "#F97316"]

# Save
fig.save("plot.png", scale_factor=2.0)
print("Plot saved to plot.png")
# Create pie chart using arc mark
# Altair uses theta encoding for pie/arc charts
base = alt.Chart(data).encode(
theta=alt.Theta("value:Q", stack=True),
color=alt.Color(
"category:N",
scale=alt.Scale(range=PYPLOTS_COLORS),
legend=alt.Legend(title="Category", orient="right", labelFontSize=16, titleFontSize=16),
),
tooltip=[
alt.Tooltip("category:N", title="Category"),
alt.Tooltip("value:Q", title="Value"),
alt.Tooltip("percentage:Q", title="Share", format=".1%"),
],
)

# Create the pie with arc mark
pie = base.mark_arc(innerRadius=0, outerRadius=300, stroke="#ffffff", strokeWidth=2)

# Add percentage labels on slices
text = base.mark_text(radius=200, fontSize=20, fontWeight="bold", color="#FFFFFF").encode(
text=alt.Text("percentage:Q", format=".1%")
)

# Combine pie and labels
chart = alt.layer(pie, text).properties(
width=800,
height=800,
title=alt.TitleParams(text="Market Share Distribution", fontSize=20, anchor="middle", fontWeight="bold"),
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title inconsistency: Uses "Market Share Distribution" in the chart title while other implementations use "Basic Pie Chart". All implementations should use the same title for consistency. Note: The comment says "Basic Pie Chart" at line 50 but this is actually just part of the title configuration.

Suggested change
title=alt.TitleParams(text="Market Share Distribution", fontSize=20, anchor="middle", fontWeight="bold"),
title=alt.TitleParams(text="Basic Pie Chart", fontSize=20, anchor="middle", fontWeight="bold"),

Copilot uses AI. Check for mistakes.
)

# Configure chart appearance
chart = chart.configure_view(strokeWidth=0).configure_legend(
labelFontSize=16, titleFontSize=16, symbolSize=200, padding=20
)

# Save - scale_factor=3 gives 2400x2400 from 800x800 base
# For pie charts, square aspect ratio is more appropriate
chart.save("plot.png", scale_factor=3.0)
Comment on lines +58 to +60
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent output dimensions: The comment at line 58-59 mentions achieving 2400x2400 pixels (square), but this doesn't match the style guide requirement of 4800x2700 pixels mentioned in other implementations. While square aspect ratio makes sense for pie charts, the dimensions should be consistent with the project's style guide. Consider either using a 2700x2700 square scaled appropriately, or documenting why pie charts use different dimensions.

Copilot uses AI. Check for mistakes.
Loading
Loading