Skip to content

Commit fde0a9c

Browse files
feat(plotly): implement errorbar-basic (#9518)
## Implementation: `errorbar-basic` - python/plotly Implements the **python/plotly** version of `errorbar-basic`. **File:** `plots/errorbar-basic/implementations/python/plotly.py` **Parent Issue:** #973 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/28473053865)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 36258fa commit fde0a9c

2 files changed

Lines changed: 226 additions & 165 deletions

File tree

Lines changed: 129 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,163 @@
11
""" anyplot.ai
22
errorbar-basic: Basic Error Bar Plot
3-
Library: plotly 6.7.0 | Python 3.14.4
4-
Quality: 85/100 | Updated: 2026-04-25
3+
Library: plotly 6.8.0 | Python 3.13.14
4+
Quality: 91/100 | Updated: 2026-06-30
55
"""
66

7+
import importlib
78
import os
9+
import sys
810

9-
import numpy as np
10-
import plotly.graph_objects as go
1111

12+
# Drop script directory from sys.path so the `plotly` package resolves, not this file
13+
sys.path[:] = [p for p in sys.path if os.path.abspath(p or ".") != os.path.dirname(os.path.abspath(__file__))]
14+
go = importlib.import_module("plotly.graph_objects")
15+
np = importlib.import_module("numpy")
1216

1317
# Theme tokens
1418
THEME = os.getenv("ANYPLOT_THEME", "light")
1519
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
1620
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
1721
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
1822
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
19-
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
20-
BRAND = "#009E73" # Okabe-Ito position 1
23+
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
24+
GRID = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)"
25+
CTRL_FILL = "rgba(26,26,23,0.05)" if THEME == "light" else "rgba(240,239,232,0.05)"
26+
27+
# Imprint palette — one hue per group (positions 1–6)
28+
IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#2ABCCD", "#954477"]
2129

2230
# Data — clinical trial response (mg/dL) with asymmetric 95% confidence intervals
23-
np.random.seed(42)
2431
groups = ["Control", "Treatment A", "Treatment B", "Treatment C", "Treatment D", "Treatment E"]
2532
x_positions = list(range(len(groups)))
2633
means = np.array([42.3, 51.7, 63.2, 47.8, 72.4, 58.9])
2734
err_upper = np.array([5.4, 7.8, 4.1, 9.3, 3.6, 6.5])
2835
err_lower = np.array([4.1, 6.2, 3.8, 7.9, 4.7, 5.1])
2936

30-
# Plot
37+
# Control CI bounds — reference band for comparison
38+
control_lo = float(means[0] - err_lower[0])
39+
control_hi = float(means[0] + err_upper[0])
40+
3141
fig = go.Figure()
3242

