* feat(line): add ridgeline as a Line variant One distribution per series, stacked at a fixed pitch on a single shared amplitude. Ships three static examples (light, dark, full editorial), the type-line.md spec, and a geometric gate. verify-ridgeline.py derives the figure's one amplitude and its baseline pitch from the figure itself, then holds every vertex to both. It also checks the drawn baseline rules, the shared bin scale, absolute M/L/Z only, the overlap ceiling, the focus pairing and every label binding. test-verify-ridgeline.py proves each check in both polarities and pins the scope treaty with the sibling Line gates. The contract binds data-bins on a <path>, which no other chart in the repo declares, so verify-slopegraph and this gate never claim the same file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(verify-ridgeline): read exponent coordinates as numbers, not commands parse_d scanned for [A-Za-z] independently of the numbers, so the `e` in a valid scientific-notation coordinate such as 4.4e2 was counted as a path command. The explicit-command rule then rejected a correct ridgeline with a message about command counts. Tokenise the d attribute left to right with the number branch tried first, so an exponent is consumed as part of its number. A letter that survives that branch is a real command and still faces the M/L/Z vocabulary check; anything else leaves the path unreadable, which stays a finding. Adds both polarities: a shipped coordinate rewritten as 4.4e2 must verify, and a falsified one written the same way must not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: 0xDarkMatter <0xDarkMatter@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
105 lines
10 KiB
HTML
105 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Request latency by service · Ridgeline</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<style>
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
:root {
|
||
--color-paper: #2d3142;
|
||
--color-ink: #f5f5f5;
|
||
--color-muted: #bfc0c0;
|
||
--color-accent: #f08a59;
|
||
--font-sans: 'Geist', system-ui, sans-serif;
|
||
--font-serif: 'Instrument Serif', serif;
|
||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||
}
|
||
body { font-family: var(--font-sans); background: var(--color-paper); color: var(--color-ink); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; }
|
||
.frame { max-width: 1200px; width: 100%; }
|
||
.eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||
h1 { font-family: var(--font-serif); font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1.5rem; }
|
||
svg { width: 100%; min-width: 760px; display: block; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="frame">
|
||
<p class="eyebrow">Ridgeline · Diagram Design</p>
|
||
<h1>Checkout is slow in two different ways</h1>
|
||
|
||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ridgeline-dark-title ridgeline-dark-desc">
|
||
<title id="ridgeline-dark-title">Checkout is slow in two different ways</title>
|
||
<desc id="ridgeline-dark-desc">Ridgeline chart of request-latency distributions for five services over one week; each ridge is the share of that service's requests falling in 40-millisecond bins, drawn on one shared amplitude scale, and checkout-api carries a second peak past 300 milliseconds that none of the other four show.</desc>
|
||
<defs>
|
||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
|
||
</pattern>
|
||
</defs>
|
||
|
||
<rect width="100%" height="100%" fill="#2d3142"/>
|
||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||
|
||
<!-- ONE amplitude on every ridge: y = baseline - 2.4 x (share in %). Per-ridge
|
||
normalisation would let a rare, flat distribution wear the same silhouette
|
||
as a tight one, which is the single lie this type is built to tell.
|
||
scripts/verify-ridgeline.py derives the amplitude from the figure and then
|
||
holds every vertex of every ridge to it. -->
|
||
<text transform="rotate(-90 24 264)" x="24" y="264" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SHARE OF REQUESTS · 1% = 2.4PX ON EVERY RIDGE</text>
|
||
|
||
<!-- One baseline per ridge, 56px pitch. The baseline is where a bin of zero
|
||
requests sits, so it can never be moved to give a ridge more room. -->
|
||
<line data-ridge="static-cdn" data-role="baseline" x1="320" y1="152" x2="680" y2="152" stroke="rgba(245,245,245,0.25)" stroke-width="1"/>
|
||
<line data-ridge="auth-api" data-role="baseline" x1="320" y1="208" x2="680" y2="208" stroke="rgba(245,245,245,0.25)" stroke-width="1"/>
|
||
<line data-ridge="search-api" data-role="baseline" x1="320" y1="264" x2="680" y2="264" stroke="rgba(245,245,245,0.25)" stroke-width="1"/>
|
||
<line data-ridge="checkout-api" data-role="baseline" x1="320" y1="320" x2="680" y2="320" stroke="rgba(245,245,245,0.25)" stroke-width="1"/>
|
||
<line data-ridge="report-export" data-role="baseline" x1="320" y1="376" x2="680" y2="376" stroke="rgba(245,245,245,0.25)" stroke-width="1"/>
|
||
|
||
<!-- Shared x-scale: every ridge is sampled at the same 13 bins, 40ms wide,
|
||
labelled at the lower edge. Straight segments between bins - no smoothing,
|
||
because a spline through 13 bins invents extrema the sample never had. -->
|
||
<path data-ridge="static-cdn" data-baseline="152" data-bins="0,26,34,21,11,5,2,1,0,0,0,0,0" d="M320,152 L350,89.6 L380,70.4 L410,101.6 L440,125.6 L470,140 L500,147.2 L530,149.6 L560,152 L590,152 L620,152 L650,152 L680,152 Z" fill="rgba(245,245,245,0.12)" stroke="rgba(245,245,245,0.80)" stroke-width="1.2" stroke-linejoin="round"/>
|
||
<text data-ridge="static-cdn" data-role="name" x="304" y="155.5" fill="#f5f5f5" font-size="11" font-weight="500" font-family="'Geist', sans-serif" text-anchor="end">static-cdn</text>
|
||
<text data-ridge="static-cdn" data-role="range" x="696" y="155.5" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace">40–280 ms</text>
|
||
<path data-ridge="auth-api" data-baseline="208" data-bins="0,6,28,36,18,7,3,1,1,0,0,0,0" d="M320,208 L350,193.6 L380,140.8 L410,121.6 L440,164.8 L470,191.2 L500,200.8 L530,205.6 L560,205.6 L590,208 L620,208 L650,208 L680,208 Z" fill="rgba(245,245,245,0.12)" stroke="rgba(245,245,245,0.74)" stroke-width="1.2" stroke-linejoin="round"/>
|
||
<text data-ridge="auth-api" data-role="name" x="304" y="211.5" fill="#f5f5f5" font-size="11" font-weight="500" font-family="'Geist', sans-serif" text-anchor="end">auth-api</text>
|
||
<text data-ridge="auth-api" data-role="range" x="696" y="211.5" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace">40–320 ms</text>
|
||
<path data-ridge="search-api" data-baseline="264" data-bins="0,1,9,22,27,19,11,6,3,1,1,0,0" d="M320,264 L350,261.6 L380,242.4 L410,211.2 L440,199.2 L470,218.4 L500,237.6 L530,249.6 L560,256.8 L590,261.6 L620,261.6 L650,264 L680,264 Z" fill="rgba(245,245,245,0.12)" stroke="rgba(245,245,245,0.68)" stroke-width="1.2" stroke-linejoin="round"/>
|
||
<text data-ridge="search-api" data-role="name" x="304" y="267.5" fill="#f5f5f5" font-size="11" font-weight="500" font-family="'Geist', sans-serif" text-anchor="end">search-api</text>
|
||
<text data-ridge="search-api" data-role="range" x="696" y="267.5" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace">40–400 ms</text>
|
||
|
||
<!-- checkout-api FOCAL - and it is the ridge whose SHAPE is the story,
|
||
not the slowest service. Drawn last so its second peak reads over the
|
||
ridge below it rather than under. -->
|
||
<path data-ridge="checkout-api" data-baseline="320" data-bins="0,1,6,17,21,14,8,6,7,9,7,4,0" d="M320,320 L350,317.6 L380,305.6 L410,279.2 L440,269.6 L470,286.4 L500,300.8 L530,305.6 L560,303.2 L590,298.4 L620,303.2 L650,310.4 L680,320 Z" fill="rgba(240,138,89,0.16)" stroke="#f08a59" stroke-width="2.4" stroke-linejoin="round"/>
|
||
<text data-ridge="checkout-api" data-role="name" x="304" y="323.5" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="end">checkout-api</text>
|
||
<text data-ridge="checkout-api" data-role="range" x="696" y="323.5" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace">40–440 ms</text>
|
||
<path data-ridge="report-export" data-baseline="376" data-bins="0,0,1,3,7,12,17,19,16,12,9,4,0" d="M320,376 L350,376 L380,373.6 L410,368.8 L440,359.2 L470,347.2 L500,335.2 L530,330.4 L560,337.6 L590,347.2 L620,354.4 L650,366.4 L680,376 Z" fill="rgba(245,245,245,0.12)" stroke="rgba(245,245,245,0.62)" stroke-width="1.2" stroke-linejoin="round"/>
|
||
<text data-ridge="report-export" data-role="name" x="304" y="379.5" fill="#f5f5f5" font-size="11" font-weight="500" font-family="'Geist', sans-serif" text-anchor="end">report-export</text>
|
||
<text data-ridge="report-export" data-role="range" x="696" y="379.5" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace">80–440 ms</text>
|
||
|
||
<!-- Bin ticks on the shared latency scale. data-bin binds the printed number,
|
||
so a caption cannot be edited without its binding. -->
|
||
<text data-tick="0" data-bin="0" x="320" y="400" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">0</text>
|
||
<text data-tick="1" data-bin="120" x="410" y="400" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">120</text>
|
||
<text data-tick="2" data-bin="240" x="500" y="400" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">240</text>
|
||
<text data-tick="3" data-bin="360" x="590" y="400" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">360</text>
|
||
<text data-tick="4" data-bin="480" x="680" y="400" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">480</text>
|
||
<text x="500" y="424" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.18em" text-anchor="middle">REQUEST LATENCY (MS), BIN LOWER EDGE</text>
|
||
|
||
<!-- Legend -->
|
||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||
<text x="40" y="478" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||
<text x="960" y="478" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.06em" text-anchor="end">SHARE OF EACH SERVICE'S OWN REQUESTS PER 40 MS BIN · 1% = 2.4PX ON EVERY RIDGE · UNSMOOTHED · ILLUSTRATIVE FIGURES</text>
|
||
|
||
<rect x="40" y="486" width="24" height="10" fill="rgba(240,138,89,0.16)"/>
|
||
<line x1="40" y1="486" x2="64" y2="486" stroke="#f08a59" stroke-width="2.4"/>
|
||
<text x="72" y="496" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">checkout-api · focal, second peak past 300 ms</text>
|
||
<rect x="360" y="486" width="24" height="10" fill="rgba(245,245,245,0.12)"/>
|
||
<line x1="360" y1="486" x2="384" y2="486" stroke="rgba(245,245,245,0.80)" stroke-width="1.2"/>
|
||
<text x="392" y="496" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Other services · strongest tone is the fastest median</text>
|
||
</svg>
|
||
</div>
|
||
</body>
|
||
</html>
|