diff --git a/plots/bump-basic/implementations/plotly.py b/plots/bump-basic/implementations/plotly.py
index 9d701fbd5f..2be7f640ef 100644
--- a/plots/bump-basic/implementations/plotly.py
+++ b/plots/bump-basic/implementations/plotly.py
@@ -1,58 +1,92 @@
""" pyplots.ai
bump-basic: Basic Bump Chart
-Library: plotly 6.5.0 | Python 3.13.11
-Quality: 92/100 | Created: 2025-12-23
+Library: plotly 6.5.2 | Python 3.14.3
+Quality: 91/100 | Updated: 2026-02-22
"""
import plotly.graph_objects as go
-# Data - Sports league standings over a season
-entities = ["Team Alpha", "Team Beta", "Team Gamma", "Team Delta", "Team Epsilon"]
-periods = ["Week 1", "Week 2", "Week 3", "Week 4", "Week 5", "Week 6"]
+# Data - Formula 1 driver standings over a season
+drivers = ["Verstappen", "Hamilton", "Norris", "Leclerc", "Piastri", "Sainz"]
+races = ["Bahrain", "Jeddah", "Melbourne", "Suzuka", "Miami", "Imola", "Monaco", "Silverstone"]
-# Rankings for each team across periods (1 = best)
rankings = {
- "Team Alpha": [3, 2, 1, 1, 2, 1],
- "Team Beta": [1, 1, 2, 3, 3, 2],
- "Team Gamma": [2, 3, 3, 2, 1, 3],
- "Team Delta": [4, 4, 5, 4, 4, 4],
- "Team Epsilon": [5, 5, 4, 5, 5, 5],
+ "Verstappen": [1, 1, 1, 1, 1, 2, 3, 2],
+ "Hamilton": [4, 3, 4, 3, 3, 3, 1, 1],
+ "Norris": [5, 5, 3, 4, 2, 1, 2, 3],
+ "Leclerc": [2, 2, 2, 2, 4, 4, 4, 4],
+ "Piastri": [3, 4, 5, 5, 5, 5, 5, 5],
+ "Sainz": [6, 6, 6, 6, 6, 6, 6, 6],
}
-# Colors - Python Blue first, then colorblind-safe palette
-colors = ["#306998", "#FFD43B", "#2ecc71", "#e74c3c", "#9b59b6"]
+# Colorblind-safe palette — Python Blue first, teal replaces green to avoid red-green issue
+colors = {
+ "Verstappen": "#306998",
+ "Hamilton": "#e74c3c",
+ "Norris": "#17becf",
+ "Leclerc": "#f39c12",
+ "Piastri": "#9b59b6",
+ "Sainz": "#95a5a6",
+}
+
+# Visual hierarchy — emphasize dynamic storylines, mute static ones
+rank_changes = {d: max(r) - min(r) for d, r in rankings.items()}
+line_widths = {d: 5 if rank_changes[d] >= 3 else 3 if rank_changes[d] >= 2 else 2 for d in drivers}
+marker_sizes = {d: 16 if rank_changes[d] >= 3 else 12 if rank_changes[d] >= 2 else 10 for d in drivers}
+opacities = {d: 1.0 if rank_changes[d] >= 3 else 0.8 if rank_changes[d] >= 2 else 0.45 for d in drivers}
# Create figure
fig = go.Figure()
-for i, (entity, ranks) in enumerate(rankings.items()):
+for driver in drivers:
+ ranks = rankings[driver]
+ color = colors[driver]
fig.add_trace(
go.Scatter(
- x=periods,
+ x=races,
y=ranks,
mode="lines+markers",
- name=entity,
- line={"width": 4, "color": colors[i]},
- marker={"size": 16, "color": colors[i]},
+ name=driver,
+ line={"width": line_widths[driver], "color": color},
+ marker={"size": marker_sizes[driver], "color": color, "line": {"width": 2, "color": "white"}},
+ opacity=opacities[driver],
+ showlegend=False,
+ hovertemplate="%{text}
%{x}: P%{y}",
+ text=[driver] * len(races),
)
)
+ # End-of-line label
+ fig.add_annotation(
+ x=races[-1],
+ y=ranks[-1],
+ text=f" {driver}" if rank_changes[driver] >= 3 else f" {driver}",
+ showarrow=False,
+ xanchor="left",
+ font={"size": 16, "color": color},
+ opacity=opacities[driver],
+ )
# Layout with inverted Y-axis (rank 1 at top)
fig.update_layout(
- title={"text": "bump-basic · plotly · pyplots.ai", "font": {"size": 28}},
- xaxis={"title": {"text": "Period", "font": {"size": 22}}, "tickfont": {"size": 18}},
+ title={"text": "bump-basic · plotly · pyplots.ai", "font": {"size": 28}, "x": 0.02, "xanchor": "left"},
+ xaxis={"title": {"text": "Race", "font": {"size": 22}}, "tickfont": {"size": 18}, "showgrid": False},
yaxis={
- "title": {"text": "Rank", "font": {"size": 22}},
+ "title": {"text": "Championship Position", "font": {"size": 22}},
"tickfont": {"size": 18},
- "autorange": "reversed", # Invert so rank 1 is at top
+ "autorange": "reversed",
"tickmode": "linear",
"tick0": 1,
"dtick": 1,
+ "gridcolor": "rgba(0,0,0,0.06)",
+ "gridwidth": 1,
+ "showgrid": True,
+ "zeroline": False,
},
- legend={"font": {"size": 18}, "x": 1.02, "y": 1, "xanchor": "left"},
template="plotly_white",
- margin={"r": 150}, # Extra margin for legend
+ margin={"r": 130, "t": 80, "l": 80, "b": 70},
+ plot_bgcolor="rgba(0,0,0,0)",
+ hoverlabel={"font_size": 16},
)
# Save as PNG (4800x2700 px)
diff --git a/plots/bump-basic/metadata/plotly.yaml b/plots/bump-basic/metadata/plotly.yaml
index e122819952..73ebe9ca30 100644
--- a/plots/bump-basic/metadata/plotly.yaml
+++ b/plots/bump-basic/metadata/plotly.yaml
@@ -1,167 +1,180 @@
library: plotly
specification_id: bump-basic
created: '2025-12-23T09:18:15Z'
-updated: '2025-12-23T09:20:33Z'
-generated_by: claude-opus-4-5-20251101
+updated: '2026-02-22T21:12:41Z'
+generated_by: claude-opus-4-6
workflow_run: 20456607003
issue: 0
-python_version: 3.13.11
-library_version: 6.5.0
+python_version: 3.14.3
+library_version: 6.5.2
preview_url: https://storage.googleapis.com/pyplots-images/plots/bump-basic/plotly/plot.png
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bump-basic/plotly/plot_thumb.png
preview_html: https://storage.googleapis.com/pyplots-images/plots/bump-basic/plotly/plot.html
-quality_score: 92
+quality_score: 91
impl_tags:
dependencies: []
- techniques: []
+ techniques:
+ - annotations
+ - hover-tooltips
+ - html-export
patterns:
- - data-generation
- iteration-over-groups
dataprep: []
- styling: []
+ styling:
+ - edge-highlighting
+ - grid-styling
review:
strengths:
- - Excellent use of inverted Y-axis to show rank 1 at top (as required by spec)
- - Clean, professional appearance with plotly_white template
- - Good colorblind-safe palette with Python Blue as primary color
- - Appropriate marker and line sizes for clear visibility
- - Interactive HTML output alongside PNG leverages plotly strengths
- - Data tells a compelling story with Team Alpha rise and Team Gamma brief lead in
- Week 5
+ - Excellent visual hierarchy system based on rank dynamism (line width, marker size,
+ opacity, bold labels)
+ - Clean end-of-line labeling instead of traditional legend — optimal for bump charts
+ - Thoughtful colorblind-safe palette with explicit consideration for accessibility
+ - Perfect spec compliance with all required features present
+ - Data tells a clear story with realistic F1 context
+ - Dual output (PNG + interactive HTML) leverages Plotly strengths
weaknesses:
- - Data is deterministic so seed is not technically needed, but consider adding np.random.seed
- for consistency if random data is ever used
- - Layout balance could be improved with legend placement closer to plot
- image_description: 'The plot displays a bump chart showing sports league standings
- over 6 weeks. The Y-axis shows ranks from 1 (top) to 5 (bottom) with rank 1 correctly
- positioned at the top (inverted axis). The X-axis shows "Period" with Week 1 through
- Week 6. Five teams are tracked with distinct colored lines and circular markers:
- Team Alpha (blue #306998), Team Beta (yellow #FFD43B), Team Gamma (green #2ecc71),
- Team Delta (red #e74c3c), and Team Epsilon (purple #9b59b6). The lines effectively
- show ranking changes - Team Alpha rises from 3rd to 1st, Team Beta falls from
- 1st to 2nd, Team Gamma has dynamic movement including briefly taking 1st in Week
- 5. The title reads "bump-basic · plotly · pyplots.ai" at top left. The legend
- is positioned to the right of the plot area. The background uses plotly_white
- template with subtle grid lines.'
+ - Sainz line at 0.45 opacity is borderline too faded for comfortable viewing
+ - Axis labels lack units (though inherently N/A for categorical/ordinal data)
+ - Could leverage more advanced Plotly features (animation, range slider) for stronger
+ library mastery
+ image_description: The plot displays a bump chart of Formula 1 championship standings
+ across 8 races (Bahrain through Silverstone) for 6 drivers. Verstappen (dark blue)
+ holds rank 1 through Miami before dropping. Hamilton (red) starts at P4 and climbs
+ to P1 by Silverstone. Norris (teal/cyan) rises dramatically from P5 to P1 at Imola
+ before settling at P3. Leclerc (orange) starts at P2 and gradually falls to P4.
+ Piastri (purple) and Sainz (gray) remain in the lower positions and are visually
+ de-emphasized with thinner lines and lower opacity. The Y-axis is inverted with
+ rank 1 at top. End-of-line labels identify each driver on the right margin. Title
+ reads "bump-basic · plotly · pyplots.ai" top-left. Background is clean white with
+ very subtle horizontal gridlines on the y-axis only. White marker borders add
+ polish. Bold labels mark the most dynamic drivers (Hamilton, Norris).
criteria_checklist:
visual_quality:
- score: 36
- max: 40
+ score: 28
+ max: 30
items:
- id: VQ-01
name: Text Legibility
- score: 10
- max: 10
+ score: 8
+ max: 8
passed: true
- comment: Title, axis labels, tick labels all clearly readable with appropriate
- font sizes
+ comment: 'All font sizes explicitly set: title 28pt, axis titles 22pt, tick
+ fonts 18pt, annotations 16pt, hover labels 16pt'
- id: VQ-02
name: No Overlap
- score: 8
- max: 8
+ score: 6
+ max: 6
passed: true
- comment: No overlapping text elements, all labels clear
+ comment: No text overlap. End-of-line labels well-separated at distinct rank
+ positions
- id: VQ-03
name: Element Visibility
- score: 8
- max: 8
+ score: 5
+ max: 6
passed: true
- comment: Lines (width=4) and markers (size=16) are well-sized for the data
- density
+ comment: Visual hierarchy with varying widths (2-5) and marker sizes (10-16).
+ Sainz at 0.45 opacity is quite faded
- id: VQ-04
name: Color Accessibility
- score: 5
- max: 5
+ score: 4
+ max: 4
passed: true
- comment: Five distinct colors that are colorblind-friendly (blue, yellow,
- green, red, purple)
+ comment: Colorblind-safe palette with teal replacing green to avoid red-green
+ issues
- id: VQ-05
- name: Layout Balance
- score: 3
- max: 5
+ name: Layout & Canvas
+ score: 4
+ max: 4
passed: true
- comment: Good overall balance, though right margin for legend takes notable
- space
+ comment: Good margins with r=130 for labels. Plot fills canvas well with balanced
+ whitespace
- id: VQ-06
- name: Axis Labels
+ name: Axis Labels & Title
score: 1
max: 2
passed: true
- comment: '"Period" and "Rank" are descriptive but lack units (though units
- aren''t really applicable here)'
- - id: VQ-07
- name: Grid & Legend
- score: 1
- max: 2
+ comment: Descriptive labels (Race, Championship Position) but no units — N/A
+ for categorical/ordinal data
+ design_excellence:
+ score: 16
+ max: 20
+ items:
+ - id: DE-01
+ name: Aesthetic Sophistication
+ score: 6
+ max: 8
+ passed: true
+ comment: Custom palette, visual hierarchy via line width/marker size/opacity,
+ end-of-line labels, white marker borders. Strong intentional design
+ - id: DE-02
+ name: Visual Refinement
+ score: 5
+ max: 6
+ passed: true
+ comment: plotly_white template, x-grid hidden, y-grid at 0.06 opacity, transparent
+ background, generous margins, marker edge highlights
+ - id: DE-03
+ name: Data Storytelling
+ score: 5
+ max: 6
passed: true
- comment: Grid is subtle, legend is well-placed but slightly distant from plot
- area
+ comment: Visual hierarchy through opacity/width/size guides viewer to Hamilton/Norris
+ storylines. Static drivers appropriately muted
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 bump chart showing rank changes over time
- - id: SC-02
- name: Data Mapping
score: 5
max: 5
passed: true
- comment: X=periods, Y=ranks correctly mapped
- - id: SC-03
+ comment: Correct bump chart showing rankings over time with connected lines
+ - id: SC-02
name: Required Features
- score: 5
- max: 5
+ score: 4
+ max: 4
passed: true
- comment: Inverted Y-axis (rank 1 at top), distinct colors, dot markers, lines
- connecting entities
- - id: SC-04
- name: Data Range
+ comment: Inverted y-axis, distinct colors, dot markers, connecting lines,
+ entity labels all present
+ - id: SC-03
+ name: Data Mapping
score: 3
max: 3
passed: true
- comment: All ranks (1-5) and periods (Week 1-6) visible
- - id: SC-05
- name: Legend Accuracy
- score: 2
- max: 2
- passed: true
- comment: Legend correctly identifies all five teams
- - id: SC-06
- name: Title Format
- score: 2
- max: 2
+ comment: X=races, Y=rank. All data visible on axes
+ - id: SC-04
+ name: Title & Legend
+ score: 3
+ max: 3
passed: true
- comment: Uses correct format "bump-basic · plotly · pyplots.ai"
+ comment: Title in correct format. End-of-line labels replace legend appropriately
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 rank changes, overtakes, stability (Team Delta stays 4th mostly);
- could show more dramatic swaps
+ comment: Shows rank changes, stability, stagnation, overtakes, convergence
+ and divergence
- id: DQ-02
name: Realistic Context
- score: 7
- max: 7
+ score: 5
+ max: 5
passed: true
- comment: Sports league standings is a perfect, real-world bump chart use case
+ comment: F1 driver standings with real driver/circuit names. Neutral sports
+ topic
- id: DQ-03
name: Appropriate Scale
score: 4
- max: 5
+ max: 4
passed: true
- comment: 5 teams over 6 weeks is appropriate; values are sensible
+ comment: 6 drivers, 8 races within spec recommendation. Rankings 1-6 correct
code_quality:
- score: 8
+ score: 10
max: 10
items:
- id: CQ-01
@@ -169,41 +182,48 @@ review:
score: 3
max: 3
passed: true
- comment: 'Simple linear structure: imports → data → plot → save'
+ comment: Clean Imports → Data → Plot → Save flow. No functions or classes
- id: CQ-02
name: Reproducibility
- score: 0
- max: 3
- passed: false
- comment: No random seed set (though data is deterministic, np.random is imported
- but not used)
+ score: 2
+ max: 2
+ passed: true
+ comment: Fully deterministic hardcoded data, no random elements
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
- comment: Only plotly.graph_objects imported, which is used
+ comment: Single import plotly.graph_objects, fully used
- id: CQ-04
- name: No Deprecated API
- score: 1
- max: 1
- passed: true
- comment: Uses current plotly API
- - id: CQ-05
- name: Output Correct
+ name: Code Elegance
score: 2
max: 2
passed: true
- comment: Saves as plot.png and plot.html
- library_features:
- score: 5
- max: 5
+ comment: Dictionary-based visual hierarchy is clean and purposeful. No fake
+ UI
+ - id: CQ-05
+ name: Output & API
+ score: 1
+ max: 1
+ passed: true
+ comment: Saves plot.png (4800x2700 via scale=3) and plot.html. Current API
+ library_mastery:
+ score: 7
+ max: 10
items:
- - id: LF-01
- name: Uses distinctive library features
- score: 5
+ - id: LM-01
+ name: Idiomatic Usage
+ score: 4
+ max: 5
+ passed: true
+ comment: Proper go.Figure, go.Scatter, add_annotation, update_layout. Appropriate
+ Graph Objects approach
+ - id: LM-02
+ name: Distinctive Features
+ score: 3
max: 5
passed: true
- comment: Uses go.Scatter with mode="lines+markers", proper plotly_white template,
- interactive HTML export, and autorange="reversed" for Y-axis inversion
+ comment: Custom hovertemplate with P-format, interactive HTML export. Plotly-specific
+ interactive features
verdict: APPROVED
diff --git a/plots/bump-basic/specification.md b/plots/bump-basic/specification.md
index e154f7a79c..8cbe102378 100644
--- a/plots/bump-basic/specification.md
+++ b/plots/bump-basic/specification.md
@@ -17,6 +17,7 @@ A bump chart visualizes how rankings change over time by plotting rank positions
- `period` (categorical or time) - Time points for ranking snapshots
- `rank` (integer) - Position at each period (1 = highest rank)
- Size: 5-10 entities, 4-8 periods typical
+- Example: Formula 1 driver standings over a 10-race season
## Notes
diff --git a/plots/bump-basic/specification.yaml b/plots/bump-basic/specification.yaml
index ddc9e8eb4b..17609d95e6 100644
--- a/plots/bump-basic/specification.yaml
+++ b/plots/bump-basic/specification.yaml
@@ -6,7 +6,7 @@ title: Basic Bump Chart
# Specification tracking
created: 2025-12-15T20:42:43Z
-updated: 2025-12-15T20:42:43Z
+updated: 2026-02-22T12:00:00Z
issue: 982
suggested: MarkusNeusinger
@@ -18,9 +18,11 @@ tags:
data_type:
- categorical
- ordinal
+ - timeseries
domain:
- general
features:
- basic
- ranking
- temporal
+ - comparison