{"spec_id":"qq-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nqq-basic: Basic Q-Q Plot\nLibrary: pygal 3.1.3 | Python 3.13.14\nQuality: 92/100 | Updated: 2026-07-24\n\"\"\"\n\nimport sys\n\n\nsys.path.pop(0)  # prevent this file from shadowing the installed pygal package\n\nimport os\nfrom statistics import NormalDist\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nBRAND = \"#009E73\"  # Imprint palette position 1 — always first series\nAMBER = \"#DDCC77\"  # Imprint semantic anchor — warning / caution, flags the slow-response tail\n\n# Data - API response times (ms): a fast, tightly-clustered core plus an\n# occasional slow request, producing the right-skewed tail that QQ plots\n# are commonly used to surface before assuming normality in latency analysis.\nnp.random.seed(42)\nbaseline = np.random.normal(150, 18, 80)\nslow_tail = 150 + np.random.exponential(35, 20)\nsample = np.sort(np.concatenate([baseline, slow_tail]))\nn = len(sample)\n\n# Calculate theoretical quantiles using standard library\n_nd = NormalDist()\nprobabilities = (np.arange(1, n + 1) - 0.5) / n\ntheoretical_quantiles = np.array([_nd.inv_cdf(float(p)) for p in probabilities])\n\n# Theoretical quantiles are unitless z-scores while the sample is in ms, so\n# the fitted reference line uses the sample's own mean/std (y = mean + std*x)\n# rather than y=x — the standard QQ convention when axes are in different units\n# (matches scipy.stats.probplot / statsmodels qqplot). The legend label spells\n# this out explicitly so it doesn't read as a spec deviation.\ntheo_margin = (theoretical_quantiles.max() - theoretical_quantiles.min()) * 0.1\nline_x_min = theoretical_quantiles.min() - theo_margin\nline_x_max = theoretical_quantiles.max() + theo_margin\n\nsample_mean = sample.mean()\nsample_std = sample.std()\n\n# Flag the slow-response tail (>2 std above the mean) so the deviation this\n# QQ plot exists to reveal is visually called out, not just implied by shape.\n# Split into two series (rather than per-point color overrides) so the\n# outliers get their own legend swatch — self-explanatory in the static PNG.\noutlier_threshold = sample_mean + 2 * sample_std\npaired = list(zip(theoretical_quantiles, sample, strict=True))\nnormal_points = [(tq, s) for tq, s in paired if s <= outlier_threshold]\noutlier_points = [(tq, s) for tq, s in paired if s > outlier_threshold]\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    # series 1 = brand green, series 2 = amber outlier anchor, series 3 (reference line) = neutral ink\n    colors=(BRAND, AMBER, INK),\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=2.5,\n)\n\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=\"qq-basic · python · pygal · anyplot.ai\",\n    x_title=\"Theoretical Quantiles\",\n    y_title=\"Sample Quantiles (ms)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    dots_size=13,\n    stroke=False,\n    show_x_guides=False,\n    show_y_guides=True,\n)\n\nchart.add(\"API Response Time\", normal_points)\nchart.add(\"Outlier (>2σ)\", outlier_points)\n\nreference_line = [\n    (line_x_min, sample_mean + sample_std * line_x_min),\n    (line_x_max, sample_mean + sample_std * line_x_max),\n]\nchart.add(\"Reference (Normal Fit)\", reference_line, stroke=True, show_dots=False, dots_size=0)\n\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}