diff --git a/plots/slope-basic/implementations/python/plotly.py b/plots/slope-basic/implementations/python/plotly.py
index 706b62e74e..a38f2448a9 100644
--- a/plots/slope-basic/implementations/python/plotly.py
+++ b/plots/slope-basic/implementations/python/plotly.py
@@ -1,7 +1,7 @@
""" anyplot.ai
slope-basic: Basic Slope Chart (Slopegraph)
-Library: plotly 6.7.0 | Python 3.13.13
-Quality: 88/100 | Updated: 2026-04-30
+Library: plotly 6.9.0 | Python 3.13.14
+Quality: 91/100 | Updated: 2026-07-25
"""
import os
@@ -12,115 +12,199 @@
# 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"
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
-# Okabe-Ito: increase = brand green, decrease = vermillion, flat = adaptive neutral
+# Imprint palette: brand green = growth, matte red = decline (finance/industrial
+# gain-loss semantic exception — see default-style-guide.md "Semantic exception")
COLOR_UP = "#009E73"
-COLOR_DOWN = "#AE3030" # imprint red — decrease
-COLOR_FLAT = INK_MUTED
-
-# Data - Product sales Q1 vs Q4 comparison (10 products showing various patterns)
-products = [
- "Laptop Pro",
- "Wireless Earbuds",
- "Smart Watch",
- "Tablet Ultra",
- "Gaming Mouse",
- "Mechanical Keyboard",
- "Webcam HD",
- "USB Hub",
- "Portable SSD",
- "Monitor Stand",
+COLOR_DOWN = "#AE3030"
+
+# Data - US manufacturing sector output ($M), 2023 vs 2024
+sectors = [
+ "Aerospace",
+ "Electronics",
+ "Pharmaceuticals",
+ "Machinery",
+ "Food Processing",
+ "Plastics",
+ "Chemicals",
+ "Metals",
+ "Automotive",
+ "Textiles",
]
-sales_q1 = [245, 180, 120, 195, 85, 110, 45, 30, 75, 55]
-sales_q4 = [310, 220, 195, 160, 145, 130, 95, 85, 70, 40]
+output_2023 = [145, 260, 190, 130, 275, 80, 205, 155, 320, 95]
+output_2024 = [210, 310, 225, 175, 290, 100, 195, 140, 285, 70]
+
+colors = [COLOR_UP if end > start else COLOR_DOWN for start, end in zip(output_2023, output_2024, strict=True)]
+
+# Emphasize the biggest movers with bolder lines/markers (data-driven, not decorative)
+pct_changes = [abs(end - start) / start for start, end in zip(output_2023, output_2024, strict=True)]
+lo, hi = min(pct_changes), max(pct_changes)
+spread = hi - lo
+emphasis = [(c - lo) / spread if spread else 0.5 for c in pct_changes]
+line_widths = [2.0 + e * 2.0 for e in emphasis]
+marker_sizes = [9 + e * 6 for e in emphasis]
+
+
+all_values = output_2023 + output_2024
+y_min, y_max = min(all_values), max(all_values)
+y_pad = (y_max - y_min) * 0.18
+y_axis_range = [y_min - y_pad, y_max + y_pad]
+min_label_gap = (y_axis_range[1] - y_axis_range[0]) * 0.052
-colors = []
-for q1, q4 in zip(sales_q1, sales_q4, strict=True):
- if q4 > q1:
- colors.append(COLOR_UP)
- elif q4 < q1:
- colors.append(COLOR_DOWN)
- else:
- colors.append(COLOR_FLAT)
+# Anti-overlap label placement: push stacked label positions apart while
+# keeping the stack centered on the original values (left/right run the same
+# pass independently, since the two label columns never interact).
+left_label_y = []
+for values in (output_2023, output_2024):
+ order = sorted(range(len(values)), key=lambda i: values[i])
+ stacked = [values[i] for i in order]
+ for i in range(1, len(stacked)):
+ if stacked[i] - stacked[i - 1] < min_label_gap:
+ stacked[i] = stacked[i - 1] + min_label_gap
+ for i in range(len(stacked) - 2, -1, -1):
+ if stacked[i + 1] - stacked[i] < min_label_gap:
+ stacked[i] = stacked[i + 1] - min_label_gap
+ adjusted = [0.0] * len(values)
+ for position, i in enumerate(order):
+ adjusted[i] = stacked[position]
+ left_label_y.append(adjusted)
+left_label_y, right_label_y = left_label_y
+
+# Label columns: leader-line anchors sit exactly at the xaxis range bounds (the
+# same x position as the axis spine), so label text - regardless of how long a
+# sector name is - always renders into the margin whitespace and never crosses
+# back over the spine into the plot area. Margins are sized from the longest
+# label string in each column so no text clips the canvas edge either.
+x_axis_range = [-0.5, 1.5]
+left_label_text = [f"{sector}: ${value}M" for sector, value in zip(sectors, output_2023, strict=True)]
+right_label_text = [f"${value}M: {sector}" for sector, value in zip(sectors, output_2024, strict=True)]
+label_fontsize = 11
+char_width = 0.62 * label_fontsize
+left_margin = round(24 + char_width * max(len(t) for t in left_label_text))
+right_margin = round(24 + char_width * max(len(t) for t in right_label_text))
+left_anchor_x = x_axis_range[0] - 0.02
+right_anchor_x = x_axis_range[1] + 0.02
# Plot
fig = go.Figure()
-for i, product in enumerate(products):
+for i, sector in enumerate(sectors):
fig.add_trace(
go.Scatter(
x=[0, 1],
- y=[sales_q1[i], sales_q4[i]],
+ y=[output_2023[i], output_2024[i]],
mode="lines+markers",
- line={"color": colors[i], "width": 3},
- marker={"size": 14, "color": colors[i]},
- name=product,
+ line={"color": colors[i], "width": line_widths[i]},
+ marker={"size": marker_sizes[i], "color": colors[i]},
+ name=sector,
showlegend=False,
- hovertemplate=f"{product}
Q1: ${sales_q1[i]}K
Q4: ${sales_q4[i]}K",
+ hovertemplate=(f"{sector}
2023: ${output_2023[i]}M
2024: ${output_2024[i]}M"),
)
)
-# Labels at Q1 (left side)
-for i, product in enumerate(products):
+# Labels at 2023 (left side) — leader line points from the (possibly nudged)
+# label position back to the true data value, so decluttering never disconnects
+# a label from the entity it describes.
+for i in range(len(sectors)):
fig.add_annotation(
- x=-0.05,
- y=sales_q1[i],
- text=f"{product}: ${sales_q1[i]}K",
- showarrow=False,
+ x=0,
+ y=output_2023[i],
+ ax=left_anchor_x,
+ ay=left_label_y[i],
+ axref="x",
+ ayref="y",
+ xref="x",
+ yref="y",
+ text=left_label_text[i],
+ showarrow=True,
+ arrowhead=0,
+ arrowwidth=1,
+ arrowcolor=colors[i],
xanchor="right",
- font={"size": 16, "color": colors[i]},
+ yanchor="middle",
+ align="right",
+ font={"size": label_fontsize, "color": colors[i]},
)
-# Labels at Q4 (right side)
-for i, product in enumerate(products):
+# Labels at 2024 (right side)
+for i in range(len(sectors)):
fig.add_annotation(
- x=1.05,
- y=sales_q4[i],
- text=f"${sales_q4[i]}K: {product}",
- showarrow=False,
+ x=1,
+ y=output_2024[i],
+ ax=right_anchor_x,
+ ay=right_label_y[i],
+ axref="x",
+ ayref="y",
+ xref="x",
+ yref="y",
+ text=right_label_text[i],
+ showarrow=True,
+ arrowhead=0,
+ arrowwidth=1,
+ arrowcolor=colors[i],
xanchor="left",
- font={"size": 16, "color": colors[i]},
+ yanchor="middle",
+ align="left",
+ font={"size": label_fontsize, "color": colors[i]},
)
# Style
+title_text = "Manufacturing Output by Sector · slope-basic · python · plotly · anyplot.ai"
+title_fontsize = round(14 * min(1.0, 67 / len(title_text)))
+
fig.update_layout(
+ autosize=False,
+ width=800,
+ height=450,
paper_bgcolor=PAGE_BG,
plot_bgcolor=PAGE_BG,
font={"color": INK},
- title={
- "text": "Product Sales Q1 vs Q4 · slope-basic · plotly · anyplot.ai",
- "font": {"size": 28, "color": INK},
- "x": 0.5,
- "xanchor": "center",
- },
+ title={"text": title_text, "font": {"size": title_fontsize, "color": INK}, "x": 0.5, "xanchor": "center"},
xaxis={
"tickmode": "array",
"tickvals": [0, 1],
- "ticktext": ["Q1 2024", "Q4 2024"],
- "tickfont": {"size": 22, "color": INK_SOFT},
- "range": [-0.5, 1.5],
+ "ticktext": ["2023", "2024"],
+ "tickfont": {"size": 13, "color": INK_SOFT},
+ "range": x_axis_range,
"showgrid": False,
"zeroline": False,
"linecolor": INK_SOFT,
+ "mirror": False,
},
yaxis={
- "title": {"text": "Sales ($K)", "font": {"size": 22, "color": INK}},
- "tickfont": {"size": 18, "color": INK_SOFT},
+ # Numeric ticks are hidden: every entity is already directly labeled with
+ # its exact value at both endpoints, and the tick-number column would sit
+ # in the same margin band as those labels and collide with them.
+ "showticklabels": False,
+ "range": y_axis_range,
"showgrid": True,
"gridwidth": 1,
"gridcolor": GRID,
"zeroline": False,
"linecolor": INK_SOFT,
+ "mirror": False,
},
- margin={"l": 220, "r": 220, "t": 80, "b": 60},
+ margin={"l": left_margin, "r": right_margin, "t": 45, "b": 35},
+)
+
+# Units label placed above the plot instead of a rotated axis title, so it
+# never competes with the wide left-side entity label column for margin space.
+fig.add_annotation(
+ x=0,
+ y=1,
+ xref="paper",
+ yref="paper",
+ xanchor="left",
+ yanchor="bottom",
+ text="Output ($M)",
+ showarrow=False,
+ font={"size": 13, "color": INK},
)
# Save
-fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3)
+fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4)
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")
diff --git a/plots/slope-basic/metadata/python/plotly.yaml b/plots/slope-basic/metadata/python/plotly.yaml
index c462247808..94b3c1ddfc 100644
--- a/plots/slope-basic/metadata/python/plotly.yaml
+++ b/plots/slope-basic/metadata/python/plotly.yaml
@@ -2,46 +2,62 @@ library: plotly
language: python
specification_id: slope-basic
created: '2025-12-23T20:45:26Z'
-updated: '2026-04-30T17:01:48Z'
+updated: '2026-07-25T23:55:55Z'
generated_by: claude-sonnet
-workflow_run: 25177301713
+workflow_run: 30179281969
issue: 981
-python_version: 3.13.13
-library_version: 6.7.0
+language_version: 3.13.14
+library_version: 6.9.0
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/plotly/plot-light.png
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/plotly/plot-dark.png
preview_html_light: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/plotly/plot-light.html
preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/plotly/plot-dark.html
-quality_score: 88
+quality_score: 91
review:
strengths:
- - Semantic color encoding (green=increase, orange=decrease) creates immediate directional
- communication
- - All theme tokens correctly applied — both light and dark renders pass legibility
- checks
- - 'Perfect spec compliance: bilateral labels, direction color coding, correct time-point
- labels'
- - Realistic neutral business data demonstrating rank inversions and varied magnitudes
- - Clean plotly idioms with proper HTML+PNG output and working interactive hover
- tooltips
+ - 'Fixed both blockers from the attempt-1 review: the left-side leader labels ("Food
+ Processing", "Pharmaceuticals") no longer collide with the y-axis spine in either
+ theme (verified via pixel-level crop), and the anti-overlap declutter logic is
+ now fully inlined in the main script body with no helper function, satisfying
+ strict KISS single-script-flow.'
+ - 'Direct bilateral value labels ("Entity: $XM" / "$XM: Entity") at both endpoints
+ for all 10 sectors, each connected to its true data point via a thin leader line,
+ decluttered vertically so labels never overlap while never disconnecting from
+ their entity.'
+ - 'Data-driven visual emphasis: line width and marker size scale with each sector''s
+ percentage-change magnitude, drawing the eye to the biggest movers (Automotive''s
+ decline, Machinery''s rise) without decorative chartjunk.'
+ - 'Correct semantic finance color mapping (brand green #009E73 for gains, Imprint
+ matte-red anchor #AE3030 for losses), identical across both themes as required.'
+ - Theme-adaptive chrome (background, ink, grid) correctly threaded through both
+ renders; rich hovertemplate exposes exact sector/year values in the interactive
+ HTML export.
weaknesses:
- - Label crowding in lower y-range ($30-$110K band) where 7 products compete for
- vertical annotation space
- - Top and right axis spines still visible — removing them would add visual refinement
- - No size or weight variation on biggest movers (Laptop Pro, Tablet Ultra) to amplify
- storytelling emphasis
+ - Matte-red (#AE3030) label text has a low luminance-contrast ratio (~2.7:1) against
+ the dark-theme background (#1A1A17) — still legible thanks to hue separation and
+ generous font size, but below the general 4.5:1 text-contrast guideline. A thin
+ ink-colored micro-stroke on the red text/leader-lines in dark theme (per the style
+ guide's 'Optional outline pattern'), applied to text only while keeping the canonical
+ hex on the connecting line/marker, would tighten this.
+ - The 'Output ($M)' units annotation sits immediately beneath the title with very
+ little vertical gap, reading almost like an unlabeled second title line rather
+ than a clearly separated axis-unit caption. A bit more top margin or a lighter/smaller
+ treatment would clarify the hierarchy.
+ - No explicit caption states 'green = increase, red = decrease' — inferable from
+ slope direction and the endpoint values, but an explicit note would remove any
+ residual ambiguity for a first-time viewer (minor, optional).
image_description: |-
Light render (plot-light.png):
- Background: Warm off-white (#FAF8F1) — correct, not pure white
- Chrome: Title "Product Sales Q1 vs Q4 · slope-basic · plotly · anyplot.ai" in dark ink, clearly readable. Y-axis label "Sales ($K)" readable. X-tick labels "Q1 2024"/"Q4 2024" in INK_SOFT, readable. Annotations in matching line colors (green/orange), readable.
- Data: 10 product lines — most in #009E73 (green, increasing), 3 in #D55E00 (orange, decreasing). Lines at width=3 with size-14 markers. Crossing lines visible (Tablet Ultra falls while Smart Watch rises).
- Legibility verdict: PASS — all text readable; minor label crowding in lower value band ($30-$110K) but no unreadable text.
+ Background: warm off-white, consistent with #FAF8F1 — not pure white.
+ Chrome: dark-ink title "Manufacturing Output by Sector · slope-basic · python · plotly · anyplot.ai" centered at top, ~50% of plot width, fully readable. "Output ($M)" units annotation sits directly below the title (soft-ink but slightly under-spaced from the title line). "2023"/"2024" x-axis tick labels in soft ink, clearly readable. Y-axis numeric ticks intentionally hidden since every entity is directly labeled with its exact value.
+ Data: Ten slope lines connect 2023 -> 2024 values for manufacturing sectors, colored Imprint brand green (#009E73, 6 increasing sectors) or Imprint matte red (#AE3030, 4 declining sectors) — correct gain/loss semantic mapping. Line width and marker size scale with each sector's percentage-change magnitude, emphasizing the biggest movers (Automotive's decline, Machinery's rise). Bilateral value+entity labels ("Entity: $XM" left, "$XM: Entity" right) flank the chart, each linked to its true data point via a thin leader line; a prior spine-through-label collision (on "Food Processing" and "Pharmaceuticals") is fixed — verified via pixel-level crop that no label text touches the y-axis spine.
+ Legibility verdict: PASS — all text is clearly readable against the light background; no light-on-light issues.
Dark render (plot-dark.png):
- Background: Warm near-black (#1A1A17) — correct, not pure black
- Chrome: Title in #F0EFE8 (light), clearly readable. Axis labels and ticks in #B8B7B0, readable. Annotations in matching line colors (same green/orange as light render), readable against dark background.
- Data: Colors identical to light render — green #009E73 and orange #D55E00 unchanged. Only background, text, and grid chrome flipped. No dark-on-dark failures detected.
- Legibility verdict: PASS — all text readable in dark theme; brand green #009E73 highly visible on near-black surface.
+ Background: warm near-black, consistent with #1A1A17 — not pure black.
+ Chrome: title and "Output ($M)" annotation now render in light ink (#F0EFE8-equivalent), x-axis tick labels in soft light tone — both fully readable against the dark surface, no dark-on-dark failures on the primary chrome.
+ Data: identical Imprint colors to the light render (#009E73 / #AE3030), confirming only chrome flips between themes as required. Same fixed leader-line/label layout, no spine collisions reproduced.
+ Legibility verdict: PASS with a minor caveat — the matte-red (#AE3030) label text carries a low luminance-contrast ratio (~2.7:1 by WCAG relative-luminance calculation) against the near-black background. It remains legible (verified via direct crop inspection: text is clearly distinguishable, not "invisible" or truly dark-on-dark) but is noticeably softer/dimmer than the green counterpart. Flagged as a weakness for a future micro-polish, not a legibility failure.
criteria_checklist:
visual_quality:
score: 28
@@ -49,76 +65,75 @@ review:
items:
- id: VQ-01
name: Text Legibility
- score: 8
+ score: 7
max: 8
passed: true
- comment: 'All font sizes explicitly set: title 28px, x-ticks 22px, y-title
- 22px, y-ticks 18px, annotations 16px'
+ comment: All text readable in both themes; matte-red dark-theme labels (~2.7:1
+ contrast) are legible but softer than the green counterpart
- id: VQ-02
name: No Overlap
- score: 4
+ score: 6
max: 6
passed: true
- comment: Moderate label crowding in lower y-band ($30-$110K) with 7 products;
- readable but not perfectly spaced
+ comment: Prior spine-through-label collision fixed and verified via pixel
+ crop; no overlaps anywhere
- id: VQ-03
name: Element Visibility
score: 6
max: 6
passed: true
- comment: Lines at width=3, markers at size=14, well-adapted for canvas size
+ comment: Sparse data (10 entities) given prominent markers/lines, sized by
+ % change magnitude
- id: VQ-04
name: Color Accessibility
score: 2
max: 2
passed: true
- comment: Okabe-Ito positions 1 and 2 are CVD-safe with strong luminance contrast
+ comment: CVD-safe Imprint hues; correct finance semantic exception (green=gain,
+ red=loss)
- id: VQ-05
name: Layout & Canvas
- score: 4
+ score: 3
max: 4
passed: true
- comment: Generous bilateral margins (220px) appropriate for slope chart annotations;
- balanced layout
+ comment: Canvas gate passed (3200x1800); title/units-annotation vertical spacing
+ is a bit tight
- id: VQ-06
name: Axis Labels & Title
score: 2
max: 2
passed: true
- comment: Y-axis Sales ($K) with units; X-axis time-point tick labels Q1 2024/Q4
- 2024
+ comment: Units label 'Output ($M)' present; x-axis time points labeled
- id: VQ-07
name: Palette Compliance
score: 2
max: 2
passed: true
- comment: 'Background #FAF8F1/#1A1A17 confirmed; first series #009E73, second
- #D55E00; chrome fully theme-adaptive'
+ comment: Brand green + matte-red anchor, identical across themes; backgrounds
+ theme-correct
design_excellence:
- score: 13
+ score: 16
max: 20
items:
- id: DE-01
name: Aesthetic Sophistication
- score: 5
+ score: 6
max: 8
passed: true
- comment: Semantic color encoding and inline labels above defaults; not yet
- at strong-design level
+ comment: Thoughtful semantic color, direct labeling, magnitude-driven emphasis
- id: DE-02
name: Visual Refinement
- score: 4
+ score: 5
max: 6
passed: true
- comment: X-axis grid hidden, y-axis grid uses rgba token; top/right spines
- still visible
+ comment: Spine-overlap defect resolved; subtle grid, L-shaped frame; minor
+ title/subtitle crowding remains
- id: DE-03
name: Data Storytelling
- score: 4
+ score: 5
max: 6
passed: true
- comment: Semantic colors communicate direction immediately; crossing lines
- tell rank-change story; no additional size variation for biggest movers
+ comment: Emphasis on biggest movers gives an immediate read on direction/magnitude
spec_compliance:
score: 15
max: 15
@@ -128,28 +143,27 @@ review:
score: 5
max: 5
passed: true
- comment: Correct slope chart/slopegraph with bilateral axes and connecting
- lines
+ comment: Correct slopegraph
- id: SC-02
name: Required Features
score: 4
max: 4
passed: true
- comment: Labels at both endpoints, direction color coding, time-point axis
- labels, 10 entities in 5-15 range
+ comment: Bilateral labels, direction color-coding, labeled time-point axes
+ all present
- id: SC-03
name: Data Mapping
score: 3
max: 3
passed: true
- comment: X=time points, Y=sales values, all 10 entities visible
+ comment: x=time point, y=value, correct
- id: SC-04
name: Title & Legend
score: 3
max: 3
passed: true
- comment: 'Title format correct: slope-basic · plotly · anyplot.ai; inline
- labels replace legend appropriately'
+ comment: Title matches mandated format; direct labels substitute for a 10-entry
+ legend appropriately
data_quality:
score: 15
max: 15
@@ -159,22 +173,19 @@ review:
score: 6
max: 6
passed: true
- comment: Shows increases, decreases, rank inversions, steep vs gradual changes
- — all slope chart features
+ comment: 10 sectors (within 5-15 spec range), two time points, direction coding
- id: DQ-02
name: Realistic Context
score: 5
max: 5
passed: true
- comment: Tech product quarterly sales comparison — neutral, recognizable business
- scenario
+ comment: Plausible, neutral US manufacturing output data
- id: DQ-03
name: Appropriate Scale
score: 4
max: 4
passed: true
- comment: Sales values $30K-$310K plausible for product category quarterly
- sales; realistic change percentages
+ comment: Sensible $M values (70-320) for manufacturing sector output
code_quality:
score: 10
max: 10
@@ -184,34 +195,32 @@ review:
score: 3
max: 3
passed: true
- comment: 'Flat: imports → tokens → data → colors → plot → annotations → layout
- → save'
+ comment: Helper function removed; anti-overlap logic now fully inlined in
+ a flat script
- id: CQ-02
name: Reproducibility
score: 2
max: 2
passed: true
- comment: All data hardcoded and deterministic; no random seed needed
+ comment: Fully deterministic, hardcoded data
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
- comment: Only os and plotly.graph_objects; both used
+ comment: Only os + plotly.graph_objects
- id: CQ-04
name: Code Elegance
score: 2
max: 2
passed: true
- comment: Clean iteration patterns for colors, traces, and annotations; no
- over-engineering
+ comment: No fake UI, appropriate complexity for the declutter algorithm
- id: CQ-05
name: Output & API
score: 1
max: 1
passed: true
- comment: Saves plot-{THEME}.png (4800x2700 via scale=3) and plot-{THEME}.html;
- correct for plotly
+ comment: write_image + write_html to plot-{THEME}.png/.html
library_mastery:
score: 7
max: 10
@@ -221,25 +230,24 @@ review:
score: 4
max: 5
passed: true
- comment: Proper go.Figure/add_trace pattern with hovertemplate and update_layout;
- good idiomatic plotly
+ comment: Correct low-level go.Figure/go.Scatter/add_annotation usage, appropriate
+ for this custom layout
- id: LM-02
name: Distinctive Features
score: 3
max: 5
passed: true
- comment: Interactive hover with hovertemplate and HTML export are genuinely
- plotly-distinctive capabilities
+ comment: Annotation-anchored leader lines + rich hovertemplate for HTML interactivity
verdict: APPROVED
impl_tags:
dependencies: []
techniques:
- annotations
+ - manual-ticks
- hover-tooltips
- html-export
patterns:
- - data-generation
- iteration-over-groups
- dataprep: []
- styling:
- - grid-styling
+ dataprep:
+ - normalization
+ styling: []