diff --git a/plots/pie-drilldown/implementations/python/letsplot.py b/plots/pie-drilldown/implementations/python/letsplot.py index 3921d8cbf9..13b727cd0f 100644 --- a/plots/pie-drilldown/implementations/python/letsplot.py +++ b/plots/pie-drilldown/implementations/python/letsplot.py @@ -1,10 +1,11 @@ -""" pyplots.ai +""" anyplot.ai pie-drilldown: Drilldown Pie Chart with Click Navigation -Library: letsplot 4.8.2 | Python 3.13.11 -Quality: 82/100 | Created: 2025-12-31 +Library: letsplot 4.9.0 | Python 3.13.13 +Quality: 85/100 | Updated: 2026-05-15 """ import json +import os import pandas as pd from lets_plot import * # noqa: F403 @@ -13,8 +14,23 @@ LetsPlot.setup_html() # noqa: F405 +# Theme tokens +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" + +# Okabe-Ito palette (using position 1 as first series) +OKABE_ITO = [ + "#009E73", # bluish green (brand) + "#D55E00", # vermillion + "#0072B2", # blue + "#CC79A7", # reddish purple +] + # Hierarchical data: Company budget breakdown by department -# Structure: id, name, value, parent (following spec's data format) hierarchy_data = { "root": {"name": "All Departments", "children": ["engineering", "marketing", "operations", "hr"]}, "engineering": { @@ -57,47 +73,41 @@ "hr_development": {"name": "Development", "parent": "hr", "value": 25000}, } -# Color palette - Python Blue first, then colorblind-safe +# Color scheme: Okabe-Ito for main departments, with shade variations for subcategories colors = { - "Engineering": "#306998", - "Marketing": "#FFD43B", - "Operations": "#4CAF50", - "Human Resources": "#E07A5F", - # Engineering sub-colors (blue shades) + "Engineering": OKABE_ITO[0], # #009E73 + "Marketing": OKABE_ITO[1], # #D55E00 + "Operations": OKABE_ITO[2], # #0072B2 + "Human Resources": OKABE_ITO[3], # #CC79A7 + # Engineering sub-colors (green shades) "Salaries": "#4A8BBE", "Tools & Software": "#6BA3D6", "Cloud Services": "#8CBBEE", "Training": "#ADD3F5", - # Marketing sub-colors (yellow/gold shades) - "Digital Ads": "#F5C800", - "Content Creation": "#E6BE35", - "Events": "#D4A72C", - "Brand Design": "#C49022", - # Operations sub-colors (green shades) - "Facilities": "#66BB6A", - "Equipment": "#81C784", - "Supplies": "#A5D6A7", - # HR sub-colors (coral shades) - "Recruiting": "#EF9A9A", - "Benefits Admin": "#F48FB1", - "Development": "#CE93D8", + # Marketing sub-colors (orange/red shades) + "Digital Ads": "#E89A3C", + "Content Creation": "#E6A84E", + "Events": "#E3B660", + "Brand Design": "#E0C472", + # Operations sub-colors (blue shades) + "Facilities": "#5A9BD4", + "Equipment": "#75AAE0", + "Supplies": "#90B9EC", + # HR sub-colors (purple shades) + "Recruiting": "#D8A7C7", + "Benefits Admin": "#DFB5D3", + "Development": "#E6C3DF", } # Create data for root level (main departments) -# Order slices so labels are distributed around the circle without overlap -# Large slices get their labels far apart, small slices fill gaps root_children = hierarchy_data["root"]["children"] -# Order by size descending, then interleave to spread labels -# Eng (45%), Mkt (28%), Ops (18%), HR (9%) - put largest at top, smallest at bottom -reordered_children = ["engineering", "marketing", "operations", "hr"] -categories = [hierarchy_data[child_id]["name"] for child_id in reordered_children] -values = [hierarchy_data[child_id]["value"] for child_id in reordered_children] +categories = [hierarchy_data[child_id]["name"] for child_id in root_children] +values = [hierarchy_data[child_id]["value"] for child_id in root_children] total = sum(values) percentages = [(v / total) * 100 for v in values] -# Format value labels with dollar amounts (e.g., "$450K") +# Format value labels value_labels = [f"${v // 1000}K" for v in values] - df = pd.DataFrame({"category": categories, "value": values, "pct": percentages, "value_label": value_labels}) # Preserve category order @@ -106,57 +116,61 @@ # Define colors in order slice_colors = [colors[cat] for cat in categories] -# Create percentage label column for proper formatting +# Create labels for display df["pct_label"] = [f"{p:.1f}%" for p in percentages] - -# Create combined label with category name, value, and percentage on one line df["combined_label"] = [ f"{cat}: {lbl} ({pct:.0f}%)" for cat, lbl, pct in zip(categories, value_labels, percentages, strict=True) ] # Create main pie chart for static PNG -# Use pie labels inside the slices to avoid overlap issues -plot = ( +anyplot_theme = ( # noqa: F405 + theme( # noqa: F405 + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), # noqa: F405 + panel_background=element_rect(fill=PAGE_BG), # noqa: F405 + axis_title=element_text(color=INK), # noqa: F405 + axis_text=element_text(color=INK_SOFT), # noqa: F405 + plot_title=element_text(color=INK, size=24), # noqa: F405 + plot_subtitle=element_text(color=INK_SOFT, size=16), # noqa: F405 + legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), # noqa: F405 + legend_text=element_text(color=INK_SOFT, size=14), # noqa: F405 + legend_title=element_text(color=INK, size=16), # noqa: F405 + ) +) + +plot = ( # noqa: F405 ggplot(df) # noqa: F405 + geom_pie( # noqa: F405 aes(slice="value", fill="category"), # noqa: F405 stat="identity", - size=50, # Good size for visibility - hole=0.35, # Donut style - stroke=3, # White borders between slices - color="white", # Border color - spacer_width=0.3, # Small gaps between slices - labels=layer_labels() # noqa: F405 - .line("@{pct_label}") # Only show percentage inside - .size(18), + size=50, + hole=0.35, + stroke=3, + color=PAGE_BG, + spacer_width=0.3, + labels=layer_labels().line("@{pct_label}").size(18), # noqa: F405 ) + scale_fill_manual(values=slice_colors) # noqa: F405 + labs( # noqa: F405 - title="pie-drilldown · letsplot · pyplots.ai", - subtitle="Company Budget Breakdown · Click slice to drill down (HTML)", - fill="", # No legend title needed - ) - + ggsize(1600, 900) # noqa: F405 - Landscape format - + guides(fill=guide_legend(ncol=1)) # noqa: F405 - Single column legend - + scale_fill_manual( # noqa: F405 - values=slice_colors, labels=[f"{cat} - {lbl}" for cat, lbl in zip(categories, value_labels, strict=True)] + title="pie-drilldown · letsplot · anyplot.ai", + subtitle="Company Budget Breakdown · Click slices to explore (interactive HTML available)", + fill="Department", ) + + ggsize(1600, 900) # noqa: F405 + + guides(fill=guide_legend(ncol=1)) # noqa: F405 + + anyplot_theme + theme_void() # noqa: F405 + theme( # noqa: F405 - plot_title=element_text(size=32, hjust=0.5, face="bold"), # noqa: F405 - plot_subtitle=element_text(size=18, hjust=0.5, color="#666666"), # noqa: F405 - legend_title=element_text(size=20), # noqa: F405 - legend_text=element_text(size=20), # noqa: F405 + plot_title=element_text(size=24, hjust=0.5, face="bold"), # noqa: F405 + plot_subtitle=element_text(size=16, hjust=0.5), # noqa: F405 legend_position="right", plot_margin=[40, 60, 40, 60], ) ) # Save static PNG (scale 3 for 4800x2700) -export_ggsave(plot, filename="plot.png", path=".", scale=3) +export_ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=3) - -# Prepare data for all levels as JSON (inline, no function) +# Prepare data for all levels as JSON (for HTML interactivity) levels_data = {} for level_id in ["root", "engineering", "marketing", "operations", "hr"]: level_data = hierarchy_data[level_id] @@ -171,17 +185,22 @@ "categories": cats, "values": vals, "percentages": pcts, - "colors": [colors.get(cat, "#306998") for cat in cats], + "colors": [colors.get(cat, OKABE_ITO[0]) for cat in cats], "children": hierarchy_data[level_id].get("children", []), "parent": hierarchy_data[level_id].get("parent"), } # HTML template with embedded JavaScript for drilldown +html_bg = "#FAF8F1" if THEME == "light" else "#1A1A17" +html_text = "#1A1A17" if THEME == "light" else "#F0EFE8" +html_text_soft = "#4A4A44" if THEME == "light" else "#B8B7B0" +html_elevated = "#FFFDF6" if THEME == "light" else "#242420" + html_content = f""" - pie-drilldown · letsplot · pyplots.ai + pie-drilldown · letsplot · anyplot.ai
-

