diff --git a/plots/altair/arc/pie-basic/default.py b/plots/altair/arc/pie-basic/default.py
new file mode 100644
index 0000000000..fe0493305c
--- /dev/null
+++ b/plots/altair/arc/pie-basic/default.py
@@ -0,0 +1,158 @@
+"""
+pie-basic: Basic Pie Chart
+Library: altair
+"""
+
+import altair as alt
+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
+
+ # 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
+
+ # 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")
+
+ # Save
+ fig.save("plot.png", scale_factor=2.0)
+ print("Plot saved to plot.png")
diff --git a/plots/bokeh/custom/pie-basic/default.py b/plots/bokeh/custom/pie-basic/default.py
new file mode 100644
index 0000000000..bf784be68d
--- /dev/null
+++ b/plots/bokeh/custom/pie-basic/default.py
@@ -0,0 +1,221 @@
+"""
+pie-basic: Basic Pie Chart
+Library: bokeh
+"""
+
+import math
+from typing import TYPE_CHECKING
+
+import pandas as pd
+from bokeh.models import ColumnDataSource, Label, Legend, LegendItem
+from bokeh.plotting import figure
+
+
+if TYPE_CHECKING:
+ from bokeh.plotting import figure as Figure
+
+# PyPlots.ai style colors
+PYPLOTS_COLORS = [
+ "#306998", # Python Blue
+ "#FFD43B", # Python Yellow
+ "#DC2626", # Signal Red
+ "#059669", # Teal Green
+ "#8B5CF6", # Violet
+ "#F97316", # Orange
+]
+
+
+def create_plot(
+ data: pd.DataFrame,
+ category: str,
+ value: str,
+ title: str | None = None,
+ colors: list[str] | None = None,
+ startangle: float = 90,
+ legend: bool = True,
+ legend_loc: str = "right",
+ **kwargs,
+) -> "Figure":
+ """
+ Create a basic pie chart using Bokeh wedge glyphs.
+
+ Bokeh does not have a native pie chart method, so this implementation
+ uses wedge glyphs to construct the pie chart manually.
+
+ Args:
+ data: Input DataFrame containing category and value columns
+ category: Column name for category labels (slice names)
+ value: Column name for numeric values (slice sizes)
+ title: Plot title (optional)
+ colors: Custom color palette for slices (defaults to PyPlots colors)
+ startangle: Starting angle for first slice in degrees (default: 90)
+ legend: Whether to display legend (default: True)
+ legend_loc: Legend location - 'right', 'left', 'above', 'below' (default: 'right')
+ **kwargs: Additional parameters passed to figure
+
+ Returns:
+ Bokeh figure object
+
+ Raises:
+ ValueError: If data is empty or values are all zero/negative
+ KeyError: If required columns not found in data
+
+ Example:
+ >>> data = pd.DataFrame({
+ ... 'category': ['A', 'B', 'C'],
+ ... 'value': [30, 50, 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 numeric values
+ if not pd.api.types.is_numeric_dtype(data[value]):
+ raise ValueError(f"Column '{value}' must contain numeric values")
+
+ if (data[value] < 0).any():
+ raise ValueError("Pie chart values must be non-negative")
+
+ total = data[value].sum()
+ if total == 0:
+ raise ValueError("Sum of values cannot be zero")
+
+ # Prepare data
+ plot_data = data.copy()
+ plot_data["angle"] = plot_data[value] / total * 2 * math.pi
+ plot_data["percentage"] = plot_data[value] / total * 100
+
+ # Calculate cumulative angles for wedge positioning
+ plot_data["end_angle"] = plot_data["angle"].cumsum()
+ plot_data["start_angle"] = plot_data["end_angle"] - plot_data["angle"]
+
+ # Apply start angle offset (convert degrees to radians, adjust for Bokeh's coordinate system)
+ start_rad = math.radians(startangle - 90)
+ plot_data["start_angle"] = plot_data["start_angle"] + start_rad
+ plot_data["end_angle"] = plot_data["end_angle"] + start_rad
+
+ # Assign colors
+ if colors is None:
+ colors = PYPLOTS_COLORS
+ # Cycle through colors if more categories than colors
+ num_categories = len(plot_data)
+ plot_data["color"] = [colors[i % len(colors)] for i in range(num_categories)]
+
+ # Create ColumnDataSource
+ source = ColumnDataSource(plot_data)
+
+ # Create figure - use range to ensure circular aspect ratio
+ # Set frame dimensions to maintain 16:9 overall but circular pie
+ fig_width = kwargs.get("width", 1600)
+ fig_height = kwargs.get("height", 900)
+
+ p = figure(
+ width=fig_width,
+ height=fig_height,
+ title=title,
+ tools="hover",
+ tooltips=[(category.capitalize(), f"@{category}"), ("Value", f"@{value}"), ("Percentage", "@percentage{0.1}%")],
+ x_range=(-1.2, 2.0 if legend else 1.2),
+ y_range=(-1.2, 1.2),
+ )
+
+ # Draw wedges (pie slices)
+ renderers = p.wedge(
+ x=0,
+ y=0,
+ radius=0.9,
+ start_angle="start_angle",
+ end_angle="end_angle",
+ line_color="white",
+ line_width=2,
+ fill_color="color",
+ source=source,
+ )
+
+ # Add percentage labels inside slices
+ for _, row in plot_data.iterrows():
+ # Calculate label position at middle of wedge, 60% from center
+ mid_angle = (row["start_angle"] + row["end_angle"]) / 2
+ label_radius = 0.55
+
+ x = label_radius * math.cos(mid_angle)
+ y = label_radius * math.sin(mid_angle)
+
+ # Only show percentage label if slice is large enough
+ if row["percentage"] >= 5:
+ label = Label(
+ x=x,
+ y=y,
+ text=f"{row['percentage']:.1f}%",
+ text_font_size="14pt",
+ text_align="center",
+ text_baseline="middle",
+ text_color="white" if row["percentage"] >= 10 else "black",
+ )
+ p.add_layout(label)
+
+ # Configure legend
+ if legend:
+ legend_items = []
+ for i, cat in enumerate(plot_data[category]):
+ legend_items.append(LegendItem(label=str(cat), renderers=[renderers], index=i))
+
+ leg = Legend(
+ items=legend_items,
+ location="center",
+ label_text_font_size="16pt",
+ background_fill_color="white",
+ background_fill_alpha=1.0,
+ border_line_color="black",
+ border_line_width=1,
+ )
+
+ p.add_layout(leg, legend_loc)
+
+ # Style configuration
+ p.axis.visible = False
+ p.grid.visible = False
+ p.outline_line_color = None
+
+ # Title styling
+ if title:
+ p.title.text_font_size = "20pt"
+ p.title.align = "center"
+
+ # Background
+ p.background_fill_color = "white"
+
+ return p
+
+
+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 - try PNG first, fall back to HTML if selenium not available
+ try:
+ from bokeh.io import export_png
+
+ export_png(fig, filename="plot.png")
+ print("Plot saved to plot.png")
+ except RuntimeError as e:
+ if "selenium" in str(e).lower():
+ from bokeh.io import output_file, save
+
+ output_file("plot.html")
+ save(fig)
+ print("Plot saved to plot.html (selenium not available for PNG export)")
+ else:
+ raise
diff --git a/plots/highcharts/pie/pie-basic/default.py b/plots/highcharts/pie/pie-basic/default.py
new file mode 100644
index 0000000000..293b3b2cca
--- /dev/null
+++ b/plots/highcharts/pie/pie-basic/default.py
@@ -0,0 +1,287 @@
+"""
+pie-basic: Basic Pie Chart
+Library: highcharts
+
+A fundamental pie chart that visualizes proportions and percentages of categorical data
+as slices of a circular chart. Each slice represents a category's share of the whole.
+
+Note: Highcharts requires a license for commercial use.
+"""
+
+from typing import Optional
+
+import pandas as pd
+from highcharts_core.chart import Chart
+from highcharts_core.options import HighchartsOptions
+from highcharts_core.options.series.pie import PieSeries
+
+
+# Style guide colors
+PYPLOTS_COLORS = [
+ "#306998", # Python Blue (Primary)
+ "#FFD43B", # Python Yellow
+ "#DC2626", # Signal Red
+ "#059669", # Teal Green
+ "#8B5CF6", # Violet
+ "#F97316", # Orange
+]
+
+
+def create_plot(
+ data: pd.DataFrame,
+ category: str,
+ value: str,
+ figsize: tuple[int, int] = (10, 8),
+ title: Optional[str] = None,
+ colors: Optional[list[str]] = None,
+ startangle: float = 90,
+ autopct: str = "%1.1f%%",
+ explode: Optional[list[float]] = None,
+ shadow: bool = False,
+ labels: Optional[list[str]] = None,
+ legend: bool = True,
+ legend_loc: str = "best",
+ width: int = 1600,
+ height: int = 900,
+ **kwargs,
+) -> Chart:
+ """
+ Create a basic pie chart from DataFrame.
+
+ Args:
+ data: Input DataFrame with categorical and numeric data
+ category: Column name for category names (slice labels)
+ value: Column name for numeric values (slice proportions)
+ figsize: Figure size as (width, height) in inches (legacy, use width/height instead)
+ title: Plot title
+ colors: Custom color palette for slices (defaults to PyPlots style guide colors)
+ startangle: Starting angle for first slice in degrees (from positive x-axis)
+ 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: Whether to display legend
+ legend_loc: Legend location (e.g., 'best', 'right', 'left')
+ width: Figure width in pixels (default: 1600)
+ height: Figure height in pixels (default: 900)
+ **kwargs: Additional parameters passed to chart options
+
+ Returns:
+ Highcharts Chart object
+
+ Raises:
+ ValueError: If data is empty or contains negative values
+ 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.tolist())
+ 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")
+
+ # Check if all values sum to zero
+ if data[value].sum() == 0:
+ raise ValueError("Sum of values cannot be zero")
+
+ # Get colors (use provided or default to PyPlots style guide)
+ slice_colors = colors if colors is not None else PYPLOTS_COLORS
+
+ # Get labels (use provided or default to category names)
+ slice_labels = labels if labels is not None else data[category].tolist()
+
+ # Create chart with container ID for rendering
+ chart = Chart(container="container")
+ chart.options = HighchartsOptions()
+
+ # Chart configuration
+ chart.options.chart = {"type": "pie", "width": width, "height": height, "backgroundColor": "#ffffff"}
+
+ # Title with style guide typography
+ if title:
+ chart.options.title = {
+ "text": title,
+ "style": {
+ "fontSize": "20px",
+ "fontWeight": "600",
+ "fontFamily": "Inter, DejaVu Sans, Arial, Helvetica, sans-serif",
+ },
+ }
+ else:
+ chart.options.title = {"text": None}
+
+ # Build data points for pie series
+ pie_data = []
+ for i, (cat, val) in enumerate(zip(data[category].tolist(), data[value].tolist(), strict=True)):
+ point = {
+ "name": slice_labels[i] if i < len(slice_labels) else cat,
+ "y": val,
+ "color": slice_colors[i % len(slice_colors)],
+ }
+
+ # Apply explode if provided
+ if explode is not None and i < len(explode) and explode[i] > 0:
+ point["sliced"] = True
+ point["selected"] = True
+
+ pie_data.append(point)
+
+ # Create pie series
+ series = PieSeries()
+ series.data = pie_data
+ series.name = value
+
+ # Configure data labels to show percentages
+ # Parse autopct format for decimal places (e.g., '%1.1f%%' -> 1 decimal)
+ decimal_places = 1
+ if autopct and "." in autopct:
+ try:
+ decimal_places = int(autopct.split(".")[1][0])
+ except (IndexError, ValueError):
+ decimal_places = 1
+
+ # Pie series options
+ series.show_in_legend = legend
+ series.start_angle = startangle
+ series.shadow = shadow
+
+ # Data labels configuration
+ series.data_labels = {
+ "enabled": True,
+ "format": f"{{point.percentage:.{decimal_places}f}}%",
+ "distance": 20,
+ "style": {
+ "fontSize": "14px",
+ "fontWeight": "normal",
+ "fontFamily": "Inter, DejaVu Sans, Arial, Helvetica, sans-serif",
+ "textOutline": "2px white",
+ },
+ }
+
+ chart.add_series(series)
+
+ # Plot options for pie
+ chart.options.plot_options = {
+ "pie": {
+ "allowPointSelect": True,
+ "cursor": "pointer",
+ "showInLegend": legend,
+ "startAngle": startangle,
+ "shadow": shadow,
+ "center": ["50%", "50%"],
+ "size": "75%",
+ }
+ }
+
+ # Legend configuration
+ if legend:
+ # Map legend_loc to Highcharts position
+ legend_config = {
+ "enabled": True,
+ "align": "right",
+ "verticalAlign": "middle",
+ "layout": "vertical",
+ "itemStyle": {"fontSize": "16px", "fontFamily": "Inter, DejaVu Sans, Arial, Helvetica, sans-serif"},
+ "backgroundColor": "#ffffff",
+ "borderWidth": 1,
+ "borderRadius": 5,
+ }
+
+ if legend_loc in ["left"]:
+ legend_config["align"] = "left"
+ elif legend_loc in ["right"]:
+ legend_config["align"] = "right"
+ elif legend_loc in ["top", "upper center"]:
+ legend_config["align"] = "center"
+ legend_config["verticalAlign"] = "top"
+ legend_config["layout"] = "horizontal"
+ elif legend_loc in ["bottom", "lower center"]:
+ legend_config["align"] = "center"
+ legend_config["verticalAlign"] = "bottom"
+ legend_config["layout"] = "horizontal"
+
+ chart.options.legend = legend_config
+ else:
+ chart.options.legend = {"enabled": False}
+
+ # Tooltip configuration
+ chart.options.tooltip = {
+ "pointFormat": "{point.percentage:.1f}%
Value: {point.y}",
+ "style": {"fontSize": "14px", "fontFamily": "Inter, DejaVu Sans, Arial, Helvetica, sans-serif"},
+ }
+
+ # Credits
+ chart.options.credits = {"enabled": False}
+
+ return chart
+
+
+if __name__ == "__main__":
+ import tempfile
+ import time
+ import urllib.request
+ from pathlib import Path
+
+ from selenium import webdriver
+ from selenium.webdriver.chrome.options import Options
+
+ # Sample data for testing (from spec)
+ sample_data = pd.DataFrame(
+ {"category": ["Product A", "Product B", "Product C", "Product D", "Other"], "value": [35, 25, 20, 15, 5]}
+ )
+
+ # Create plot
+ chart = create_plot(sample_data, "category", "value", title="Market Share Distribution")
+
+ # Download Highcharts JS (required for headless Chrome which can't load CDN)
+ highcharts_url = "https://code.highcharts.com/highcharts.js"
+ with urllib.request.urlopen(highcharts_url, timeout=30) as response:
+ highcharts_js = response.read().decode("utf-8")
+
+ # Export to PNG via Selenium screenshot
+ html_str = chart.to_js_literal()
+ html_content = f"""
+
+
+
+
+
+
+
+
+
+"""
+
+ # Write temp HTML and take screenshot
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False) as f:
+ f.write(html_content)
+ temp_path = f.name
+
+ chrome_options = Options()
+ chrome_options.add_argument("--headless")
+ chrome_options.add_argument("--no-sandbox")
+ chrome_options.add_argument("--disable-dev-shm-usage")
+ chrome_options.add_argument("--disable-gpu")
+ chrome_options.add_argument("--window-size=1600,900")
+
+ driver = webdriver.Chrome(options=chrome_options)
+ driver.get(f"file://{temp_path}")
+ time.sleep(5) # Wait for chart to render
+ driver.save_screenshot("plot.png")
+ driver.quit()
+
+ Path(temp_path).unlink() # Clean up temp file
+ print("Plot saved to plot.png")
diff --git a/plots/matplotlib/pie/pie-basic/default.py b/plots/matplotlib/pie/pie-basic/default.py
new file mode 100644
index 0000000000..3ed1855eae
--- /dev/null
+++ b/plots/matplotlib/pie/pie-basic/default.py
@@ -0,0 +1,169 @@
+"""
+pie-basic: Basic Pie Chart
+Library: matplotlib
+"""
+
+from typing import TYPE_CHECKING
+
+import matplotlib.pyplot as plt
+import pandas as pd
+
+
+if TYPE_CHECKING:
+ from matplotlib.figure import Figure
+
+
+# PyPlots.ai color palette
+PYPLOTS_COLORS = [
+ "#306998", # Python Blue
+ "#FFD43B", # Python Yellow
+ "#DC2626", # Signal Red
+ "#059669", # Teal Green
+ "#8B5CF6", # Violet
+ "#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 showing proportions of categorical data.
+
+ Args:
+ data: Input DataFrame containing category and value columns.
+ category: Column name for category labels.
+ value: Column name for numeric values.
+ figsize: Figure size as (width, height). Defaults to (10, 8).
+ title: Plot title. Defaults to None.
+ colors: Custom color palette for slices. Defaults to PyPlots palette.
+ startangle: Starting angle for first slice in degrees. Defaults to 90.
+ autopct: Format string for percentage labels. Defaults to '%1.1f%%'.
+ explode: Offset distances for each slice. Defaults to None.
+ shadow: Add shadow effect for 3D appearance. Defaults to False.
+ labels: Custom labels for slices. Defaults to category values.
+ legend: Display legend. Defaults to True.
+ legend_loc: Legend location. Defaults to 'best'.
+ **kwargs: Additional parameters passed to ax.pie().
+
+ Returns:
+ Matplotlib Figure object.
+
+ Raises:
+ ValueError: If data is empty or values contain negatives or sum to zero.
+ KeyError: If required columns are not found in data.
+
+ Example:
+ >>> data = pd.DataFrame({
+ ... 'category': ['A', 'B', 'C'],
+ ... 'value': [30, 50, 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}")
+
+ values = data[value]
+ if (values < 0).any():
+ raise ValueError("Values cannot be negative for pie charts")
+
+ if values.sum() == 0:
+ raise ValueError("Values cannot all be zero")
+
+ # Prepare data
+ categories = data[category].tolist()
+ pie_values = values.tolist()
+ slice_labels = labels if labels is not None else categories
+
+ # Use PyPlots colors if not provided
+ if colors is None:
+ n_slices = len(pie_values)
+ colors = (PYPLOTS_COLORS * ((n_slices // len(PYPLOTS_COLORS)) + 1))[:n_slices]
+
+ # Create figure
+ fig, ax = plt.subplots(figsize=figsize)
+
+ # Configure text properties for style guide compliance
+ textprops = {"fontsize": 14, "fontfamily": ["Inter", "DejaVu Sans", "Arial", "Helvetica", "sans-serif"]}
+
+ # Plot pie chart
+ wedges, texts, autotexts = ax.pie(
+ pie_values,
+ labels=slice_labels if not legend else None,
+ colors=colors,
+ startangle=startangle,
+ autopct=autopct,
+ explode=explode,
+ shadow=shadow,
+ textprops=textprops,
+ **kwargs,
+ )
+
+ # Style percentage labels
+ for autotext in autotexts:
+ autotext.set_fontsize(14)
+ autotext.set_fontweight("bold")
+
+ # Ensure circular shape (equal aspect ratio)
+ ax.set_aspect("equal")
+
+ # Add title if provided
+ if title:
+ ax.set_title(
+ title,
+ fontsize=20,
+ fontweight="semibold",
+ fontfamily=["Inter", "DejaVu Sans", "Arial", "Helvetica", "sans-serif"],
+ pad=20,
+ )
+
+ # Add legend if requested
+ if legend:
+ ax.legend(
+ wedges,
+ slice_labels,
+ loc=legend_loc,
+ fontsize=16,
+ frameon=True,
+ facecolor="white",
+ edgecolor="black",
+ framealpha=1.0,
+ )
+
+ # Tight layout
+ plt.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 - ALWAYS use 'plot.png'!
+ plt.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white")
+ print("Plot saved to plot.png")
diff --git a/plots/plotly/pie/pie-basic/default.py b/plots/plotly/pie/pie-basic/default.py
new file mode 100644
index 0000000000..4ef5cfcebd
--- /dev/null
+++ b/plots/plotly/pie/pie-basic/default.py
@@ -0,0 +1,217 @@
+"""
+pie-basic: Basic Pie Chart
+Library: plotly
+"""
+
+from typing import TYPE_CHECKING
+
+import pandas as pd
+import plotly.graph_objects as go
+
+
+if TYPE_CHECKING:
+ pass
+
+# PyPlots.ai color palette
+PYPLOTS_COLORS = [
+ "#306998", # Python Blue
+ "#FFD43B", # Python Yellow
+ "#DC2626", # Signal Red
+ "#059669", # Teal Green
+ "#8B5CF6", # Violet
+ "#F97316", # Orange
+]
+
+
+def create_plot(
+ data: pd.DataFrame,
+ category: str,
+ value: str,
+ figsize: tuple[int, int] = (1600, 900),
+ 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,
+) -> go.Figure:
+ """
+ Create a basic pie chart for categorical data composition.
+
+ A fundamental pie chart that visualizes proportions and percentages of
+ categorical data as slices of a circular chart. Each slice represents
+ a category's share of the whole, making it 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 names (slice labels).
+ value: Column name for numeric values (slice sizes).
+ figsize: Figure size as (width, height) in pixels. Defaults to (1600, 900).
+ title: Plot title. Defaults to None.
+ colors: Custom color palette for slices. Defaults to PyPlots palette.
+ startangle: Starting angle for first slice in degrees. Defaults to 90.
+ autopct: Format string for percentage labels. Defaults to '%1.1f%%'.
+ explode: Offset distances for each slice (0-0.1 typical). Defaults to None.
+ shadow: Add shadow effect (not fully supported in Plotly). Defaults to False.
+ labels: Custom labels (defaults to category names). Defaults to None.
+ legend: Display legend. Defaults to True.
+ legend_loc: Legend location (Plotly uses different positioning). Defaults to 'best'.
+ **kwargs: Additional parameters passed to go.Pie.
+
+ Returns:
+ Plotly Figure object containing the pie chart.
+
+ Raises:
+ ValueError: If data is empty or contains negative values.
+ KeyError: If required columns are not found in data.
+
+ Example:
+ >>> data = pd.DataFrame({
+ ... 'category': ['Product A', 'Product B', 'Product C', 'Product D'],
+ ... 'value': [35, 25, 20, 20]
+ ... })
+ >>> fig = 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 sum to zero
+ if data[value].sum() == 0:
+ raise ValueError("Pie chart values cannot all be zero")
+
+ # Get data values
+ categories = labels if labels is not None else data[category].tolist()
+ values = data[value].tolist()
+
+ # Set colors - use custom colors or default PyPlots palette
+ color_sequence = colors if colors is not None else PYPLOTS_COLORS
+ # Extend colors if needed
+ while len(color_sequence) < len(values):
+ color_sequence = color_sequence + PYPLOTS_COLORS
+ slice_colors = color_sequence[: len(values)]
+
+ # Handle explode/pull parameter
+ pull_values = explode if explode is not None else [0] * len(values)
+ # Ensure pull_values has correct length
+ if len(pull_values) < len(values):
+ pull_values = list(pull_values) + [0] * (len(values) - len(pull_values))
+
+ # Create figure
+ fig = go.Figure()
+
+ # Build texttemplate from autopct format
+ # Convert matplotlib format to plotly format
+ if "%%" in autopct:
+ # Parse the format string - e.g., '%1.1f%%' -> '%{percent:.1%}'
+ try:
+ # Extract precision from format like '%1.1f%%'
+ import re
+
+ match = re.search(r"%(\d+)\.(\d+)f%%", autopct)
+ if match:
+ precision = int(match.group(2))
+ text_template = f"%{{percent:.{precision}%}}"
+ else:
+ text_template = "%{percent:.1%}"
+ except Exception:
+ text_template = "%{percent:.1%}"
+ else:
+ text_template = "%{percent:.1%}"
+
+ # Add pie trace
+ fig.add_trace(
+ go.Pie(
+ labels=categories,
+ values=values,
+ marker={"colors": slice_colors, "line": {"color": "white", "width": 2}},
+ textinfo="percent",
+ texttemplate=text_template,
+ textfont={"size": 14, "family": "Inter, DejaVu Sans, Arial, sans-serif"},
+ textposition="inside",
+ insidetextorientation="horizontal",
+ pull=pull_values,
+ rotation=startangle,
+ showlegend=legend,
+ hovertemplate="%{label}
Value: %{value}
Percentage: %{percent}",
+ **kwargs,
+ )
+ )
+
+ # Configure legend position based on legend_loc
+ legend_config = {
+ "font": {"size": 16, "family": "Inter, DejaVu Sans, Arial, sans-serif"},
+ "bgcolor": "rgba(255, 255, 255, 1)",
+ "bordercolor": "rgba(0, 0, 0, 0.3)",
+ "borderwidth": 1,
+ }
+
+ # Map matplotlib legend locations to Plotly positions
+ if legend_loc in ["right", "center right"]:
+ legend_config.update({"x": 1.02, "y": 0.5, "xanchor": "left", "yanchor": "middle"})
+ elif legend_loc in ["left", "center left"]:
+ legend_config.update({"x": -0.15, "y": 0.5, "xanchor": "right", "yanchor": "middle"})
+ elif legend_loc in ["upper right"]:
+ legend_config.update({"x": 1.02, "y": 1, "xanchor": "left", "yanchor": "top"})
+ elif legend_loc in ["upper left"]:
+ legend_config.update({"x": -0.15, "y": 1, "xanchor": "right", "yanchor": "top"})
+ elif legend_loc in ["lower right"]:
+ legend_config.update({"x": 1.02, "y": 0, "xanchor": "left", "yanchor": "bottom"})
+ elif legend_loc in ["lower left"]:
+ legend_config.update({"x": -0.15, "y": 0, "xanchor": "right", "yanchor": "bottom"})
+ else:
+ # Default 'best' - place on the right
+ legend_config.update({"x": 1.02, "y": 0.5, "xanchor": "left", "yanchor": "middle"})
+
+ # Update layout with styling
+ fig.update_layout(
+ title={
+ "text": title,
+ "x": 0.5,
+ "xanchor": "center",
+ "font": {"size": 20, "family": "Inter, DejaVu Sans, Arial, sans-serif", "weight": 600},
+ }
+ if title
+ else None,
+ template="plotly_white",
+ width=figsize[0],
+ height=figsize[1],
+ showlegend=legend,
+ legend=legend_config if legend else None,
+ margin={"l": 40, "r": 150 if legend else 40, "t": 80 if title else 40, "b": 40},
+ paper_bgcolor="white",
+ plot_bgcolor="white",
+ )
+
+ # Ensure pie chart is circular (equal aspect ratio)
+ fig.update_layout(yaxis={"scaleanchor": "x", "scaleratio": 1})
+
+ 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.write_image("plot.png", width=1600, height=900, scale=2)
+ print("Plot saved to plot.png")
diff --git a/plots/plotnine/pie/pie-basic/default.py b/plots/plotnine/pie/pie-basic/default.py
new file mode 100644
index 0000000000..cf8942839e
--- /dev/null
+++ b/plots/plotnine/pie/pie-basic/default.py
@@ -0,0 +1,195 @@
+"""
+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.
+"""
+
+from typing import TYPE_CHECKING
+
+import matplotlib.pyplot as plt
+import pandas as pd
+
+
+if TYPE_CHECKING:
+ from matplotlib.figure import Figure
+
+# PyPlots.ai color palette
+PYPLOTS_COLORS = [
+ "#306998", # Python Blue (Primary)
+ "#FFD43B", # Python Yellow
+ "#DC2626", # Signal Red
+ "#059669", # Teal Green
+ "#8B5CF6", # Violet
+ "#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")
diff --git a/plots/pygal/pie/pie-basic/default.py b/plots/pygal/pie/pie-basic/default.py
new file mode 100644
index 0000000000..ca44c93104
--- /dev/null
+++ b/plots/pygal/pie/pie-basic/default.py
@@ -0,0 +1,147 @@
+"""
+pie-basic: Basic Pie Chart
+Library: pygal
+"""
+
+import pandas as pd
+import pygal
+from pygal.style import Style
+
+
+# PyPlots.ai color palette
+PYPLOTS_COLORS = (
+ "#306998", # Python Blue (Primary)
+ "#FFD43B", # Python Yellow
+ "#DC2626", # Signal Red
+ "#059669", # Teal Green
+ "#8B5CF6", # Violet
+ "#F97316", # Orange
+)
+
+
+def create_plot(
+ data: pd.DataFrame,
+ category: str,
+ value: str,
+ figsize: tuple[int, int] = (1600, 900),
+ title: str | None = None,
+ colors: list[str] | None = None,
+ startangle: float = 90,
+ legend: bool = True,
+ legend_loc: str = "right",
+ inner_radius: float = 0,
+ **kwargs,
+) -> pygal.Pie:
+ """
+ Create a basic pie chart for visualizing proportions of categorical data.
+
+ Args:
+ data: Input DataFrame containing the data to plot.
+ category: Column name for category labels.
+ value: Column name for numeric values representing each slice's proportion.
+ figsize: Figure size as (width, height) in pixels.
+ title: Optional plot title.
+ colors: Custom color palette for slices (defaults to PyPlots palette).
+ startangle: Starting angle for first slice in degrees (not used in pygal).
+ legend: Whether to display legend.
+ legend_loc: Legend location ('right', 'bottom', or 'top').
+ inner_radius: Inner radius for donut chart (0-1, 0 for solid pie).
+ **kwargs: Additional parameters passed to pygal.Pie.
+
+ Returns:
+ pygal.Pie chart object.
+
+ Raises:
+ ValueError: If data is empty or contains negative values.
+ 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}")
+
+ # Handle missing values
+ clean_data = data[[category, value]].dropna()
+
+ if clean_data.empty:
+ raise ValueError("Data cannot be empty after removing missing values")
+
+ # Validate non-negative values
+ if (clean_data[value] < 0).any():
+ raise ValueError("Pie chart values must be non-negative")
+
+ # Check if all values sum to zero
+ total = clean_data[value].sum()
+ if total == 0:
+ raise ValueError("Values sum to zero; cannot create pie chart")
+
+ # Use provided colors or default PyPlots palette
+ chart_colors = tuple(colors) if colors else PYPLOTS_COLORS
+
+ # Create custom style
+ custom_style = Style(
+ background="white",
+ plot_background="white",
+ foreground="#333333",
+ foreground_strong="#333333",
+ foreground_subtle="#666666",
+ colors=chart_colors,
+ font_family="Inter, DejaVu Sans, Arial, Helvetica, sans-serif",
+ title_font_size=20,
+ legend_font_size=16,
+ value_font_size=14,
+ tooltip_font_size=14,
+ )
+
+ # Determine legend position
+ legend_at_bottom = legend_loc == "bottom"
+ legend_box_size = 16 if legend else 0
+
+ # Create chart
+ chart = pygal.Pie(
+ width=figsize[0],
+ height=figsize[1],
+ title=title,
+ style=custom_style,
+ show_legend=legend,
+ legend_at_bottom=legend_at_bottom,
+ legend_box_size=legend_box_size,
+ inner_radius=inner_radius,
+ print_values=True,
+ value_formatter=lambda x: f"{x:.1f}%",
+ **kwargs,
+ )
+
+ # Add each category as a separate slice with percentage value
+ for _, row in clean_data.iterrows():
+ cat_name = str(row[category])
+ cat_value = float(row[value])
+ percentage = (cat_value / total) * 100
+ chart.add(cat_name, [{"value": percentage, "label": cat_name}])
+
+ 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
+ chart = create_plot(sample_data, "category", "value", title="Market Share Distribution")
+
+ # Save to PNG
+ chart.render_to_png("plot.png")
+ print("Plot saved to plot.png")
diff --git a/plots/seaborn/pie/pie-basic/default.py b/plots/seaborn/pie/pie-basic/default.py
new file mode 100644
index 0000000000..dbaf1eb03a
--- /dev/null
+++ b/plots/seaborn/pie/pie-basic/default.py
@@ -0,0 +1,184 @@
+"""
+pie-basic: Basic Pie Chart
+Library: seaborn
+
+Note: Seaborn does not have a native pie chart function. This implementation uses
+matplotlib's pie chart with seaborn's styling context for consistent aesthetics.
+"""
+
+from typing import TYPE_CHECKING
+
+import matplotlib.pyplot as plt
+import pandas as pd
+import seaborn as sns
+
+
+if TYPE_CHECKING:
+ from matplotlib.figure import Figure
+
+
+# PyPlots.ai color palette
+PYPLOTS_COLORS = [
+ "#306998", # Python Blue (Primary)
+ "#FFD43B", # Python Yellow
+ "#DC2626", # Signal Red
+ "#059669", # Teal Green
+ "#8B5CF6", # Violet
+ "#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 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.
+
+ Note: Seaborn does not have a native pie chart function. This implementation
+ uses matplotlib's pie chart with seaborn's styling context.
+
+ Args:
+ data: Input DataFrame containing category and value columns
+ category: Column name for category labels
+ value: Column name for numeric values (proportions)
+ figsize: Figure size as (width, height) in inches
+ title: Plot title (optional)
+ colors: Custom color palette for slices (uses PyPlots.ai palette if None)
+ startangle: Starting angle for first slice in degrees from positive x-axis
+ 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 if None)
+ legend: Whether to display legend
+ legend_loc: Legend location
+ **kwargs: Additional parameters passed to ax.pie()
+
+ Returns:
+ Matplotlib Figure object
+
+ Raises:
+ ValueError: If data is empty or contains negative values
+ KeyError: If required columns not found in data
+
+ Example:
+ >>> data = pd.DataFrame({
+ ... 'category': ['Product A', 'Product B', 'Product C'],
+ ... 'value': [35, 40, 25]
+ ... })
+ >>> fig = 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}")
+
+ # Extract values and validate
+ values = data[value].values
+ categories = data[category].values
+
+ if (values < 0).any():
+ raise ValueError("Pie chart values cannot be negative")
+
+ if values.sum() == 0:
+ raise ValueError("Sum of values cannot be zero")
+
+ # Set seaborn style context for consistent aesthetics
+ sns.set_theme(style="white")
+
+ # Create figure with equal aspect ratio to prevent elliptical distortion
+ fig, ax = plt.subplots(figsize=figsize)
+ ax.set_aspect("equal")
+
+ # Determine colors
+ if colors is None:
+ # Extend palette if needed for more categories
+ n_categories = len(categories)
+ if n_categories <= len(PYPLOTS_COLORS):
+ pie_colors = PYPLOTS_COLORS[:n_categories]
+ else:
+ # Use seaborn color palette for many categories
+ pie_colors = sns.color_palette("husl", n_categories)
+ else:
+ pie_colors = colors
+
+ # Determine labels
+ pie_labels = labels if labels is not None else categories
+
+ # Create pie chart
+ wedges, texts, autotexts = ax.pie(
+ values,
+ labels=pie_labels if not legend else None,
+ autopct=autopct,
+ startangle=startangle,
+ explode=explode,
+ shadow=shadow,
+ colors=pie_colors,
+ wedgeprops={"edgecolor": "white", "linewidth": 1.5},
+ textprops={"fontsize": 12},
+ pctdistance=0.75,
+ **kwargs,
+ )
+
+ # Style percentage labels
+ for autotext in autotexts:
+ autotext.set_fontsize(11)
+ autotext.set_fontweight("bold")
+ autotext.set_color("white")
+
+ # Add legend if requested
+ if legend:
+ ax.legend(
+ wedges,
+ pie_labels,
+ title=category,
+ loc=legend_loc,
+ bbox_to_anchor=(1, 0, 0.5, 1),
+ frameon=True,
+ facecolor="white",
+ edgecolor="gray",
+ fontsize=11,
+ )
+
+ # Set title if provided
+ if title is not None:
+ ax.set_title(title, fontsize=16, fontweight="semibold", pad=20)
+
+ # Layout adjustment
+ plt.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 - ALWAYS use 'plot.png'!
+ plt.savefig("plot.png", dpi=300, bbox_inches="tight")
+ print("Plot saved to plot.png")
diff --git a/specs/pie-basic.md b/specs/pie-basic.md
new file mode 100644
index 0000000000..1ff6a0236a
--- /dev/null
+++ b/specs/pie-basic.md
@@ -0,0 +1,84 @@
+# pie-basic: Basic Pie Chart
+
+A fundamental pie chart that visualizes proportions and percentages of categorical data as slices of a circular chart. Each slice represents a category's share of the whole, making it ideal for showing composition and distribution across a small number of categories.
+
+## Data Requirements
+
+| Column | Type | Required | Description |
+|--------|------|----------|-------------|
+| category | string | Yes | Category names for each slice |
+| value | numeric | Yes | Numeric values representing each category's proportion |
+
+## Optional Parameters
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| figsize | tuple | (10, 8) | Figure size as (width, height) |
+| title | string | None | Plot title |
+| colors | list | None | Custom color palette for slices |
+| startangle | float | 90 | Starting angle for first slice (degrees from positive x-axis) |
+| autopct | string | '%1.1f%%' | Format string for percentage labels |
+| explode | list | None | Offset distances for each slice (0-0.1 typical) |
+| shadow | bool | False | Add shadow effect for 3D appearance |
+| labels | list | None | Custom labels (defaults to category names) |
+| legend | bool | True | Display legend |
+| legend_loc | string | 'best' | Legend location |
+
+## Quality Criteria
+
+- [ ] All slices are clearly distinguishable with sufficient color contrast
+- [ ] Percentage labels are readable and do not overlap with slice boundaries
+- [ ] Category labels or legend clearly identify each slice
+- [ ] Percentages sum to approximately 100% (within rounding tolerance)
+- [ ] Chart is circular (equal aspect ratio maintained)
+- [ ] Small slices (< 5%) remain visible and labeled appropriately
+- [ ] Color palette is colorblind-friendly
+- [ ] Title is positioned clearly above the chart
+- [ ] Legend does not overlap with the pie chart
+- [ ] Type hints and input validation are present
+
+## Expected Output
+
+A circular pie chart divided into colored slices, where each slice's arc length is proportional to its value relative to the total. The chart should display:
+- Clear slice boundaries with distinct colors for each category
+- Percentage labels positioned inside or near each slice
+- Category names either as direct labels, in a legend, or both
+- A centered, balanced appearance with no visual distortion
+- Professional styling with appropriate spacing between elements
+
+The chart should be immediately readable, allowing viewers to quickly compare relative proportions and identify the largest and smallest categories.
+
+## Tags
+
+pie, composition, proportions, categorical, basic, 2d
+
+## Use Cases
+
+- Market share distribution showing company percentages in an industry
+- Budget allocation breakdown displaying spending across departments
+- Survey response analysis visualizing answer percentages
+- Portfolio composition showing asset class distribution in investments
+- Demographic breakdown displaying population segments by age or region
+- Resource utilization showing time or capacity allocation across projects
+
+## Example Data
+
+```python
+import pandas as pd
+
+data = pd.DataFrame({
+ 'category': ['Product A', 'Product B', 'Product C', 'Product D', 'Other'],
+ 'value': [35, 25, 20, 15, 5]
+})
+
+fig = create_plot(data, 'category', 'value', title='Market Share Distribution')
+```
+
+## Implementation Notes
+
+- Validate that values are non-negative
+- Handle cases where values sum to zero gracefully
+- Consider using a donut hole (wedgeprops) for modern aesthetics in some libraries
+- For many categories (> 7), consider grouping small slices into "Other"
+- Ensure aspect ratio is equal to prevent elliptical distortion
+- Position percentage labels to avoid overlap, especially for small slices