diff --git a/plots/ecdf-basic/implementations/python/letsplot.py b/plots/ecdf-basic/implementations/python/letsplot.py index 7f23e26007..3d294305e0 100644 --- a/plots/ecdf-basic/implementations/python/letsplot.py +++ b/plots/ecdf-basic/implementations/python/letsplot.py @@ -1,53 +1,74 @@ -""" pyplots.ai +""" anyplot.ai ecdf-basic: Basic ECDF Plot -Library: letsplot 4.8.2 | Python 3.13.11 -Quality: 96/100 | Created: 2025-12-23 +Library: letsplot 4.9.0 | Python 3.14.4 +Quality: 87/100 | Updated: 2026-04-24 """ +import os + import numpy as np import pandas as pd -from lets_plot import * # noqa: F403 -from lets_plot.export import ggsave as export_ggsave +from lets_plot import ( + LetsPlot, + aes, + element_blank, + element_line, + element_rect, + element_text, + ggplot, + ggsize, + labs, + scale_y_continuous, + stat_ecdf, + theme, + theme_minimal, +) +from lets_plot.export import ggsave + +LetsPlot.setup_html() -LetsPlot.setup_html() # noqa: F405 +# Theme tokens +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +GRID = "#C9C7C1" if THEME == "light" else "#565551" +BRAND = "#009E73" -# Data - Response times (ms) from a web service +# Data — Web service response times (ms) with mixed distribution np.random.seed(42) response_times = np.concatenate( - [ - np.random.exponential(scale=50, size=150), # Fast responses - np.random.normal(loc=200, scale=30, size=50), # Slower responses - ] + [np.random.exponential(scale=50, size=150), np.random.normal(loc=200, scale=30, size=50)] ) +df = pd.DataFrame({"response_time": response_times}) -# Sort data and calculate ECDF values -sorted_values = np.sort(response_times) -ecdf_values = np.arange(1, len(sorted_values) + 1) / len(sorted_values) - -df = pd.DataFrame({"response_time": sorted_values, "ecdf": ecdf_values}) - -# Plot - ECDF as step function +# Plot — ECDF using stat_ecdf with step geometry plot = ( - ggplot(df, aes(x="response_time", y="ecdf")) # noqa: F405 - + geom_step(color="#306998", size=2) # noqa: F405 - + labs( # noqa: F405 - x="Response Time (ms)", y="Cumulative Proportion", title="ecdf-basic · letsplot · pyplots.ai" + ggplot(df, aes(x="response_time")) + + stat_ecdf(geom="step", color=BRAND, size=2) + + labs( + x="Response Time (ms)", + y="Cumulative Proportion", + title="Web Service Response Times · ecdf-basic · letsplot · anyplot.ai", ) - + scale_y_continuous(limits=[0, 1], breaks=[0, 0.25, 0.5, 0.75, 1.0]) # noqa: F405 - + ggsize(1600, 900) # noqa: F405 - + theme_minimal() # noqa: F405 - + theme( # noqa: F405 - axis_text=element_text(size=16), # noqa: F405 - axis_title=element_text(size=20), # noqa: F405 - plot_title=element_text(size=24), # noqa: F405 - panel_grid_major=element_line(color="#CCCCCC", size=0.5, linetype="dashed"), # noqa: F405 - panel_grid_minor=element_blank(), # noqa: F405 + + scale_y_continuous(limits=[0, 1], breaks=[0, 0.25, 0.5, 0.75, 1.0]) + + ggsize(1600, 900) + + theme_minimal() + + theme( + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), + panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG), + legend_background=element_rect(fill=PAGE_BG, color=PAGE_BG), + panel_grid_major=element_line(color=GRID, size=0.6), + panel_grid_minor=element_blank(), + axis_line=element_line(color=INK_SOFT, size=0.6), + axis_ticks=element_line(color=INK_SOFT, size=0.5), + axis_text=element_text(size=16, color=INK_SOFT), + axis_title=element_text(size=20, color=INK), + plot_title=element_text(size=24, color=INK), ) ) -# Save PNG (scale 3x to get 4800 x 2700 px) -export_ggsave(plot, filename="plot.png", path=".", scale=3) - -# Save HTML for interactive version -export_ggsave(plot, filename="plot.html", path=".") +# Save +ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=3) +ggsave(plot, filename=f"plot-{THEME}.html", path=".") diff --git a/plots/ecdf-basic/metadata/python/letsplot.yaml b/plots/ecdf-basic/metadata/python/letsplot.yaml index 4ebecadbe3..29ddc7dde6 100644 --- a/plots/ecdf-basic/metadata/python/letsplot.yaml +++ b/plots/ecdf-basic/metadata/python/letsplot.yaml @@ -1,155 +1,190 @@ library: letsplot +language: python specification_id: ecdf-basic created: '2025-12-23T13:02:33Z' -updated: '2025-12-23T13:09:25Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20461377301 -issue: 0 -python_version: 3.13.11 -library_version: 4.8.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/ecdf-basic/letsplot/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/ecdf-basic/letsplot/plot.html -quality_score: 96 -impl_tags: - dependencies: [] - techniques: - - html-export - - grid-styling - patterns: - - data-generation - - explicit-figure - dataprep: [] - styling: [] +updated: '2026-04-24T13:27:16Z' +generated_by: claude-opus +workflow_run: 24891460839 +issue: 976 +python_version: 3.14.4 +library_version: 4.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/ecdf-basic/python/letsplot/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/ecdf-basic/python/letsplot/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/ecdf-basic/python/letsplot/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/ecdf-basic/python/letsplot/plot-dark.html +quality_score: 87 review: strengths: - - Excellent realistic data scenario with web service response times showing mixed - distribution characteristics - - Clean ECDF step function with perfect y-axis range (0 to 1) and sensible tick - marks at quartiles - - Proper title format and descriptive axis labels with units - - Good use of theme_minimal() with appropriately sized text elements - - Correct manual ECDF calculation showing understanding of the algorithm + - Correct ECDF implementation using stat_ecdf(geom='step') — idiomatic grammar-of-graphics + approach that computes the distribution natively + - 'Perfect code quality: KISS structure, reproducible seed, clean imports, correct + output filenames for both PNG and HTML' + - Realistic and well-chosen data scenario (web service response times with bimodal + exponential+normal distribution) + - 'Full theme-adaptive chrome: PAGE_BG, INK, INK_SOFT tokens applied correctly across + both light and dark renders' + - Explicitly set font sizes (24pt title, 20pt axis labels, 16pt ticks) matching + the style guide requirements + - 'Brand green #009E73 used correctly as the single-series color, consistent across + both renders' weaknesses: - - Grid lines are too subtle (dashed at 0.5 width) making percentile reading difficult - - the spec notes grid lines should help with reading specific percentile values - image_description: The plot displays an ECDF (Empirical Cumulative Distribution - Function) showing web service response times. The chart uses a dark blue (#306998) - step line against a white background with light gray dashed grid lines. The X-axis - is labeled "Response Time (ms)" ranging from 0 to ~270ms, and the Y-axis shows - "Cumulative Proportion" from 0 to 1 with breaks at 0.25, 0.5, 0.75, and 1.0. The - title "ecdf-basic · letsplot · pyplots.ai" appears at the top. The step function - shows a steep initial rise (reflecting the exponential distribution of fast responses) - that gradually flattens, with a secondary steeper section around 180-220ms (reflecting - the normal distribution of slower responses). + - 'Grid lines are too prominent: GRID colors (#C9C7C1 light / #565551 dark) are + ~2.5x more opaque than the style guide''s recommended rgba(26,26,23,0.10) — grid + competes with the data line rather than fading into the background' + - 'No data storytelling: the bimodal distribution shape (exponential fast-path + + normal slow-path) is a clear insight that goes unexplained — key percentile markers + (e.g. p50, p90) or a note about the two populations would lift DE-03' + - 'Title format does not match required spec: should be ''ecdf-basic · letsplot + · anyplot.ai'' — the descriptive prefix ''Web Service Response Times · '' makes + it a 4-part title instead of the prescribed 3-part format' + - 'LM-02 is minimal: the implementation uses stat_ecdf which is available in any + ggplot-style library; lets-plot''s distinctive interactive capabilities (tooltips, + HTML export) are not leveraged in the plot layer itself' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white consistent with #FAF8F1 — not pure white, correct brand surface. + Chrome: Title "Web Service Response Times · ecdf-basic · letsplot · anyplot.ai" rendered in dark ink at top-left; axis labels "Response Time (ms)" (x) and "Cumulative Proportion" (y) clearly readable in dark ink; tick labels at 0/0.25/0.5/0.75/1.0 (y) and 0–270 (x) readable in dark-soft ink. All text visible against the light background. + Data: Step-function ECDF rendered in #009E73 (brand green); line is clearly visible with size=2 (scaled 6px). The characteristic bimodal shape shows a fast initial rise (exponential component) followed by a plateau then second rise (normal component). Grid lines (both axes) in medium warm gray — somewhat prominent but not overwhelming. + Legibility verdict: PASS — all text readable, no light-on-light issues. + + Dark render (plot-dark.png): + Background: Deep warm near-black consistent with #1A1A17 — not pure black, correct dark surface. + Chrome: Title and axis labels rendered in light off-white (#F0EFE8); tick labels in light secondary ink (#B8B7B0). All text elements clearly visible against the dark background. No dark-on-dark failure observed. + Data: ECDF line is identical #009E73 green — data colors unchanged from light render, only chrome has flipped. Grid lines appear in a muted warm gray (#565551), somewhat visible but more subtle than in the light render. + Legibility verdict: PASS — all text readable in both themes, no dark-on-dark issues detected. criteria_checklist: visual_quality: - score: 38 - max: 40 + score: 30 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 10 - max: 10 + score: 8 + max: 8 passed: true - comment: all text readable at full size, appropriate font sizes + comment: 'All font sizes explicitly set: 24pt title, 20pt axis labels, 16pt + ticks. Readable in both themes.' - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: no overlapping text anywhere + comment: No overlapping elements in either render. - id: VQ-03 name: Element Visibility - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: step line perfectly visible with size=2 + comment: ECDF step line clearly visible in both renders with size=2 scaled + to 6px. - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 2 + max: 2 passed: true - comment: single blue color is colorblind-safe + comment: 'Single series in #009E73, CVD-safe, good contrast on both surfaces.' - id: VQ-05 - name: Layout Balance - score: 5 - max: 5 + name: Layout & Canvas + score: 4 + max: 4 passed: true - comment: plot fills canvas well with balanced margins + comment: Plot fills canvas well, balanced margins, axes appropriately scaled + to data range. - id: VQ-06 - name: Axis Labels + name: Axis Labels & Title score: 2 max: 2 passed: true - comment: descriptive labels with units + comment: Response Time (ms) and Cumulative Proportion are descriptive with + appropriate units. - id: VQ-07 - name: Grid & Legend - score: 0 + name: Palette Compliance + score: 2 max: 2 + passed: true + comment: '#009E73 first series, #FAF8F1 light background, #1A1A17 dark background, + chrome adaptive.' + design_excellence: + score: 10 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 4 + max: 8 passed: false - comment: grid lines are barely visible (too subtle) + comment: Well-configured library default; correct colors and clean layout + but not exceptional — no typographic hierarchy, no focal emphasis. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: false + comment: 'Some refinement: minor grids removed, axis lines styled. Grid color + too opaque (#C9C7C1 vs recommended rgba(26,26,23,0.10)). Not fully polished.' + - id: DE-03 + name: Data Storytelling + score: 2 + max: 6 + passed: false + comment: Data displayed but not interpreted. Bimodal shape reveals two response-time + populations but nothing highlights this insight. spec_compliance: - score: 25 - max: 25 + score: 14 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: correct ECDF step function - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: X/Y correctly assigned - - id: SC-03 + comment: Correct ECDF step function via stat_ecdf(geom='step'). + - id: SC-02 name: Required Features - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: step function, 0-1 y-axis, grid present - - id: SC-04 - name: Data Range + comment: Step function, y-axis 0-1, distinct line style, grid lines for percentile + reading — all present. + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: shows full data range - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: N/A for single series - - id: SC-06 - name: Title Format + comment: X=response_time, Y=cumulative proportion [0,1]. Correct. + - id: SC-04 + name: Title & Legend score: 2 - max: 2 - passed: true - comment: uses correct format + max: 3 + passed: false + comment: Title has extra prefix 'Web Service Response Times · ' making it + 4-part instead of prescribed 3-part format 'ecdf-basic · letsplot · anyplot.ai'. + No legend needed for single series. data_quality: - score: 20 - max: 20 + score: 15 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: shows mixed distribution (exponential + normal) + comment: 'Bimodal distribution showcases ECDF strengths: non-parametric estimation, + percentile reading, distribution shape comparison without binning.' - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: web service response times is excellent + comment: Web service response times with realistic mixed distribution (exponential + fast-path + normal slow-path). Neutral, professional scenario. - id: DQ-03 name: Appropriate Scale - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: 0-270ms is realistic for web latency + comment: 0-270ms range is realistic for web services. 200 observations within + the 50-500 spec recommendation. code_quality: score: 10 max: 10 @@ -159,21 +194,58 @@ review: score: 3 max: 3 passed: true - comment: clean imports → data → plot → save + comment: 'Clean: imports → tokens → data → plot → save. No functions or classes.' - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) set. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: All imports used; no unused dependencies. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: np.random.seed(42) present - library_features: - score: 3 - max: 5 + comment: Clean, Pythonic, appropriate complexity. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png and plot-{THEME}.html with scale=3. Current + API. + library_mastery: + score: 8 + max: 10 items: - - id: LF-01 - name: Uses geom_step correctly - score: 3 + - id: LM-01 + name: Idiomatic Usage + score: 5 max: 5 passed: true - comment: functional but could use stat_ecdf() - verdict: APPROVED + comment: Grammar of graphics approach with stat_ecdf statistical layer. Idiomatic + lets-plot/ggplot2 pattern. + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: false + comment: stat_ecdf is shared with plotnine; HTML export is lets-plot specific. + Could leverage lets-plot's interactive tooltip customization or theme_bw/theme_grey + variants more distinctively. + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - html-export + patterns: + - data-generation + dataprep: [] + styling: + - grid-styling