diff --git a/plots/circos-basic/implementations/python/bokeh.py b/plots/circos-basic/implementations/python/bokeh.py index eb3dabab0a..5001cec9aa 100644 --- a/plots/circos-basic/implementations/python/bokeh.py +++ b/plots/circos-basic/implementations/python/bokeh.py @@ -1,46 +1,56 @@ -""" pyplots.ai +""" anyplot.ai circos-basic: Circos Plot -Library: bokeh 3.8.1 | Python 3.13.11 -Quality: 90/100 | Created: 2025-12-31 +Library: bokeh 3.9.0 | Python 3.13.13 +Quality: 94/100 | Updated: 2026-05-15 """ -import numpy as np -from bokeh.io import export_png, output_file, save -from bokeh.models import ColumnDataSource -from bokeh.plotting import figure +import sys +from pathlib import Path +script_dir = str(Path(__file__).parent.absolute()) +sys.path = [p for p in sys.path if p != script_dir and p != ""] + +import os # noqa: E402 +import time # noqa: E402 + +import numpy as np # noqa: E402 +from bokeh.io import output_file, save # noqa: E402 +from bokeh.models import ColumnDataSource # noqa: E402 +from bokeh.plotting import figure # noqa: E402 +from selenium import webdriver # noqa: E402 +from selenium.webdriver.chrome.options import Options # noqa: E402 + + +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" + +OKABE_ITO = ["#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9", "#F0E442"] + np.random.seed(42) -# Data: Regional trade flows between economic regions regions = ["Asia", "Europe", "N. America", "S. America", "Africa", "Oceania"] n_regions = len(regions) -# Connection matrix (trade flows between regions in billions USD) -# Row = source, Column = target flow_matrix = np.array( [ - [0, 45, 52, 18, 15, 22], # Asia exports to... - [38, 0, 35, 12, 20, 8], # Europe exports to... - [48, 42, 0, 28, 10, 15], # N. America exports to... - [15, 18, 25, 0, 8, 5], # S. America exports to... - [12, 25, 8, 10, 0, 3], # Africa exports to... - [20, 10, 18, 6, 4, 0], # Oceania exports to... + [0, 45, 52, 18, 15, 22], + [38, 0, 35, 12, 20, 8], + [48, 42, 0, 28, 10, 15], + [15, 18, 25, 0, 8, 5], + [12, 25, 8, 10, 0, 3], + [20, 10, 18, 6, 4, 0], ] ) -# Segment sizes (total trade volume for each region) segment_sizes = flow_matrix.sum(axis=0) + flow_matrix.sum(axis=1) - -# Track data (GDP growth rate for each region) track_values = np.array([4.2, 1.8, 2.5, 1.5, 3.8, 2.2]) -# Color palette for regions -colors = ["#306998", "#FFD43B", "#E85C47", "#4DAF4A", "#984EA3", "#FF7F00"] - -# Calculate segment positions (angles) total_size = segment_sizes.sum() -gap = 0.03 # Gap between segments (radians) +gap = 0.03 total_gap = gap * n_regions available_angle = 2 * np.pi - total_gap @@ -53,26 +63,27 @@ segment_angles.append((start, end)) current_angle = end + gap -# Create figure (square for circular plot) p = figure( width=3600, height=3600, - title="circos-basic · bokeh · pyplots.ai", + title="circos-basic · bokeh · anyplot.ai", x_range=(-1.5, 1.5), y_range=(-1.5, 1.5), tools="", toolbar_location=None, ) -# Styling -p.title.text_font_size = "48pt" +p.title.text_font_size = "36pt" p.title.align = "center" +p.title.text_color = INK + p.xaxis.visible = False p.yaxis.visible = False p.xgrid.visible = False p.ygrid.visible = False p.outline_line_color = None -p.background_fill_color = "white" +p.background_fill_color = PAGE_BG +p.border_fill_color = PAGE_BG outer_radius = 1.0 inner_radius = 0.85 @@ -80,10 +91,7 @@ track_inner = 0.70 ribbon_radius = 0.65 - -# Draw outer segments (arcs) for i, (start, end) in enumerate(segment_angles): - # Outer arc theta = np.linspace(start, end, 50) outer_x = outer_radius * np.cos(theta) outer_y = outer_radius * np.sin(theta) @@ -94,15 +102,14 @@ ys = np.concatenate([outer_y, inner_y, [outer_y[0]]]) source = ColumnDataSource(data={"xs": [xs], "ys": [ys]}) - p.patches(xs="xs", ys="ys", source=source, fill_color=colors[i], line_color="white", line_width=2, alpha=0.9) + color = OKABE_ITO[i % len(OKABE_ITO)] + p.patches(xs="xs", ys="ys", source=source, fill_color=color, line_color=INK_SOFT, line_width=1, alpha=0.85) - # Add region label mid_angle = (start + end) / 2 label_radius = outer_radius + 0.12 label_x = label_radius * np.cos(mid_angle) label_y = label_radius * np.sin(mid_angle) - # Rotate text based on position angle = mid_angle * 180 / np.pi if 90 < angle < 270: angle += 180 @@ -111,20 +118,18 @@ x=[label_x], y=[label_y], text=[regions[i]], - text_font_size="28pt", + text_font_size="20pt", text_align="center", text_baseline="middle", - text_color="#333333", + text_color=INK, angle=[np.radians(angle - 90)], ) -# Draw inner track (GDP growth rate) max_track = track_values.max() min_track = track_values.min() track_range = max_track - min_track for i, (start, end) in enumerate(segment_angles): - # Normalized track value norm_val = (track_values[i] - min_track) / track_range if track_range > 0 else 0.5 bar_radius = track_inner + norm_val * (track_outer - track_inner) @@ -138,20 +143,18 @@ ys = np.concatenate([outer_y, inner_y, [outer_y[0]]]) source = ColumnDataSource(data={"xs": [xs], "ys": [ys]}) - p.patches(xs="xs", ys="ys", source=source, fill_color=colors[i], line_color=None, alpha=0.6) + color = OKABE_ITO[i % len(OKABE_ITO)] + p.patches(xs="xs", ys="ys", source=source, fill_color=color, line_color=None, alpha=0.4) -# Draw track reference circle track_ref_theta = np.linspace(0, 2 * np.pi, 100) track_ref_x = track_inner * np.cos(track_ref_theta) track_ref_y = track_inner * np.sin(track_ref_theta) -p.line(track_ref_x, track_ref_y, line_color="#cccccc", line_width=1, line_alpha=0.5) +p.line(track_ref_x, track_ref_y, line_color=INK_SOFT, line_width=1, line_alpha=0.2) -# Draw ribbons (connections between regions) -# Filter significant flows flow_threshold = 15 for i in range(n_regions): - for j in range(i + 1, n_regions): # Only upper triangle to avoid duplicates + for j in range(i + 1, n_regions): flow_ij = flow_matrix[i, j] flow_ji = flow_matrix[j, i] total_flow = flow_ij + flow_ji @@ -159,55 +162,39 @@ if total_flow < flow_threshold: continue - # Calculate ribbon widths proportional to flow - # Source segment i start_i, end_i = segment_angles[i] seg_span_i = end_i - start_i ribbon_width_i = (total_flow / segment_sizes[i]) * seg_span_i * 0.8 - # Target segment j start_j, end_j = segment_angles[j] seg_span_j = end_j - start_j ribbon_width_j = (total_flow / segment_sizes[j]) * seg_span_j * 0.8 - # Position ribbons at center of segments mid_i = (start_i + end_i) / 2 mid_j = (start_j + end_j) / 2 - # Ribbon endpoints on source theta_i_start = mid_i - ribbon_width_i / 2 theta_i_end = mid_i + ribbon_width_i / 2 - # Ribbon endpoints on target theta_j_start = mid_j - ribbon_width_j / 2 theta_j_end = mid_j + ribbon_width_j / 2 - # Create bezier-like ribbon using quadratic curves n_curve = 30 - - # Path: from i_start arc to j_start, then j arc, then back via bezier - # Side 1: from i_start to j_start t = np.linspace(0, 1, n_curve) - # Control point at center ctrl_x, ctrl_y = 0, 0 - # Start point x1_start = ribbon_radius * np.cos(theta_i_start) y1_start = ribbon_radius * np.sin(theta_i_start) - # End point x1_end = ribbon_radius * np.cos(theta_j_start) y1_end = ribbon_radius * np.sin(theta_j_start) - # Quadratic bezier curve1_x = (1 - t) ** 2 * x1_start + 2 * (1 - t) * t * ctrl_x + t**2 * x1_end curve1_y = (1 - t) ** 2 * y1_start + 2 * (1 - t) * t * ctrl_y + t**2 * y1_end - # Arc at j arc_j_theta = np.linspace(theta_j_start, theta_j_end, 10) arc_j_x = ribbon_radius * np.cos(arc_j_theta) arc_j_y = ribbon_radius * np.sin(arc_j_theta) - # Side 2: from j_end back to i_end x2_start = ribbon_radius * np.cos(theta_j_end) y2_start = ribbon_radius * np.sin(theta_j_end) x2_end = ribbon_radius * np.cos(theta_i_end) @@ -216,50 +203,70 @@ curve2_x = (1 - t) ** 2 * x2_start + 2 * (1 - t) * t * ctrl_x + t**2 * x2_end curve2_y = (1 - t) ** 2 * y2_start + 2 * (1 - t) * t * ctrl_y + t**2 * y2_end - # Arc at i arc_i_theta = np.linspace(theta_i_end, theta_i_start, 10) arc_i_x = ribbon_radius * np.cos(arc_i_theta) arc_i_y = ribbon_radius * np.sin(arc_i_theta) - # Combine all points ribbon_x = np.concatenate([curve1_x, arc_j_x, curve2_x, arc_i_x]) ribbon_y = np.concatenate([curve1_y, arc_j_y, curve2_y, arc_i_y]) - # Use gradient color (blend of source and target) - ribbon_color = colors[i] + ribbon_color = OKABE_ITO[i % len(OKABE_ITO)] source = ColumnDataSource(data={"xs": [ribbon_x], "ys": [ribbon_y]}) p.patches( - xs="xs", ys="ys", source=source, fill_color=ribbon_color, line_color=ribbon_color, line_width=0.5, alpha=0.5 + xs="xs", + ys="ys", + source=source, + fill_color=ribbon_color, + line_color=ribbon_color, + line_width=0.5, + alpha=0.45, ) -# Add legend manually legend_x = 1.15 legend_y_start = 0.8 legend_spacing = 0.15 for i, region in enumerate(regions): y_pos = legend_y_start - i * legend_spacing - # Color box - p.rect(x=[legend_x], y=[y_pos], width=0.08, height=0.08, fill_color=colors[i], line_color=None) - # Label + color = OKABE_ITO[i % len(OKABE_ITO)] + p.rect(x=[legend_x], y=[y_pos], width=0.08, height=0.08, fill_color=color, line_color=None) p.text( - x=[legend_x + 0.08], + x=[legend_x + 0.12], y=[y_pos], text=[region], text_font_size="16pt", text_align="left", text_baseline="middle", - text_color="#333333", + text_color=INK_SOFT, ) -# Add title for track (positioned near the inner track for clarity) -p.text(x=[-0.45], y=[-0.45], text=["Inner track:"], text_font_size="20pt", text_color="#666666", text_align="center") -p.text(x=[-0.45], y=[-0.55], text=["GDP Growth (%)"], text_font_size="20pt", text_color="#666666", text_align="center") - -# Save outputs -export_png(p, filename="plot.png") +p.text( + x=[-0.35], + y=[-0.20], + text=["Inner track: GDP Growth (%)"], + text_font_size="18pt", + text_color=INK_SOFT, + text_align="center", +) -# Save HTML for interactivity -output_file("plot.html") +output_file(f"plot-{THEME}.html") save(p) + +W, H = 3600, 3600 +opts = Options() +for arg in ( + "--headless=new", + "--no-sandbox", + "--disable-dev-shm-usage", + "--disable-gpu", + f"--window-size={W},{H}", + "--hide-scrollbars", +): + opts.add_argument(arg) +driver = webdriver.Chrome(options=opts) +driver.set_window_size(W, H) +driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") +time.sleep(3) +driver.save_screenshot(f"plot-{THEME}.png") +driver.quit() diff --git a/plots/circos-basic/metadata/python/bokeh.yaml b/plots/circos-basic/metadata/python/bokeh.yaml index e64a898da1..cd397fa5ef 100644 --- a/plots/circos-basic/metadata/python/bokeh.yaml +++ b/plots/circos-basic/metadata/python/bokeh.yaml @@ -1,172 +1,190 @@ library: bokeh +language: python specification_id: circos-basic created: '2025-12-31T11:12:52Z' -updated: '2025-12-31T11:28:09Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20617695887 +updated: '2026-05-15T09:36:55Z' +generated_by: claude-haiku +workflow_run: 25910352749 issue: 3005 -python_version: 3.13.11 -library_version: 3.8.1 -preview_url: https://storage.googleapis.com/anyplot-images/plots/circos-basic/bokeh/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/circos-basic/bokeh/plot.html -quality_score: 90 -impl_tags: - dependencies: [] - techniques: - - bezier-curves - - html-export - - columndatasource - patterns: - - data-generation - - matrix-construction - - iteration-over-groups - dataprep: [] - styling: - - alpha-blending +python_version: 3.13.13 +library_version: 3.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/circos-basic/python/bokeh/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/circos-basic/python/bokeh/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/circos-basic/python/bokeh/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/circos-basic/python/bokeh/plot-dark.html +quality_score: 94 review: strengths: - - Excellent circular layout with proper segment sizing proportional to total trade - volume - - Well-implemented bezier ribbons connecting regions with appropriate transparency - - Good use of inner track to display additional GDP growth data - - Realistic trade flow scenario with sensible numeric values - - Clean legend placement and region labeling with proper text rotation - - Both PNG and HTML outputs generated for static and interactive viewing + - 'Perfect visual quality: all text explicitly sized and readable in both light + and dark themes' + - 'Complete spec compliance: all circos features (segments, gaps, proportional ribbons, + tracks) correctly implemented' + - Excellent theme-adaptive design with Okabe-Ito palette applied flawlessly across + both renders + - Complex geometric calculations correctly implemented (Bezier curves for smooth + ribbons, proper angle calculations) + - Clean, reproducible code with deterministic seed and KISS structure + - Realistic trade flow data with appropriate scale and factual correctness + - Professional Bokeh API usage with ColumnDataSource patterns and idiomatic library + patterns weaknesses: - - Inner track annotation positioned at bottom-left is quite small and easy to miss - - Title font at 48pt appears relatively small for the 3600x3600 canvas size - - Some ribbon colors could use slightly more differentiation when many overlap in - the center - image_description: 'The plot displays a Circos diagram showing trade flows between - six global economic regions (Asia, Europe, N. America, S. America, Africa, Oceania). - The circular layout features an outer ring with colored segments representing - each region - blue for Asia, yellow for Europe, red-coral for N. America, green - for S. America, purple for Africa, and orange for Oceania. Region labels are positioned - outside the circle, properly rotated for readability. Inside the outer ring is - a secondary track showing GDP growth rates as lighter-colored bars of varying - heights. The center contains semi-transparent ribbons connecting regions, with - ribbon width proportional to trade flow volume. The ribbons use quadratic bezier - curves passing through the center. A legend is positioned on the right side with - colored boxes and region labels. The title "circos-basic · bokeh · pyplots.ai" - appears at the top center. At the bottom-left corner, there is small text noting - "Inner track: GDP Growth (%)".' + - 'Design Excellence could be higher: well-executed but lacks exceptional aesthetic + polish or unique visual flourishes to reach publication-ready status' + - Data storytelling is functional but doesn't highlight a particular insight or + create visual emphasis that guides viewer attention + - 'Library Mastery: geometric computation is generic; more Bokeh-distinctive features + (callbacks, glyphs, or interactive elements in HTML) would elevate the score' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) - correct surface color + Chrome: Title "circos-basic · bokeh · anyplot.ai" in 36pt dark text clearly visible at top; region labels (Asia, Europe, N. America, S. America, Africa, Oceania) in 20pt dark text positioned radially around circle - all fully readable; legend on right with 16pt labels clearly readable; inner track annotation "Inner track: GDP Growth (%)" in 18pt secondary text readable at bottom + Data: Six segments in Okabe-Ito colors (#009E73 green, #D55E00 orange, #0072B2 blue, #CC79A7 pink, #E69F00 yellow, #56B4E9 sky blue) in canonical order; flowing ribbons with proportional widths connecting regions; inner concentric track with variable heights; subtle reference circle; all colors vibrant and distinct + Legibility verdict: PASS - all text is dark on light background, excellent contrast, no legibility issues in any element + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) - correct dark surface color + Chrome: Title in light text (#F0EFE8) clearly visible; region labels in light gray (#B8B7B0) fully readable around circle; legend labels light-colored and clear; inner track annotation adapted to light gray text - all readable without any dark-on-dark failures + Data: All Okabe-Ito data colors (#009E73, #D55E00, #0072B2, #CC79A7, #E69F00, #56B4E9) remain perfectly identical to light render - confirming only chrome flipped; segments, ribbons, and track maintain excellent contrast against dark background + Legibility verdict: PASS - all text is light on dark background with no visibility issues; data colors identical to light render; perfect theme adaptation with no "dark-on-dark" failures criteria_checklist: visual_quality: - score: 36 - max: 40 + score: 30 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 9 - max: 10 + score: 8 + max: 8 passed: true - comment: Title and region labels are readable, legend text is clear; inner - track label at bottom-left is somewhat small + comment: All font sizes explicitly set (36pt title, 20pt labels, 18pt annotation, + 16pt legend); perfectly readable in both light and dark themes - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements, labels well-positioned + comment: Region labels radially positioned with proper spacing; legend isolated + to right side; no overlapping text elements - id: VQ-03 name: Element Visibility - score: 7 - max: 8 + score: 6 + max: 6 passed: true - comment: Outer segments and ribbons are visible, though some ribbons overlap - each other due to transparency + comment: Segments, track bars, and ribbons excellently visible; alpha values + (0.85, 0.4, 0.45, 0.2) optimally adapted to data density - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 2 + max: 2 passed: true - comment: Distinct colors for each region, colorblind-friendly palette + comment: Okabe-Ito palette is colorblind-safe; excellent contrast; no red-green + as sole distinguishing signal - id: VQ-05 - name: Layout Balance + name: Layout & Canvas score: 4 - max: 5 + max: 4 passed: true - comment: Circular plot is well-centered, good use of canvas; legend placement - is good + comment: 3600×3600 square format ideal for circular plot; plot fills 60-70% + of canvas; balanced margins throughout - id: VQ-06 - name: Axis Labels - score: 1 + name: Axis Labels & Title + score: 2 max: 2 passed: true - comment: N/A for circular plots, but track label present (partial) + comment: Correct title format (circos-basic · bokeh · anyplot.ai); descriptive + region labels; annotation includes units (%) - id: VQ-07 - name: Grid & Legend + name: Palette Compliance score: 2 max: 2 passed: true - comment: Legend well-placed with clear labels, reference circle subtle + comment: 'First series #009E73 correct; Okabe-Ito positions 1-6 in canonical + order; backgrounds #FAF8F1/#1A1A17 correct; text colors theme-correct in + both renders' + design_excellence: + score: 16 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Thoughtful circos design with custom angle calculations, proportional + ribbons, proper gap sizing, intentional alpha blending. Professional execution + but lacks exceptional aesthetic flourishes for publication-ready status. + - id: DE-02 + name: Visual Refinement + score: 6 + max: 6 + passed: true + comment: Axes hidden, grid removed, generous whitespace, subtle reference + lines, refined segment borders; excellent attention to detail + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Clear narrative of global trade flows; color coding makes relationships + easy to follow; visual hierarchy through scale and color, but no particular + emphasis highlighting a specific insight spec_compliance: - score: 23 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correct Circos plot with circular segments and connecting ribbons - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Source/target regions and values correctly mapped to ribbons - - id: SC-03 + comment: Correct circos chart type with all variants (segments, tracks, ribbons) + - id: SC-02 name: Required Features score: 4 - max: 5 + max: 4 passed: true - comment: Has outer segments, ribbons, and inner track; ribbons show relationships - well - - id: SC-04 - name: Data Range + comment: 'All features present: segments with gaps, proportional ribbon widths, + distinct colors, 1-3 concentric tracks' + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: All regions visible with proportional sizing - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: Legend correctly identifies all six regions - - id: SC-06 - name: Title Format - score: 1 - max: 2 + comment: Regions → segments; flow values → ribbon widths; track values → bar + heights; all data visible + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: Uses correct format but title font could be larger + comment: Correct title format; legend labels match regions exactly data_quality: - score: 18 - max: 20 + score: 15 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 7 - max: 8 + score: 6 + max: 6 passed: true - comment: Shows connections, segment sizing, inner track; demonstrates various - ribbon widths + comment: 'Shows all circos aspects: outer segments, multiple connections, + proportional ribbons, inner track with variation, threshold filtering' - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Regional trade flows is a compelling real-world scenario + comment: Global trade flows between real regions; plausible magnitudes; realistic + GDP growth percentages; neutral, comprehensible - id: DQ-03 name: Appropriate Scale score: 4 - max: 5 + max: 4 passed: true - comment: Trade values in billions USD are realistic; GDP growth percentages - are plausible + comment: Trade values (3-52) appropriate for magnitude; GDP growth (1.5-4.2%) + factually plausible; proportions logically sound code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 @@ -174,40 +192,66 @@ review: score: 3 max: 3 passed: true - comment: 'Linear flow: imports → data → plot → save' + comment: 'Linear flow: imports → data generation → plot → save; no functions, + no classes' - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: Uses np.random.seed(42) + comment: np.random.seed(42) set; deterministic data generation - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports used + comment: 'All imports used: sys, Path, os, time, numpy, bokeh modules, selenium; + no dead imports' - id: CQ-04 - name: No Deprecated API - score: 0 - max: 1 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: Uses export_png which is current + comment: Clean, Pythonic; complexity appropriate for circos visualization; + correct Bezier math; no over-engineering - id: CQ-05 - name: Output Correct + name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png - library_features: - score: 4 - max: 5 + comment: Saves as plot-{THEME}.html and plot-{THEME}.png correctly + library_mastery: + score: 8 + max: 10 items: - - id: LF-01 - name: Uses distinctive library features - score: 4 + - id: LM-01 + name: Idiomatic Usage + score: 5 max: 5 passed: true - comment: Good use of patches, ColumnDataSource, and bezier curves; exports - both PNG and HTML + comment: 'Expert Bokeh usage: figure(), patches() with custom coordinates, + line(), text(), rect(), ColumnDataSource; follows recommended patterns' + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: Uses patches() with custom polygons, Bezier curves, manual geometric + layout. Mostly generic geometric computation adaptable to other libraries; + could leverage more Bokeh-distinctive features (callbacks, glyphs, interactive + HTML features) verdict: APPROVED +impl_tags: + dependencies: + - selenium + techniques: + - patches + - html-export + - bezier-curves + patterns: + - data-generation + - matrix-construction + - iteration-over-groups + dataprep: [] + styling: + - alpha-blending