Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 75 additions & 30 deletions plots/ecdf-basic/implementations/python/plotly.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
""" anyplot.ai
ecdf-basic: Basic ECDF Plot
Library: plotly 6.7.0 | Python 3.14.4
Quality: 88/100 | Updated: 2026-04-24
Library: plotly 6.8.0 | Python 3.13.14
Quality: 88/100 | Updated: 2026-06-25
"""

import os
import sys


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

import numpy as np
import plotly.graph_objects as go
Expand All @@ -16,58 +22,97 @@
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
BRAND = "#009E73" # Okabe-Ito position 1 — always first series
GRID = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)"
BRAND = "#009E73" # Imprint palette position 1 — always first series
ANYPLOT_AMBER = "#DDCC77" # annotation accent

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

# ECDF
sorted_times = np.sort(finish_times)
cumulative_proportion = np.arange(1, n_runners + 1) / n_runners
sorted_latency = np.sort(latency_ms)
cumulative_proportion = np.arange(1, n_requests + 1) / n_requests

# Key percentiles
p50 = np.percentile(sorted_latency, 50)
p90 = np.percentile(sorted_latency, 90)
p99 = np.percentile(sorted_latency, 99)
x_max = np.percentile(sorted_latency, 99.5)

# Plot
fig = go.Figure(
fig = go.Figure()

# ECDF line
fig.add_trace(
go.Scatter(
x=sorted_times,
x=sorted_latency,
y=cumulative_proportion,
mode="lines",
line={"color": BRAND, "width": 3.5, "shape": "hv"},
hovertemplate=("<b>Finish Time</b>: %{x:.1f} min<br><b>Cumulative</b>: %{y:.1%}<br><extra></extra>"),
line={"color": BRAND, "width": 2.5, "shape": "hv"},
hovertemplate="<b>Latency</b>: %{x:.1f} ms<br><b>Cumulative</b>: %{y:.1%}<extra></extra>",
showlegend=False,
)
)

# Percentile reference lines (dashed horizontal)
for pct_val, pct_label, y_pct in [(p50, "P50", 0.50), (p90, "P90", 0.90), (p99, "P99", 0.99)]:
fig.add_shape(
type="line",
x0=0,
x1=pct_val,
y0=y_pct,
y1=y_pct,
line={"color": INK_SOFT, "width": 1, "dash": "dot"},
layer="below",
)
fig.add_shape(
type="line",
x0=pct_val,
x1=pct_val,
y0=0,
y1=y_pct,
line={"color": INK_SOFT, "width": 1, "dash": "dot"},
layer="below",
)
fig.add_annotation(
x=pct_val,
y=y_pct,
text=f"<b>{pct_label}</b> {pct_val:.0f} ms",
showarrow=False,
xanchor="left",
yanchor="bottom",
xshift=6,
yshift=4,
font={"size": 10, "color": INK_SOFT},
)

# Style
title_text = "HTTP API Latency · ecdf-basic · python · plotly · anyplot.ai"
fig.update_layout(
title={
"text": "Marathon Finishing Times · ecdf-basic · plotly · anyplot.ai",
"font": {"size": 28, "color": INK},
"x": 0.5,
"xanchor": "center",
"y": 0.95,
},
autosize=False,
title={"text": title_text, "font": {"size": 16, "color": INK}, "x": 0.5, "xanchor": "center", "y": 0.95},
xaxis={
"title": {"text": "Finishing Time (minutes)", "font": {"size": 22, "color": INK}, "standoff": 12},
"tickfont": {"size": 18, "color": INK_SOFT},
"title": {"text": "Response Time (ms)", "font": {"size": 12, "color": INK}, "standoff": 12},
"tickfont": {"size": 10, "color": INK_SOFT},
"gridcolor": GRID,
"gridwidth": 1,
"linecolor": INK_SOFT,
"zeroline": False,
"showline": True,
"showline": False,
"mirror": False,
"ticksuffix": " min",
"range": [0, x_max],
},
yaxis={
"title": {"text": "Cumulative Proportion of Runners", "font": {"size": 22, "color": INK}, "standoff": 12},
"tickfont": {"size": 18, "color": INK_SOFT},
"title": {"text": "Cumulative Proportion of Requests", "font": {"size": 12, "color": INK}, "standoff": 12},
"tickfont": {"size": 10, "color": INK_SOFT},
"gridcolor": GRID,
"gridwidth": 1,
"linecolor": INK_SOFT,
"zeroline": False,
"showline": True,
"showline": False,
"mirror": False,
"range": [0, 1.02],
"tickformat": ".0%",
Expand All @@ -76,13 +121,13 @@
paper_bgcolor=PAGE_BG,
plot_bgcolor=PAGE_BG,
font={"color": INK, "family": "Inter, Helvetica Neue, Arial, sans-serif"},
margin={"l": 110, "r": 70, "t": 110, "b": 90},
margin={"l": 80, "r": 40, "t": 80, "b": 60},
hovermode="x unified",
hoverlabel={"bgcolor": ELEVATED_BG, "bordercolor": INK_SOFT, "font": {"color": INK, "size": 15}, "align": "left"},
hoverlabel={"bgcolor": ELEVATED_BG, "bordercolor": INK_SOFT, "font": {"color": INK, "size": 12}, "align": "left"},
)

# 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",
Expand Down
Loading
Loading