pie-drilldown · letsplot · pyplots.ai

+

pie-drilldown · letsplot · anyplot.ai

Company Budget Breakdown with Interactive Navigation

""" -with open("plot.html", "w") as f: +with open(f"plot-{THEME}.html", "w") as f: f.write(html_content) diff --git a/plots/pie-drilldown/metadata/python/letsplot.yaml b/plots/pie-drilldown/metadata/python/letsplot.yaml index f265129efd..cc1c354ce5 100644 --- a/plots/pie-drilldown/metadata/python/letsplot.yaml +++ b/plots/pie-drilldown/metadata/python/letsplot.yaml @@ -1,40 +1,247 @@ library: letsplot +language: python specification_id: pie-drilldown created: '2025-12-31T21:35:57Z' -updated: '2025-12-31T22:00:45Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20627530595 +updated: '2026-05-15T17:14:10Z' +generated_by: claude-haiku +workflow_run: 25930557762 issue: 3072 -python_version: 3.13.11 -library_version: 4.8.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/letsplot/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/letsplot/plot.html -quality_score: 82 +python_version: 3.13.13 +library_version: 4.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/letsplot/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/letsplot/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/letsplot/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/pie-drilldown/python/letsplot/plot-dark.html +quality_score: 85 +review: + strengths: + - Excellent theme adaptation with data colors identical across light/dark renders + - 'Perfect visual quality: all text legible, no overlaps, proper layout and proportions' + - Correct Okabe-Ito palette usage with brand green as first series + - Clean, well-organized code with proper theme token definitions + - Realistic professional data context (company budgets) with accurate proportions + - Accurate percentage labels and legend values + - Correct title format and descriptive subtitle + weaknesses: + - Design lacks visual storytelling - shows proportions but doesn't emphasize key + insights + - Limited use of distinctive letsplot features; implementation is fairly generic + for the library + - PNG shows only root level due to static format; interactive drilldown is in HTML + only (acceptable but spec features not visually demonstrated in primary output) + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) - correctly rendered, not pure white + Chrome: Title "pie-drilldown · letsplot · anyplot.ai" in dark text (#1A1A17), subtitle in secondary color (#4A4A44), both clearly readable. Legend on right with department names and color swatches. All text properly sized and legible. + Data: Donut chart with four slices using Okabe-Ito palette - green (#009E73) 45.0%, orange (#D55E00) 28.0%, blue (#0072B2) 18.0%, pink (#CC79A7) 9.0%. Percentage labels clearly visible on each slice. White stroke separates slices clearly. + Legibility verdict: PASS - all elements clearly readable against light background. Excellent contrast and spacing. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) - correctly rendered, not pure black + Chrome: Title in light text (#F0EFE8), subtitle/legend in secondary light (#B8B7B0). All text clearly visible against dark surface. No dark-on-dark failures. + Data: Identical donut colors to light render - green, orange, blue, pink remain exactly the same (only chrome adapted to theme). Percentage labels visible and readable. Dark stroke on donut appropriately themed. + Legibility verdict: PASS - both renders have identical data colors (theme-independent positions 1-7), chrome properly flipped to light colors for readability. + verdict: APPROVED + criteria_checklist: + visual_quality: + score: 30 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: All font sizes explicitly set (title 24pt, subtitle 16pt, legend + 14pt). Perfectly readable in both light and dark themes. + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping text. Legend positioned cleanly to right, labels well-spaced, + percentage labels clear on slices. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Donut slices clearly visible and distinct. Center hole well-proportioned. + All data elements properly sized and visible. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Okabe-Ito palette is colorblind-safe. All colors provide sufficient + contrast and are distinguishable. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: 'Perfect layout: chart centered, legend appropriately positioned, + balanced margins. Good use of canvas area.' + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Descriptive title with library/source attribution. Subtitle provides + context about interactivity. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series correctly #009E73. Multi-series follows Okabe-Ito: + #D55E00, #0072B2, #CC79A7. Backgrounds #FAF8F1 (light) and #1A1A17 (dark). + Chrome correctly theme-adaptive in both renders.' + design_excellence: + score: 10 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 4 + max: 8 + passed: false + comment: Well-configured library defaults with clean minimal aesthetic. Uses + Okabe-Ito correctly and theme_void() appropriately. No exceptional design + choices beyond defaults. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: false + comment: 'Good refinement: theme_void() removes unnecessary axes, generous + spacing, clean colors. Could use more custom styling.' + - id: DE-03 + name: Data Storytelling + score: 2 + max: 6 + passed: false + comment: Data displayed straightforwardly without visual hierarchy. Shows + proportions but doesn't emphasize insights like 'Engineering dominates'. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct donut/pie chart implementation. + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Root-level pie shown with percentage labels. Hierarchical data structure + present. HTML provides full drilldown/breadcrumbs/click interaction. Static + PNG is appropriate for letsplot; interactive version available. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Slice sizes correctly represent budget proportions. All departments + visible with accurate percentages. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: 'Title format correct: ''pie-drilldown · letsplot · anyplot.ai''. + Legend labels match department data.' + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: 'Shows all four main departments with realistic variation: Engineering + 45%, Marketing 28%, Operations 18%, HR 9%.' + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Professional company budget scenario. Departments are realistic and + neutral. Real-world plausible distribution. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 'Budget proportions are realistic: Engineering typically largest, + followed by Marketing, Operations, then HR support. Factually plausible.' + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: 'Clean linear flow: imports → data → plot → export. No unnecessary + functions or classes.' + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Hardcoded deterministic data. No random elements requiring seed. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: Only necessary imports (json, os, pandas, lets_plot). All are actually + used in the code. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, Pythonic code. Proper theme token definitions. No fake functionality + or over-engineering. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Correctly outputs plot-{THEME}.png and plot-{THEME}.html using export_ggsave + function. + library_mastery: + score: 5 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: false + comment: Correct ggplot API usage (geom_pie, scale_fill_manual, theme customization). + Follows letsplot patterns well but fairly standard approach. + - id: LM-02 + name: Distinctive Features + score: 1 + max: 5 + passed: false + comment: Uses layer_labels() for slice labeling (letsplot-specific), but this + is basic. No advanced library-specific features beyond standard pie chart. impl_tags: dependencies: [] techniques: - html-export - - custom-legend patterns: - data-generation - - matrix-construction dataprep: [] styling: - - alpha-blending - minimal-chrome -review: - strengths: - - Clean donut chart design with clear percentage labels inside slices - - Colorblind-safe color palette with good visual distinction between categories - - Comprehensive HTML implementation with full drilldown functionality, breadcrumb - navigation, and cursor changes on hover - - Realistic company budget data with sensible hierarchical structure (4 departments, - 3-4 subcategories each) - - Legend includes both category names and dollar values for context - weaknesses: - - Static PNG only shows root level without any indication that drilldown is available - (no visual cues that slices are clickable) - - Legend could include a visual indicator or note about drilldown functionality - - Layout has significant whitespace on the right side between chart and legend - - HTML drilldown uses custom Canvas/JavaScript rather than leveraging lets-plot - native interactive capabilities