Skip to content

Commit a2518d7

Browse files
feat(plotly): implement ecdf-basic (#9485)
## Implementation: `ecdf-basic` - python/plotly Implements the **python/plotly** version of `ecdf-basic`. **File:** `plots/ecdf-basic/implementations/python/plotly.py` **Parent Issue:** #976 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/28160341179)* --------- 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 d6806c4 commit a2518d7

2 files changed

Lines changed: 173 additions & 124 deletions

File tree

plots/ecdf-basic/implementations/python/plotly.py

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
""" anyplot.ai
22
ecdf-basic: Basic ECDF Plot
3-
Library: plotly 6.7.0 | Python 3.14.4
4-
Quality: 88/100 | Updated: 2026-04-24
3+
Library: plotly 6.8.0 | Python 3.13.14
4+
Quality: 88/100 | Updated: 2026-06-25
55
"""
66

77
import os
8+
import sys
9+
10+
11+
# Remove the script's own directory from sys.path so this file doesn't shadow
12+
# the installed plotly package (script is named plotly.py).
13+
sys.path = [p for p in sys.path if p not in ("", os.path.dirname(os.path.abspath(__file__)))]
814

915
import numpy as np
1016
import plotly.graph_objects as go
@@ -16,58 +22,97 @@
1622
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
1723
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
1824
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 — always first series
25+
GRID = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)"
26+
BRAND = "#009E73" # Imprint palette position 1 — always first series
27+
ANYPLOT_AMBER = "#DDCC77" # annotation accent
2128

22-
# Data: marathon finishing times (minutes) for 300 runners
29+
# Data: HTTP API response latencies (ms) for 400 requests to a web service
2330
np.random.seed(42)
24-
n_runners = 300
25-
finish_times = np.random.normal(loc=240, scale=32, size=n_runners)
31+
n_requests = 400
32+
# Lognormal distribution models real-world latency well (long right tail)
33+
latency_ms = np.random.lognormal(mean=4.8, sigma=0.5, size=n_requests)
2634

2735
# ECDF
28-
sorted_times = np.sort(finish_times)
29-
cumulative_proportion = np.arange(1, n_runners + 1) / n_runners
36+
sorted_latency = np.sort(latency_ms)
37+
cumulative_proportion = np.arange(1, n_requests + 1) / n_requests
38+
39+
# Key percentiles
40+
p50 = np.percentile(sorted_latency, 50)
41+
p90 = np.percentile(sorted_latency, 90)
42+
p99 = np.percentile(sorted_latency, 99)
43+
x_max = np.percentile(sorted_latency, 99.5)
3044

3145
# Plot
32-
fig = go.Figure(
46+
fig = go.Figure()
47+
48+
# ECDF line
49+
fig.add_trace(
3350
go.Scatter(
34-
x=sorted_times,
51+
x=sorted_latency,
3552
y=cumulative_proportion,
3653
mode="lines",
37-
line={"color": BRAND, "width": 3.5, "shape": "hv"},
38-
hovertemplate=("<b>Finish Time</b>: %{x:.1f} min<br><b>Cumulative</b>: %{y:.1%}<br><extra></extra>"),
54+
line={"color": BRAND, "width": 2.5, "shape": "hv"},
55+
hovertemplate="<b>Latency</b>: %{x:.1f} ms<br><b>Cumulative</b>: %{y:.1%}<extra></extra>",
3956
showlegend=False,
4057
)
4158
)
4259

60+
# Percentile reference lines (dashed horizontal)
61+
for pct_val, pct_label, y_pct in [(p50, "P50", 0.50), (p90, "P90", 0.90), (p99, "P99", 0.99)]:
62+
fig.add_shape(
63+
type="line",
64+
x0=0,
65+
x1=pct_val,
66+
y0=y_pct,
67+
y1=y_pct,
68+
line={"color": INK_SOFT, "width": 1, "dash": "dot"},
69+
layer="below",
70+
)
71+
fig.add_shape(
72+
type="line",
73+
x0=pct_val,
74+
x1=pct_val,
75+
y0=0,
76+
y1=y_pct,
77+
line={"color": INK_SOFT, "width": 1, "dash": "dot"},
78+
layer="below",
79+
)
80+
fig.add_annotation(
81+
x=pct_val,
82+
y=y_pct,
83+
text=f"<b>{pct_label}</b> {pct_val:.0f} ms",
84+
showarrow=False,
85+
xanchor="left",
86+
yanchor="bottom",
87+
xshift=6,
88+
yshift=4,
89+
font={"size": 10, "color": INK_SOFT},
90+
)
91+
4392
# Style
93+
title_text = "HTTP API Latency · ecdf-basic · python · plotly · anyplot.ai"
4494
fig.update_layout(
45-
title={
46-
"text": "Marathon Finishing Times · ecdf-basic · plotly · anyplot.ai",
47-
"font": {"size": 28, "color": INK},
48-
"x": 0.5,
49-
"xanchor": "center",
50-
"y": 0.95,
51-
},
95+
autosize=False,
96+
title={"text": title_text, "font": {"size": 16, "color": INK}, "x": 0.5, "xanchor": "center", "y": 0.95},
5297
xaxis={
53-
"title": {"text": "Finishing Time (minutes)", "font": {"size": 22, "color": INK}, "standoff": 12},
54-
"tickfont": {"size": 18, "color": INK_SOFT},
98+
"title": {"text": "Response Time (ms)", "font": {"size": 12, "color": INK}, "standoff": 12},
99+
"tickfont": {"size": 10, "color": INK_SOFT},
55100
"gridcolor": GRID,
56101
"gridwidth": 1,
57102
"linecolor": INK_SOFT,
58103
"zeroline": False,
59-
"showline": True,
104+
"showline": False,
60105
"mirror": False,
61-
"ticksuffix": " min",
106+
"range": [0, x_max],
62107
},
63108
yaxis={
64-
"title": {"text": "Cumulative Proportion of Runners", "font": {"size": 22, "color": INK}, "standoff": 12},
65-
"tickfont": {"size": 18, "color": INK_SOFT},
109+
"title": {"text": "Cumulative Proportion of Requests", "font": {"size": 12, "color": INK}, "standoff": 12},
110+
"tickfont": {"size": 10, "color": INK_SOFT},
66111
"gridcolor": GRID,
67112
"gridwidth": 1,
68113
"linecolor": INK_SOFT,
69114
"zeroline": False,
70-
"showline": True,
115+
"showline": False,
71116
"mirror": False,
72117
"range": [0, 1.02],
73118
"tickformat": ".0%",
@@ -76,13 +121,13 @@
76121
paper_bgcolor=PAGE_BG,
77122
plot_bgcolor=PAGE_BG,
78123
font={"color": INK, "family": "Inter, Helvetica Neue, Arial, sans-serif"},
79-
margin={"l": 110, "r": 70, "t": 110, "b": 90},
124+
margin={"l": 80, "r": 40, "t": 80, "b": 60},
80125
hovermode="x unified",
81-
hoverlabel={"bgcolor": ELEVATED_BG, "bordercolor": INK_SOFT, "font": {"color": INK, "size": 15}, "align": "left"},
126+
hoverlabel={"bgcolor": ELEVATED_BG, "bordercolor": INK_SOFT, "font": {"color": INK, "size": 12}, "align": "left"},
82127
)
83128

84129
# Save
85-
fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3)
130+
fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4)
86131
fig.write_html(
87132
f"plot-{THEME}.html",
88133
include_plotlyjs="cdn",

0 commit comments

Comments
 (0)