|
1 | 1 | """ anyplot.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import importlib |
7 | 8 | import os |
| 9 | +import sys |
8 | 10 |
|
9 | | -import numpy as np |
10 | | -import plotly.graph_objects as go |
11 | 11 |
|
| 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") |
12 | 16 |
|
13 | 17 | # Theme tokens |
14 | 18 | THEME = os.getenv("ANYPLOT_THEME", "light") |
15 | 19 | PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
16 | 20 | ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
17 | 21 | INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
18 | 22 | 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"] |
21 | 29 |
|
22 | 30 | # Data — clinical trial response (mg/dL) with asymmetric 95% confidence intervals |
23 | | -np.random.seed(42) |
24 | 31 | groups = ["Control", "Treatment A", "Treatment B", "Treatment C", "Treatment D", "Treatment E"] |
25 | 32 | x_positions = list(range(len(groups))) |
26 | 33 | means = np.array([42.3, 51.7, 63.2, 47.8, 72.4, 58.9]) |
27 | 34 | err_upper = np.array([5.4, 7.8, 4.1, 9.3, 3.6, 6.5]) |
28 | 35 | err_lower = np.array([4.1, 6.2, 3.8, 7.9, 4.7, 5.1]) |
29 | 36 |
|
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 | + |
31 | 41 | fig = go.Figure() |
32 | 42 |
|
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 | + ) |
57 | 79 | ) |
| 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, |
58 | 111 | ) |
59 | 112 |
|
60 | | -# Style |
61 | 113 | 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 | + }, |
90 | 143 | paper_bgcolor=PAGE_BG, |
91 | 144 | 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}, |
94 | 147 | 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}}, |
106 | 159 | ) |
107 | 160 |
|
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) |
110 | 163 | fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") |
0 commit comments