33-
fig.add_trace(
34-
go.Scatter(
35-
x=x_positions,
36-
y=means,
37-
mode="markers",
38-
marker=dict(size=28, color=BRAND, line=dict(color=PAGE_BG, width=3)),
39-
error_y=dict(
40-
type="data",
41-
symmetric=False,
42-
array=err_upper,
43-
arrayminus=err_lower,
44-
visible=True,
45-
thickness=4,
46-
width=18,
47-
color=BRAND,
48-
),
49-
name="Mean ± 95% CI",
50-
customdata=np.stack([np.array(groups), means - err_lower, means + err_upper], axis=-1),
51-
hovertemplate=(
52-
"<b>%{customdata[0]}</b><br>"
53-
"Mean: %{y:.1f} mg/dL<br>"
54-
"95%% CI: [%{customdata[1]:.1f}, %{customdata[2]:.1f}]"
55-
"<extra></extra>"
56-
),
43+
# Subtle control CI reference band (drawn below data)
44+
fig.add_hrect(y0=control_lo, y1=control_hi, fillcolor=CTRL_FILL, line_width=0, layer="below")
45+
46+
# Control baseline dashed reference line
47+
fig.add_hline(y=float(means[0]), line_dash="dot", line_color=INK_MUTED, line_width=1.5)
48+
49+
# Per-group traces — Imprint palette assigns each group a distinct hue
50+
for i, (group, x, mean, eu, el) in enumerate(
51+
zip(groups, x_positions, means.tolist(), err_upper.tolist(), err_lower.tolist(), strict=False)
52+
):
53+
color = IMPRINT_PALETTE[i]
54+
fig.add_trace(
55+
go.Scatter(
56+
x=[x],
57+
y=[mean],
58+
mode="markers",
59+
marker={"size": 20, "color": color, "line": {"color": PAGE_BG, "width": 2}},
60+
error_y={
61+
"type": "data",
62+
"symmetric": False,
63+
"array": [eu],
64+
"arrayminus": [el],
65+
"visible": True,
66+
"thickness": 3,
67+
"width": 12,
68+
"color": color,
69+
},
70+
name=group,
71+
customdata=[[group, round(mean - el, 1), round(mean + eu, 1)]],
72+
hovertemplate=(
73+
"<b>%{customdata[0]}</b><br>"
74+
"Mean: %{y:.1f} mg/dL<br>"
75+
"95%% CI: [%{customdata[1]}, %{customdata[2]}]"
76+
"<extra></extra>"
77+
),
78+
)
5779
)
80+
81+
# Annotation: Treatment D (index 4) — highest mean response
82+
fig.add_annotation(
83+
x=4,
84+
y=float(means[4] + err_upper[4]),
85+
text="Peak response",
86+
showarrow=True,
87+
arrowhead=2,
88+
arrowcolor=INK_SOFT,
89+
arrowwidth=1.5,
90+
ax=0,
91+
ay=-32,
92+
font={"size": 10, "color": INK_SOFT},
93+
bgcolor=ELEVATED_BG,
94+
bordercolor=INK_SOFT,
95+
borderwidth=1,
96+
borderpad=3,
97+
xanchor="center",
98+
)
99+
100+
# Annotation: control baseline label
101+
fig.add_annotation(
102+
x=5.55,
103+
y=float(means[0]),
104+
text="Control<br>baseline",
105+
showarrow=False,
106+
font={"size": 10, "color": INK_MUTED},
107+
xanchor="left",
108+
yanchor="middle",
109+
bgcolor=ELEVATED_BG,
110+
borderpad=2,
58111
)
59112

60-
# Style
61113
fig.update_layout(
62-
title=dict(
63-
text="Clinical Response by Group · errorbar-basic · plotly · pyplots.ai",
64-
font=dict(size=28, color=INK),
65-
x=0.5,
66-
xanchor="center",
67-
y=0.95,
68-
),
69-
xaxis=dict(
70-
title=dict(text="Experimental Group", font=dict(size=22, color=INK)),
71-
tickfont=dict(size=18, color=INK_SOFT),
72-
tickmode="array",
73-
tickvals=x_positions,
74-
ticktext=groups,
75-
showgrid=False,
76-
linecolor=INK_SOFT,
77-
zeroline=False,
78-
ticks="",
79-
),
80-
yaxis=dict(
81-
title=dict(text="Response (mg/dL)", font=dict(size=22, color=INK)),
82-
tickfont=dict(size=18, color=INK_SOFT),
83-
gridcolor=GRID,
84-
gridwidth=1,
85-
linecolor=INK_SOFT,
86-
zeroline=False,
87-
range=[0, 90],
88-
ticks="",
89-
),
114+
autosize=False,
115+
title={
116+
"text": "Clinical Response by Group · errorbar-basic · python · plotly · anyplot.ai",
117+
"font": {"size": 16, "color": INK},
118+
"x": 0.5,
119+
"xanchor": "center",
120+
},
121+
xaxis={
122+
"title": {"text": "Experimental Group", "font": {"size": 12, "color": INK}},
123+
"tickfont": {"size": 10, "color": INK_SOFT},
124+
"tickmode": "array",
125+
"tickvals": x_positions,
126+
"ticktext": groups,
127+
"showgrid": False,
128+
"linecolor": INK_SOFT,
129+
"zeroline": False,
130+
"ticks": "",
131+
"range": [-0.6, 6.0],
132+
},
133+
yaxis={
134+
"title": {"text": "Response (mg/dL)", "font": {"size": 12, "color": INK}},
135+
"tickfont": {"size": 10, "color": INK_SOFT},
136+
"gridcolor": GRID,
137+
"gridwidth": 1,
138+
"linecolor": INK_SOFT,
139+
"zeroline": False,
140+
"range": [28, 88],
141+
"ticks": "",
142+
},
90143
paper_bgcolor=PAGE_BG,
91144
plot_bgcolor=PAGE_BG,
92-
font=dict(color=INK),
93-
margin=dict(l=140, r=80, t=130, b=120),
145+
font={"color": INK},
146+
margin={"l": 80, "r": 80, "t": 80, "b": 60},
94147
showlegend=True,
95-
legend=dict(
96-
font=dict(size=16, color=INK_SOFT),
97-
bgcolor=ELEVATED_BG,
98-
bordercolor=INK_SOFT,
99-
borderwidth=1,
100-
x=0.98,
101-
y=0.98,
102-
xanchor="right",
103-
yanchor="top",
104-
),
105-
hoverlabel=dict(bgcolor=ELEVATED_BG, bordercolor=INK_SOFT, font=dict(color=INK, size=16)),
148+
legend={
149+
"font": {"size": 10, "color": INK_SOFT},
150+
"bgcolor": ELEVATED_BG,
151+
"bordercolor": INK_SOFT,
152+
"borderwidth": 1,
153+
"x": 0.98,
154+
"y": 0.98,
155+
"xanchor": "right",
156+
"yanchor": "top",
157+
},
158+
hoverlabel={"bgcolor": ELEVATED_BG, "bordercolor": INK_SOFT, "font": {"color": INK, "size": 10}},
106159
)
107160

108-
# Save
109-
fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3)
161+
# Save — landscape 3200 × 1800 (width=800 × scale=4, height=450 × scale=4)
162+
fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4)
110163
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

0 commit comments

Comments
 (0)