diff --git a/plots/spectrum-basic/implementations/python/plotly.py b/plots/spectrum-basic/implementations/python/plotly.py
index 641258d784..ac16641446 100644
--- a/plots/spectrum-basic/implementations/python/plotly.py
+++ b/plots/spectrum-basic/implementations/python/plotly.py
@@ -1,13 +1,24 @@
-""" pyplots.ai
+""" anyplot.ai
spectrum-basic: Frequency Spectrum Plot
-Library: plotly 6.5.0 | Python 3.13.11
-Quality: 93/100 | Created: 2025-12-31
+Library: plotly 6.7.0 | Python 3.13.13
+Quality: 93/100 | Updated: 2026-05-14
"""
+import os
+
import numpy as np
import plotly.graph_objects as go
+# 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"
+GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
+BRAND = "#009E73"
+
# Data - Generate synthetic signal with multiple frequency components
np.random.seed(42)
@@ -28,7 +39,7 @@
# Compute FFT
fft_result = np.fft.rfft(signal)
frequency = np.fft.rfftfreq(n_samples, 1 / sample_rate)
-amplitude_db = 20 * np.log10(np.abs(fft_result) / n_samples + 1e-10) # Convert to dB
+amplitude_db = 20 * np.log10(np.abs(fft_result) / n_samples + 1e-10)
# Plot
fig = go.Figure()
@@ -38,32 +49,40 @@
x=frequency,
y=amplitude_db,
mode="lines",
- line=dict(color="#306998", width=2),
+ line=dict(color=BRAND, width=3),
fill="tozeroy",
- fillcolor="rgba(48, 105, 152, 0.3)",
+ fillcolor="rgba(0, 158, 115, 0.15)",
name="Spectrum",
+ hovertemplate="Frequency: %{x:.1f} Hz
Amplitude: %{y:.1f} dB",
)
)
-# Layout
+# Layout with theme-adaptive colors
fig.update_layout(
- title=dict(text="spectrum-basic · plotly · pyplots.ai", font=dict(size=28), x=0.5, xanchor="center"),
+ title=dict(text="spectrum-basic · plotly · anyplot.ai", font=dict(size=28, color=INK), x=0.5, xanchor="center"),
xaxis=dict(
- title=dict(text="Frequency (Hz)", font=dict(size=22)),
- tickfont=dict(size=18),
+ title=dict(text="Frequency (Hz)", font=dict(size=22, color=INK)),
+ tickfont=dict(size=18, color=INK_SOFT),
range=[0, 500],
- gridcolor="rgba(128, 128, 128, 0.3)",
- gridwidth=1,
+ gridcolor=GRID,
+ gridwidth=0.8,
+ linecolor=INK_SOFT,
+ zerolinecolor=INK_SOFT,
),
yaxis=dict(
- title=dict(text="Amplitude (dB)", font=dict(size=22)),
- tickfont=dict(size=18),
- gridcolor="rgba(128, 128, 128, 0.3)",
- gridwidth=1,
+ title=dict(text="Amplitude (dB)", font=dict(size=22, color=INK)),
+ tickfont=dict(size=18, color=INK_SOFT),
+ gridcolor=GRID,
+ gridwidth=0.8,
+ linecolor=INK_SOFT,
+ zerolinecolor=INK_SOFT,
),
- template="plotly_white",
+ paper_bgcolor=PAGE_BG,
+ plot_bgcolor=PAGE_BG,
+ font=dict(color=INK),
showlegend=False,
- margin=dict(l=80, r=40, t=80, b=60),
+ hovermode="x unified",
+ margin=dict(l=100, r=60, t=100, b=80),
)
# Add annotations for peak frequencies
@@ -76,14 +95,14 @@
text=f"{freq} Hz",
showarrow=True,
arrowhead=2,
- arrowsize=1,
+ arrowsize=1.5,
arrowwidth=2,
- arrowcolor="#FFD43B",
- font=dict(size=16, color="#306998"),
+ arrowcolor=INK_SOFT,
+ font=dict(size=16, color=INK),
ax=0,
- ay=-40,
+ ay=-50,
)
# Save
-fig.write_image("plot.png", width=1600, height=900, scale=3)
-fig.write_html("plot.html")
+fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3)
+fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")
diff --git a/plots/spectrum-basic/metadata/python/plotly.yaml b/plots/spectrum-basic/metadata/python/plotly.yaml
index 31433971c3..568135c44e 100644
--- a/plots/spectrum-basic/metadata/python/plotly.yaml
+++ b/plots/spectrum-basic/metadata/python/plotly.yaml
@@ -1,164 +1,182 @@
library: plotly
+language: python
specification_id: spectrum-basic
created: '2025-12-31T05:35:05Z'
-updated: '2025-12-31T05:47:43Z'
-generated_by: claude-opus-4-5-20251101
-workflow_run: 20612812571
+updated: '2026-05-14T09:17:26Z'
+generated_by: claude-haiku
+workflow_run: 25851877319
issue: 2926
-python_version: 3.13.11
-library_version: 6.5.0
-preview_url: https://storage.googleapis.com/anyplot-images/plots/spectrum-basic/plotly/plot.png
-preview_html: https://storage.googleapis.com/anyplot-images/plots/spectrum-basic/plotly/plot.html
+python_version: 3.13.13
+library_version: 6.7.0
+preview_url_light: https://storage.googleapis.com/anyplot-images/plots/spectrum-basic/python/plotly/plot-light.png
+preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/spectrum-basic/python/plotly/plot-dark.png
+preview_html_light: https://storage.googleapis.com/anyplot-images/plots/spectrum-basic/python/plotly/plot-light.html
+preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/spectrum-basic/python/plotly/plot-dark.html
quality_score: 93
-impl_tags:
- dependencies: []
- techniques:
- - annotations
- - fill-under-curve
- - html-export
- patterns:
- - data-generation
- dataprep: []
- styling: []
review:
strengths:
- - Excellent peak annotation with clear yellow arrows pointing to dominant frequencies
- - Clean dB scale calculation with proper normalization and epsilon for log safety
- - Realistic synthetic signal with three distinct frequency components plus noise
- - Proper use of FFT with rfft/rfftfreq for real-valued signals
- - Well-proportioned fill area under the curve enhances visual clarity
+ - Excellent visual quality with perfect text legibility in both light and dark themes
+ - Flawless palette compliance using Okabe-Ito brand green with correct theme-adaptive
+ chrome
+ - Professional data visualization with clear storytelling through peak frequency
+ annotations
+ - Perfect spec compliance with realistic signal processing example data
+ - Expert use of Plotly-specific features including area fills and annotation arrows
+ - Clean, reproducible code with clear structure and explicit font sizing
weaknesses:
- - Does not leverage Plotly's interactive features (hover tooltips showing exact
- dB values, zoom capabilities display)
- - Grid transparency could be more subtle (currently visible but not obtrusive)
- image_description: The plot displays a frequency spectrum showing amplitude (dB)
- on the Y-axis (ranging from -80 to 0) versus frequency (Hz) on the X-axis (ranging
- from 0 to 500). The spectrum is rendered as a blue line (#306998) with a light
- blue fill underneath. Three prominent peaks are clearly visible at 50 Hz, 120
- Hz, and 300 Hz, each annotated with yellow arrow pointers and labeled with their
- respective frequencies. The 50 Hz peak is the strongest at approximately -3 dB,
- the 120 Hz peak reaches about -10 dB, and the 300 Hz peak is around -15 dB. A
- noisy baseline fluctuates between -45 and -70 dB. The title "spectrum-basic ·
- plotly · pyplots.ai" is centered at the top. The background is white with subtle
- gray grid lines.
+ - 'DE-01: Design sophistication relies on well-configured defaults rather than distinctive
+ custom aesthetic choices'
+ - 'DE-03: Data storytelling is clear but straightforward - could emphasize the frequency
+ components more dramatically through visual hierarchy'
+ image_description: |-
+ Light render (plot-light.png):
+ Background: Warm off-white (#FAF8F1) surface providing excellent contrast
+ Chrome: Title, axis labels ("Frequency (Hz)", "Amplitude (dB)"), and tick labels all clearly visible in dark text (#1A1A17) against light background. Font sizes explicitly set (title 28px, labels 22px, ticks 18px).
+ Data: Spectrum line rendered in brand green (#009E73) with semi-transparent fill beneath the curve (alpha~0.15). Three peak frequency annotations (50 Hz, 120 Hz, 300 Hz) with arrows clearly visible and readable. Grid lines subtle but visible.
+ Legibility verdict: PASS - All elements perfectly readable in light theme.
+
+ Dark render (plot-dark.png):
+ Background: Warm near-black (#1A1A17) surface providing excellent contrast
+ Chrome: Title, axis labels, and tick labels all clearly visible in light text (#F0EFE8) against dark background. Font sizes identical to light render (28px, 22px, 18px respectively). Grid lines subtle but visible.
+ Data: Spectrum line remains identical green (#009E73) as in light render (confirming data colors don't flip). Semi-transparent fill has same appearance. Peak frequency annotations still clearly visible and readable. No "dark-on-dark" issues detected.
+ Legibility verdict: PASS - All elements perfectly readable in dark theme. Data colors identical between light and dark renders.
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: Title, axis labels, and tick marks are all clearly readable at appropriate
- sizes
+ comment: All font sizes explicitly set (title 28px, labels 22px, ticks 18px).
+ 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; annotations are well-positioned above peaks
+ comment: No overlapping elements. All text fully readable and well-spaced.
- id: VQ-03
name: Element Visibility
- score: 8
- max: 8
+ score: 6
+ max: 6
passed: true
- comment: Line width is appropriate for the frequency resolution; peaks are
- clearly distinguishable from noise floor
+ comment: Spectrum line and fill optimally visible. Peak annotations clearly
+ marked with arrows. Data density appropriate.
- id: VQ-04
name: Color Accessibility
- score: 5
- max: 5
+ score: 2
+ max: 2
passed: true
- comment: Blue color scheme with yellow annotations provides good contrast
- and is colorblind-safe
+ comment: Okabe-Ito green (#009E73) provides excellent contrast in both themes.
+ Colorblind-safe.
- id: VQ-05
- name: Layout Balance
- score: 5
- max: 5
+ name: Layout & Canvas
+ score: 4
+ max: 4
passed: true
- comment: Plot fills the canvas well with balanced margins
+ comment: Plot fills 70-80% of canvas with balanced margins. Excellent whitespace
+ utilization.
- id: VQ-06
- name: Axis Labels
+ name: Axis Labels & Title
score: 2
max: 2
passed: true
- comment: 'Both axes have descriptive labels with units: "Frequency (Hz)" and
- "Amplitude (dB)"'
+ comment: 'Descriptive labels with units: ''Frequency (Hz)'', ''Amplitude (dB)''.
+ Correct title format.'
- id: VQ-07
- name: Grid & Legend
- score: 0
+ name: Palette Compliance
+ score: 2
max: 2
+ passed: true
+ comment: 'First series #009E73 brand green. Backgrounds #FAF8F1 (light) and
+ #1A1A17 (dark). Theme-adaptive chrome perfect. Data colors identical between
+ renders.'
+ design_excellence:
+ score: 13
+ max: 20
+ items:
+ - id: DE-01
+ name: Aesthetic Sophistication
+ score: 5
+ max: 8
+ passed: false
+ comment: Professional design with intentional color choices and clean layout.
+ Uses well-configured defaults with brand green and theme tokens effectively,
+ but more library-default than distinctively custom.
+ - id: DE-02
+ name: Visual Refinement
+ score: 4
+ max: 6
+ passed: false
+ comment: Subtle grid styling and good whitespace balance. Relies primarily
+ on Plotly's built-in refinement rather than custom visual tweaks.
+ - id: DE-03
+ name: Data Storytelling
+ score: 4
+ max: 6
passed: false
- comment: Grid is present but alpha is slightly high; no legend needed for
- single trace
+ comment: Clear visual hierarchy through peak annotations and arrows highlighting
+ dominant frequencies. Story is readable but relatively straightforward—no
+ additional visual emphasis techniques.
spec_compliance:
- score: 25
- max: 25
+ score: 15
+ max: 15
items:
- id: SC-01
name: Plot Type
- score: 8
- max: 8
- passed: true
- comment: Correct frequency spectrum line plot
- - id: SC-02
- name: Data Mapping
score: 5
max: 5
passed: true
- comment: Frequency correctly on X-axis, amplitude (dB) on Y-axis
- - id: SC-03
+ comment: Correct frequency spectrum plot using line chart with filled area.
+ - id: SC-02
name: Required Features
- score: 5
- max: 5
+ score: 4
+ max: 4
passed: true
- comment: FFT computation, dB scale, peak annotations as suggested in spec
- notes
- - id: SC-04
- name: Data Range
+ comment: 'All spec features present: frequency axis, amplitude axis, spectrum
+ line, peak frequency annotations.'
+ - id: SC-03
+ name: Data Mapping
score: 3
max: 3
passed: true
- comment: X-axis limited to 0-500 Hz showing the meaningful frequency range
- - id: SC-05
- name: Legend Accuracy
- score: 2
- max: 2
- passed: true
- comment: Single trace, no legend needed; trace named appropriately
- - id: SC-06
- name: Title Format
- score: 2
- max: 2
+ comment: X-axis (0-500 Hz) and Y-axis (dB) correctly mapped. All 256 frequency
+ bins displayed.
+ - id: SC-04
+ name: Title & Legend
+ score: 3
+ max: 3
passed: true
- comment: 'Correct format: "spectrum-basic · plotly · pyplots.ai"'
+ comment: Title 'spectrum-basic · plotly · anyplot.ai' correctly formatted.
+ Legend appropriately disabled 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 multiple frequency components at different amplitudes, plus
- realistic noise floor
+ comment: 'Shows all spectrum features: dominant peaks (50, 120, 300 Hz), noise
+ floor, complete frequency coverage.'
- id: DQ-02
name: Realistic Context
- score: 7
- max: 7
+ score: 5
+ max: 5
passed: true
- comment: Signal processing scenario with plausible 50/120/300 Hz components
- (power line frequency, harmonics)
+ comment: 'Real signal processing scenario with realistic parameters: 1000
+ Hz sample rate, 1-second window. Neutral scientific context.'
- id: DQ-03
name: Appropriate Scale
- score: 5
- max: 5
+ score: 4
+ max: 4
passed: true
- comment: Frequency range (0-500 Hz) and dB values (-80 to 0) are realistic
- for audio/electrical signals
+ comment: 'Factually correct: FFT of synthetic signal with specified components.
+ Amplitude scale (dB) and frequency values accurate.'
code_quality:
score: 10
max: 10
@@ -168,42 +186,60 @@ review:
score: 3
max: 3
passed: true
- comment: 'Clean linear flow: imports → data generation → FFT → plot → save'
+ comment: 'Linear flow: imports → signal generation → FFT → plot → save. No
+ unnecessary functions or classes.'
- id: CQ-02
name: Reproducibility
- score: 3
- max: 3
+ score: 2
+ max: 2
passed: true
- comment: Uses np.random.seed(42)
+ comment: Uses np.random.seed(42) for reproducible noise generation.
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
- comment: Only numpy and plotly.graph_objects imported, both used
+ comment: 'All imports necessary: os (THEME), numpy (FFT), plotly (plotting).
+ No unused imports.'
- id: CQ-04
- name: No Deprecated API
- score: 1
- max: 1
+ name: Code Elegance
+ score: 2
+ max: 2
passed: true
- comment: Uses current Plotly APIs
+ comment: Pythonic, well-structured. No over-engineering or fake functionality.
- id: CQ-05
- name: Output Correct
+ name: Output & API
score: 1
max: 1
passed: true
- comment: Saves as plot.png and plot.html
- library_features:
- score: 0
- max: 5
+ comment: 'Correct output format: plot-{THEME}.png and plot-{THEME}.html. Current
+ Plotly API.'
+ library_mastery:
+ score: 10
+ max: 10
items:
- - id: LF-01
- name: Uses distinctive library features
- score: 0
+ - id: LM-01
+ name: Idiomatic Usage
+ score: 5
max: 5
- passed: false
- comment: While the implementation is correct, it doesn't leverage Plotly's
- interactive features in the static output; the fill area and annotations
- are basic plotly features but nothing that showcases Plotly's unique strengths
- like hover info customization or range sliders
+ passed: true
+ comment: 'Expert use of Plotly patterns: go.Figure(), go.Scatter(), update_layout(),
+ annotations. High-level API used effectively.'
+ - id: LM-02
+ name: Distinctive Features
+ score: 5
+ max: 5
+ passed: true
+ comment: 'Leverages Plotly-specific features: fill=''tozeroy'' with custom
+ alpha, annotation arrows, hover templates, hovermode unified.'
verdict: APPROVED
+impl_tags:
+ dependencies: []
+ techniques:
+ - annotations
+ - html-export
+ patterns:
+ - data-generation
+ dataprep: []
+ styling:
+ - alpha-blending