Agentic code review is becoming an essential piece of how development happens. It helps you inspect pull requests, catch issues, and decide what deserves attention before code ships.
But the quality of existing AI reviewers can be hard to measure, and you need to know the strengths of a reviewer before you know if it will help you. Some reviewers surface more issues, some produce less noise, and some are stronger at catching critical problems while others surface smaller improvements, too. You may need code review to do different things within your workflow.
That makes it important to understand how reviewers actually compare: what different systems catch, what they miss, and the tradeoffs they make. A good code review benchmark should reflect the diversity of real pull requests, capture a broad set of review findings, and support meaningful breakdowns by severity, category, and precision-recall preferences. For teams building code review agents, the benchmark should also provide an offline signal that reliably tracks whether changes are likely to improve the experience in production. Existing benchmarks often make tradeoffs between label quality, coverage, and how well they represent real-world code review, leaving a gap for a rigorous and reproducible evaluation methodology that brings these pieces together.
We built ReviewBench, a new code review offline benchmark, to address that gap, and it is available for you to use today. It follows the language, repo size, and size distribution of pull requests, modeled after over 100 million real pull requests on GitHub. It uses a multi-source golden set and a consistent evaluation rubric and has been independently validated by senior engineers. Just as important, with the help of ReviewBench, our offline evaluation of Copilot code review (CCR) has become more effective at anticipating the direction of production experiments, giving us greater confidence that measured improvements reflect meaningful gains for users.
In this post, we’ll walk through how ReviewBench is constructed, how it establishes reliable ground truth and scoring, and how to onboard your own code review system and submit results.
/*
Copyright 2022 The Mona Sans Project Authors (https://github.com/github/mona-sans), with Reserved Font Name “Mona”
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
———————————————————–
SIL OPEN FONT LICENSE Version 1.1 – 26 February 2007
———————————————————–
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
“Font Software” refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
“Reserved Font Name” refers to any names specified as such after the
copyright statement(s).
“Original Version” refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
“Modified Version” refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
“Author” refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
*/
/* Static fonts from github/mona-sans v2.0.27, SIL OFL 1.1. See assets/fonts/OFL.txt. */
@font-face {
font-family: “GH Diagram Sans”;
src: url(“data:font/woff2;base64,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”) format(“woff2”);
font-style: normal;
font-weight: 400;
font-display: block;
}
@font-face {
font-family: “GH Diagram Sans”;
src: url(“data:font/woff2;base64,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”) format(“woff2”);
font-style: normal;
font-weight: 500;
font-display: block;
}
@font-face {
font-family: “GH Diagram Mono”;
src: url(“data:font/woff2;base64,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”) format(“woff2”);
font-style: normal;
font-weight: 400;
font-display: block;
}
@font-face {
font-family: “GH Diagram Mono”;
src: url(“data:font/woff2;base64,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”) format(“woff2”);
font-style: normal;
font-weight: 500;
font-display: block;
}
.ghdiagram-html {
–ghd-text: #1f2328;
–ghd-muted: #5a615c;
–ghd-canvas: #f2f5f3;
–ghd-card: #ffffff;
–ghd-border: #c4ccc6;
–ghd-accent: #0fbf3e;
–ghd-connector: #0b862b;
–ghd-padding: 24px;
box-sizing: border-box;
width: 100%;
max-width: 699px;
margin: 0 auto;
color: var(–ghd-text);
font: 400 15px/22px “GH Diagram Sans”, sans-serif;
font-synthesis: none;
font-optical-sizing: none;
font-variation-settings: normal;
font-variant-ligatures: none;
letter-spacing: 0;
text-align: left;
-webkit-font-smoothing: antialiased;
}
.ghdiagram-html *,
.ghdiagram-html *::before,
.ghdiagram-html *::after { box-sizing: border-box; }
.ghdiagram-html [hidden] { display: none !important; }
.ghdiagram-html .ghdiagram-html__canvas {
padding: var(–ghd-padding);
background: var(–ghd-canvas);
}
.ghdiagram-html .ghdiagram-html__heading { display: grid; gap: 8px; margin-bottom: 28px; }
.ghdiagram-html .ghdiagram-html__title,
.ghdiagram-html .ghdiagram-html__node-title,
.ghdiagram-html .ghdiagram-html__section-title {
margin: 0;
padding: 0;
border: 0;
color: var(–ghd-text);
font: 500 18px/26px “GH Diagram Sans”, sans-serif;
font-variation-settings: normal;
letter-spacing: 0;
text-transform: none;
overflow-wrap: anywhere;
}
.ghdiagram-html .ghdiagram-html__title { font-size: 32px; line-height: 40px; }
.ghdiagram-html .ghdiagram-html__eyebrow {
margin: 0;
color: var(–ghd-muted);
/* Match the timeline charts’ eyebrow: Mona Sans (not Mono), tracked uppercase.
The bundled Sans tops out at 500, so use that rather than faux-bolding to 700. */
font-family: “GH Diagram Sans”, sans-serif;
font-size: clamp(12px, 1.5vw, 13px);
font-weight: 500;
line-height: 1.3;
letter-spacing: 0.12em;
text-transform: uppercase;
font-variation-settings: normal;
}
.ghdiagram-html .ghdiagram-html__viewport { overflow-x: auto; }
.ghdiagram-html .ghdiagram-html__plane { position: relative; display: grid; }
.ghdiagram-html .ghdiagram-html__row { display: grid; align-items: stretch; }
.ghdiagram-html .ghdiagram-html__row–flow {
grid-template-columns: repeat(auto-fit, minmax(min(100%, var(–ghd-flow-min-width, 180px)), 1fr));
}
.ghdiagram-html .ghdiagram-html__node {
min-width: 0;
position: relative;
z-index: 1;
padding: 24px;
border: 0;
border-radius: 8px;
outline: 1px solid var(–ghd-border);
outline-offset: -0.5px;
background: var(–ghd-card);
}
.ghdiagram-html .ghdiagram-html__content { display: grid; align-content: start; gap: 16px; min-width: 0; }
.ghdiagram-html .ghdiagram-html__node–annotation .ghdiagram-html__content { gap: 8px; }
.ghdiagram-html .ghdiagram-html__node–dashed { outline-style: dashed; }
.ghdiagram-html[data-ghd-state=”ready”] .ghdiagram-html__node–dashed { outline: none; }
.ghdiagram-html .ghdiagram-html__node–accent { –ghd-title-size: 20px; border-radius: 0 0 8px 8px; }
.ghdiagram-html .ghdiagram-html__node–accent::before {
content: “”;
position: absolute;
inset: 0 0 auto;
height: 3px;
border-radius: 1.5px;
background: var(–ghd-accent);
}
.ghdiagram-html .ghdiagram-html__node–accent .ghdiagram-html__node-title { font-size: 20px; }
.ghdiagram-html .ghdiagram-html__node-heading { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center; }
.ghdiagram-html .ghdiagram-html__node-heading–stat { display: grid; gap: 4px; }
.ghdiagram-html .ghdiagram-html__stat {
display: block;
color: var(–ghd-accent);
font-size: calc(var(–ghd-title-size, 18px) * 2);
font-weight: 500;
line-height: 1.2;
}
.ghdiagram-html .ghdiagram-html__body {
margin: 0;
color: var(–ghd-muted);
font: inherit;
white-space: pre-line;
overflow-wrap: anywhere;
}
.ghdiagram-html .ghdiagram-html__chips {
display: flex;
flex-wrap: wrap;
align-content: start;
gap: 8px;
margin: 0;
padding: 0;
list-style: none;
}
.ghdiagram-html .ghdiagram-html__chip,
.ghdiagram-html .ghdiagram-html__badge {
display: block;
max-width: 100%;
margin: 0;
padding: 5.5px 10px;
border: 0;
border-radius: 6px;
outline: 1px solid var(–ghd-border);
outline-offset: -0.5px;
background: var(–ghd-canvas);
color: var(–ghd-text);
font: 400 14px/20px “GH Diagram Mono”, monospace;
font-variation-settings: normal;
overflow-wrap: anywhere;
}
.ghdiagram-html .ghdiagram-html__chip–dashed { outline-style: dashed; }
.ghdiagram-html .ghdiagram-html__chip–icon {
display: grid;
grid-template-columns: var(–ghd-icon-size, 16px) minmax(0, 1fr);
align-items: start;
gap: 8px;
}
.ghdiagram-html .ghdiagram-html__chip–icon .ghdiagram-html__icon { color: inherit; }
.ghdiagram-html .ghdiagram-html__chip-text { min-width: 0; }
.ghdiagram-html .ghdiagram-html__chip–tone {
color: var(–ghd-tone-text);
background: var(–ghd-tone-bg);
outline-color: var(–ghd-tone-border);
}
.ghdiagram-html .ghdiagram-html__chip { margin: 0.5px 0; }
.ghdiagram-html .ghdiagram-html__badge { padding: 4px 8px; color: var(–ghd-muted); background: var(–ghd-card); }
.ghdiagram-html .ghdiagram-html__sections { display: grid; gap: 24px; min-width: 0; }
.ghdiagram-html .ghdiagram-html__sections–two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ghdiagram-html .ghdiagram-html__section { display: grid; align-content: start; gap: 4px; min-width: 0; }
.ghdiagram-html .ghdiagram-html__section-title { font-size: 16px; line-height: 22px; }
.ghdiagram-html[data-compact=”true”] .ghdiagram-html__sections–two { grid-template-columns: minmax(0, 1fr); }
.ghdiagram-html[data-compact=”true”] .ghdiagram-html__node { padding: 16px; }
.ghdiagram-html .ghdiagram-html__icon {
display: block;
width: var(–ghd-icon-size, 16px);
height: var(–ghd-icon-size, 16px);
min-width: var(–ghd-icon-size, 16px);
flex: 0 0 var(–ghd-icon-size, 16px);
color: var(–ghd-connector);
fill: currentColor;
}
.ghdiagram-html[data-flow=”true”] .ghdiagram-html__node { padding: 12px; }
.ghdiagram-html[data-flow=”true”] .ghdiagram-html__content { gap: 12px; }
.ghdiagram-html[data-flow=”true”] .ghdiagram-html__sections–two {
grid-template-columns: max-content minmax(0, 1fr);
gap: 8px;
}
.ghdiagram-html[data-flow=”true”][data-compact=”true”] .ghdiagram-html__sections–two {
grid-template-columns: minmax(0, 1fr);
}
.ghdiagram-html .ghdiagram-html__node.ghdiagram-html__node–section-heading,
.ghdiagram-html .ghdiagram-html__node.ghdiagram-html__node–caption {
padding: 0;
background: transparent;
outline: none;
}
.ghdiagram-html .ghdiagram-html__node–section-heading .ghdiagram-html__node-title { font-size: 22px; line-height: 30px; }
.ghdiagram-html .ghdiagram-html__node–section-heading .ghdiagram-html__content { gap: 8px; }
.ghdiagram-html .ghdiagram-html__node–numbered .ghdiagram-html__content {
grid-template-columns: 40px minmax(0, 1fr);
align-items: start;
gap: 12px;
}
.ghdiagram-html .ghdiagram-html__heading-copy {
display: grid;
gap: 2px;
min-width: 0;
padding-top: 5px;
}
.ghdiagram-html .ghdiagram-html__step {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 50%;
font: 500 24px/40px “GH Diagram Sans”, sans-serif;
}
.ghdiagram-html .ghdiagram-html__list {
display: grid;
gap: 4px;
list-style: none;
margin: 0;
padding: 0;
color: var(–ghd-muted);
font: 400 13px/20px “GH Diagram Sans”, sans-serif;
}
.ghdiagram-html .ghdiagram-html__list-item { display: grid; grid-template-columns: 6px minmax(0, 1fr); column-gap: 4px; }
.ghdiagram-html .ghdiagram-html__list-marker { width: 6px; height: 6px; margin-top: 7px; border-radius: 50%; background: var(–ghd-muted); }
.ghdiagram-html .ghdiagram-html__list-text { min-width: 0; overflow-wrap: anywhere; }
.ghdiagram-html .ghdiagram-html__node–divided .ghdiagram-html__section-title,
.ghdiagram-html .ghdiagram-html__node–listed .ghdiagram-html__section-title { font-size: 14px; line-height: 20px; }
.ghdiagram-html[data-flow=”true”] .ghdiagram-html__node–divided .ghdiagram-html__sections–two,
.ghdiagram-html[data-flow=”true”] .ghdiagram-html__node–listed .ghdiagram-html__sections–two {
grid-template-columns: max-content minmax(0, 1fr);
gap: 8px;
}
.ghdiagram-html .ghdiagram-html__node–divided .ghdiagram-html__sections–two > .ghdiagram-html__section + .ghdiagram-html__section {
border-inline-start: 1px solid var(–ghd-border);
padding-inline-start: 6px;
}
.ghdiagram-html .ghdiagram-html__node–caption .ghdiagram-html__node-heading { justify-content: center; gap: 16px; }
.ghdiagram-html .ghdiagram-html__node–caption .ghdiagram-html__node-title { font-size: 14px; line-height: 20px; color: var(–ghd-connector); }
.ghdiagram-html .ghdiagram-html__node–caption .ghdiagram-html__node-heading::before,
.ghdiagram-html .ghdiagram-html__node–caption .ghdiagram-html__node-heading::after {
content: “”;
height: 1px;
flex: 1;
background: var(–ghd-border);
}
.ghdiagram-html .ghdiagram-html__node–callout .ghdiagram-html__content {
grid-template-columns: var(–ghd-icon-size, 16px) minmax(0, 1fr) minmax(0, 1.4fr);
align-items: center;
gap: 16px;
}
.ghdiagram-html .ghdiagram-html__node–callout .ghdiagram-html__node-title { font-size: 22px; line-height: 30px; }
.ghdiagram-html[data-compact=”true”] .ghdiagram-html__node–callout .ghdiagram-html__content { grid-template-columns: var(–ghd-icon-size, 16px) minmax(0, 1fr); }
.ghdiagram-html[data-compact=”true”] .ghdiagram-html__node–callout .ghdiagram-html__body { grid-column: 2; }
.ghdiagram-html .ghdiagram-html__node–callout.ghdiagram-html__node–divided .ghdiagram-html__body {
border-inline-start: 1px solid var(–ghd-border);
padding-inline-start: 16px;
}
.ghdiagram-html[data-compact=”true”] .ghdiagram-html__node–callout.ghdiagram-html__node–divided .ghdiagram-html__body {
border-inline-start: 0;
border-block-start: 1px solid var(–ghd-border);
padding-inline-start: 0;
padding-block-start: 12px;
}
.ghdiagram-html[data-flow=”true”] .ghdiagram-html__node–annotation .ghdiagram-html__chips {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.ghdiagram-html[data-flow=”true”][data-compact=”true”] .ghdiagram-html__node–annotation .ghdiagram-html__chips {
grid-template-columns: minmax(0, 1fr);
}
.ghdiagram-html .ghdiagram-html__node–terminal { border-radius: 999px; }
.ghdiagram-html .ghdiagram-html__node–decision { display: grid; align-items: center; background: transparent; outline: none; padding: 24px 25%; }
.ghdiagram-html[data-compact=”true”] .ghdiagram-html__node–decision { padding: 24px 25%; }
.ghdiagram-html .ghdiagram-html__node–decision .ghdiagram-html__content { position: relative; }
.ghdiagram-html .ghdiagram-html__node–decision .ghdiagram-html__node-heading,
.ghdiagram-html .ghdiagram-html__node–terminal .ghdiagram-html__node-heading { justify-content: center; }
.ghdiagram-html .ghdiagram-html__node–decision .ghdiagram-html__node-title,
.ghdiagram-html .ghdiagram-html__node–terminal .ghdiagram-html__node-title { text-align: center; }
.ghdiagram-html .ghdiagram-html__diamond { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.ghdiagram-html .ghdiagram-html__diamond polygon { fill: var(–ghd-card); stroke: var(–ghd-border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.ghdiagram-html .ghdiagram-html__boundary { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.ghdiagram-html .ghdiagram-html__boundary rect { fill: none; stroke: var(–ghd-border); stroke-width: 1; stroke-dasharray: 6 4; }
.ghdiagram-html .ghdiagram-html__connections {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
overflow: visible;
pointer-events: none;
}
.ghdiagram-html .ghdiagram-html__connections path { fill: none; stroke: var(–ghd-connector); stroke-width: 1.75px; stroke-linecap: round; stroke-linejoin: round; }
.ghdiagram-html .ghdiagram-html__edge-label {
position: absolute;
z-index: 2;
padding: 4px 8px;
border-radius: 4px;
background: var(–ghd-card);
color: var(–ghd-muted);
font: 400 14px/20px “GH Diagram Mono”, monospace;
font-variation-settings: normal;
white-space: nowrap;
}
.ghdiagram-html .ghdiagram-html__error { margin: 12px 0; color: #cf222e; font: 400 14px/22px system-ui, sans-serif; }
.ghdiagram-html__viewport:focus-visible { outline: 2px solid #0969da; outline-offset: 4px; }
/* Shared semantic model/routing and legacy v1 scene support. Never part of a chart bundle. */
(function (global) {
“use strict”;
var VERSION = 10;
if (global.GHDiagramEngine && global.GHDiagramEngine.version >= VERSION) return;
var NS = “http://www.w3.org/2000/svg”;
var FAMILY = “GH Diagram Mona Sans”;
var FONTS = [
{ family: “Mona Sans”, style: “Regular”, weight: 400 },
{ family: “Mona Sans”, style: “SemiBold”, weight: 600 },
{ family: “Mona Sans”, style: “Bold”, weight: 700 }
];
var C = { text: “#1f2328”, muted: “#59636e”, border: “#c4ccc6”, inset: “#f2f5f3”, white: “#ffffff” };
var TONES = {
danger: { foreground: “#cf222e”, marker: “#cf222e”, background: “#ffebe9”, border: “#ffcecb” },
warning: { foreground: “#9a6700”, marker: “#bf8700”, background: “#fff8c5”, border: “#eed888” },
success: { foreground: “#1a7f37”, marker: “#1a7f37”, background: “#dafbe1”, border: “#aceebb” }
};
// Unmodified 16px paths from primer/octicons, pinned below. All use nonzero fill.
var OCTICONS = {
“arrow-up-right”: [“M4.53 4.75A.75.75 0 0 1 5.28 4h6.01a.75.75 0 0 1 .75.75v6.01a.75.75 0 0 1-1.5 0v-4.2l-5.26 5.261a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L9.48 5.5h-4.2a.75.75 0 0 1-.75-.75Z”],
“arrow-down-right”: [“M4.22 4.179a.75.75 0 0 1 1.06 0l5.26 5.26v-4.2a.75.75 0 0 1 1.5 0v6.01a.75.75 0 0 1-.75.75H5.28a.75.75 0 0 1 0-1.5h4.2L4.22 5.24a.75.75 0 0 1 0-1.06Z”],
“mark-github”: [“M6.766 11.328c-2.063-.25-3.516-1.734-3.516-3.656 0-.781.281-1.625.75-2.188-.203-.515-.172-1.609.063-2.062.625-.078 1.468.25 1.968.703.594-.187 1.219-.281 1.985-.281.765 0 1.39.094 1.953.265.484-.437 1.344-.765 1.969-.687.218.422.25 1.515.046 2.047.5.593.766 1.39.766 2.203 0 1.922-1.453 3.375-3.547 3.64.531.344.89 1.094.89 1.954v1.625c0 .468.391.734.86.547C13.781 14.359 16 11.53 16 8.03 16 3.61 12.406 0 7.984 0 3.563 0 0 3.61 0 8.031a7.88 7.88 0 0 0 5.172 7.422c.422.156.828-.125.828-.547v-1.25c-.219.094-.5.156-.75.156-1.031 0-1.64-.562-2.078-1.609-.172-.422-.36-.672-.719-.719-.187-.015-.25-.093-.25-.187 0-.188.313-.328.625-.328.453 0 .844.281 1.25.86.313.452.64.655 1.031.655s.641-.14 1-.5c.266-.265.47-.5.657-.656”],
“git-pull-request”: [“M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z”],
“database”: [“M1 3.5c0-.626.292-1.165.7-1.59.406-.422.956-.767 1.579-1.041C4.525.32 6.195 0 8 0c1.805 0 3.475.32 4.722.869.622.274 1.172.62 1.578 1.04.408.426.7.965.7 1.591v9c0 .626-.292 1.165-.7 1.59-.406.422-.956.767-1.579 1.041C11.476 15.68 9.806 16 8 16c-1.805 0-3.475-.32-4.721-.869-.623-.274-1.173-.62-1.579-1.04-.408-.426-.7-.965-.7-1.591Zm1.5 0c0 .133.058.318.282.551.227.237.591.483 1.101.707C4.898 5.205 6.353 5.5 8 5.5c1.646 0 3.101-.295 4.118-.742.508-.224.873-.471 1.1-.708.224-.232.282-.417.282-.55 0-.133-.058-.318-.282-.551-.227-.237-.591-.483-1.101-.707C11.102 1.795 9.647 1.5 8 1.5c-1.646 0-3.101.295-4.118.742-.508.224-.873.471-1.1.708-.224.232-.282.417-.282.55Zm0 4.5c0 .133.058.318.282.551.227.237.591.483 1.101.707C4.898 9.705 6.353 10 8 10c1.646 0 3.101-.295 4.118-.742.508-.224.873-.471 1.1-.708.224-.232.282-.417.282-.55V5.724c-.241.15-.503.286-.778.407C11.475 6.68 9.805 7 8 7c-1.805 0-3.475-.32-4.721-.869a6.15 6.15 0 0 1-.779-.407Zm0 2.225V12.5c0 .133.058.318.282.55.227.237.592.484 1.1.708 1.016.447 2.471.742 4.118.742 1.647 0 3.102-.295 4.117-.742.51-.224.874-.47 1.101-.707.224-.233.282-.418.282-.551v-2.275c-.241.15-.503.285-.778.406-1.247.549-2.917.869-4.722.869-1.805 0-3.475-.32-4.721-.869a6.327 6.327 0 0 1-.779-.406Z”],
“checklist”: [“M2.5 1.75v11.5c0 .138.112.25.25.25h3.17a.75.75 0 0 1 0 1.5H2.75A1.75 1.75 0 0 1 1 13.25V1.75C1 .784 1.784 0 2.75 0h8.5C12.216 0 13 .784 13 1.75v7.736a.75.75 0 0 1-1.5 0V1.75a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13.274 9.537v-.001l-4.557 4.45a.75.75 0 0 1-1.055-.008l-1.943-1.95a.75.75 0 0 1 1.062-1.058l1.419 1.425 4.026-3.932a.75.75 0 1 1 1.048 1.074ZM4.75 4h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM4 7.75A.75.75 0 0 1 4.75 7h2a.75.75 0 0 1 0 1.5h-2A.75.75 0 0 1 4 7.75Z”],
“graph”: [“M1.5 1.75V13.5h13.75a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75V1.75a.75.75 0 0 1 1.5 0Zm14.28 2.53-5.25 5.25a.75.75 0 0 1-1.06 0L7 7.06 4.28 9.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.25-3.25a.75.75 0 0 1 1.06 0L10 7.94l4.72-4.72a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z”],
“goal”: [
“M13.637 2.363h-.001l1.676.335c.09.018.164.084.19.173a.25.25 0 0 1-.062.249l-1.373 1.374a.876.876 0 0 1-.619.256H12.31L9.45 7.611A1.5 1.5 0 1 1 6.5 8a1.501 1.501 0 0 1 1.889-1.449l2.861-2.862V2.552c0-.232.092-.455.256-.619L12.88.559a.25.25 0 0 1 .249-.062c.089.026.155.1.173.19Z”,
“M2 8a6 6 0 1 0 11.769-1.656.751.751 0 1 1 1.442-.413 7.502 7.502 0 0 1-12.513 7.371A7.501 7.501 0 0 1 10.069.789a.75.75 0 0 1-.413 1.442A6.001 6.001 0 0 0 2 8Z”,
“M5 8a3.002 3.002 0 0 0 4.699 2.476 3 3 0 0 0 1.28-2.827.748.748 0 0 1 1.045-.782.75.75 0 0 1 .445.61A4.5 4.5 0 1 1 8.516 3.53a.75.75 0 1 1-.17 1.49A3 3 0 0 0 5 8Z”
],
“people”: [“M2 5.5a3.5 3.5 0 1 1 5.898 2.549 5.508 5.508 0 0 1 3.034 4.084.75.75 0 1 1-1.482.235 4 4 0 0 0-7.9 0 .75.75 0 0 1-1.482-.236A5.507 5.507 0 0 1 3.102 8.05 3.493 3.493 0 0 1 2 5.5ZM11 4a3.001 3.001 0 0 1 2.22 5.018 5.01 5.01 0 0 1 2.56 3.012.749.749 0 0 1-.885.954.752.752 0 0 1-.549-.514 3.507 3.507 0 0 0-2.522-2.372.75.75 0 0 1-.574-.73v-.352a.75.75 0 0 1 .416-.672A1.5 1.5 0 0 0 11 5.5.75.75 0 0 1 11 4Zm-5.5-.5a2 2 0 1 0-.001 3.999A2 2 0 0 0 5.5 3.5Z”],
“gear”: [“M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z”],
“list-unordered”: [“M5.75 2.5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5ZM2 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-6a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM2 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z”],
“check-circle”: [“M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm1.5 0a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm10.28-1.72-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l1.47 1.47 3.97-3.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z”],
“pulse”: [“M6 2c.306 0 .582.187.696.471L10 10.731l1.304-3.26A.751.751 0 0 1 12 7h3.25a.75.75 0 0 1 0 1.5h-2.742l-1.812 4.528a.751.751 0 0 1-1.392 0L6 4.77 4.696 8.03A.75.75 0 0 1 4 8.5H.75a.75.75 0 0 1 0-1.5h2.742l1.812-4.529A.751.751 0 0 1 6 2Z”]
};
var OCTICON_SOURCE = “https://github.com/primer/octicons/tree/90af1f14984832de34e94b2d530043fbcf85eb7f”;
var OCTICON_LICENSE = [
“MIT License”, “”, “Copyright (c) 2026 GitHub Inc.”, “”,
‘Permission is hereby granted, free of charge, to any person obtaining a copy’,
‘of this software and associated documentation files (the “Software”), to deal’,
“in the Software without restriction, including without limitation the rights”,
“to use, copy, modify, merge, publish, distribute, sublicense, and/or sell”,
“copies of the Software, and to permit persons to whom the Software is”,
“furnished to do so, subject to the following conditions:”, “”,
“The above copyright notice and this permission notice shall be included in all”,
“copies or substantial portions of the Software.”, “”,
‘THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR’,
“IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,”,
“FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE”,
“AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER”,
“LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,”,
“OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE”,
“SOFTWARE.”
].join(“n”);
function icon(name) {
requireThat(Object.prototype.hasOwnProperty.call(OCTICONS, name), “unsupported 16px Octicon: ” + name);
return { name: name, width: 16, height: 16, paths: OCTICONS[name].map(function (data) {
return { data: data, windingRule: “NONZERO” };
}) };
}
function iconAssets(config) {
var icons = {};
config.nodes.forEach(function (node) {
if (node.icon) icons[node.icon] = icon(node.icon);
(node.chips || []).forEach(function (chip) { if (chip.icon) icons[chip.icon] = icon(chip.icon); });
});
return { source: OCTICON_SOURCE, size: 16, license: OCTICON_LICENSE, icons: icons };
}
function tone(name) {
requireThat(Object.prototype.hasOwnProperty.call(TONES, name), “unsupported semantic tone: ” + name);
return Object.assign({}, TONES[name]);
}
function textOn(background) {
color(background, “step color”);
return contrast(C.white, background) >= 3 ? C.white : C.text;
}
function requireThat(condition, message) {
if (!condition) throw new Error(“Diagram: ” + message);
}
function number(value, label, minimum) {
requireThat(typeof value === “number” && Number.isFinite(value) && value >= minimum,
label + ” must be a finite number >= ” + minimum + “.”);
}
function text(value, label, allowEmpty) {
requireThat(typeof value === “string” && (allowEmpty || value.trim().length > 0), label + ” must be text.”);
}
function slug(value, label) {
requireThat(typeof value === “string” && /^[a-z][a-z0-9-]*$/.test(value), label + ” must be a lowercase slug.”);
}
function color(value, label) {
requireThat(typeof value === “string” && /^#[0-9a-f]{6}$/i.test(value), label + ” must be a six-digit hex color.”);
}
function flag(object, key) {
requireThat(object[key] === undefined || typeof object[key] === “boolean”, key + ” must be Boolean.”);
}
function unique(ids, id, label) {
slug(id, label);
requireThat(!ids.has(id), “duplicate ” + label + “: ” + id);
ids.add(id);
}
function rgb(hex) {
return [1, 3, 5].map(function (i) { return parseInt(hex.slice(i, i + 2), 16); });
}
// The same 30%-black emphasis step used by blog-chart-design’s strongOf().
function strongOf(hex) {
return “#” + rgb(hex).map(function (v) { return Math.round(v * 0.7).toString(16).padStart(2, “0”); }).join(“”);
}
function luminance(hex) {
var v = rgb(hex).map(function (c) {
c /= 255;
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return v[0] * 0.2126 + v[1] * 0.7152 + v[2] * 0.0722;
}
function contrast(a, b) {
var x = luminance(a), y = luminance(b);
return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
}
function connectionGeometry(edge, boxes, yOffset) {
function port(endpoint) {
var n = boxes[endpoint.node], f = endpoint.offset === undefined ? 0.5 : endpoint.offset;
requireThat(n, "missing endpoint bounds on " + edge.id);
var p = { x: n.x, y: n.y }, normal;
if (endpoint.side === "top" || endpoint.side === "bottom") {
p.x += n.width * f;
if (endpoint.side === "bottom") p.y += n.height;
normal = { x: 0, y: endpoint.side === "top" ? -1 : 1 };
} else {
p.y += n.height * f;
if (endpoint.side === "right") p.x += n.width;
normal = { x: endpoint.side === "left" ? -1 : 1, y: 0 };
}
return { point: p, normal: normal };
}
var from = port(edge.from), to = port(edge.to);
var a = from.point, b = to.point;
// Opposed straight connectors can drift a few px apart when a fixed
// fractional port offset was tuned for one width but is viewed at another
// (the fixed layout padding is a larger share of a narrow plane, so a
// bottom-centre port and a top-offset port no longer share an axis). Left
// alone the orthogonal router inserts a tiny mid-line "jog" that reads as a
// broken arrow. When the perpendicular gap is sub-threshold and there are no
// explicit waypoints, snap both endpoints onto one axis so the shaft stays a
// single straight segment with its arrowheads still attached.
var SNAP = 12;
if (!(edge.via && edge.via.length)) {
if (from.normal.y && to.normal.y && Math.abs(a.x – b.x) <= SNAP) {
a.x = b.x = (a.x + b.x) / 2;
} else if (from.normal.x && to.normal.x && Math.abs(a.y – b.y) = 3,
“choose a darker accent; its emphasis step needs 3:1 contrast for essential connectors.”);
requireThat([1, 2, 3].indexOf(config.exportScale) !== -1, “exportScale must be 1, 2, or 3.”);
var layout = config.layout;
requireThat(layout && typeof layout === “object”, “layout is required.”);
number(layout.width, “layout.width”, 320);
number(layout.padding, “layout.padding”, 16);
number(layout.rowGap, “layout.rowGap”, 32);
number(layout.columnGap, “layout.columnGap”, 24);
flag(layout, “flow”);
requireThat(!layout.flow || Array.isArray(layout.rows), “flow layout requires rows.”);
if (layout.iconSize !== undefined) requireThat([16, 24, 32].indexOf(layout.iconSize) !== -1, “iconSize must be 16, 24, or 32.”);
if (layout.minCardWidth !== undefined) {
requireThat(layout.flow, “minCardWidth requires flow layout.”);
number(layout.minCardWidth, “minCardWidth”, 180);
}
requireThat(layout.width > layout.padding * 2 + 100, “layout is too narrow for its padding.”);
requireThat(Array.isArray(config.nodes) && config.nodes.length > 0, “nodes must be a nonempty array.”);
requireThat(Array.isArray(config.edges), “edges must be an array.”);
var ids = new Set();
var byId = Object.create(null);
config.nodes.forEach(function (node) {
requireThat(node && typeof node === “object”, “each node must be an object.”);
unique(ids, node.id, “node id”);
requireThat(node.id !== “header”, ‘”header” is reserved for the diagram heading.’);
byId[node.id] = node;
text(node.title, node.id + ” title”);
requireThat([“card”, “annotation”, “decision”, “terminal”].indexOf(node.shape) !== -1, “unknown shape on ” + node.id);
[“body”, “badge”].forEach(function (key) {
if (node[key] !== undefined) text(node[key], node.id + ” ” + key);
});
[“dashed”, “accent”, “dividers”].forEach(function (key) { flag(node, key); });
requireThat(!node.dividers || (node.sections && node.sections.length === 2 && node.columns === 2) || node.variant === “callout”,
“dividers require exactly two section columns or a callout.”);
if (node.stat !== undefined) {
text(node.stat, “stat”);
requireThat(node.shape === “card” && !node.badge && !node.variant, “stats require an unbadged card.”);
requireThat(contrast(config.theme.accent, C.white) >= 3, “choose a darker accent for the large stat text.”);
}
if (node.step !== undefined) {
requireThat(node.variant === “section-heading” && !node.icon, “steps require a section heading without an icon.”);
requireThat(node.step && typeof node.step === “object” && /^[1-9][0-9]?$/.test(node.step.text) &&
typeof node.step.text === “string”, “step text must be a section number from 1 to 99.”);
if (node.step.color !== undefined) color(node.step.color, “step color”);
}
if (node.icon !== undefined) { slug(node.icon, “icon”); icon(node.icon); }
if (node.variant !== undefined) {
requireThat(node.shape === “annotation” && [“section-heading”, “caption”, “callout”].indexOf(node.variant) !== -1,
“unsupported annotation variant on ” + node.id);
requireThat(!node.chips && !node.sections && !node.badge && !node.accent,
“annotation variants use title, body and icon only.”);
if (node.variant === “callout”) requireThat(node.icon && node.body, “callout requires an icon and body.”);
}
if (node.minHeight !== undefined) number(node.minHeight, “minHeight”, 0);
if (node.columns !== undefined) requireThat([1, 2].indexOf(node.columns) !== -1, “columns must be 1 or 2.”);
if (node.chips !== undefined) {
requireThat(Array.isArray(node.chips), “chips must be an array.”);
node.chips.forEach(function (chip) {
requireThat(chip && typeof chip === “object”, “each chip must be an object.”);
text(chip.text, “chip text”);
flag(chip, “dashed”);
if (chip.icon !== undefined) { slug(chip.icon, “chip icon”); icon(chip.icon); }
if (chip.tone !== undefined) tone(chip.tone);
});
}
if (node.sections !== undefined) {
requireThat(Array.isArray(node.sections), “sections must be an array.”);
var sections = new Set();
node.sections.forEach(function (section) {
requireThat(section && typeof section === “object”, “each section must be an object.”);
unique(sections, section.id, “section id”);
text(section.title, “section title”);
requireThat((section.body !== undefined) !== (section.items !== undefined), “sections require body OR items.”);
if (section.body !== undefined) text(section.body, “section body”);
if (section.items !== undefined) {
requireThat(Array.isArray(section.items) && section.items.length > 0, “section items must be a nonempty array.”);
section.items.forEach(function (item) {
requireThat(item && typeof item === “object”, “list items must be objects.”);
text(item.text, “list item text”);
if (item.tone !== undefined) tone(item.tone);
});
}
});
}
if (node.shape === “decision” || node.shape === “terminal”) {
requireThat(!node.chips && !node.sections && !node.badge && !node.accent && !node.icon && !node.variant,
node.shape + ” nodes support title/body only.”);
}
});
requireThat(Array.isArray(layout.rows) !== Array.isArray(layout.placements), “choose rows OR placements.”);
var placed = new Set();
function place(id) {
requireThat(byId[id] && !placed.has(id), “unknown or repeated placement: ” + id);
placed.add(id);
}
if (layout.rows) {
layout.rows.forEach(function (row) {
requireThat(Array.isArray(row) && row.length > 0, “each layout row must contain node IDs.”);
row.forEach(place);
});
} else {
layout.placements.forEach(function (p) {
requireThat(p && typeof p === “object”, “invalid placement.”);
place(p.node);
number(p.x, “placement.x”, layout.padding);
number(p.y, “placement.y”, 0);
number(p.width, “placement.width”, 100);
requireThat(p.x + p.width <= layout.width – layout.padding, "placement exceeds the artboard.");
});
}
requireThat(placed.size === ids.size, "every node must appear exactly once in the layout.");
var edgeIds = new Set();
var flowOrder = layout.flow ? layout.rows.reduce(function (all, row) { return all.concat(row); }, []) : [];
config.edges.forEach(function (edge) {
requireThat(edge && typeof edge === "object", "each edge must be an object.");
unique(edgeIds, edge.id, "edge id");
["from", "to"].forEach(function (key) {
var port = edge[key];
requireThat(port && byId[port.node], "dangling endpoint on " + edge.id);
requireThat(["top", "right", "bottom", "left"].indexOf(port.side) !== -1, "invalid port side on " + edge.id);
if (port.offset !== undefined) {
number(port.offset, "port offset", 0);
requireThat(port.offset <= 1, "port offset exceeds 1.");
if (["decision", "terminal"].indexOf(byId[port.node].shape) !== -1) {
requireThat(port.offset === 0.5, "decision/terminal ports must be centered.");
}
}
});
requireThat(edge.from.node !== edge.to.node, "self connections require a separate explicit annotation.");
requireThat(["forward", "both", "none"].indexOf(edge.direction) !== -1, "invalid edge direction.");
flag(edge, "dashed");
if (layout.flow) {
requireThat(flowOrder.indexOf(edge.to.node) === flowOrder.indexOf(edge.from.node) + 1,
"flow connections must follow adjacent nodes in reading order.");
requireThat(edge.via === undefined && edge.from.offset === undefined && edge.to.offset === undefined,
"flow connections use measured centered ports, not fixed waypoints or offsets.");
}
if (edge.label !== undefined) text(edge.label, "edge label");
if (edge.via !== undefined) {
requireThat(Array.isArray(edge.via), "via must be an array.");
edge.via.forEach(function (p) {
requireThat(p && typeof p === "object", "invalid waypoint.");
number(p.x, "waypoint.x", 0);
number(p.y, "waypoint.y", 0);
});
}
});
}
function flowGeometry(edge, boxes, yOffset) {
var from = boxes[edge.from.node], to = boxes[edge.to.node];
requireThat(from && to, "missing flow endpoint bounds on " + edge.id);
var sameRow = Math.abs(from.y – to.y) = from.x + from.width : to.y >= from.y + from.height,
edge.id + ” does not follow the flow’s visual reading order.”);
return connectionGeometry(Object.assign({}, edge, {
from: { node: edge.from.node, side: sameRow ? “right” : “bottom” },
to: { node: edge.to.node, side: sameRow ? “left” : “top” }
}), boxes, yOffset);
}
function loadFonts() {
requireThat(document.fonts && document.fonts.load, “this browser cannot load the required fonts.”);
return Promise.all(FONTS.map(function (font) {
return document.fonts.load(font.weight + ‘ 16px “‘ + FAMILY + ‘”‘).then(function (faces) {
requireThat(faces.some(function (face) {
return face.status === “loaded” && face.weight === String(font.weight) &&
face.family.replace(/[“‘]/g, “”) === FAMILY;
}),
“Mona Sans ” + font.style + ” is unavailable. Serve the bundled diagram.css and assets/fonts/ files.”);
}, function (error) {
throw new Error(“Diagram: could not load Mona Sans ” + font.style +
“. Check the bundled assets/fonts/ files. ” + error.message);
});
}));
}
function makeScene(config) {
var ctx = document.createElement(“canvas”).getContext(“2d”);
requireThat(ctx, “text measurement is unavailable.”);
var accent = config.theme.accent;
var emphasis = strongOf(accent);
function widthOf(value, size, weight) {
ctx.font = weight + ” ” + size + ‘px “‘ + FAMILY + ‘”‘;
if (“fontKerning” in ctx) ctx.fontKerning = “normal”;
return ctx.measureText(value).width;
}
function wrap(value, width, size, weight) {
requireThat(width > 0, “text column has no available width.”);
var lines = [];
value.split(“n”).forEach(function (paragraph) {
var words = paragraph.trim().split(/s+/);
var line = “”;
words.forEach(function (word) {
requireThat(widthOf(word, size, weight) width) {
lines.push(line);
line = word;
} else line = next;
});
lines.push(line);
});
return lines.join(“n”);
}
function label(id, value, width, size, weight, lineHeight, fill, align) {
var wrapped = wrap(value, width, size, weight);
return {
id: id, name: value, type: “text”, x: 0, y: 0,
width: width, height: wrapped.split(“n”).length * lineHeight,
text: wrapped, sourceText: value, fontFamily: “Mona Sans”,
fontStyle: weight === 700 ? “Bold” : weight === 600 ? “SemiBold” : “Regular”,
fontWeight: weight, fontSize: size, lineHeight: lineHeight,
color: fill || C.text, align: align || “left”
};
}
function group(id, name, width, height, children, layout) {
var g = { id: id, name: name, type: “group”, x: 0, y: 0, width: width, height: height, children: children };
if (layout) g.layout = layout;
return g;
}
function stack(id, name, width, children, gap) {
var y = 0;
children.forEach(function (child, i) {
child.y = y;
y += child.height + (i < children.length – 1 ? gap : 0);
});
return group(id, name, width, y, children, { direction: "vertical", gap: gap, padding: 0, align: "start" });
}
var iconSize = config.layout.iconSize || 16;
function glyph(id, name, fill) {
return { id: id, name: "Octicon " + name, type: "icon",
x: 0, y: 0, width: iconSize, height: iconSize, paths: icon(name).paths,
fill: fill || emphasis, stroke: null, strokeWidth: 0 };
}
function divider(id, height) {
return { id: id, name: "Divider", type: "rect", x: 0, y: 0, width: 1, height: height,
fill: C.border, stroke: null, strokeWidth: 0, radius: 0 };
}
function list(id, items, width) {
return stack(id, "Bulleted list", width, items.map(function (item, i) {
var t = label(id + "/" + i + "/text", item.text, width – 10, 13, 400, 20, C.muted);
t.x = 10;
var marker = { id: id + "/" + i + "/marker", name: item.tone || "Bullet", type: "ellipse",
x: 0, y: 7, width: 6, height: 6, fill: item.tone ? tone(item.tone).marker : C.muted,
stroke: null, strokeWidth: 0 };
return group(id + "/" + i, item.text, width, t.height, [marker, t]);
}), 4);
}
function chip(id, value, dashed, maxWidth, details) {
details = details || {};
var colors = details.tone ? tone(details.tone) : null;
var iconSpace = details.icon ? iconSize + 8 : 0;
// Native Figma short-label metrics can be up to 2px wider than the browser.
var w = Math.min(Math.ceil(widthOf(value, 14, 400)) + 2 + 20 + iconSpace, maxWidth || Infinity);
var t = label(id + "/text", value, w – 20 – iconSpace, 14, 400, 20, colors ? colors.foreground : C.text);
t.x = 10 + iconSpace; t.y = 6;
var parts = [t];
if (details.icon) {
var image = glyph(id + "/icon", details.icon, colors ? colors.foreground : emphasis);
image.x = 10; image.y = 6; parts.unshift(image);
}
var result = group(id, value, w, Math.max(t.height, details.icon ? iconSize : 0) + 12, parts);
Object.assign(result, { fill: colors ? colors.background : C.white,
stroke: colors ? colors.border : C.border, strokeWidth: 1, radius: 6 });
if (dashed) result.dash = [4, 3];
return result;
}
function makeNode(node, width) {
var plain = node.variant === "section-heading" || node.variant === "caption";
var pad = plain ? 0 : config.layout.flow ? 12 : 24;
var inner = width – pad * 2;
var children = [];
var callout = node.variant === "callout", bodyHandled = false;
if (node.icon && !callout) children.push(glyph(node.id + "/icon", node.icon));
var headWidth = node.step ? inner – 52 : callout ? (inner – iconSize – 32) / 2.4 : inner;
var badge;
if (node.badge) {
badge = chip(node.id + "/badge", node.badge, true);
headWidth -= badge.width + 12;
}
var heading = label(node.id + "/title", node.title, headWidth,
node.variant === "section-heading" || callout ? 22 : node.variant === "caption" ? 14 : node.accent ? 20 : 18,
600, node.variant === "section-heading" || callout ? 30 : node.variant === "caption" ? 20 : 26,
C.text, node.variant === "caption" ? "center" : "left");
if (node.step) {
var stepColor = node.step.color || accent;
var stepText = label(node.id + "/step/text", node.step.text, 40, 24, 600, 40, textOn(stepColor), "center");
var step = group(node.id + "/step", "Section " + node.step.text, 40, 40, [
{ id: node.id + "/step/circle", name: "Section circle", type: "ellipse",
x: 0, y: 0, width: 40, height: 40, fill: stepColor, stroke: null, strokeWidth: 0 },
stepText
]);
var headingParts = [heading];
if (node.body) headingParts.push(label(node.id + "/body", node.body, headWidth, 15, 400, 22, C.muted));
var headingCopy = stack(node.id + "/heading-stack", node.title, headWidth, headingParts, 2);
headingCopy.y = 5;
var right = group(node.id + "/heading-copy", node.title, headWidth, headingCopy.height + 5, [headingCopy]);
right.x = 52;
children.push(group(node.id + "/numbered-heading", node.title, inner, Math.max(40, right.height), [step, right],
{ direction: "horizontal", gap: 12, padding: 0, align: "start" }));
bodyHandled = true;
} else if (callout) {
var bodyWidth = inner – iconSize – 32 – headWidth;
var inset = node.dividers ? 17 : 0;
var body = label(node.id + "/body", node.body, bodyWidth – inset, 15, 400, 22, C.muted);
var calloutIcon = glyph(node.id + "/icon", node.icon);
var calloutHeight = Math.max(iconSize, heading.height, body.height);
body.x = inset; body.y = (calloutHeight – body.height) / 2;
var bodyParts = [body];
if (node.dividers) bodyParts.unshift(divider(node.id + "/divider", calloutHeight));
var bodyGroup = group(node.id + "/body-group", "Agreement explanation", bodyWidth, calloutHeight, bodyParts);
calloutIcon.y = (calloutHeight – iconSize) / 2;
heading.x = iconSize + 16; heading.y = (calloutHeight – heading.height) / 2;
bodyGroup.x = heading.x + headWidth + 16;
children.push(group(node.id + "/callout", node.title, inner, calloutHeight, [calloutIcon, heading, bodyGroup],
{ direction: "horizontal", gap: 16, padding: 0, align: "center" }));
bodyHandled = true;
} else if (node.stat) {
var size = (node.accent ? 20 : 18) * 2;
children.push(stack(node.id + "/stat-heading", node.title, inner,
[label(node.id + "/stat", node.stat, inner, size, 600, size * 1.2, accent), heading], 4));
} else if (badge) {
var rowHeight = Math.max(heading.height, badge.height);
heading.y = (rowHeight – heading.height) / 2;
badge.x = headWidth + 12;
badge.y = (rowHeight – badge.height) / 2;
children.push(group(node.id + "/heading", node.title, inner, rowHeight, [heading, badge]));
} else children.push(heading);
if (node.body && !bodyHandled) children.push(label(node.id + "/body", node.body, inner, 15, 400, 22, C.muted));
if (node.chips && node.chips.length) {
var rows = [], row = [], x = 0, rowIndex = 0;
function finishRow() {
var height = Math.max.apply(null, row.map(function (item) { return item.height; }));
row.forEach(function (item) { item.y = (height – item.height) / 2; });
rows.push(group(node.id + "/chips-row-" + rowIndex, "Labels", inner, height, row,
{ direction: "horizontal", gap: 8, padding: 0, align: "center" }));
row = []; x = 0; rowIndex++;
}
node.chips.forEach(function (item, i) {
var c = chip(node.id + "/chip-" + i, item.text, item.dashed, config.layout.flow ? inner : undefined, item);
requireThat(c.width inner) finishRow();
c.x = x; row.push(c); x += c.width + 8;
});
if (row.length) finishRow();
children.push(stack(node.id + “/chips”, “Labels”, inner, rows, 8));
}
if (node.sections && node.sections.length) {
var compactSections = node.dividers || node.sections.every(function (section) { return section.items; });
var columns = node.columns || 1;
if (config.layout.flow && inner < 220 && !compactSections) columns = 1;
var sectionGap = compactSections ? 8 : 24;
var cellWidth = (inner – sectionGap * (columns – 1)) / columns;
var firstWidth = cellWidth;
var asymmetric = compactSections && columns === 2 && node.sections[0].items;
if (asymmetric) {
firstWidth = Math.ceil(Math.max.apply(null, [widthOf(node.sections[0].title, 14, 600)]
.concat(node.sections[0].items.map(function (item) { return widthOf(item.text, 13, 400) + 10; }))));
}
var sections = [], sy = 0;
for (var i = 0; i = 148, “too many columns; use fewer columns or a wider artboard.”);
for (var offset = 0; offset < row.length; offset += columns) {
var rowNodes = row.slice(offset, offset + columns).map(function (id) { return makeNode(configs[id], w); });
var h = Math.max.apply(null, rowNodes.map(function (n) { return n.height; }));
rowNodes.forEach(function (n, i) {
n.x = padding + i * (w + layout.columnGap); n.y = y;
if (configs[n.id].shape === "card" || configs[n.id].shape === "annotation") n.height = h;
nodes.push(n); byId[n.id] = n;
});
bottom = y + h; y = bottom + layout.rowGap;
}
});
} else {
layout.placements.forEach(function (p) {
var n = makeNode(configs[p.node], p.width);
n.x = p.x; n.y = contentTop + p.y;
nodes.push(n); byId[n.id] = n; bottom = Math.max(bottom, n.y + n.height);
});
}
function overlaps(a, b) {
return a.x b.x && a.y b.y;
}
nodes.forEach(function (a, i) {
nodes.slice(i + 1).forEach(function (b) { requireThat(!overlaps(a, b), a.id + ” overlaps ” + b.id + “.”); });
});
function path(id, name, points, dashed) {
var xs = points.map(function (p) { return p.x; }), ys = points.map(function (p) { return p.y; });
var x = Math.min.apply(null, xs), y = Math.min.apply(null, ys);
var result = { id: id, name: name, type: “path”, x: x, y: y,
width: Math.max(1, Math.max.apply(null, xs) – x), height: Math.max(1, Math.max.apply(null, ys) – y),
points: points.map(function (p) { return { x: p.x – x, y: p.y – y }; }),
closed: false, fill: null, stroke: emphasis, strokeWidth: 1.75 };
if (dashed) result.dash = [5, 4];
return result;
}
function obstructed(a, b, n) {
if (a.x === b.x) return a.x > n.x + 0.1 && a.x n.y + 0.1 && Math.min(a.y, b.y) n.y + 0.1 && a.y n.x + 0.1 && Math.min(a.x, b.x) = 4 && p.x = contentTop, edge.id + ” leaves the drawable area.”);
bottom = Math.max(bottom, p.y);
if (i) {
var previous = points[i – 1];
requireThat(p.x === previous.x || p.y === previous.y, edge.id + ” needs orthogonal waypoints.”);
nodes.forEach(function (n) {
requireThat(!obstructed(previous, p, n), edge.id + ” crosses ” + n.id + “; add explicit waypoints.”);
});
}
});
var parts = [path(“edge/” + edge.id + “/line”, edge.id, points, edge.dashed)];
function arrow(p, normal, suffix) {
var tip = p;
var base = { x: p.x + normal.x * 7, y: p.y + normal.y * 7 };
parts.push(path(“edge/” + edge.id + “/” + suffix, “Arrowhead”, [
{ x: base.x – normal.y * 4, y: base.y + normal.x * 4 }, tip,
{ x: base.x + normal.y * 4, y: base.y – normal.x * 4 }
], false));
}
if (edge.direction === “both”) arrow(a, from.normal, “start”);
if (edge.direction !== “none”) arrow(b, to.normal, “end”);
if (edge.label) {
var middle = points[Math.floor(points.length / 2)];
var lw = Math.ceil(widthOf(edge.label, 14, 400)) + 16;
var lt = label(“edge/” + edge.id + “/label/text”, edge.label, lw – 16, 14, 400, 20, C.muted);
lt.x = 8; lt.y = 4;
var lg = group(“edge/” + edge.id + “/label”, edge.label, lw, 28, [lt]);
lg.x = middle.x – lw / 2; lg.y = middle.y – 14; lg.fill = C.white; lg.radius = 4;
requireThat(lg.x >= 0 && lg.x + lw = contentTop, “edge label exceeds the artboard.”);
nodes.forEach(function (n) { requireThat(!overlaps(lg, n), edge.id + ” label overlaps ” + n.id + “.”); });
bottom = Math.max(bottom, lg.y + lg.height);
parts.push(lg);
}
connections = connections.concat(parts);
});
return { width: layout.width, height: bottom + padding, background: C.white,
fonts: FONTS.map(function (font) { return Object.assign({}, font); }), children: [header].concat(connections, nodes) };
}
async function buildHandoff(config) {
validate(config);
var snapshot = JSON.parse(JSON.stringify(config));
await loadFonts();
var handoff = { format: “github-blog-diagram”, schemaVersion: 1, config: snapshot, scene: makeScene(snapshot) };
var octicons = iconAssets(snapshot);
if (Object.keys(octicons.icons).length) {
handoff.licenses = [{ name: “Octicons”, source: octicons.source, text: octicons.license }];
}
return handoff;
}
function svgEl(tag, attrs) {
var e = document.createElementNS(NS, tag);
Object.keys(attrs || {}).forEach(function (key) { e.setAttribute(key, attrs[key]); });
return e;
}
function paint(e, item) {
e.setAttribute(“fill”, item.fill || “none”);
e.setAttribute(“stroke”, item.stroke || “none”);
e.setAttribute(“stroke-width”, item.strokeWidth || 0);
if (item.dash) e.setAttribute(“stroke-dasharray”, item.dash.join(” “));
}
function renderItem(item) {
var e;
if (item.type === “group”) {
e = svgEl(“g”, { transform: “translate(” + item.x + ” ” + item.y + “)” });
if (item.fill || item.stroke) {
var bg = svgEl(“rect”, { width: item.width, height: item.height, rx: item.radius || 0 });
paint(bg, item); e.appendChild(bg);
}
item.children.forEach(function (child) { e.appendChild(renderItem(child)); });
} else if (item.type === “text”) {
e = svgEl(“text”, { x: item.x, y: item.y, fill: item.color, “font-size”: item.fontSize,
“font-weight”: item.fontWeight, “font-family”: FAMILY, “dominant-baseline”: “central”,
“text-anchor”: item.align === “center” ? “middle” : “start” });
item.text.split(“n”).forEach(function (line, i) {
var span = svgEl(“tspan”, {
x: item.x + (item.align === “center” ? item.width / 2 : 0),
y: item.y + item.lineHeight * (i + 0.5)
});
span.textContent = line; e.appendChild(span);
});
} else if (item.type === “icon”) {
e = svgEl(“svg”, { x: item.x, y: item.y, width: item.width, height: item.height, viewBox: “0 0 16 16”, “aria-hidden”: “true” });
item.paths.forEach(function (part) {
e.appendChild(svgEl(“path”, { d: part.data, fill: item.fill,
“fill-rule”: part.windingRule === “EVENODD” ? “evenodd” : “nonzero” }));
});
} else if (item.type === “rect” || item.type === “ellipse”) {
e = item.type === “rect” ?
svgEl(“rect”, { x: item.x, y: item.y, width: item.width, height: item.height, rx: item.radius || 0 }) :
svgEl(“ellipse”, { cx: item.x + item.width / 2, cy: item.y + item.height / 2, rx: item.width / 2, ry: item.height / 2 });
paint(e, item);
} else {
requireThat(item.type === “path”, “unknown scene primitive.”);
e = svgEl(“path”, { d: item.points.map(function (p, i) {
return (i ? “L” : “M”) + (p.x + item.x) + ” ” + (p.y + item.y);
}).join(” “) + (item.closed ? ” Z” : “”), “stroke-linecap”: “round”, “stroke-linejoin”: “round” });
paint(e, item);
}
e.setAttribute(“data-diagram-id”, item.id);
return e;
}
function describe(config) {
var parts = config.eyebrow ? [config.eyebrow, config.title] : [config.title];
config.nodes.forEach(function (node) {
parts.push((node.step ? node.step.text + ” ” : “”) + (node.stat ? node.stat + ” ” : “”) +
node.title + (node.badge ? ” (” + node.badge + “)” : “”));
if (node.body) parts.push(node.body);
if (node.chips) parts.push(node.chips.map(function (chip) { return chip.text; }).join(“; “));
if (node.sections) node.sections.forEach(function (s) {
parts.push(s.title + “: ” + (s.items ? s.items.map(function (item) { return item.text; }).join(“n”) : s.body));
});
});
var titles = Object.create(null);
config.nodes.forEach(function (n) { titles[n.id] = n.title; });
config.edges.forEach(function (edge) {
var prefix = edge.direction === “both” ? “Two-way connection: ” :
edge.direction === “none” ? “Undirected connection: ” : “Connection: “;
parts.push(prefix + titles[edge.from.node] + (edge.direction === “forward” ? ” to ” : ” and “) + titles[edge.to.node] +
(edge.label ? ” (” + edge.label + “)” : “”) + (edge.dashed ? ” [dashed]” : “”) + “.”);
});
return parts.join(“nn”);
}
function renderHandoff(handoff, root) {
requireThat(root && root.nodeType === 1, “a preview mount is required.”);
var scene = handoff.scene;
root.textContent = “”;
root.classList.add(“ghdiagram”);
var svg = svgEl(“svg”, { viewBox: “0 0 ” + scene.width + ” ” + scene.height,
width: scene.width, height: scene.height, role: “img”, “aria-label”: handoff.config.altText });
svg.style.background = scene.background;
var title = svgEl(“title”); title.textContent = handoff.config.title; svg.appendChild(title);
scene.children.forEach(function (item) { svg.appendChild(renderItem(item)); });
root.appendChild(svg);
var details = document.createElement(“details”);
details.className = “ghdiagram__description”;
var summary = document.createElement(“summary”); summary.textContent = “Diagram text and connections”;
var description = document.createElement(“div”); description.textContent = describe(handoff.config);
details.appendChild(summary); details.appendChild(description); root.appendChild(details);
}
global.GHDiagramEngine = {
version: VERSION,
validate: validate,
connectionGeometry: connectionGeometry,
flowGeometry: flowGeometry,
icon: icon,
iconAssets: iconAssets,
tone: tone,
textOn: textOn,
strongOf: strongOf,
buildHandoff: buildHandoff,
renderHandoff: renderHandoff,
describe: describe,
render: function (config, root) {
return buildHandoff(config).then(function (handoff) { renderHandoff(handoff, root); return handoff; });
}
};
})(typeof window !== “undefined” ? window : this);
/* Real HTML content and SVG relationships. The v1 model remains the semantic source. */
(function (global) {
“use strict”;
var VERSION = 10;
if (global.GHDiagramHTML && global.GHDiagramHTML.version >= VERSION) return;
var model = global.GHDiagramEngine;
var NS = “http://www.w3.org/2000/svg”;
var controllers = new WeakMap();
var fontPromise = null;
function requireThat(condition, message) {
if (!condition) throw new Error(“HTML diagram: ” + message);
}
function element(tag, name, text) {
var node = document.createElement(tag);
if (name) node.className = “ghdiagram-html__” + name;
if (text !== undefined) node.textContent = text;
return node;
}
function svg(tag, attributes) {
var node = document.createElementNS(NS, tag);
Object.keys(attributes || {}).forEach(function (key) { node.setAttribute(key, attributes[key]); });
return node;
}
function snapshot(config) {
requireThat(model && model.connectionGeometry, “load the shared diagram model before the HTML renderer.”);
model.validate(config);
return JSON.parse(JSON.stringify(config));
}
function fonts() {
if (!fontPromise) {
requireThat(document.fonts && document.fonts.load, “this browser cannot load the bundled static fonts.”);
var required = [[“GH Diagram Sans”, 400], [“GH Diagram Sans”, 500], [“GH Diagram Mono”, 400], [“GH Diagram Mono”, 500]];
fontPromise = Promise.all(required.map(function (font) {
var family = font[0], weight = String(font[1]);
return document.fonts.load(weight + ‘ 16px “‘ + family + ‘”‘).then(function (faces) {
requireThat(faces.some(function (face) {
return face.status === “loaded” && face.weight === weight && face.family.replace(/[“‘]/g, “”) === family;
}), “missing ” + family + ” ” + weight + “. Include the complete diagram CSS and its embedded fonts.”);
}, function (error) {
throw new Error(“HTML diagram: could not load font ” + family + ” ” + weight +
“. Check the bundled CSS and font files. ” + error.message);
});
})).catch(function (error) {
fontPromise = null;
throw error;
});
}
return fontPromise;
}
function iconElement(name, size) {
var glyph = svg(“svg”, { width: size, height: size, viewBox: “0 0 16 16”, “aria-hidden”: “true”, focusable: “false” });
glyph.classList.add(“ghdiagram-html__icon”);
glyph.setAttribute(“data-octicon”, name);
model.icon(name).paths.forEach(function (part) {
glyph.appendChild(svg(“path”, { d: part.data, “fill-rule”: part.windingRule === “EVENODD” ? “evenodd” : “nonzero” }));
});
return glyph;
}
function card(node, config) {
var iconSize = config.layout.iconSize || 16;
var box = element(“section”, “node ghdiagram-html__node–” + node.shape);
box.setAttribute(“data-ghd-node”, node.id);
if (node.dashed) box.classList.add(“ghdiagram-html__node–dashed”);
if (node.accent) box.classList.add(“ghdiagram-html__node–accent”);
if (node.variant) box.classList.add(“ghdiagram-html__node–” + node.variant);
if (node.dividers) box.classList.add(“ghdiagram-html__node–divided”);
if (node.sections && node.sections.length && node.sections.every(function (section) { return section.items; })) {
box.classList.add(“ghdiagram-html__node–listed”);
}
if (node.step) box.classList.add(“ghdiagram-html__node–numbered”);
if (node.minHeight) box.style.minHeight = node.minHeight + “px”;
if (node.shape === “decision”) {
var diamond = svg(“svg”, { viewBox: “0 0 100 100”, preserveAspectRatio: “none”, “aria-hidden”: “true” });
diamond.classList.add(“ghdiagram-html__diamond”);
var polygon = svg(“polygon”, { points: “50,0 100,50 50,100 0,50” });
if (node.dashed) polygon.setAttribute(“stroke-dasharray”, “6 4”);
diamond.appendChild(polygon);
box.appendChild(diamond);
} else if (node.dashed) {
var boundary = svg(“svg”, { “aria-hidden”: “true”, focusable: “false” });
boundary.classList.add(“ghdiagram-html__boundary”);
boundary.appendChild(svg(“rect”, { x: 0.5, y: 0.5, rx: node.shape === “terminal” ? 999 : 8 }));
box.appendChild(boundary);
}
var content = element(“div”, “content”);
if (node.icon) content.appendChild(iconElement(node.icon, iconSize));
if (node.step) {
var step = element(“span”, “step”, node.step.text);
var fill = node.step.color || config.theme.accent;
step.style.backgroundColor = fill;
step.style.color = model.textOn(fill);
step.setAttribute(“aria-hidden”, “true”);
content.appendChild(step);
}
var heading = element(“div”, “node-heading”);
if (node.stat) {
heading.classList.add(“ghdiagram-html__node-heading–stat”);
heading.appendChild(element(“span”, “stat”, node.stat));
}
var title = element(“h3”, “node-title”, node.title);
if (node.step) title.setAttribute(“aria-label”, node.step.text + ” ” + node.title);
heading.appendChild(title);
if (node.badge) heading.appendChild(element(“span”, “badge”, node.badge));
var copy = content;
if (node.step) {
copy = element(“div”, “heading-copy”);
content.appendChild(copy);
}
copy.appendChild(heading);
if (node.body) copy.appendChild(element(“p”, “body”, node.body));
if (node.chips && node.chips.length) {
var chips = element(“ul”, “chips”);
node.chips.forEach(function (chip) {
var item = element(“li”, “chip”);
if (chip.icon) {
item.classList.add(“ghdiagram-html__chip–icon”);
item.appendChild(iconElement(chip.icon, iconSize));
}
item.appendChild(element(“span”, “chip-text”, chip.text));
if (chip.tone) {
var tone = model.tone(chip.tone);
item.classList.add(“ghdiagram-html__chip–tone”);
item.setAttribute(“data-tone”, chip.tone);
item.style.setProperty(“–ghd-tone-text”, tone.foreground);
item.style.setProperty(“–ghd-tone-bg”, tone.background);
item.style.setProperty(“–ghd-tone-border”, tone.border);
}
if (chip.dashed) item.classList.add(“ghdiagram-html__chip–dashed”);
chips.appendChild(item);
});
content.appendChild(chips);
}
if (node.sections && node.sections.length) {
var sections = element(“div”, “sections”);
if (node.columns === 2) sections.classList.add(“ghdiagram-html__sections–two”);
node.sections.forEach(function (section) {
var group = element(“section”, “section”);
group.appendChild(element(“h4”, “section-title”, section.title));
if (section.items) {
var list = element(“ul”, “list”);
list.setAttribute(“role”, “list”);
section.items.forEach(function (item) {
var li = element(“li”, “list-item”);
var marker = element(“span”, “list-marker”);
marker.setAttribute(“aria-hidden”, “true”);
if (item.tone) {
marker.style.backgroundColor = model.tone(item.tone).marker;
marker.setAttribute(“data-tone”, item.tone);
}
li.appendChild(marker);
li.appendChild(element(“span”, “list-text”, item.text));
list.appendChild(li);
});
group.appendChild(list);
} else group.appendChild(element(“p”, “body”, section.body));
sections.appendChild(group);
});
content.appendChild(sections);
}
box.appendChild(content);
return box;
}
function build(config, mountId) {
var root = document.createElement(“div”);
root.className = “ghdiagram-html”;
if (mountId) root.id = mountId;
root.setAttribute(“role”, “figure”);
root.setAttribute(“aria-label”, config.altText);
root.setAttribute(“data-flow”, String(!!config.layout.flow));
root.style.maxWidth = Math.min(699, config.layout.width) + “px”;
root.style.setProperty(“–ghd-accent”, config.theme.accent);
root.style.setProperty(“–ghd-connector”, model.strongOf(config.theme.accent));
root.style.setProperty(“–ghd-padding”, config.layout.padding + “px”);
root.style.setProperty(“–ghd-icon-size”, (config.layout.iconSize || 16) + “px”);
root.style.setProperty(“–ghd-flow-min-width”, (config.layout.minCardWidth || 180) + “px”);
var canvas = element(“div”, “canvas”);
var heading = element(“header”, “heading”);
if (config.eyebrow) heading.appendChild(element(“p”, “eyebrow”, config.eyebrow));
heading.appendChild(element(“h2”, “title”, config.title));
canvas.appendChild(heading);
var viewport = element(“div”, “viewport”);
var plane = element(“div”, “plane”);
var lines = svg(“svg”, { “aria-hidden”: “true”, focusable: “false” });
lines.classList.add(“ghdiagram-html__connections”);
plane.appendChild(lines);
var byId = Object.create(null);
config.nodes.forEach(function (node) { byId[node.id] = card(node, config); });
if (config.layout.rows) {
plane.style.rowGap = config.layout.rowGap + “px”;
config.layout.rows.forEach(function (ids) {
var row = element(“div”, “row”);
if (config.layout.flow) {
row.classList.add(“ghdiagram-html__row–flow”);
row.style.rowGap = config.layout.rowGap + “px”;
} else row.style.gridTemplateColumns = “repeat(” + ids.length + “, minmax(0, 1fr))”;
row.style.columnGap = config.layout.columnGap + “px”;
ids.forEach(function (id) { row.appendChild(byId[id]); });
plane.appendChild(row);
});
} else {
plane.style.display = “block”;
plane.style.width = config.layout.width – config.layout.padding * 2 + “px”;
viewport.tabIndex = 0;
viewport.setAttribute(“aria-label”, “Diagram layout; scroll horizontally on narrow screens”);
config.layout.placements.forEach(function (placement) {
var box = byId[placement.node];
box.style.position = “absolute”;
box.style.left = placement.x – config.layout.padding + “px”;
box.style.top = placement.y + “px”;
box.style.width = placement.width + “px”;
plane.appendChild(box);
});
}
viewport.appendChild(plane);
canvas.appendChild(viewport);
root.appendChild(canvas);
// No-JS fallback only. The visible diagram is the whole export; the figure’s
// aria-label already carries the description for assistive tech, and this
// noscript lists the connections when scripts (which draw the arrows) are off.
var noscript = element(“noscript”);
noscript.appendChild(element(“p”, null, “JavaScript draws the arrows connecting these cards. ” + model.describe(config)));
root.appendChild(noscript);
return root;
}
function overlaps(a, b) {
return a.x b.x + 0.1 &&
a.y b.y + 0.1;
}
function crosses(a, b, box) {
if (Math.abs(a.x – b.x) box.x + 0.1 && a.x box.y + 0.1 && Math.min(a.y, b.y) box.y + 0.1 && a.y box.x + 0.1 && Math.min(a.x, b.x) < box.x + box.width – 0.1;
}
function path(points) {
return svg("path", { d: points.map(function (p, i) { return (i ? "L" : "M") + p.x + " " + p.y; }).join(" ") });
}
function draw(config, root) {
var plane = root.querySelector(".ghdiagram-html__plane");
var lines = plane.querySelector(".ghdiagram-html__connections");
plane.style.minHeight = "0px";
root.setAttribute("data-compact", config.layout.rows && root.clientWidth = config.layout.padding && p.x = 0,
edge.id + ” leaves the diagram; revise its ports or waypoints.”);
bottom = Math.max(bottom, p.y);
if (i) {
var previous = points[i – 1];
requireThat(Math.abs(p.x – previous.x) < 0.1 || Math.abs(p.y – previous.y) = 0 && x + box.width = 0, label.id + ” label exceeds the diagram.”);
ids.forEach(function (id) { requireThat(!overlaps(box, boxes[id]), label.id + ” label overlaps ” + id); });
placedLabels.forEach(function (previous) {
requireThat(!overlaps(box, previous.box), label.id + ” label overlaps ” + previous.id + “; revise the waypoints.”);
});
placedLabels.push({ id: label.id, box: box });
text.style.left = x + “px”; text.style.top = y + “px”;
bottom = Math.max(bottom, y + box.height);
});
plane.style.minHeight = bottom + “px”;
lines.setAttribute(“viewBox”, “0 0 ” + plane.clientWidth + ” ” + bottom);
lines.replaceChildren(fragment);
root.setAttribute(“data-ghd-state”, “ready”);
root.removeAttribute(“aria-busy”);
var error = root.querySelector(“.ghdiagram-html__error”);
if (error) error.remove();
root.dispatchEvent(new CustomEvent(“ghdiagram-layout”, { detail: { state: “ready” } }));
}
function report(root, error) {
var lines = root.querySelector(“.ghdiagram-html__connections”);
if (lines) lines.replaceChildren();
root.querySelectorAll(“.ghdiagram-html__edge-label”).forEach(function (node) { node.remove(); });
var message = root.querySelector(“.ghdiagram-html__error”);
if (!message) {
message = element(“p”, “error”);
message.setAttribute(“role”, “alert”);
root.appendChild(message);
}
message.textContent = error.message;
root.setAttribute(“data-ghd-state”, “error”);
root.removeAttribute(“aria-busy”);
root.dispatchEvent(new CustomEvent(“ghdiagram-layout”, { detail: { state: “error” } }));
}
function destroy(root) {
var controller = controllers.get(root);
if (controller) controller.destroy();
}
async function render(config, root) {
requireThat(root && root.nodeType === 1, “a mount element is required.”);
var data = snapshot(config);
destroy(root);
var tree = build(data, root.id);
root.classList.add(“ghdiagram-html”);
root.setAttribute(“role”, “figure”);
root.setAttribute(“aria-label”, data.altText);
root.setAttribute(“data-flow”, String(!!data.layout.flow));
root.style.cssText = tree.style.cssText;
root.replaceChildren.apply(root, Array.from(tree.childNodes));
root.setAttribute(“aria-busy”, “true”);
root.setAttribute(“data-ghd-state”, “loading”);
var active = true, frame = 0, observer;
var controller = {
config: data,
destroy: function () {
active = false;
cancelAnimationFrame(frame);
if (observer) observer.disconnect();
global.removeEventListener(“resize”, schedule);
controllers.delete(root);
}
};
controllers.set(root, controller);
function schedule() {
if (!active) return;
cancelAnimationFrame(frame);
frame = requestAnimationFrame(function () {
if (!active) return;
try { draw(data, root); } catch (error) { report(root, error); }
});
}
try {
await fonts();
if (!active) return controller;
draw(data, root);
if (global.ResizeObserver) {
observer = new ResizeObserver(schedule);
observer.observe(root.querySelector(“.ghdiagram-html__canvas”));
} else global.addEventListener(“resize”, schedule);
return controller;
} catch (error) {
if (active) { report(root, error); controller.destroy(); }
throw error;
}
}
global.GHDiagramHTML = {
version: VERSION, render: render, destroy: destroy,
markup: function (config, mountId) {
requireThat(typeof mountId === “string” && /^[a-z][a-z0-9-]*$/.test(mountId), “use a unique lowercase mount ID.”);
var root = build(snapshot(config));
root.setAttribute(“data-ghd-embed”, mountId);
return root.outerHTML + “n”;
}
};
})(typeof window !== “undefined” ? window : this);
;(function () {
“use strict”;
var CONFIG = {
“schemaVersion”: 1,
“kind”: “diagram”,
“id”: “reviewbench-at-a-glance”,
“name”: “ReviewBench at a glance”,
“title”: “ReviewBench at a glance”,
“altText”: “ReviewBench’s six-stage construction process flows from 103.9M GitHub pull requests to objective evaluation. A separate five-stage trust chain flows from a published rubric to published agreement. Senior engineers reach 96.6% agreement, and offline benchmark movements are checked against online experiments.”,
“theme”: {
“accent”: “#499d6f”
},
“exportScale”: 2,
“layout”: {
“width”: 699,
“padding”: 24,
“rowGap”: 40,
“columnGap”: 24,
“flow”: true,
“minCardWidth”: 200,
“iconSize”: 24,
“rows”: [
[
“built”
],
[
“pull-requests”,
“corpus”,
“golden-set”,
“findings”,
“metrics”,
“evaluation”
],
[
“trustworthy”
],
[
“rubric”,
“dev-set”,
“grader”,
“labeling”,
“published-agreement”
],
[
“audit”
],
[
“agreement”
],
[
“production”
]
]
},
“nodes”: [
{
“id”: “built”,
“shape”: “annotation”,
“variant”: “section-heading”,
“step”: {
“text”: “1”
},
“title”: “What we built”,
“body”: “A realistic, comprehensive benchmark for AI code review agents”
},
{
“id”: “pull-requests”,
“shape”: “card”,
“icon”: “mark-github”,
“stat”: “103.9M”,
“title”: “GitHub pull requests”,
“body”: “Analyze distributions by language, repository size, and change shape.”
},
{
“id”: “corpus”,
“shape”: “card”,
“icon”: “git-pull-request”,
“title”: “Representative benchmark corpus”,
“body”: “219 public pull requests across 19 languages, aligned to GitHub-wide distributions while preserving substantive review cases.”
},
{
“id”: “golden-set”,
“shape”: “card”,
“icon”: “database”,
“title”: “Multi-source golden set”,
“chips”: [
{
“text”: “Human reviewers”
},
{
“text”: “Frontier LLMs”
},
{
“text”: “Static analysis”
}
]
},
{
“id”: “findings”,
“shape”: “card”,
“icon”: “checklist”,
“title”: “Structured findings”,
“body”: “Every finding is labeled for severity and category, enabling user-tailored slices.”,
“columns”: 2,
“sections”: [
{
“id”: “severity”,
“title”: “Severity”,
“items”: [
{
“text”: “Critical”,
“tone”: “danger”
},
{
“text”: “Medium”,
“tone”: “warning”
},
{
“text”: “Low”,
“tone”: “success”
}
]
},
{
“id”: “category”,
“title”: “Category”,
“items”: [
{
“text”: “Correctness”
},
{
“text”: “Security”
},
{
“text”: “Reliability”
},
{
“text”: “Maintainability”
},
{
“text”: “Testing”
},
{
“text”: “……”
}
]
}
]
},
{
“id”: “metrics”,
“shape”: “card”,
“icon”: “graph”,
“title”: “Evaluation metrics”,
“body”: “Four metrics measure both known and newly discovered issues.”,
“chips”: [
{
“text”: “Grounded precision”
},
{
“text”: “Grounded recall”
},
{
“text”: “Augmented precision”
},
{
“text”: “Augmented recall”
}
]
},
{
“id”: “evaluation”,
“shape”: “card”,
“icon”: “goal”,
“title”: “Objective evaluation”,
“body”: “Measure improvement and compare across agents objectively. Help users choose the reviewer that fits their needs the best.”
},
{
“id”: “trustworthy”,
“shape”: “annotation”,
“variant”: “section-heading”,
“step”: {
“text”: “2”
},
“title”: “How we keep it trustworthy”,
“body”: “An auditable chain from rubric to expert validation and production checks”
},
{
“id”: “rubric”,
“shape”: “card”,
“icon”: “checklist”,
“title”: “Published rubric”,
“body”: “One explicit standard for all findings.”
},
{
“id”: “dev-set”,
“shape”: “card”,
“icon”: “people”,
“title”: “Human-labeled dev set”,
“body”: “Senior engineers establish ground truth.”
},
{
“id”: “grader”,
“shape”: “card”,
“icon”: “gear”,
“title”: “Calibrated grader”,
“body”: “Aligned with human judgment.”
},
{
“id”: “labeling”,
“shape”: “card”,
“icon”: “list-unordered”,
“title”: “Uniform labeling”,
“body”: “Same standard across all sources.”
},
{
“id”: “published-agreement”,
“shape”: “card”,
“icon”: “check-circle”,
“title”: “Published agreement”,
“body”: “Expert audit of benchmark quality.”
},
{
“id”: “audit”,
“shape”: “annotation”,
“variant”: “caption”,
“title”: “Auditable end to end”
},
{
“id”: “agreement”,
“shape”: “annotation”,
“variant”: “callout”,
“dividers”: true,
“icon”: “check-circle”,
“title”: “96.6% agreement”,
“body”: “Senior engineers independently labeled golden true-positives before release.”
},
{
“id”: “production”,
“shape”: “annotation”,
“icon”: “pulse”,
“title”: “Offline signals that anticipate production”,
“body”: “Benchmark movement is checked against online experiments.”,
“chips”: [
{
“text”: “Improvements tend to show up online”,
“icon”: “arrow-up-right”,
“tone”: “success”
},
{
“text”: “Regressions tend to show up online too”,
“icon”: “arrow-down-right”,
“tone”: “danger”
}
]
}
],
“edges”: [
{
“id”: “sample”,
“from”: {
“node”: “pull-requests”,
“side”: “right”
},
“to”: {
“node”: “corpus”,
“side”: “left”
},
“direction”: “forward”
},
{
“id”: “combine”,
“from”: {
“node”: “corpus”,
“side”: “right”
},
“to”: {
“node”: “golden-set”,
“side”: “left”
},
“direction”: “forward”
},
{
“id”: “structure”,
“from”: {
“node”: “golden-set”,
“side”: “bottom”
},
“to”: {
“node”: “findings”,
“side”: “top”
},
“direction”: “forward”
},
{
“id”: “measure”,
“from”: {
“node”: “findings”,
“side”: “right”
},
“to”: {
“node”: “metrics”,
“side”: “left”
},
“direction”: “forward”
},
{
“id”: “evaluate”,
“from”: {
“node”: “metrics”,
“side”: “right”
},
“to”: {
“node”: “evaluation”,
“side”: “left”
},
“direction”: “forward”
},
{
“id”: “label”,
“from”: {
“node”: “rubric”,
“side”: “right”
},
“to”: {
“node”: “dev-set”,
“side”: “left”
},
“direction”: “forward”
},
{
“id”: “calibrate”,
“from”: {
“node”: “dev-set”,
“side”: “right”
},
“to”: {
“node”: “grader”,
“side”: “left”
},
“direction”: “forward”
},
{
“id”: “standardize”,
“from”: {
“node”: “grader”,
“side”: “bottom”
},
“to”: {
“node”: “labeling”,
“side”: “top”
},
“direction”: “forward”
},
{
“id”: “audit”,
“from”: {
“node”: “labeling”,
“side”: “right”
},
“to”: {
“node”: “published-agreement”,
“side”: “left”
},
“direction”: “forward”
}
]
};
var TOKEN = “ghdiagram-reviewbench-at-a-glance-1rwmu5t171tf8y”;
function boot() {
document.querySelectorAll(‘[data-ghd-embed=”‘ + TOKEN + ‘”]’).forEach(function (root) {
if (Number(root.getAttribute(“data-ghd-boot”)) >= window.GHDiagramHTML.version) return;
root.setAttribute(“data-ghd-boot”, String(window.GHDiagramHTML.version));
window.GHDiagramHTML.render(CONFIG, root).catch(function (error) {
root.removeAttribute(“data-ghd-boot”);
root.setAttribute(“data-ghd-state”, “error”);
if (!root.querySelector(“.ghdiagram-html__error”)) {
var message = document.createElement(“p”);
message.className = “ghdiagram-html__error”;
message.setAttribute(“role”, “alert”);
message.textContent = error.message;
root.appendChild(message);
}
console.error(error);
});
});
}
if (document.readyState === “loading”) document.addEventListener(“DOMContentLoaded”, boot);
else boot();
})();
ReviewBench at a glance
JavaScript draws the arrows connecting these cards. ReviewBench at a glance
1 What we built
A realistic, comprehensive benchmark for AI code review agents
103.9M GitHub pull requests
Analyze distributions by language, repository size, and change shape.
Representative benchmark corpus
219 public pull requests across 19 languages, aligned to GitHub-wide distributions while preserving substantive review cases.
Multi-source golden set
Human reviewers; Frontier LLMs; Static analysis
Structured findings
Every finding is labeled for severity and category, enabling user-tailored slices.
Severity: Critical
Medium
Low
Category: Correctness
Security
Reliability
Maintainability
Testing
……
Evaluation metrics
Four metrics measure both known and newly discovered issues.
Grounded precision; Grounded recall; Augmented precision; Augmented recall
Objective evaluation
Measure improvement and compare across agents objectively. Help users choose the reviewer that fits their needs the best.
2 How we keep it trustworthy
An auditable chain from rubric to expert validation and production checks
Published rubric
One explicit standard for all findings.
Human-labeled dev set
Senior engineers establish ground truth.
Calibrated grader
Aligned with human judgment.
Uniform labeling
Same standard across all sources.
Published agreement
Expert audit of benchmark quality.
Auditable end to end
96.6% agreement
Senior engineers independently labeled golden true-positives before release.
Offline signals that anticipate production
Benchmark movement is checked against online experiments.
Improvements tend to show up online; Regressions tend to show up online too
Connection: GitHub pull requests to Representative benchmark corpus.
Connection: Representative benchmark corpus to Multi-source golden set.
Connection: Multi-source golden set to Structured findings.
Connection: Structured findings to Evaluation metrics.
Connection: Evaluation metrics to Objective evaluation.
Connection: Published rubric to Human-labeled dev set.
Connection: Human-labeled dev set to Calibrated grader.
Connection: Calibrated grader to Uniform labeling.
Connection: Uniform labeling to Published agreement.
How ReviewBench works
Our benchmark is built around five principles:
1. Representative pull requests, not a demo set
We analyzed 103.9 million GitHub pull requests to characterize the real-world distribution of code review workloads. ReviewBench contains 219 pull requests from 187 public open source licensed repositories spanning 19 languages, with its language and repository-size distributions closely matching GitHub overall. The complete benchmark dataset is publicly available.
We make one deliberate adjustment to this distribution: while language and repository size mirror GitHub directly, pull request size is weighted toward the reviewable middle and tail. This reduces the overrepresentation of tiny, single-file changes while preserving more substantive, multi-file pull requests where review quality matters most.
Quick corpus snapshot:
.ghchart {
/* Theme tokens */
–ghc-text: #1f2328;
–ghc-muted: #656d76;
–ghc-grid: #d0d7de;
–ghc-axis: #59636e;
–ghc-card-bg: #ffffff;
–ghc-card-border: #d0d7de;
–ghc-canvas: #f2f5f3;
–ghc-divider: #b6bfb8;
–ghc-guide: #afb8c1;
/* GitHub brand font — matches the github.blog stack (Mona Sans, with
the blog’s metric fallback) so it renders identically in-context. */
font-family: “Mona Sans”, “Mona Sans Fallback”, -apple-system, “system-ui”,
“Segoe UI”, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
position: relative;
box-sizing: border-box;
width: 100%;
max-width: 699px;
margin: 0 auto;
color: var(–ghc-text);
line-height: 1.4;
}
.ghchart *,
.ghchart *::before,
.ghchart *::after { box-sizing: border-box; }
/* —————————————————————— */
/* Header + legend */
/* —————————————————————— */
.ghchart__header {
display: flex;
justify-content: flex-end;
align-items: center;
min-height: 24px;
margin-bottom: 4px;
}
.ghchart__legend {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
justify-content: flex-end;
}
.ghchart__legend-item {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: clamp(14px, 1.9vw, 17px);
font-weight: 600;
color: var(–ghc-muted);
white-space: nowrap;
}
.ghchart__swatch {
width: 14px;
height: 14px;
border-radius: 4px;
flex: 0 0 auto;
}
/* —————————————————————— */
/* Plot (SVG) */
/* —————————————————————— */
.ghchart__plot {
display: block;
width: 100%;
height: auto;
overflow: visible;
touch-action: pan-y;
}
.ghchart__gridline {
stroke: var(–ghc-grid);
stroke-width: 1;
stroke-dasharray: 2 6;
stroke-linecap: round;
opacity: 0.9;
}
.ghchart__axislabel {
fill: var(–ghc-axis);
font-size: 22px;
font-weight: 500;
}
.ghchart__axislabel–x { font-weight: 600; }
.ghchart__line {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Trailing partial/incomplete segment of a line (e.g. a month still in
progress) — dashed so it reads as “not final yet” without a legend. */
.ghchart__line–dashed {
stroke-dasharray: 7 6;
}
.ghchart__dot { transition: r 0.12s ease; }
/* Dashed threshold line (e.g. a target/reference level) + floating label.
Stroke color is set inline per chart; dash + label styling live here. */
.ghchart__threshold {
stroke-width: 2;
stroke-dasharray: 7 6;
stroke-linecap: round;
opacity: 0.9;
}
.ghchart__threshold-label {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Hatched band marking a partial / projected trailing region. */
.ghchart__projection { pointer-events: none; }
.ghchart__guide {
stroke: var(–ghc-guide);
stroke-width: 1.5;
stroke-dasharray: 4 4;
opacity: 0;
transition: opacity 0.12s ease;
}
.ghchart__hit { fill: transparent; }
/* —————————————————————— */
/* Scatter plot */
/* —————————————————————— */
.ghchart__gridline–v { /* vertical gridlines reuse the dashed grid style */ }
.ghchart__axistitle {
fill: var(–ghc-muted);
font-size: 20px;
font-weight: 600;
letter-spacing: -0.01em;
}
/* Dotted reference crosshair through the “reference” point. Stroke color
is set inline (tracks the reference series color). */
.ghchart__crosshair {
stroke-width: 2;
stroke-dasharray: 2 7;
stroke-linecap: round;
opacity: 0.85;
pointer-events: none;
}
.ghchart__halo { opacity: 0.18; pointer-events: none; }
.ghchart__pt { transition: r 0.12s ease; }
.ghchart__pt-label {
fill: var(–ghc-muted);
font-size: 19px;
font-weight: 600;
letter-spacing: -0.01em;
pointer-events: none;
}
.ghchart__pt-label–strong { font-size: 21px; font-weight: 700; }
.ghchart__pthit { fill: transparent; cursor: default; outline: none; }
.ghchart__pthit:focus-visible {
outline: 2px solid var(–ghc-axis);
outline-offset: 1px;
}
.ghchart__swatch–dot { border-radius: 50%; }
/* —————————————————————— */
/* Trend chart (dated time-series with narrative headline) */
/* —————————————————————— */
.ghchart__headline {
font-size: clamp(17px, 2.3vw, 20px);
line-height: 1.32;
font-weight: 700;
letter-spacing: -0.01em;
color: var(–ghc-text);
margin: 0 0 8px;
}
/* Trend legend sits left-aligned under the headline/subtitle. */
.ghchart__legend–trend {
justify-content: flex-start;
margin: 2px 0 14px;
}
.ghchart__glyph { flex: 0 0 auto; }
.ghchart__glyph–square {
width: 13px; height: 13px; border-radius: 2px;
}
.ghchart__glyph–diamond {
width: 12px; height: 12px; border: 1.5px solid; transform: rotate(45deg);
}
.ghchart__glyph–line {
width: 20px; height: 0; border-top: 2px dashed; border-radius: 0;
}
/* Phase background columns + separators + top labels. */
.ghchart__phase { fill: #f6f8fa; opacity: 0.55; }
.ghchart__phase–alt { fill: #ffffff; opacity: 0; }
.ghchart__phase-sep {
stroke: var(–ghc-grid); stroke-width: 1; stroke-dasharray: 2 5; opacity: 0.8;
}
.ghchart__phase-label {
fill: var(–ghc-muted);
font-size: 15px;
font-weight: 700;
letter-spacing: 0.06em;
}
.ghchart__phase-avg {
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Dashed trend line + ± noise band + slope label. */
.ghchart__trendband { opacity: 0.12; pointer-events: none; }
.ghchart__trendline {
stroke-width: 2.5;
stroke-dasharray: 8 6;
stroke-linecap: round;
fill: none;
}
.ghchart__slope {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Horizontal reference line + label. */
.ghchart__refline {
stroke-width: 2;
stroke-dasharray: 7 6;
stroke-linecap: round;
opacity: 0.85;
}
.ghchart__reflabel {
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Data squares — values reveal on hover only. */
.ghchart__square { transition: width 0.12s ease, height 0.12s ease, x 0.12s ease, y 0.12s ease; }
.ghchart__squarehit { fill: transparent; cursor: default; outline: none; }
.ghchart__squarehit:focus-visible {
outline: 2px solid var(–ghc-axis);
outline-offset: 1px;
}
/* Invalid-run markers + leader-line annotations. */
.ghchart__invalid { stroke-width: 2; }
.ghchart__invalid-x { stroke-width: 2; stroke-linecap: round; }
.ghchart__leader { stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.8; }
.ghchart__leader–callout { stroke-dasharray: none; opacity: 0.6; }
.ghchart__anno-title {
font-size: 15px;
font-weight: 700;
letter-spacing: -0.01em;
}
.ghchart__anno-sub {
fill: var(–ghc-muted);
font-size: 13px;
font-weight: 500;
}
/* Final-callout labels read a touch larger than the invalid-run notes. */
.ghchart__anno-title–callout { font-size: 17px; }
.ghchart__anno-sub–callout { font-size: 14px; }
/* Bracket span annotation. */
.ghchart__bracket { stroke: var(–ghc-axis); stroke-width: 1.5; opacity: 0.75; }
.ghchart__bracket-label {
fill: var(–ghc-muted);
font-size: 13px;
font-weight: 700;
letter-spacing: 0.04em;
}
/* Final callout ring. */
.ghchart__callout-ring { fill: none; stroke-width: 2; }
/* —————————————————————— */
/* Title + divider */
/* —————————————————————— */
.ghchart__title {
/* Matches the github.blog H2 (.wp-block-heading) exactly */
display: block;
font-size: 32px;
line-height: 40px;
font-weight: 700;
letter-spacing: -0.18px;
color: var(–ghc-text);
}
.ghchart__rule {
height: 1.5px;
border-radius: 1.5px;
background: var(–ghc-divider);
margin: 10px 0 18px;
}
/* —————————————————————— */
/* Bar chart: subtitle, panels, bars, caption */
/* —————————————————————— */
.ghchart__subtitle {
margin: -6px 0 20px;
font-size: clamp(13px, 1.6vw, 15px);
line-height: 1.5;
color: var(–ghc-muted);
}
.ghchart__panel { margin-top: 30px; }
.ghchart__panel:first-of-type { margin-top: 6px; }
.ghchart__panel-title {
margin: 0 0 6px;
font-size: clamp(15px, 1.9vw, 18px);
font-weight: 700;
letter-spacing: -0.01em;
color: var(–ghc-text);
}
.ghchart__plot–bar { touch-action: pan-y; }
.ghchart__axisline {
stroke: var(–ghc-axis);
stroke-width: 1.25;
opacity: 0.35;
}
.ghchart__bar { transition: opacity 0.12s ease; }
.ghchart__barhit { fill: transparent; }
/* Always-on value labels above selected bars. */
.ghchart__barvalue {
fill: var(–ghc-text);
font-size: 20px;
font-weight: 700;
font-variant-numeric: tabular-nums;
pointer-events: none;
}
/* Anchor annotation: dotted reference line + floating pill. */
.ghchart__anno-line {
stroke: #8b949e;
stroke-width: 1.5;
stroke-dasharray: 4 3;
stroke-linecap: round;
pointer-events: none;
}
.ghchart__anno-chip {
fill: #ffebe9;
stroke: #ffcecb;
stroke-width: 1;
pointer-events: none;
}
.ghchart__anno-chip-text {
fill: #a0111f;
font-weight: 700;
pointer-events: none;
}
.ghchart__caption {
margin-top: 22px;
padding-top: 12px;
border-top: 1px solid var(–ghc-card-border);
font-size: 12.5px;
line-height: 1.55;
color: var(–ghc-muted);
}
.ghchart__caption b { color: var(–ghc-text); font-weight: 700; }
.ghchart__caption code {
font-family: ui-monospace, SFMono-Regular, “SF Mono”, Menlo, Consolas, monospace;
font-size: 0.92em;
padding: 1px 5px;
background: #f6f8fa;
border: 1px solid var(–ghc-card-border);
border-radius: 5px;
}
.ghchart__tag {
display: inline-block;
font-size: 10px;
font-weight: 650;
letter-spacing: 0.03em;
text-transform: uppercase;
padding: 2px 7px;
border-radius: 999px;
border: 1px solid #aceebb;
color: #0a5223;
background: #dafbe1;
white-space: nowrap;
vertical-align: middle;
}
/* —————————————————————— */
/* Distribution dashboard (segmented mix bar + ranked bar panels) */
/* —————————————————————— */
.ghchart__dist-section { margin-top: 28px; }
.ghchart__dist-section:first-of-type { margin-top: 12px; }
.ghchart–panel-cards { padding: 24px; background: var(–ghc-canvas); }
.ghchart–panel-cards .ghchart__dist-cols { gap: 24px; }
.ghchart–panel-cards .ghchart__dist-cols .ghchart__dist-section {
min-width: 0;
margin: 0;
padding: 24px;
border: 1px solid var(–ghc-card-border);
border-radius: 8px;
background: var(–ghc-card-bg);
}
@media (max-width: 480px) {
.ghchart–panel-cards,
.ghchart–panel-cards .ghchart__dist-cols .ghchart__dist-section { padding: 16px; }
}
/* Segmented proportional “mix” bar: name inside, count/percent below.
Separators are 2px inside borders (box-sizing:border-box) rather than a
flex gap, so the three segments sum to exactly 100% with no overflow. */
.ghchart__seg {
display: flex;
width: 100%;
height: 42px;
margin-top: 2px;
border-radius: 7px;
overflow: hidden;
}
.ghchart__seg-cell {
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
color: #ffffff;
font-size: 15px;
font-weight: 700;
letter-spacing: -0.01em;
white-space: nowrap;
overflow: hidden;
}
.ghchart__seg-cell + .ghchart__seg-cell { border-left: 2px solid var(–ghc-card-bg); }
.ghchart__seg-note-row {
display: flex;
width: 100%;
margin-top: 8px;
}
.ghchart__seg-note {
min-width: 0;
text-align: center;
font-size: 12.5px;
color: var(–ghc-muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* Keep the outer notes from spilling past the chart edges. */
.ghchart__seg-note:first-child { text-align: left; }
.ghchart__seg-note:last-child { text-align: right; }
.ghchart__seg-note b { color: var(–ghc-text); font-weight: 700; }
/* Ranked horizontal bar list: [category | bar track | value].
The list is the grid and rows use display:contents, so the category,
bar, and value columns line up across every row in a panel. */
.ghchart__dist-list {
display: grid;
grid-template-columns: max-content 1fr max-content;
align-items: center;
column-gap: 12px;
row-gap: 10px;
margin-top: 4px;
}
.ghchart__dist-row { display: contents; }
.ghchart__dist-cat {
text-align: right;
font-size: 13px;
line-height: 1.25;
color: var(–ghc-text);
white-space: nowrap;
}
.ghchart__dist-track { width: 100%; }
.ghchart__dist-bar {
height: 15px;
min-width: 2px;
border-radius: 3px;
}
.ghchart__dist-val {
font-size: 12.5px;
color: var(–ghc-muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.ghchart__dist-val b { color: var(–ghc-text); font-weight: 700; }
.ghchart__dist-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 16px; margin: 0; }
.ghchart__dist-stat { display: flex; flex-direction: column; gap: 4px; }
.ghchart__dist-stat dt { color: var(–ghc-muted); font-size: 13px; }
.ghchart__dist-stat dd { order: -1; margin: 0; font-size: 30px; font-weight: 800; letter-spacing: -0.02em; }
.ghchart__comparison-row { display: grid; grid-template-columns: 145px minmax(0, 1fr); gap: 20px; padding: 14px 0; }
.ghchart__comparison-label { align-self: center; font-size: 15px; font-weight: 600; }
.ghchart__comparison-plot { position: relative; padding: 6px 0; display: grid; gap: 8px; }
.ghchart__comparison-grid {
position: absolute; top: 0; bottom: 0; border-left: 1px dashed var(–ghc-grid);
transform: translateX(-50%); pointer-events: none;
}
.ghchart__comparison-zero { border-left: 2px solid var(–ghc-axis); z-index: 1; }
.ghchart__comparison-track { position: relative; height: 28px; }
.ghchart__comparison-bar {
position: absolute; top: 0; height: 28px;
border-radius: 0 2px 2px 0;
pointer-events: none;
}
.ghchart__comparison-bar–negative { border-radius: 2px 0 0 2px; }
.ghchart__comparison-value {
position: absolute;
top: 4px;
z-index: 2;
width: max-content;
color: var(–ghc-text);
font-size: 13px;
font-weight: 600;
line-height: 20px;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.ghchart__comparison-track–stacked .ghchart__comparison-value {
position: relative;
top: 0;
width: 100%;
white-space: normal;
overflow-wrap: anywhere;
background: var(–ghc-card-bg);
}
.ghchart__comparison-axis { position: relative; height: 24px; }
.ghchart__comparison-tick { position: absolute; transform: translateX(-50%); white-space: nowrap; font-size: 12px; color: var(–ghc-axis); }
@media (max-width: 480px) {
.ghchart__comparison-row { grid-template-columns: minmax(0, 1fr); gap: 8px; }
.ghchart__comparison-tick { font-size: 11px; }
}
/* Two-up panel grid (collapses to one column on narrow screens).
minmax(0,1fr) lets columns shrink so long labels never force overflow. */
.ghchart__dist-cols {
margin-top: 26px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px 30px;
}
.ghchart__dist-cols .ghchart__dist-section { margin-top: 0; }
.ghchart__dist-cols–stack { grid-template-columns: 1fr; }
.ghchart__dist-cols–stack .ghchart__dist-section { margin-top: 28px; }
.ghchart__dist-cols–stack .ghchart__dist-section:first-child { margin-top: 0; }
/* Tighten the label/value type a touch in the 2-up layout so the bars
keep room at the 699px blog width. */
.ghchart__dist-cols:not(.ghchart__dist-cols–stack) .ghchart__dist-cat { font-size: 12px; }
.ghchart__dist-cols:not(.ghchart__dist-cols–stack) .ghchart__dist-val { font-size: 11.5px; }
.ghchart__dist-cols:not(.ghchart__dist-cols–stack) .ghchart__dist-list { column-gap: 9px; }
@media (max-width: 600px) {
.ghchart__dist-cols { grid-template-columns: 1fr; gap: 26px; }
.ghchart__dist-cols .ghchart__dist-section ~ .ghchart__dist-section { margin-top: 2px; }
}
/* Let long category labels wrap on very narrow (mobile) widths. */
@media (max-width: 480px) {
.ghchart__dist-cat { white-space: normal; }
}
/* —————————————————————— */
/* Timeline / staged-duration chart (request lifecycle, before/after) */
/* —————————————————————— */
.ghchart__tl-eyebrow {
font-size: clamp(12px, 1.5vw, 13px);
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(–ghc-muted);
margin: 0 0 6px;
}
.ghchart__tl-headline { margin-bottom: 14px; }
/* Before -> after summary card. */
.ghchart__tl-stat {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 16px 24px;
padding: 16px 22px;
border: 1px solid var(–ghc-card-border);
border-radius: 12px;
/* Greenish grey to match the AI gateway diagram canvas (–ghd-canvas). */
background: #f2f5f3;
margin: 0 0 18px;
}
.ghchart__tl-stat-main { display: flex; align-items: center; gap: 20px; }
.ghchart__tl-stat-col { display: flex; flex-direction: column; gap: 2px; }
.ghchart__tl-stat-k {
font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
text-transform: uppercase; color: var(–ghc-muted);
}
.ghchart__tl-stat-v {
font-size: clamp(28px, 4.4vw, 40px); font-weight: 800;
letter-spacing: -0.02em; line-height: 1; color: var(–ghc-text);
}
.ghchart__tl-stat-v–muted { color: var(–ghc-muted); font-weight: 700; }
.ghchart__tl-stat-arrow { font-size: 26px; color: var(–ghc-muted); }
.ghchart__tl-stat-delta { text-align: right; }
.ghchart__tl-stat-delta-v {
font-size: clamp(26px, 4vw, 38px); font-weight: 800;
letter-spacing: -0.02em; line-height: 1; color: #1a7f37;
}
.ghchart__tl-stat-delta-k { font-size: 13px; color: var(–ghc-muted); margin-top: 4px; }
/* Stage legend (2-up, monospace-free, stacks on mobile). */
.ghchart__tl-legend {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 28px;
margin: 0 0 20px;
}
.ghchart__tl-legend-item { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
.ghchart__tl-swatch { width: 13px; height: 13px; border-radius: 3px; flex: 0 0 auto; }
.ghchart__tl-legend-text {
font-size: clamp(13px, 1.7vw, 15px); color: var(–ghc-text);
font-variant-numeric: tabular-nums;
}
.ghchart__tl-rule {
height: 1.5px; border-radius: 1.5px; background: var(–ghc-divider); margin: 4px 0 18px;
}
/* SVG plot text. */
.ghchart__tl-rowlabel { fill: var(–ghc-text); font-size: 22px; font-weight: 700; letter-spacing: 0.02em; }
.ghchart__tl-rowsub { fill: var(–ghc-muted); font-size: 18px; font-weight: 500; }
.ghchart__tl-seg-label { fill: #ffffff; font-size: 23px; font-weight: 700; letter-spacing: -0.01em; }
.ghchart__tl-callout-line { stroke: var(–ghc-guide); stroke-width: 1.5; fill: none; }
.ghchart__tl-callout-label { font-size: 21px; font-weight: 600; letter-spacing: -0.01em; }
/* Segments + ribbons ease their opacity for the before/after hover emphasis. */
.ghchart__tl-seg { transition: opacity 0.14s ease; }
.ghchart__tl-seg[data-stage] { cursor: pointer; }
.ghchart__tl-seg[data-stage]:focus-visible { outline: 2px solid var(–ghc-axis); outline-offset: 1px; }
.ghchart__tl-flow { opacity: 0.16; transition: opacity 0.14s ease; }
/* Timeline millisecond gridlines: thicker + larger dashes so the axis marks
read clearly behind the bars (scoped so scatter gridlines are unaffected). */
.ghchart__plot–timeline .ghchart__gridline–v {
stroke-width: 1.5;
stroke-dasharray: 5 5;
opacity: 1;
}
/* Timeline bracket labels read larger than the trend bracket note (they are
structural spans, not a small secondary annotation). */
.ghchart__tl-bracket-label { font-size: 20px; }
/* Wrapper so DOM labels can sit over the SVG at viewBox-proportional %s. */
.ghchart__tl-plotwrap { position: relative; }
/* Right-bracket label as a DOM node: a fixed, readable size that does NOT
scale down with the plot on small windows (unlike SVG text). */
.ghchart__tl-bracket-label-dom {
position: absolute;
transform: translateY(-50%);
font-size: clamp(12px, 1.5vw, 14px);
font-weight: 700;
letter-spacing: 0.02em;
color: var(–ghc-muted);
white-space: nowrap;
pointer-events: none;
}
.ghchart__tl-tick { fill: var(–ghc-axis); }
/* Axis title rendered as a DOM caption below the plot; matches the legend
labels’ size/weight/color instead of scaling with the SVG. */
.ghchart__tl-axis-caption {
font-size: clamp(13px, 1.7vw, 15px);
font-weight: 400;
color: var(–ghc-text);
text-align: center;
margin-top: 8px;
}
@media (max-width: 560px) {
.ghchart__tl-legend { grid-template-columns: 1fr; }
.ghchart__tl-stat { justify-content: flex-start; }
.ghchart__tl-stat-delta { text-align: left; }
}
/* —————————————————————— */
/* Calendar heatmap (GitHub contribution-graph style) */
/* —————————————————————— */
.ghheat__eyebrow { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; }
.ghheat__eyedot {
width: 9px; height: 9px; border-radius: 50%;
background: #cf222e; box-shadow: 0 0 0 3px rgba(207, 34, 46, 0.14);
flex: 0 0 auto;
}
.ghheat__eyetext {
font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
text-transform: uppercase; color: #cf222e;
}
/* Subtitle here is a prominent lede, not the muted variant. */
.ghheat__subtitle {
color: var(–ghc-text);
font-size: clamp(14px, 1.7vw, 16px);
/* 24px top gap matches the github.blog H2 → paragraph block spacing. */
margin: 24px 0 8px;
}
.ghheat__subtitle b { font-weight: 700; }
.ghheat__panel { margin-top: 36px; }
.ghheat__phead {
display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
margin-bottom: 16px;
}
.ghheat__badge {
display: inline-flex; align-items: center; justify-content: center;
width: 22px; height: 22px; border-radius: 50%;
color: #fff; font-size: 12px; font-weight: 700;
flex: 0 0 auto; align-self: center;
}
.ghheat__ptitle {
font-size: clamp(16px, 2vw, 19px); font-weight: 700;
letter-spacing: -0.01em; color: var(–ghc-text);
}
.ghheat__pdesc { font-size: 14px; color: var(–ghc-muted); }
.ghheat__body { display: flex; gap: 30px; align-items: flex-start; flex-wrap: wrap; }
.ghheat__gridwrap { flex: 1 1 340px; min-width: 260px; }
.ghheat__grid { display: block; width: 100%; height: auto; overflow: visible; }
.ghheat__cell {
stroke: rgba(27, 31, 36, 0.06);
stroke-width: 1;
transition: stroke 0.1s ease;
}
.ghheat__cell:hover { stroke: #24292f; stroke-width: 1.4; }
.ghheat__cell–pad { opacity: 0; pointer-events: none; }
.ghheat__wd, .ghheat__month {
fill: var(–ghc-muted); font-size: 11px; font-weight: 600;
}
.ghheat__side { flex: 1 1 250px; min-width: 230px; }
.ghheat__stat { display: flex; align-items: baseline; gap: 7px; }
.ghheat__statbig {
font-size: clamp(26px, 4vw, 34px); font-weight: 800; letter-spacing: -0.02em;
line-height: 1;
}
.ghheat__statsmall { font-size: 15px; font-weight: 600; color: var(–ghc-muted); }
.ghheat__note {
margin: 10px 0 16px; font-size: 14px; line-height: 1.55; color: var(–ghc-muted);
}
.ghheat__note b { color: var(–ghc-text); font-weight: 700; }
.ghheat__legend {
display: flex; align-items: center; gap: 5px;
font-size: 12.5px; color: var(–ghc-muted); flex-wrap: wrap;
}
.ghheat__sw { width: 12px; height: 12px; border-radius: 3px; display: inline-block; flex: 0 0 auto; }
.ghheat__sw–empty { border: 1px solid #d0d7de; }
.ghheat__legunit { margin-left: 2px; }
.ghheat__extra {
display: flex; align-items: flex-start; gap: 7px; margin-top: 14px;
font-size: 12px; line-height: 1.5; color: var(–ghc-muted); max-width: 330px;
}
.ghheat__extra .ghheat__sw { margin-top: 2px; }
/* —————————————————————— */
/* Tooltip */
/* —————————————————————— */
.ghchart__tooltip {
position: absolute;
z-index: 5;
top: 0;
left: 0;
/* `width: max-content` keeps the box sized to its own content instead of
the browser’s absolute-position “shrink-to-fit” formula, which derives
available width from how far `left` sits from the containing block’s
edge — near the right edge that shrinks toward zero and forces the
row text to wrap awkwardly even though the box is later clamped into
view. max-content is unaffected by `left`, so wrapping only happens if
content truly exceeds max-width. */
width: max-content;
min-width: 128px;
max-width: 260px;
padding: 10px 12px;
background: var(–ghc-card-bg);
border: 1px solid var(–ghc-card-border);
border-radius: 12px;
/* Layered drop shadow from Primer Brand’s ActionMenu (.ActionMenu__menu) */
box-shadow:
0px 100px 80px rgba(0, 0, 0, 0.01),
0px 41px 33px rgba(0, 0, 0, 0.02),
0px 22px 17px rgba(0, 0, 0, 0.02),
0px 12px 10px rgba(0, 0, 0, 0.03),
0px 6px 5px rgba(0, 0, 0, 0.04),
0px 2px 2px rgba(0, 0, 0, 0.07);
pointer-events: none;
opacity: 0;
transform: translate(-50%, -100%);
/* Fade in on show and out on hide; quickly ease left/top when tracking
from one point to the next. The position ease is suppressed on the
first appearance (see chart.js) so the tooltip fades in place rather
than sliding in from its previous spot. */
transition: opacity 0.12s ease,
left 0.1s cubic-bezier(0.4, 0, 0.2, 1),
top 0.1s cubic-bezier(0.4, 0, 0.2, 1);
will-change: opacity, transform, left, top;
}
.ghchart__tooltip[data-visible=”true”] { opacity: 1; }
.ghchart__tt-title {
font-size: 13px;
font-weight: 700;
color: var(–ghc-text);
margin-bottom: 6px;
}
.ghchart__tt-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
line-height: 1.6;
color: var(–ghc-muted);
}
.ghchart__tt-swatch { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.ghchart__tt-name { flex: 1 1 auto; }
.ghchart__tt-value { font-weight: 700; color: var(–ghc-text); font-variant-numeric: tabular-nums; }
/* Optional “partial data” note, shown only for a hovered point past a
line’s config.partial.fromIndex (see .ghchart__line–dashed). */
.ghchart__tt-note {
margin-top: 6px;
padding-top: 6px;
border-top: 1px solid var(–ghc-card-border);
font-size: 12px;
font-style: italic;
color: var(–ghc-muted);
}
/* Bar tooltip body: a title line + free-form text lines (no swatches). */
.ghchart__tt-line {
font-size: 13px;
line-height: 1.5;
color: var(–ghc-muted);
}
.ghchart__tt-line:first-child { color: var(–ghc-text); font-weight: 600; }
.ghchart__tt-line + .ghchart__tt-line { margin-top: 2px; }
/* Timeline before/after tooltip: only the title is bold; both value lines stay
regular weight and the same color (no emphasised first line). */
.ghchart__tt-body–plain .ghchart__tt-line,
.ghchart__tt-body–plain .ghchart__tt-line:first-child {
font-weight: 400;
color: var(–ghc-text);
}
@media (prefers-reduced-motion: reduce) {
.ghchart__dot,
.ghchart__guide,
.ghchart__bar,
.ghchart__pt,
.ghchart__square,
.ghheat__cell,
.ghchart__tl-seg,
.ghchart__tl-flow,
.ghchart__tooltip { transition: none; }
}
/*
Shared chart engine — reusable renderer for the blog-charts library.
——————————————————————
Exposes window.GHChartEngine = { renderers, render(config, mountEl?), destroy(mountEl) }.
Renderers are keyed by config.type (currently “line”); add a new viz type
by registering GHChartEngine.renderers[] = function (root, config) {…}.
This same file is embedded verbatim at the top of each exported WordPress
chart.js, followed by that chart’s CONFIG + a small auto-boot. It has no
external dependencies and is safe to include more than once on a page.
*/
(function (global) {
“use strict”;
var SVGNS = “http://www.w3.org/2000/svg”;
function el(tag, attrs) {
var node = document.createElementNS(SVGNS, tag);
if (attrs) {
for (var k in attrs) {
if (Object.prototype.hasOwnProperty.call(attrs, k)) {
node.setAttribute(k, attrs[k]);
}
}
}
return node;
}
function html(tag, cls) {
var node = document.createElement(tag);
if (cls) node.className = cls;
return node;
}
function clamp(v, lo, hi) { return v hi ? hi : v; }
var WHITE = [255, 255, 255], BLACK = [0, 0, 0];
function hexToRgb(h) {
h = String(h).replace(“#”, “”);
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
}
function rgbToHex(c) {
return “#” + c.map(function (x) {
x = Math.max(0, Math.min(255, Math.round(x)));
return (x < 16 ? "0" : "") + x.toString(16);
}).join("");
}
function mix(a, b, t) {
return [a[0] + (b[0] – a[0]) * t, a[1] + (b[1] – a[1]) * t, a[2] + (b[2] – a[2]) * t];
}
function rampFrom(baseHex) {
var b = hexToRgb(baseHex);
return [
rgbToHex(mix(b, WHITE, 0.80)),
rgbToHex(mix(b, WHITE, 0.52)),
rgbToHex(mix(b, WHITE, 0.20)),
baseHex,
rgbToHex(mix(b, BLACK, 0.30))
];
}
function strongOf(baseHex) { return rampFrom(baseHex)[4]; }
function labelColorOn(hex) {
function luminance(color) {
var c = hexToRgb(color).map(function (channel) {
channel /= 255;
return channel = 4.5) return “#1f2328”;
return 1.05 / (background + 0.05) >= 4.5 ? “#ffffff” : “#000000”;
}
var renderers = {};
var renderCleanups = (global.GHChartEngine && global.GHChartEngine._renderCleanups) || new WeakMap();
renderers.comparison = function (root, config) {
var axis = config.xAxis, series = config.series, rows = config.rows;
if (!axis || !Number.isFinite(axis.min) || !Number.isFinite(axis.max) ||
axis.min > 0 || axis.max < 0 || axis.max <= axis.min ||
!Array.isArray(axis.ticks) || !Array.isArray(series) || !series.length ||
series.some(function (s) { return !s || typeof s.color !== "string" || !/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(s.color); }) ||
!Array.isArray(rows) || !rows.length ||
axis.ticks.some(function (v) { return !Number.isFinite(v) || v axis.max; }) ||
rows.some(function (row) {
return !Array.isArray(row.values) || row.values.length !== series.length ||
row.values.some(function (v) { return !Number.isFinite(v) || v axis.max; }) ||
!Array.isArray(row.decimals) || row.decimals.length !== series.length ||
row.decimals.some(function (n) { return !Number.isInteger(n) || n 10; });
})) throw new Error(“Comparison chart requires a zero-inclusive axis, hex series colors, and matching finite values/decimal precision for each series.”);
root.textContent = “”;
root.setAttribute(“role”, “figure”);
root.setAttribute(“aria-label”, config.title);
var heading = html(“div”, “ghchart__headline”);
heading.textContent = config.title;
root.appendChild(heading);
if (config.subtitle) {
var subtitle = html(“p”, “ghchart__subtitle”);
subtitle.textContent = config.subtitle;
root.appendChild(subtitle);
}
var legend = html(“div”, “ghchart__legend ghchart__legend–trend”);
series.forEach(function (s) {
var item = html(“span”, “ghchart__legend-item”);
var swatch = html(“span”, “ghchart__swatch”);
swatch.style.background = s.color;
item.appendChild(swatch);
item.appendChild(document.createTextNode(s.name));
legend.appendChild(item);
});
root.appendChild(legend);
var marks = [];
function at(value) { return (value – axis.min) / (axis.max – axis.min) * 100; }
function signed(value, decimals) { return (value > 0 ? “+” : “”) + value.toFixed(decimals) + “%”; }
rows.forEach(function (row) {
var group = html(“div”, “ghchart__comparison-row”);
group.setAttribute(“role”, “group”);
group.setAttribute(“aria-label”, row.label);
var label = html(“div”, “ghchart__comparison-label”);
label.textContent = row.label;
label.setAttribute(“aria-hidden”, “true”);
group.appendChild(label);
var plot = html(“div”, “ghchart__comparison-plot”);
axis.ticks.forEach(function (value) {
var line = html(“span”, “ghchart__comparison-grid”);
line.style.left = at(value) + “%”;
line.setAttribute(“aria-hidden”, “true”);
plot.appendChild(line);
});
var zero = html(“span”, “ghchart__comparison-grid ghchart__comparison-zero”);
zero.style.left = at(0) + “%”;
zero.setAttribute(“aria-hidden”, “true”);
plot.appendChild(zero);
series.forEach(function (s, index) {
var value = row.values[index], formatted = signed(value, row.decimals[index]);
var track = html(“div”, “ghchart__comparison-track”);
track.setAttribute(“role”, “group”);
track.setAttribute(“aria-label”, s.name);
var bar = html(“span”, “ghchart__comparison-bar”);
// Anchor the zero-facing edge, without fractional transform snapping.
if (value < 0) {
bar.style.right = "calc(100% – " + at(0) + "%)";
bar.classList.add("ghchart__comparison-bar–negative");
} else bar.style.left = at(0) + "%";
bar.style.width = Math.abs(at(value) – at(0)) + "%";
bar.style.background = s.color;
bar.setAttribute("aria-hidden", "true");
var valueLabel = html("span", "ghchart__comparison-value");
valueLabel.textContent = formatted;
track.appendChild(bar);
track.appendChild(valueLabel);
marks.push({ track: track, bar: bar, label: valueLabel, value: value, color: s.color });
plot.appendChild(track);
});
group.appendChild(plot);
root.appendChild(group);
});
var axisRow = html("div", "ghchart__comparison-row");
axisRow.appendChild(html("span"));
var ticks = html("div", "ghchart__comparison-axis");
axis.ticks.forEach(function (value) {
var tick = html("span", "ghchart__comparison-tick");
tick.style.left = at(value) + "%";
if (value === axis.min) tick.style.transform = "none";
if (value === axis.max) tick.style.transform = "translateX(-100%)";
tick.textContent = value === 0 ? "0" : signed(value, 0);
ticks.appendChild(tick);
});
axisRow.appendChild(ticks);
root.appendChild(axisRow);
var active = true, frame = 0, observer;
function positionLabel(mark, side, coordinate, placement) {
mark.label.style.left = side === "left" ? "calc(" + at(coordinate) + "% + 8px)" : "auto";
mark.label.style.right = side === "right" ? "calc(100% – " + at(coordinate) + "% + 8px)" : "auto";
mark.label.style.color = placement === "inside" ? labelColorOn(mark.color) : "";
mark.label.setAttribute("data-placement", placement);
}
function layoutValues() {
if (!active || !root.clientWidth) return;
marks.forEach(function (mark) {
mark.track.classList.remove("ghchart__comparison-track–stacked");
mark.track.style.height = "";
mark.bar.style.top = "";
mark.label.style.left = "0px";
mark.label.style.right = "auto";
mark.label.style.color = "";
var width = mark.track.clientWidth, textWidth = mark.label.offsetWidth + 1;
var zero = at(0) * width / 100, end = at(mark.value) * width / 100;
var negative = mark.value = textWidth + 8) {
positionLabel(mark, negative ? “right” : “left”, mark.value, “outside”);
} else if (Math.abs(end – zero) >= textWidth + 16) {
positionLabel(mark, negative ? “left” : “right”, mark.value, “inside”);
} else if (opposite >= textWidth + 8) {
positionLabel(mark, negative ? “left” : “right”, 0, “opposite”);
} else {
// Very narrow plots or long precision strings get a separate label line.
mark.track.classList.add(“ghchart__comparison-track–stacked”);
mark.label.setAttribute(“data-placement”, “stacked”);
var top = mark.label.offsetHeight + 6;
mark.bar.style.top = top + “px”;
mark.track.style.height = top + 28 + “px”;
}
});
}
function schedule() {
if (!active) return;
cancelAnimationFrame(frame);
frame = requestAnimationFrame(layoutValues);
}
layoutValues();
if (global.ResizeObserver) {
observer = new ResizeObserver(schedule);
observer.observe(root);
} else global.addEventListener(“resize”, schedule);
if (document.fonts) {
document.fonts.ready.then(schedule);
document.fonts.addEventListener(“loadingdone”, schedule);
}
return function () {
active = false;
cancelAnimationFrame(frame);
if (observer) observer.disconnect();
global.removeEventListener(“resize”, schedule);
if (document.fonts) document.fonts.removeEventListener(“loadingdone”, schedule);
};
};
/* ================================================================
Line chart renderer
================================================================ */
renderers.line = function (root, config) {
// viewBox geometry (design units; scales fluidly to any width).
var VB = { w: 1000, h: 560 };
var M = { top: 30, right: 34, bottom: 56, left: 86 };
// Inset the plotted points slightly from the axis so the first/last
// months (e.g. “Jan”) don’t crowd the y-axis labels (e.g. “50”).
var PAD_X = 18;
// Reset (in case of re-render) and use a unique id prefix so multiple
// charts on one page never share gradient ids.
root.textContent = “”;
var uid = config.mountId;
var n = config.labels.length;
var yMin = config.yAxis.min;
var yMax = config.yAxis.max;
var step = config.yAxis.step;
// Y-axis tick labels can format differently from the tooltip values
// (e.g. a plain “40” on the axis but “40%” in the tooltip).
var fmtAxis = config.formatAxis || config.formatValue;
// Widen the left gutter when the formatted tick labels need it (a unit
// suffix like “2,000M” is far wider than a bare “40”). Labels are real
// SVG we can’t measure before layout, so estimate from the
// glyph mix at the 22-unit axis font size.
var AXIS_FONT = 22;
var Y_LABEL_GAP = 22;
function estimateTextW(s) {
var w = 0;
for (var i = 0; i < s.length; i++) {
var c = s.charAt(i);
if (c === "," || c === "." || c === " ") w += 0.28;
else if (/[0-9]/.test(c)) w += 0.58;
else if (/[A-Z]/.test(c)) w += 0.72;
else w += 0.55;
}
return w * AXIS_FONT;
}
var widestY = 0;
for (var tv = yMin; tv <= yMax + 0.0001; tv += step) {
widestY = Math.max(widestY, estimateTextW(String(fmtAxis(tv))));
}
M.left = Math.max(M.left, Math.ceil(widestY + Y_LABEL_GAP + 8));
var innerW = VB.w – M.left – M.right;
var innerH = VB.h – M.top – M.bottom;
var plotW = innerW – 2 * PAD_X;
var stepX = n <= 1 ? 0 : plotW / (n – 1);
function xAt(i) { return n <= 1 ? M.left + innerW / 2 : M.left + PAD_X + stepX * i; }
function yAt(v) { return M.top + innerH * (1 – (v – yMin) / (yMax – yMin)); }
var baseY = yAt(yMin);
/* —- Title + divider (top) ———————————- */
var title = html("span", "ghchart__title");
title.textContent = config.title;
root.appendChild(title);
root.appendChild(html("div", "ghchart__rule"));
/* —- Header + legend —————————————- */
if (!config.hideLegend) {
var header = html("div", "ghchart__header");
var legend = html("div", "ghchart__legend");
config.series.forEach(function (s) {
var item = html("span", "ghchart__legend-item");
var sw = html("span", "ghchart__swatch");
sw.style.background = s.color;
item.appendChild(sw);
item.appendChild(document.createTextNode(s.name));
legend.appendChild(item);
});
header.appendChild(legend);
root.appendChild(header);
}
/* —- SVG plot ———————————————– */
var svg = el("svg", {
"class": "ghchart__plot",
viewBox: "0 0 " + VB.w + " " + VB.h,
preserveAspectRatio: "xMidYMid meet",
role: "img",
"aria-label": config.title + " line chart"
});
// Gradient defs (one per area series).
var defs = el("defs");
config.series.forEach(function (s, si) {
if (!s.area) return;
var g = el("linearGradient", {
id: uid + "-grad-" + si, x1: "0", y1: "0", x2: "0", y2: "1"
});
g.appendChild(el("stop", { offset: "0", "stop-color": s.color, "stop-opacity": "0.28" }));
g.appendChild(el("stop", { offset: "0.55", "stop-color": s.color, "stop-opacity": "0.08" }));
g.appendChild(el("stop", { offset: "1", "stop-color": s.color, "stop-opacity": "0" }));
defs.appendChild(g);
});
// Diagonal-hatch pattern for an optional "partial / projected" band.
if (config.projection) {
var pat = el("pattern", {
id: uid + "-hatch", width: 7, height: 7,
patternUnits: "userSpaceOnUse", patternTransform: "rotate(45)"
});
pat.appendChild(el("line", {
x1: 0, y1: 0, x2: 0, y2: 7,
stroke: config.projection.color || "#8c959f",
"stroke-width": 1.1, "stroke-opacity": 0.5
}));
defs.appendChild(pat);
}
svg.appendChild(defs);
// Gridlines + y-axis labels.
for (var v = yMin; v cubic Bezier) path builder.
function smooth(points) {
if (!points.length) return “”;
if (points.length < 2) return "M" + points[0].x + "," + points[0].y;
var d = "M " + points[0].x + "," + points[0].y;
for (var i = 0; i < points.length – 1; i++) {
var p0 = points[i – 1] || points[i];
var p1 = points[i];
var p2 = points[i + 1];
var p3 = points[i + 2] || p2;
var t = 1 / 6;
var c1x = p1.x + (p2.x – p0.x) * t;
var c1y = p1.y + (p2.y – p0.y) * t;
var c2x = p2.x – (p3.x – p1.x) * t;
var c2y = p2.y – (p3.y – p1.y) * t;
d += " C " + c1x + "," + c1y + " " + c2x + "," + c2y + " " + p2.x + "," + p2.y;
}
return d;
}
// Area fills (drawn first, behind lines). When config.partial marks a
// trailing partial/incomplete range (e.g. a month still in progress),
// the fill stops at the last complete point instead of extending under
// the partial segment — visually distinguishing "real" coverage from
// data that isn't final yet.
var partialIdx = config.partial ? config.partial.fromIndex : null;
config.series.forEach(function (s, si) {
if (!s.area) return;
var p = pts[si];
var fillP = partialIdx == null ? p : p.slice(0, partialIdx + 1);
if (fillP.length < 2) return;
var d = smooth(fillP) + " L " + fillP[fillP.length – 1].x + "," + baseY +
" L " + fillP[0].x + "," + baseY + " Z";
svg.appendChild(el("path", { d: d, fill: "url(#" + uid + "-grad-" + si + ")" }));
});
// Vertical hover guide (hidden until hover).
var guide = el("line", {
"class": "ghchart__guide",
x1: xAt(0), y1: M.top – 6, x2: xAt(0), y2: baseY
});
svg.appendChild(guide);
// Lines (draw in reverse so the first series sits on top). The trailing
// partial segment (from the last complete point onward) renders as its
// own dashed path so the "not final yet" range reads clearly without
// a legend entry — mirroring the projected-region convention above.
config.series.slice().reverse().forEach(function (s) {
var si = config.series.indexOf(s);
var p = pts[si];
var solidP = partialIdx == null ? p : p.slice(0, partialIdx + 1);
svg.appendChild(el("path", {
"class": "ghchart__line",
d: smooth(solidP),
stroke: s.color,
"stroke-width": s.width || (si === 0 ? 4 : 3.25)
}));
if (partialIdx != null && partialIdx < p.length – 1) {
// Straight (not curve-fit) dashed segment: fitting a Catmull-Rom
// curve to just this short tail would use different neighbor
// context than the solid curve and create a visible kink at the
// junction point; a straight dashed line reads as "projected" and
// joins the solid curve's end point cleanly.
var dashedP = p.slice(partialIdx);
var dd = "M " + dashedP[0].x + "," + dashedP[0].y;
var segLen = 0;
for (var di = 1; di 0 && segLen / PERIOD partialIdx) ? “” : “none”;
}
// Anchor above the highest point in this column.
var topSi = 0, topY = Infinity;
config.series.forEach(function (s, si) {
var yy = yAt(s.values[i]);
if (yy < topY) { topY = yy; topSi = si; }
});
var anchor = dotEls[topSi][i].getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
// On the first appearance, fade in exactly at the point: suppress the
// position ease so it doesn't slide in from its previous resting spot.
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = "opacity 0.12s ease";
tip.setAttribute("data-visible", "true");
// Reset left before measuring: a browser sizes an absolutely
// positioned auto/max-content-width box using the *current* `left`
// to derive available space, so measuring while `left` still holds
// the previous (possibly near-edge) position creates a feedback loop
// that keeps squeezing the tooltip narrower and wrapping its rows.
// Setting left to 0 first guarantees a true, unconstrained natural
// width; this happens synchronously before the next paint, so
// there's no visible flash.
tip.style.left = "0px";
// Clamp horizontally within the container.
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
// Flip below the point if there's no room above.
if (top – tip.offsetHeight < 0) {
top = anchor.bottom – rootRect.top + 12;
tip.style.transform = "translate(-50%, 0)";
} else {
tip.style.transform = "translate(-50%, -100%)";
}
tip.style.left = left + "px";
tip.style.top = top + "px";
// Commit that position without animating, then restore the CSS
// transition so subsequent point-to-point moves ease into place.
if (suppress) {
void tip.offsetWidth;
tip.style.transition = "";
}
}
function hide() {
activeIndex = -1;
guide.style.opacity = "0";
tip.setAttribute("data-visible", "false");
dotEls.forEach(function (col, si) {
col.forEach(function (c) { c.setAttribute("r", baseR[si]); });
});
}
hit.addEventListener("pointermove", function (e) { show(indexFromClientX(e.clientX)); });
hit.addEventListener("pointerdown", function (e) { show(indexFromClientX(e.clientX)); });
hit.addEventListener("pointerleave", hide);
// Keyboard access.
svg.setAttribute("tabindex", "0");
svg.addEventListener("keydown", function (e) {
if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
var start = activeIndex { title, lines:[…] } }
No printed value labels — values surface on hover (consistent with
the line chart). Bars use a single tunable color with rounded tops.
================================================================ */
renderers.bar = function (root, config) {
root.textContent = “”;
var barColor = (config.series && config.series[0] && config.series[0].color) || “#ff7b72”;
var barRadius = config.barRadius != null ? config.barRadius : 8;
// Per-bar fill: a bar may point at a different series (e.g. a muted
// “preliminary” color) via panel.barSeries[index]; otherwise series[0].
function barColorFor(panel, j) {
var si = (panel.barSeries && panel.barSeries[j] != null) ? panel.barSeries[j] : 0;
var s = config.series && config.series[si];
return (s && s.color) || barColor;
}
var motionMQ = window.matchMedia
? window.matchMedia(“(prefers-reduced-motion: reduce)”)
: null;
function reduceMotion() { return !!(motionMQ && motionMQ.matches); }
/* —- Title + divider (+ optional subtitle) —————— */
var title = html(“span”, “ghchart__title”);
title.textContent = config.title;
root.appendChild(title);
root.appendChild(html(“div”, “ghchart__rule”));
if (config.subtitle) {
var sub = html(“p”, “ghchart__subtitle”);
sub.textContent = config.subtitle;
root.appendChild(sub);
}
/* —- One shared tooltip for all panels ———————- */
var tip = html(“div”, “ghchart__tooltip”);
tip.setAttribute(“role”, “status”);
var tipTitle = html(“div”, “ghchart__tt-title”);
var tipBody = html(“div”, “ghchart__tt-body”);
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var allBars = []; // { el, pi, bi }
var activeKey = null; // “pi:bi” of the hovered bar
function hideTip() {
activeKey = null;
tip.setAttribute(“data-visible”, “false”);
allBars.forEach(function (b) { b.el.style.opacity = “1”; });
}
// Hoisted: referenced by the per-column pointer handlers below.
function showTip(pi, bi, panel, panelBars) {
var reappearing = activeKey === null;
activeKey = pi + “:” + bi;
allBars.forEach(function (b) {
b.el.style.opacity = (b.pi === pi && b.bi === bi) ? “1” : “0.42”;
});
var content = panel.tooltip
? panel.tooltip(bi, panel)
: { title: (panel.labels || [])[bi],
lines: [ (panel.formatValue || String)(panel.values[bi]) ] };
tipTitle.textContent = content.title != null ? content.title : “”;
tipBody.textContent = “”;
(content.lines || []).forEach(function (ln) {
var row = html(“div”, “ghchart__tt-line”);
row.textContent = ln;
tipBody.appendChild(row);
});
var anchor = panelBars[bi].el.getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = “opacity 0.12s ease”;
tip.setAttribute(“data-visible”, “true”);
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight 0) {
for (var v = yMin; v <= yMax + 1e-9; v += step) {
var gy = yAt(v);
svg.appendChild(el("line", {
"class": "ghchart__gridline", x1: M.left, y1: gy, x2: VB.w – M.right, y2: gy
}));
var yl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–y",
x: M.left – 18, y: gy, dy: "0.32em", "text-anchor": "end"
});
yl.textContent = fmt(v);
svg.appendChild(yl);
}
}
// Solid baseline.
svg.appendChild(el("line", {
"class": "ghchart__axisline", x1: M.left, y1: baseY, x2: VB.w – M.right, y2: baseY
}));
// x-axis labels.
for (var i = 0; i < nb; i++) {
var xl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–x",
x: xCenter(i), y: VB.h – M.bottom + 30, "text-anchor": "middle"
});
if (panel.xLabelSize) xl.setAttribute("style", "font-size:" + panel.xLabelSize + "px");
xl.textContent = labels[i];
svg.appendChild(xl);
}
// Bars (rounded top corners only) + full-height hover columns.
var panelBars = [];
for (var j = 0; j dark ramp is derived from a single tunable base color.
================================================================ */
renderers.heatmap = function (root, config) {
root.textContent = “”;
function trim0(s) { return s.replace(/.0$/, “”); }
function fmtM(v) {
if (v >= 1000) { var g = v / 1000; return (g >= 10 ? Math.round(g) : trim0(g.toFixed(1))) + “B”; }
if (v >= 1) return trim0(v.toFixed(1)) + “M”;
return Math.round(v * 1000) + “K”;
}
var MON = [“Jan”, “Feb”, “Mar”, “Apr”, “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”];
var MON_FULL = [“January”, “February”, “March”, “April”, “May”, “June”,
“July”, “August”, “September”, “October”, “November”, “December”];
var WD = [“Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”];
var year = config.year || 2026;
var startM = config.startMonth != null ? config.startMonth : 0;
var endM = config.endMonth != null ? config.endMonth : 5;
var emptyColor = config.emptyColor || “#ebedf0”;
function daysInMonth(y, m) { return new Date(y, m + 1, 0).getDate(); }
function parseISO(s) { var p = s.split(“-“); return new Date(+p[0], +p[1] – 1, +p[2]); }
function dayStart(dt) { return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate()); }
/* —- Eyebrow + title + subtitle —————————– */
if (config.eyebrow) {
var eb = html(“div”, “ghheat__eyebrow”);
eb.appendChild(html(“span”, “ghheat__eyedot”));
var ebt = html(“span”, “ghheat__eyetext”);
ebt.textContent = config.eyebrow;
eb.appendChild(ebt);
root.appendChild(eb);
}
var title = html(“span”, “ghchart__title”);
title.textContent = config.title;
root.appendChild(title);
if (config.subtitle) {
var sub = html(“p”, “ghchart__subtitle ghheat__subtitle”);
sub.innerHTML = config.subtitle;
root.appendChild(sub);
}
/* —- One shared tooltip ————————————– */
var tip = html(“div”, “ghchart__tooltip”);
tip.setAttribute(“role”, “status”);
var tipTitle = html(“div”, “ghchart__tt-title”);
var tipBody = html(“div”, “ghchart__tt-body”);
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var motionMQ = window.matchMedia ? window.matchMedia(“(prefers-reduced-motion: reduce)”) : null;
function reduceMotion() { return !!(motionMQ && motionMQ.matches); }
var activeCell = null;
function hideTip() { activeCell = null; tip.setAttribute(“data-visible”, “false”); }
function showTip(rectEl, titleText, lines) {
var reappearing = activeCell === null;
activeCell = rectEl;
tipTitle.textContent = titleText;
tipBody.textContent = “”;
lines.forEach(function (ln) {
var r = html(“div”, “ghchart__tt-line”);
r.innerHTML = ln;
tipBody.appendChild(r);
});
var a = rectEl.getBoundingClientRect(), rr = root.getBoundingClientRect();
var left = a.left + a.width / 2 – rr.left, top = a.top – rr.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = “opacity 0.12s ease”;
tip.setAttribute(“data-visible”, “true”);
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight < 0) {
top = a.bottom – rr.top + 12;
tip.style.transform = "translate(-50%, 0)";
} else {
tip.style.transform = "translate(-50%, -100%)";
}
tip.style.left = left + "px";
tip.style.top = top + "px";
if (suppress) { void tip.offsetWidth; tip.style.transition = ""; }
}
/* —- Panels ————————————————– */
(config.panels || []).forEach(function (panel, pi) {
var base = (config.series && config.series[pi] && config.series[pi].color) || panel.base || "#3094ff";
var ramp = rampFrom(base);
var strong = ramp[4];
var totals = panel.months || [];
var activeVals = [];
for (var mi = startM; mi <= endM; mi++) { if (totals[mi] != null) activeVals.push(totals[mi]); }
var vmin = activeVals.length ? Math.min.apply(null, activeVals) : 0;
var vmax = activeVals.length ? Math.max.apply(null, activeVals) : 1;
function levelFor(t) {
if (t == null) return -1;
if (vmax === vmin) return 4;
return Math.round((t – vmin) / (vmax – vmin) * 4);
}
var af = panel.activeFrom ? parseISO(panel.activeFrom) : new Date(year, startM, 1);
var at = panel.activeTo ? parseISO(panel.activeTo) : new Date(year, endM, daysInMonth(year, endM));
var wrap = html("div", "ghheat__panel");
var head = html("div", "ghheat__phead");
var badge = html("span", "ghheat__badge");
badge.textContent = panel.badge != null ? panel.badge : (pi + 1);
badge.style.background = strong;
head.appendChild(badge);
var pt = html("span", "ghheat__ptitle");
pt.textContent = panel.title;
head.appendChild(pt);
if (panel.descriptor) {
var pd = html("span", "ghheat__pdesc");
pd.textContent = panel.descriptor;
head.appendChild(pd);
}
wrap.appendChild(head);
var body = html("div", "ghheat__body");
/* —- Calendar grid (SVG) —- */
var gridStart = new Date(year, startM, 1);
var gs = new Date(gridStart);
gs.setDate(gs.getDate() – gs.getDay()); // back to Sunday
var gridEnd = new Date(year, endM, daysInMonth(year, endM));
var ge = new Date(gridEnd);
ge.setDate(ge.getDate() + (6 – ge.getDay())); // forward to Saturday
var nCols = Math.round((ge – gs) / (7 * 86400000)) + 1;
var cell = 13, sq = 11, rad = 2.5, labL = 34, labT = 18;
var W = labL + nCols * cell, H = labT + 7 * cell;
var svg = el("svg", {
"class": "ghheat__grid",
viewBox: "0 0 " + W + " " + H,
preserveAspectRatio: "xMinYMin meet",
role: "img",
"aria-label": panel.title + " calendar heatmap"
});
[1, 3, 5].forEach(function (r) {
var t = el("text", {
"class": "ghheat__wd", x: labL – 6, y: labT + r * cell + sq / 2,
dy: "0.32em", "text-anchor": "end"
});
t.textContent = WD[r];
svg.appendChild(t);
});
// Month labels above the column where each month begins.
for (var lm = startM; lm <= endM; lm++) {
var first = new Date(year, lm, 1);
var colOf = Math.floor((dayStart(first) – gs) / (7 * 86400000));
var mlab = el("text", { "class": "ghheat__month", x: labL + colOf * cell, y: 12, "text-anchor": "start" });
mlab.textContent = MON[lm];
svg.appendChild(mlab);
}
var afS = dayStart(af), atS = dayStart(at);
var d = new Date(gs);
for (var i = 0; i = startM && m = afS && ds = 0 ? ramp[lvl] : emptyColor
});
if (inRange) {
(function (dt, mm, has) {
var dateStr = WD[dt.getDay()] + ” u00b7 ” + MON[mm] + ” ” + dt.getDate() + “, ” + year;
var lines;
if (has) {
var daily = totals[mm] / daysInMonth(year, mm);
lines = [
“” + fmtM(daily) + “ ” + panel.legendLabel,
MON_FULL[mm] + ” u00b7 ” + fmtM(totals[mm]) + ” ” + panel.unitTotal
];
} else {
lines = [panel.emptyNote || “No vetted data”];
}
cellEl.addEventListener(“pointermove”, function () { showTip(cellEl, dateStr, lines); });
cellEl.addEventListener(“pointerdown”, function () { showTip(cellEl, dateStr, lines); });
cellEl.addEventListener(“pointerleave”, hideTip);
})(new Date(d), m, hasData);
} else {
cellEl.setAttribute(“class”, “ghheat__cell ghheat__cell–pad”);
}
svg.appendChild(cellEl);
d.setDate(d.getDate() + 1);
}
var gridwrap = html(“div”, “ghheat__gridwrap”);
gridwrap.appendChild(svg);
body.appendChild(gridwrap);
/* —- Side column: stat + note + legend + extra —- */
var side = html(“div”, “ghheat__side”);
if (panel.stat) {
var stat = html(“div”, “ghheat__stat”);
var big = html(“span”, “ghheat__statbig”);
big.textContent = panel.stat.big;
big.style.color = strong;
stat.appendChild(big);
if (panel.stat.small) {
var small = html(“span”, “ghheat__statsmall”);
small.textContent = panel.stat.small;
stat.appendChild(small);
}
side.appendChild(stat);
}
if (panel.note) {
var note = html(“p”, “ghheat__note”);
note.innerHTML = panel.note;
side.appendChild(note);
}
var leg = html(“div”, “ghheat__legend”);
var lw1 = html(“span”, “ghheat__legword”); lw1.textContent = “Fewer”; leg.appendChild(lw1);
ramp.forEach(function (c) {
var s = html(“span”, “ghheat__sw”); s.style.background = c; leg.appendChild(s);
});
var lw2 = html(“span”, “ghheat__legword”); lw2.textContent = “More”; leg.appendChild(lw2);
if (panel.legendLabel) {
var lu = html(“span”, “ghheat__legunit”); lu.textContent = “u00b7 ” + panel.legendLabel; leg.appendChild(lu);
}
side.appendChild(leg);
if (panel.extraNote) {
var ex = html(“div”, “ghheat__extra”);
var exsw = html(“span”, “ghheat__sw ghheat__sw–empty”);
exsw.style.background = emptyColor;
ex.appendChild(exsw);
var ext = html(“span”, “ghheat__extratext”);
ext.innerHTML = panel.extraNote;
ex.appendChild(ext);
side.appendChild(ex);
}
body.appendChild(side);
wrap.appendChild(body);
root.appendChild(wrap);
});
if (config.caption) {
var cap = html(“div”, “ghchart__caption”);
cap.innerHTML = config.caption;
root.appendChild(cap);
}
};
/* ================================================================
Scatter renderer — X/Y numeric plot with categorical points.
Used for cost-vs-quality benchmark charts: a highlighted “primary”
point (glow halo), a “reference” point with a dotted crosshair, and
muted “other” points. Values never print on the marks — a point’s
name shows for identity and the exact (y, x) reveals on hover.
================================================================ */
renderers.scatter = function (root, config) {
root.textContent = “”;
var uid = config.mountId;
var VB = { w: 1000, h: 600 };
var M = { top: 40, right: 30, bottom: 74, left: 96 };
var innerW = VB.w – M.left – M.right;
var innerH = VB.h – M.top – M.bottom;
var xa = config.xAxis, ya = config.yAxis;
var fmtX = xa.format || function (v) { return String(v); };
var fmtY = ya.format || function (v) { return String(v); };
var tipSuffix = config.tipSuffix || “”;
function xAt(v) { return M.left + innerW * (v – xa.min) / (xa.max – xa.min); }
function yAt(v) { return M.top + innerH * (1 – (v – ya.min) / (ya.max – ya.min)); }
var baseY = yAt(ya.min);
var motionMQ = window.matchMedia
? window.matchMedia(“(prefers-reduced-motion: reduce)”)
: null;
function reduceMotion() { return !!(motionMQ && motionMQ.matches); }
function colorOf(pt) {
var s = config.series && config.series[pt.series || 0];
return (s && s.color) || “#8c959f”;
}
/* —- Title (compact narrative headline, no rule) ———— */
var title = html(“div”, “ghchart__headline”);
title.textContent = config.title;
root.appendChild(title);
/* —- Header + legend (from series) ————————– */
if (!config.hideLegend) {
var header = html(“div”, “ghchart__header”);
var legend = html(“div”, “ghchart__legend”);
(config.series || []).forEach(function (s) {
var item = html(“span”, “ghchart__legend-item”);
var sw = html(“span”, “ghchart__swatch ghchart__swatch–dot”);
sw.style.background = s.color;
item.appendChild(sw);
item.appendChild(document.createTextNode(s.name));
legend.appendChild(item);
});
header.appendChild(legend);
root.appendChild(header);
}
/* —- SVG plot ———————————————– */
var svg = el(“svg”, {
“class”: “ghchart__plot ghchart__plot–scatter”,
viewBox: “0 0 ” + VB.w + ” ” + VB.h,
preserveAspectRatio: “xMidYMid meet”,
role: “img”,
“aria-label”: config.title + ” scatter plot”
});
// Gridlines (both directions) + tick labels.
var eps = 1e-6;
for (var yv = ya.min; yv <= ya.max + eps; yv += ya.step) {
var gy = yAt(yv);
svg.appendChild(el("line", {
"class": "ghchart__gridline",
x1: M.left, y1: gy, x2: VB.w – M.right, y2: gy
}));
var yl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–y",
x: M.left – 16, y: gy, dy: "0.32em", "text-anchor": "end"
});
yl.textContent = fmtY(yv);
svg.appendChild(yl);
}
for (var xv = xa.min; xv <= xa.max + eps; xv += xa.step) {
var gx = xAt(xv);
svg.appendChild(el("line", {
"class": "ghchart__gridline ghchart__gridline–v",
x1: gx, y1: M.top, x2: gx, y2: baseY
}));
var isFirst = Math.abs(xv – xa.min) < eps;
var isLast = Math.abs(xv – xa.max) < eps;
var xl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–x",
x: gx, y: baseY + 34,
"text-anchor": isFirst ? "start" : (isLast ? "end" : "middle")
});
xl.textContent = fmtX(xv);
svg.appendChild(xl);
}
// Axis titles.
if (xa.title) {
var xt = el("text", {
"class": "ghchart__axistitle",
x: M.left + innerW / 2, y: VB.h – 12, "text-anchor": "middle"
});
xt.textContent = xa.title;
svg.appendChild(xt);
}
if (ya.title) {
var ytX = 24, ytY = M.top + innerH / 2;
var yt = el("text", {
"class": "ghchart__axistitle",
x: ytX, y: ytY, "text-anchor": "middle",
transform: "rotate(-90 " + ytX + " " + ytY + ")"
});
yt.textContent = ya.title;
svg.appendChild(yt);
}
// Reference crosshair through the flagged point (dual dotted lines).
var refPt = (config.points || []).filter(function (p) { return p.reference; })[0];
if (refPt) {
var rColor = colorOf(refPt);
var rx = xAt(refPt.x), ry = yAt(refPt.y);
svg.appendChild(el("line", {
"class": "ghchart__crosshair",
x1: rx, y1: M.top, x2: rx, y2: baseY, stroke: rColor
}));
svg.appendChild(el("line", {
"class": "ghchart__crosshair",
x1: M.left, y1: ry, x2: VB.w – M.right, y2: ry, stroke: rColor
}));
}
// Points: halo (highlight/reference) + dot + name label.
var dotEls = [], hitEls = [];
(config.points || []).forEach(function (pt, i) {
var cx = xAt(pt.x), cy = yAt(pt.y), c = colorOf(pt);
var big = pt.highlight || pt.reference;
if (big) {
svg.appendChild(el("circle", {
"class": "ghchart__halo", cx: cx, cy: cy, r: 17, fill: c
}));
}
var dot = el("circle", {
"class": "ghchart__pt", cx: cx, cy: cy,
r: big ? 9 : 5.5, fill: c
});
svg.appendChild(dot);
dotEls.push({ el: dot, baseR: big ? 9 : 5.5 });
// Name label — bold+colored for the story points, muted for the rest.
var pos = pt.labelPos || (pt.highlight ? "below" : pt.reference ? "above" : "right");
var dx = 0, dy = 0, anchor = "middle";
var gap = big ? 15 : 9;
if (pos === "right") { dx = gap + 4; dy = 5; anchor = "start"; }
else if (pos === "left") { dx = -(gap + 4); dy = 5; anchor = "end"; }
else if (pos === "above") { dy = -(gap + 6); anchor = "middle"; }
else { dy = gap + 16; anchor = "middle"; } // below
var lbl = el("text", {
"class": "ghchart__pt-label" + (big ? " ghchart__pt-label–strong" : ""),
x: cx + dx, y: cy + dy, "text-anchor": anchor,
fill: big ? c : null
});
lbl.textContent = pt.label;
svg.appendChild(lbl);
// Transparent hit target for hover/focus.
var hit = el("circle", {
"class": "ghchart__pthit", cx: cx, cy: cy, r: 20,
tabindex: "0", role: "img",
"aria-label": pt.label + ": " + fmtY(pt.y) + ", " + fmtX(pt.x) + tipSuffix
});
svg.appendChild(hit);
hitEls.push(hit);
});
root.appendChild(svg);
/* —- Tooltip (title + free lines) ————————— */
var tip = html("div", "ghchart__tooltip");
tip.setAttribute("role", "status");
var tipTitle = html("div", "ghchart__tt-title");
var tipBody = html("div", "ghchart__tt-body");
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var activeIndex = -1;
function show(i) {
var pt = config.points[i];
if (!pt) return;
var reappearing = activeIndex === -1;
activeIndex = i;
dotEls.forEach(function (d, di) {
d.el.setAttribute("r", di === i ? d.baseR + 2.5 : d.baseR);
});
tipTitle.textContent = pt.label;
tipBody.textContent = "";
var line = html("div", "ghchart__tt-line");
line.textContent = fmtY(pt.y) + " · " + fmtX(pt.x) + tipSuffix;
tipBody.appendChild(line);
var anchor = dotEls[i].el.getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = "opacity 0.12s ease";
tip.setAttribute("data-visible", "true");
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight 0) {
svg.appendChild(el(“line”, {
“class”: “ghchart__phase-sep”, x1: x0, y1: M.top, x2: x0, y2: baseY
}));
}
var pl = el(“text”, {
“class”: “ghchart__phase-label”, x: (x0 + x1) / 2, y: M.top – 40,
“text-anchor”: “middle”
});
pl.textContent = ph.label;
svg.appendChild(pl);
if (ph.avg) {
var pa = el(“text”, {
“class”: “ghchart__phase-avg”, x: (x0 + x1) / 2, y: M.top + 24,
“text-anchor”: “middle”, fill: cPoint
});
pa.textContent = ph.avg;
svg.appendChild(pa);
}
});
// Horizontal gridlines + y tick labels (skip a gridline at the reference y).
var refY = config.reference ? config.reference.y : null;
(ya.ticks || []).forEach(function (tv) {
var gy = yAt(tv);
if (refY === null || Math.abs(tv – refY) > 1e-9) {
svg.appendChild(el(“line”, {
“class”: “ghchart__gridline”, x1: M.left, y1: gy, x2: VB.w – M.right, y2: gy
}));
}
var yl = el(“text”, {
“class”: “ghchart__axislabel ghchart__axislabel–y”,
x: M.left – 16, y: gy, dy: “0.32em”, “text-anchor”: “end”
});
yl.textContent = fmtY(tv);
svg.appendChild(yl);
});
// X tick labels (dated).
(xa.ticks || []).forEach(function (t) {
var gx = xAt(t.at);
var first = Math.abs(t.at – xa.min) < 1e-9, last = Math.abs(t.at – xa.max) < 1e-9;
var xl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–x",
x: gx, y: baseY + 30, "text-anchor": first ? "start" : (last ? "end" : "middle")
});
xl.textContent = t.text;
svg.appendChild(xl);
});
// Axis titles.
if (ya.title) {
var ytX = 22, ytY = M.top + innerH / 2;
var yt = el("text", {
"class": "ghchart__axistitle", x: ytX, y: ytY, "text-anchor": "middle",
transform: "rotate(-90 " + ytX + " " + ytY + ")"
});
yt.textContent = ya.title;
svg.appendChild(yt);
}
// Trend: optional ±band (linear only), then the dashed line + slope label.
var tr = config.trend;
if (tr) {
function trendY(x) {
return tr.from.y + (tr.to.y – tr.from.y) * (x – tr.from.x) / (tr.to.x – tr.from.x);
}
if (tr.band && !isLog) {
var p = tr.band;
var pts = [
[xAt(tr.from.x), yAt(trendY(tr.from.x) + p)],
[xAt(tr.to.x), yAt(trendY(tr.to.x) + p)],
[xAt(tr.to.x), yAt(trendY(tr.to.x) – p)],
[xAt(tr.from.x), yAt(trendY(tr.from.x) – p)]
].map(function (a) { return a[0] + "," + a[1]; }).join(" ");
svg.appendChild(el("polygon", {
"class": "ghchart__trendband", points: pts, fill: cTrend
}));
}
svg.appendChild(el("line", {
"class": "ghchart__trendline",
x1: xAt(tr.from.x), y1: yAt(tr.from.y),
x2: xAt(tr.to.x), y2: yAt(tr.to.y), stroke: cTrend
}));
if (tr.label) {
var lx, lyy;
if (tr.labelAt) { lx = xAt(tr.labelAt.x); lyy = yAt(tr.labelAt.y); }
else {
lx = xAt(tr.to.x) + (tr.labelDX == null ? -6 : tr.labelDX);
lyy = yAt(tr.to.y) + (tr.labelDY == null ? -12 : tr.labelDY);
}
var sl = el("text", {
"class": "ghchart__slope", x: lx, y: lyy,
"text-anchor": tr.labelAnchor || "end", fill: cTrend
});
sl.textContent = tr.label;
svg.appendChild(sl);
}
}
// Reference line (horizontal dashed) + right-aligned label.
if (config.reference) {
var rc = config.reference.color || "#24292f";
var ryy = yAt(config.reference.y);
svg.appendChild(el("line", {
"class": "ghchart__refline", x1: M.left, y1: ryy, x2: VB.w – M.right, y2: ryy,
stroke: rc
}));
if (config.reference.label) {
var rl = el("text", {
"class": "ghchart__reflabel", x: VB.w – M.right, y: ryy – 8,
"text-anchor": "end", fill: rc
});
rl.textContent = config.reference.label;
svg.appendChild(rl);
}
}
// Data squares (values reveal on hover only).
var side = 13, hSide = side / 2;
var sqEls = [], hitEls = [];
(config.points || []).forEach(function (pt, i) {
var cx = xAt(pt.x), cy = yAt(pt.y);
var sq = el("rect", {
"class": "ghchart__square", x: cx – hSide, y: cy – hSide,
width: side, height: side, rx: 2, fill: cPoint
});
svg.appendChild(sq);
sqEls.push({ el: sq, cx: cx, cy: cy });
// Hover/focus hit target — omitted when hover is disabled so the
// (illustrative) point values never surface, on screen or to a11y.
if (!config.disableHover) {
var hit = el("rect", {
"class": "ghchart__squarehit", x: cx – 13, y: cy – 13,
width: 26, height: 26, tabindex: "0", role: "img",
"aria-label": (pt.d || "") + " " + fmtY(pt.y)
});
svg.appendChild(hit);
hitEls.push(hit);
}
});
// Invalid-run markers: red diamond + X, leader line, annotation text.
(config.invalids || []).forEach(function (iv) {
var cx = xAt(iv.x), cy = yAt(iv.y);
svg.appendChild(el("rect", {
"class": "ghchart__invalid", x: cx – hSide, y: cy – hSide,
width: side, height: side, rx: 1.5, fill: "none", stroke: cInvalid,
transform: "rotate(45 " + cx + " " + cy + ")"
}));
var q = 3.4;
svg.appendChild(el("line", {
"class": "ghchart__invalid-x", x1: cx – q, y1: cy – q, x2: cx + q, y2: cy + q,
stroke: cInvalid
}));
svg.appendChild(el("line", {
"class": "ghchart__invalid-x", x1: cx – q, y1: cy + q, x2: cx + q, y2: cy – q,
stroke: cInvalid
}));
if (iv.title) {
var tx = cx + (iv.labelDX == null ? 16 : iv.labelDX);
var ty = cy + (iv.labelDY == null ? -26 : iv.labelDY);
svg.appendChild(el("line", {
"class": "ghchart__leader", x1: cx, y1: cy, x2: tx, y2: ty, stroke: cInvalid
}));
var anchor = iv.labelAnchor || "start";
var at = el("text", {
"class": "ghchart__anno-title", x: tx, y: ty – 4, "text-anchor": anchor, fill: cInvalid
});
at.textContent = iv.title;
svg.appendChild(at);
if (iv.sub) {
var asu = el("text", {
"class": "ghchart__anno-sub", x: tx, y: ty + 11, "text-anchor": anchor
});
asu.textContent = iv.sub;
svg.appendChild(asu);
}
}
});
// Bracket annotation (span with end ticks + centered label above).
(config.brackets || []).forEach(function (b) {
var x0 = xAt(b.fromX), x1 = xAt(b.toX), by = yAt(b.y);
svg.appendChild(el("line", {
"class": "ghchart__bracket", x1: x0, y1: by, x2: x1, y2: by
}));
svg.appendChild(el("line", { "class": "ghchart__bracket", x1: x0, y1: by, x2: x0, y2: by + 7 }));
svg.appendChild(el("line", { "class": "ghchart__bracket", x1: x1, y1: by, x2: x1, y2: by + 7 }));
var bl = el("text", {
"class": "ghchart__bracket-label", x: (x0 + x1) / 2, y: by – 8, "text-anchor": "middle"
});
bl.textContent = b.text;
svg.appendChild(bl);
});
// Final callout: ringed point + leader + title/sub placed inside the plot.
if (config.callout) {
var co = config.callout;
var ccx = xAt(co.x), ccy = yAt(co.y);
svg.appendChild(el("circle", {
"class": "ghchart__callout-ring", cx: ccx, cy: ccy, r: 12, stroke: cPoint
}));
var ctx = ccx + (co.labelDX == null ? -16 : co.labelDX);
var cty = ccy + (co.labelDY == null ? -30 : co.labelDY);
svg.appendChild(el("line", {
"class": "ghchart__leader ghchart__leader–callout", x1: ccx, y1: ccy, x2: ctx, y2: cty,
stroke: cPoint
}));
var can = co.labelAnchor || "end";
var ct = el("text", {
"class": "ghchart__anno-title ghchart__anno-title–callout", x: ctx, y: cty – 4, "text-anchor": can, fill: cPoint
});
ct.textContent = co.title;
svg.appendChild(ct);
if (co.sub) {
var cs = el("text", {
"class": "ghchart__anno-sub ghchart__anno-sub–callout", x: ctx, y: cty + 12, "text-anchor": can
});
cs.textContent = co.sub;
svg.appendChild(cs);
}
}
root.appendChild(svg);
if (config.caption) {
var cap = html("div", "ghchart__caption");
cap.textContent = config.caption;
root.appendChild(cap);
}
/* —- Tooltip + hover interaction (skipped when disabled) —— */
if (!config.disableHover) {
var tip = html("div", "ghchart__tooltip");
tip.setAttribute("role", "status");
var tipTitle = html("div", "ghchart__tt-title");
var tipBody = html("div", "ghchart__tt-body");
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var activeIndex = -1;
function show(i) {
var pt = config.points[i];
if (!pt) return;
var reappearing = activeIndex === -1;
activeIndex = i;
sqEls.forEach(function (s, si) {
var big = si === i;
var w = big ? side + 5 : side;
s.el.setAttribute("width", w);
s.el.setAttribute("height", w);
s.el.setAttribute("x", s.cx – w / 2);
s.el.setAttribute("y", s.cy – w / 2);
});
var tipInfo = config.pointTip
? config.pointTip(pt)
: { title: pt.d || "", line: fmtY(pt.y) };
tipTitle.textContent = tipInfo.title;
tipBody.textContent = "";
var line = html("div", "ghchart__tt-line");
line.textContent = tipInfo.line;
tipBody.appendChild(line);
var anchor = sqEls[i].el.getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = "opacity 0.12s ease";
tip.setAttribute("data-visible", "true");
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight < 0) {
top = anchor.bottom – rootRect.top + 12;
tip.style.transform = "translate(-50%, 0)";
} else {
tip.style.transform = "translate(-50%, -100%)";
}
tip.style.left = left + "px";
tip.style.top = top + "px";
if (suppress) { void tip.offsetWidth; tip.style.transition = ""; }
}
function hide() {
activeIndex = -1;
tip.setAttribute("data-visible", "false");
sqEls.forEach(function (s) {
s.el.setAttribute("width", side);
s.el.setAttribute("height", side);
s.el.setAttribute("x", s.cx – hSide);
s.el.setAttribute("y", s.cy – hSide);
});
}
hitEls.forEach(function (hit, i) {
hit.addEventListener("pointerenter", function () { show(i); });
hit.addEventListener("pointermove", function () { show(i); });
hit.addEventListener("pointerleave", hide);
hit.addEventListener("focus", function () { show(i); });
hit.addEventListener("blur", hide);
});
}
};
/* ================================================================
Distribution dashboard renderer
—————————————————————-
A composition figure (no hover — every value is printed as text, so
the marks are inherently screen-reader accessible):
• a compact narrative headline + muted subtitle;
• an optional segmented "mix" bar — proportional colored segments,
each with its name inside and count/percent below;
• one or more panels of ranked horizontal bars, each row printing
its category (left), a bar, and its count/percent (right).
Colors come from config.series so the harness can tune them live:
series[0] = bar fill (single base hex);
series[1..] = segment fills, in the order of config.segments.items.
================================================================ */
renderers.distribution = function (root, config) {
root.textContent = "";
root.setAttribute("role", "figure");
if (config.title) root.setAttribute("aria-label", config.title);
var series = config.series || [];
var barColor = (series[0] && series[0].color) || "#8250df";
var total = config.total ||
(config.segments && config.segments.items
? config.segments.items.reduce(function (a, b) { return a + b.value; }, 0)
: 0);
function pctText(v) {
if (!total) return "";
return (Math.round((v / total) * 1000) / 10).toFixed(1) + "%";
}
/* Label format is config-driven so a chart can print counts, percents,
or both without a renderer change: config.valueFormat =
"both" (default) | "percent" | "count". Counts stay in the data either
way so the percentages are always derived from them. */
var valueFormat = config.valueFormat || "both";
function valueHTML(v) {
var p = pctText(v);
if (valueFormat === "percent") return p;
if (valueFormat === "count") return "” + v + ““;
return “” + v + “” + (p ? ” (” + p + “)” : “”);
}
/* —- Headline + subtitle ————————————- */
if (config.title) {
var head = html(“div”, “ghchart__headline”);
head.textContent = config.title;
root.appendChild(head);
}
if (config.subtitle) {
var sub = html(“p”, “ghchart__subtitle”);
sub.textContent = config.subtitle;
root.appendChild(sub);
}
/* —- Segmented “mix” bar ————————————– */
var seg = config.segments;
if (seg && seg.items && seg.items.length) {
var segSec = html(“div”, “ghchart__dist-section”);
if (seg.title) {
var segTitle = html(“div”, “ghchart__panel-title”);
segTitle.textContent = seg.title;
segSec.appendChild(segTitle);
}
var segTotal = seg.items.reduce(function (a, b) { return a + b.value; }, 0) || 1;
var bar = html(“div”, “ghchart__seg”);
var notes = html(“div”, “ghchart__seg-note-row”);
seg.items.forEach(function (it, i) {
var basis = (it.value / segTotal) * 100;
var col = (series[i + 1] && series[i + 1].color) || it.color || barColor;
var cell = html(“div”, “ghchart__seg-cell”);
cell.style.flex = “0 0 ” + basis + “%”;
cell.style.background = col;
cell.textContent = it.label;
bar.appendChild(cell);
var note = html(“div”, “ghchart__seg-note”);
note.style.flex = “0 0 ” + basis + “%”;
note.innerHTML = valueHTML(it.value);
notes.appendChild(note);
});
segSec.appendChild(bar);
segSec.appendChild(notes);
root.appendChild(segSec);
}
/* —- Ranked bar panels (grid: 2-up, stacking on narrow) —— */
var panels = config.panels || [];
if (panels.length) {
var cols = html(“div”, “ghchart__dist-cols”);
if (config.columns === 1 || panels.length === 1) {
cols.classList.add(“ghchart__dist-cols–stack”);
}
panels.forEach(function (panel) {
var sec = html(“div”, “ghchart__dist-section”);
if (panel.title) {
var pt = html(“div”, “ghchart__panel-title”);
pt.textContent = panel.title;
sec.appendChild(pt);
}
if (panel.subtitle) {
var descriptor = html(“p”, “ghchart__subtitle”);
descriptor.textContent = panel.subtitle;
sec.appendChild(descriptor);
}
if (panel.stats) {
var stats = html(“dl”, “ghchart__dist-stats”);
panel.stats.forEach(function (stat) {
var pair = html(“div”, “ghchart__dist-stat”);
var term = html(“dt”);
term.textContent = stat.label;
var value = html(“dd”);
value.textContent = stat.value;
value.style.color = strongOf((series[panel.series || 0] || series[0]).color);
pair.appendChild(term);
pair.appendChild(value);
stats.appendChild(pair);
});
sec.appendChild(stats);
cols.appendChild(sec);
return;
}
var list = html(“div”, “ghchart__dist-list”);
var pmax = (panel.items || []).reduce(function (m, it) {
return it.value > m ? it.value : m;
}, 0) || 1;
(panel.items || []).forEach(function (it) {
var row = html(“div”, “ghchart__dist-row”);
var cat = html(“div”, “ghchart__dist-cat”);
cat.textContent = it.label;
row.appendChild(cat);
var track = html(“div”, “ghchart__dist-track”);
var b = html(“div”, “ghchart__dist-bar”);
b.style.width = Math.max((it.value / pmax) * 100, 1.2) + “%”;
b.style.background = it.color || barColor;
track.appendChild(b);
row.appendChild(track);
var val = html(“div”, “ghchart__dist-val”);
val.innerHTML = valueHTML(it.value);
row.appendChild(val);
list.appendChild(row);
});
sec.appendChild(list);
cols.appendChild(sec);
});
root.appendChild(cols);
}
/* —- Caption / footnote ————————————– */
if (config.caption) {
var cap = html(“div”, “ghchart__caption”);
cap.innerHTML = config.caption;
root.appendChild(cap);
}
};
/* ================================================================
Timeline / staged-duration renderer
—————————————————————-
A horizontal time axis broken into labeled stage segments. Composes
only the pieces the data needs (like the trend renderer):
• eyebrow kicker + narrative headline + optional subtitle;
• an optional before/after stat card and a stage legend;
• one or more rows of segmented duration bars on a shared time
scale — a row is either sequential (segments end to end) or
parallel (a full-height “gate” segment then concurrent lanes);
• optional flow ribbons linking matching stages between the first
two rows (the sequential -> parallel “collapse”);
• optional elbow-leader callouts above narrow segments;
• optional span brackets (below a row, or right of a parallel group);
• an optional numeric axis (ticks + gridlines + title).
No hover — every label is printed, so it reads on paper and to screen
readers. Stage colors come from config.series so the harness tunes
them live: each entry is { id, name, color }.
================================================================ */
renderers.timeline = function (root, config) {
root.textContent = “”;
root.setAttribute(“role”, “img”);
if (config.title) root.setAttribute(“aria-label”, config.title);
var stages = config.series || [];
var stageById = {};
stages.forEach(function (s) { if (s && s.id) stageById[s.id] = s; });
function colorOf(seg) {
return seg.color || (stageById[seg.id] && stageById[seg.id].color) || “#8250df”;
}
function nameOf(seg) {
if (seg.label != null) return seg.label;
return seg.name || (stageById[seg.id] && stageById[seg.id].name) || “”;
}
/* —- DOM header: eyebrow + headline + subtitle ————— */
if (config.eyebrow) {
var eb = html(“div”, “ghchart__tl-eyebrow”);
eb.textContent = config.eyebrow;
root.appendChild(eb);
}
if (config.title) {
var hl = html(“div”, “ghchart__headline ghchart__tl-headline”);
hl.textContent = config.title;
root.appendChild(hl);
}
if (config.subtitle) {
var sbt = html(“p”, “ghchart__subtitle”);
sbt.textContent = config.subtitle;
root.appendChild(sbt);
}
/* —- Stat card (before -> after + delta) ——————– */
if (config.stat) {
var st = config.stat;
var card = html(“div”, “ghchart__tl-stat”);
var main = html(“div”, “ghchart__tl-stat-main”);
function statCol(k, v, muted) {
var col = html(“div”, “ghchart__tl-stat-col”);
var kk = html(“div”, “ghchart__tl-stat-k”); kk.textContent = k; col.appendChild(kk);
var vv = html(“div”, “ghchart__tl-stat-v” + (muted ? ” ghchart__tl-stat-v–muted” : “”));
vv.textContent = v; col.appendChild(vv);
return col;
}
main.appendChild(statCol(st.beforeLabel, st.beforeValue, true));
var arrow = html(“div”, “ghchart__tl-stat-arrow”); arrow.textContent = “u2192”;
main.appendChild(arrow);
main.appendChild(statCol(st.afterLabel, st.afterValue, false));
card.appendChild(main);
if (st.delta) {
var dl = html(“div”, “ghchart__tl-stat-delta”);
var dv = html(“div”, “ghchart__tl-stat-delta-v”); dv.textContent = st.delta; dl.appendChild(dv);
if (st.deltaLabel) {
var dk = html(“div”, “ghchart__tl-stat-delta-k”); dk.textContent = st.deltaLabel; dl.appendChild(dk);
}
card.appendChild(dl);
}
root.appendChild(card);
}
/* —- Legend ————————————————– */
if (config.legend && config.legend.length) {
var lg = html(“div”, “ghchart__tl-legend”);
config.legend.forEach(function (item) {
var s = stageById[item.id] || {};
var it = html(“span”, “ghchart__tl-legend-item”);
var sw = html(“span”, “ghchart__tl-swatch”);
sw.style.background = item.color || s.color || “#8250df”;
it.appendChild(sw);
var tx = html(“span”, “ghchart__tl-legend-text”);
tx.textContent = (item.name || s.name || “”) + (item.value ? ” u00b7 ” + item.value : “”);
it.appendChild(tx);
lg.appendChild(it);
});
root.appendChild(lg);
}
if (config.rule) root.appendChild(html(“div”, “ghchart__tl-rule”));
/* —- Plot geometry ——————————————- */
var rows = config.rows || [];
function rowTotal(r) {
if (r.parallel) {
var g = r.gate ? r.gate.value : 0, mx = 0;
r.parallel.forEach(function (p) { if (p.value > mx) mx = p.value; });
return g + mx;
}
var t = 0; (r.segments || []).forEach(function (s) { t += s.value; }); return t;
}
var maxTotal = 0;
rows.forEach(function (r) { var t = rowTotal(r); if (t > maxTotal) maxTotal = t; });
var axis = config.axis;
var scaleMax = config.scaleMax || (axis && axis.max) || maxTotal || 1;
var VB_W = 1000;
var hasRowLabels = rows.some(function (r) { return r.label; });
var M = { left: hasRowLabels ? 150 : 44, right: 46 };
var innerW = VB_W – M.left – M.right;
function xAt(v) { return M.left + innerW * v / scaleMax; }
function wAt(v) { return innerW * v / scaleMax; }
var SEQ_H = 56, LANE_H = 13, LANE_GAP = 6, SEG_R = 4;
function rowBandH(r) {
if (r.parallel) {
var n = r.parallel.length;
return Math.max(SEQ_H, n * LANE_H + (n – 1) * LANE_GAP);
}
return SEQ_H;
}
var flows = !!config.flows && rows.length >= 2;
var FLOW_GAP = config.flowGap != null ? config.flowGap : 250, ROW_GAP = 48;
// Reserve vertical space for above-callouts (tallest tier wins).
var anyAbove = rows.some(function (r) {
return (r.segments || []).some(function (s) { return s.labelPos === “above”; });
});
var maxTier = 0;
rows.forEach(function (r) {
(r.segments || []).forEach(function (s) {
if (s.labelPos === “above” && (s.tier || 0) > maxTier) maxTier = s.tier || 0;
});
});
var CALLOUT_TIER_H = 40;
var topPad = anyAbove ? (18 + (maxTier + 1) * CALLOUT_TIER_H) : 16;
// Assign each row a vertical band.
var cursor = topPad;
var rowLayout = rows.map(function (r, i) {
var top = cursor, h = rowBandH(r);
cursor = top + h;
if (i row1), matched by stage id (drawn under bars).
if (flows) {
var r0 = rows[0], l0 = rowLayout[0], r1 = rows[1], l1 = rowLayout[1];
var beforeSpan = {}, acc0 = 0;
(r0.segments || []).forEach(function (s) {
beforeSpan[s.id] = { x0: xAt(acc0), x1: xAt(acc0 + s.value) }; acc0 += s.value;
});
var afterSpan = {}, gv1 = r1.gate ? r1.gate.value : 0;
if (r1.gate) afterSpan[r1.gate.id] = { x0: xAt(0), x1: xAt(gv1), y: l1.top };
(r1.parallel || []).forEach(function (p, k) {
// Land each ribbon on its companion lane’s actual depth, not the row top.
afterSpan[p.id] = {
x0: xAt(gv1), x1: xAt(gv1 + p.value), y: l1.top + k * (LANE_H + LANE_GAP)
};
});
var by = l0.bottom;
Object.keys(beforeSpan).forEach(function (id) {
var b = beforeSpan[id], a = afterSpan[id];
if (!a) return;
var ay = a.y, ddy = (ay – by) * 0.5;
var d = “M ” + b.x0 + ” ” + by +
” C ” + b.x0 + ” ” + (by + ddy) + ” ” + a.x0 + ” ” + (ay – ddy) + ” ” + a.x0 + ” ” + ay +
” L ” + a.x1 + ” ” + ay +
” C ” + a.x1 + ” ” + (ay – ddy) + ” ” + b.x1 + ” ” + (by + ddy) + ” ” + b.x1 + ” ” + by + ” Z”;
var flowPath = el(“path”, {
“class”: “ghchart__tl-flow”, d: d, fill: colorOf({ id: id }), “data-stage”: id
});
svg.appendChild(flowPath);
hoverSlot(id).flowEl = flowPath;
});
}
// Rows: segmented bars.
rows.forEach(function (r, ri) {
var L = rowLayout[ri];
if (r.label) {
var rl = el(“text”, {
“class”: “ghchart__tl-rowlabel”, x: M.left – 20, y: L.top + L.h / 2 – 3, “text-anchor”: “end”
});
rl.textContent = r.label; svg.appendChild(rl);
if (r.sublabel) {
var rs = el(“text”, {
“class”: “ghchart__tl-rowsub”, x: M.left – 20, y: L.top + L.h / 2 + 18, “text-anchor”: “end”
});
rs.textContent = r.sublabel; svg.appendChild(rs);
}
}
if (r.parallel) {
var gv = r.gate ? r.gate.value : 0;
if (r.gate) {
var gateEl = el(“rect”, {
“class”: “ghchart__tl-seg”, x: xAt(0), y: L.top,
width: Math.max(0, wAt(gv)), height: L.h, rx: SEG_R, fill: colorOf(r.gate),
“data-stage”: r.gate.id
});
svg.appendChild(gateEl);
var gslot = hoverSlot(r.gate.id);
gslot.afterEl = gateEl; gslot.after = r.gate.value;
gslot.name = gslot.name || nameOf(r.gate);
}
r.parallel.forEach(function (p, k) {
var ly = L.top + k * (LANE_H + LANE_GAP);
var laneEl = el(“rect”, {
“class”: “ghchart__tl-seg”, x: xAt(gv), y: ly,
width: Math.max(1, wAt(p.value)), height: LANE_H, rx: 3, fill: colorOf(p),
“data-stage”: p.id
});
svg.appendChild(laneEl);
var pslot = hoverSlot(p.id);
pslot.afterEl = laneEl; pslot.after = p.value;
pslot.name = pslot.name || nameOf(p);
});
} else {
var acc = 0;
(r.segments || []).forEach(function (s) {
var x = xAt(acc), w = Math.max(1, wAt(s.value));
var segEl = el(“rect”, {
“class”: “ghchart__tl-seg”, x: x, y: L.top, width: w, height: L.h, rx: SEG_R,
fill: colorOf(s), “data-stage”: s.id
});
svg.appendChild(segEl);
var slot = hoverSlot(s.id);
slot.name = slot.name || nameOf(s);
if (ri === 0) { slot.beforeEl = segEl; slot.before = s.value; }
else { slot.afterEl = segEl; slot.after = s.value; }
if (s.labelPos === “inside”) {
var ilAttrs = {
“class”: “ghchart__tl-seg-label”, x: x + w / 2, y: L.top + L.h / 2,
dy: “0.34em”, “text-anchor”: “middle”
};
// Inline style (not a fill attribute) so it overrides the class’s
// default white fill — lets a bright segment take dark label text.
if (s.labelColor) ilAttrs.style = “fill:” + s.labelColor;
var il = el(“text”, ilAttrs);
il.textContent = nameOf(s); svg.appendChild(il);
}
acc += s.value;
});
}
});
// Elbow-leader callouts above narrow segments.
rows.forEach(function (r, ri) {
if (r.parallel) return;
var L = rowLayout[ri], acc = 0;
(r.segments || []).forEach(function (s) {
var x = xAt(acc), w = wAt(s.value), cx = x + w / 2;
if (s.labelPos === “above”) {
var tier = s.tier || 0;
var tierY = topPad – 14 – tier * CALLOUT_TIER_H;
// labelX pins the label to an absolute data-x (so callouts can be
// left-aligned into a stack); labelDX is a relative nudge otherwise.
var lx = s.labelX != null ? xAt(s.labelX) : cx + (s.labelDX || 0);
var anchor = s.labelAnchor || “start”;
svg.appendChild(el(“path”, {
“class”: “ghchart__tl-callout-line”,
d: “M ” + cx + ” ” + L.top + ” L ” + cx + ” ” + tierY + ” L ” + lx + ” ” + tierY, fill: “none”
}));
var pad = anchor === “start” ? 6 : (anchor === “end” ? -6 : 0);
var cl = el(“text”, {
“class”: “ghchart__tl-callout-label”, x: lx + pad, y: tierY,
dy: “0.34em”, “text-anchor”: anchor, fill: s.labelColor || colorOf(s)
});
cl.textContent = nameOf(s); svg.appendChild(cl);
}
acc += s.value;
});
});
// Below brackets (span with up-ticks + label beneath).
var belowY = (rowLayout.length ? rowLayout[rowLayout.length – 1].bottom : topPad) + 14;
belowBrackets.forEach(function (b, bi) {
var x0 = xAt(b.from), x1 = xAt(b.to), yy = belowY + bi * 46;
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: x0, y1: yy, x2: x1, y2: yy }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: x0, y1: yy, x2: x0, y2: yy – 7 }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: x1, y1: yy, x2: x1, y2: yy – 7 }));
var bl = el(“text”, {
“class”: “ghchart__bracket-label ghchart__tl-bracket-label”, x: (x0 + x1) / 2, y: yy + 22, “text-anchor”: “middle”
});
bl.textContent = b.text; svg.appendChild(bl);
});
// Right brackets (vertical span beside a parallel group + label).
rightBrackets.forEach(function (b) {
var L = rowLayout[b.row || 0];
var bx = xAt(b.at != null ? b.at : rowTotal(rows[b.row || 0])) + 14;
var y0 = L.top, y1 = L.bottom;
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: bx, y1: y0, x2: bx, y2: y1 }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: bx, y1: y0, x2: bx – 7, y2: y0 }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: bx, y1: y1, x2: bx – 7, y2: y1 }));
// Label as a DOM node (not SVG text) so it keeps a fixed, readable size
// instead of scaling down with the plot on small windows.
var rlabel = html(“div”, “ghchart__tl-bracket-label-dom”);
rlabel.textContent = b.text;
rlabel.style.left = ((bx + 12) / VB_W * 100) + “%”;
rlabel.style.top = (((y0 + y1) / 2) / VB_H * 100) + “%”;
plotwrap.appendChild(rlabel);
});
// Numeric axis: tick labels + title.
if (axis && axis.ticks) {
axis.ticks.forEach(function (tv, i) {
var gx = xAt(tv);
var first = i === 0, last = i === axis.ticks.length – 1;
var xl = el(“text”, {
“class”: “ghchart__axislabel ghchart__axislabel–x ghchart__tl-tick”,
x: gx, y: axisY + 26, “text-anchor”: first ? “start” : (last ? “end” : “middle”)
});
xl.textContent = axis.format ? axis.format(tv) : String(tv);
svg.appendChild(xl);
});
}
/* —- Hover interaction (before/after chart) ——————
Emphasise a stage’s whole connection (its before block, after block
and ribbon) and show a tooltip with the before/after values. Only
when there are flows to connect and hover isn’t disabled. */
if (flows && config.hover !== false) {
var unit = config.unit ? ” ” + config.unit : “”;
function fmtHover(v) { return v == null ? “u2013” : v + unit; }
var tip = html(“div”, “ghchart__tooltip”);
tip.setAttribute(“role”, “status”);
var tipTitle = html(“div”, “ghchart__tt-title”);
var tipBody = html(“div”, “ghchart__tt-body ghchart__tt-body–plain”);
tip.appendChild(tipTitle); tip.appendChild(tipBody);
root.appendChild(tip);
var focused = null;
function focusStage(id) {
focused = id;
Object.keys(stageHover).forEach(function (k) {
var s = stageHover[k], on = (k === id);
if (s.beforeEl) s.beforeEl.style.opacity = on ? “1” : “0.16”;
if (s.afterEl) s.afterEl.style.opacity = on ? “1” : “0.16”;
if (s.flowEl) s.flowEl.style.opacity = on ? “0.5” : “0.04”;
});
var st = stageHover[id];
tipTitle.textContent = st.name || id;
tipBody.textContent = “”;
var lb = html(“div”, “ghchart__tt-line”); lb.textContent = “Before ” + fmtHover(st.before);
var la = html(“div”, “ghchart__tt-line”); la.textContent = “After ” + fmtHover(st.after);
tipBody.appendChild(lb); tipBody.appendChild(la);
}
function clearStage() {
focused = null;
Object.keys(stageHover).forEach(function (k) {
var s = stageHover[k];
if (s.beforeEl) s.beforeEl.style.opacity = “”;
if (s.afterEl) s.afterEl.style.opacity = “”;
if (s.flowEl) s.flowEl.style.opacity = “”;
});
tip.setAttribute(“data-visible”, “false”);
}
function placeTip(clientX, clientY) {
var rr = root.getBoundingClientRect();
var left = clientX – rr.left, top = clientY – rr.top – 14;
tip.setAttribute(“data-visible”, “true”);
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight existing.version) {
/* This bundle is newer. Every export embeds the COMPLETE engine, so this
bundle’s `renderers` is a self-contained, up-to-date set. Adopt it as
the shared registry (upgrading older same-named renderers in place),
while defensively preserving any renderer types only the older engine
happened to have. Then take over render(). */
for (var ek in existing.renderers) {
if (Object.prototype.hasOwnProperty.call(existing.renderers, ek) && !renderers[ek]) {
renderers[ek] = existing.renderers[ek];
}
}
existing.renderers = renderers;
existing.render = render;
existing.destroy = destroy;
existing.version = ENGINE_VERSION;
existing._renderCleanups = renderCleanups;
} else {
/* Same or older version already present: only contribute renderer types
the shared registry is missing (don’t clobber an equal/newer engine). */
for (var rk in renderers) {
if (Object.prototype.hasOwnProperty.call(renderers, rk) && !existing.renderers[rk]) {
existing.renderers[rk] = renderers[rk];
}
}
}
} else {
/* No engine yet, or a pre-versioning (old-style) engine loaded first.
Take over, preserving any renderers the old-style engine registered. */
if (existing && existing.renderers) {
for (var ok in existing.renderers) {
if (Object.prototype.hasOwnProperty.call(existing.renderers, ok) && !renderers[ok]) {
renderers[ok] = existing.renderers[ok];
}
}
}
global.GHChartEngine = { renderers: renderers, render: render, destroy: destroy, version: ENGINE_VERSION, _renderCleanups: renderCleanups };
}
})(typeof window !== “undefined” ? window : this);
;(function () {
“use strict”;
var CONFIG = {
mountId: “gh-chart-reviewbench-dataset”,
type: “distribution”,
title: “ReviewBench dataset overview”,
total: 219,
columns: 2,
panelCards: true,
valueFormat: “count”,
series: [
{
name: “Bars”,
color: “#499d6f”
},
{
name: “Repository statistics”,
color: “#499d6f”
}
],
panels: [
{
title: “Primary languages”,
subtitle: “By PR count; all other languages total 51 PRs”,
items: [
{
label: “TypeScript”,
value: 68
},
{
label: “Python”,
value: 41
},
{
label: “C#”,
value: 25
},
{
label: “Go”,
value: 19
},
{
label: “JavaScript”,
value: 15
},
{
label: “Other”,
value: 51
}
]
},
{
title: “PR change size”,
subtitle: “Added + removed lines”,
items: [
{
label: “≤ 50 lines”,
value: 17
},
{
label: “51–200”,
value: 40
},
{
label: “201–500”,
value: 44
},
{
label: “501–1,000”,
value: 40
},
{
label: “> 1,000”,
value: 78
}
]
},
{
title: “Primary PR types”,
subtitle: “Inferred from titles and descriptions for coverage analysis; not formal human labels”,
items: [
{
label: “Feature”,
value: 79
},
{
label: “Bug fix”,
value: 59
},
{
label: “Documentation”,
value: 15
},
{
label: “Performance”,
value: 14
},
{
label: “Refactor”,
value: 12
},
{
label: “Other”,
value: 40
}
]
},
{
title: “Repository concentration”,
subtitle: “The largest repository accounts for only 4.6%; concentration is not a primary risk”,
series: 1,
stats: [
{
value: “187”,
label: “Distinct repositories”
},
{
value: “10”,
label: “Most PRs from a single repository”
},
{
value: “1.17”,
label: “Average PRs per repository”
},
{
value: “4.6%”,
label: “Largest repository’s share”
}
]
}
]
};
function boot() {
var root = document.getElementById(CONFIG.mountId);
if (!root || root.getAttribute(“data-gh-rendered”)) return;
root.setAttribute(“data-gh-rendered”, “1”);
window.GHChartEngine.render(CONFIG);
}
if (document.readyState === “loading”) {
document.addEventListener(“DOMContentLoaded”, boot);
} else {
boot();
}
})();
2. Broad ground truth discovery, independently judged
No single reviewer, whether human or model, can identify everything worth finding in a pull request. To build a broader and more reliable golden set for ground truth findings, we follow a three-stage process:
- Gather candidate findings from diverse sources. We collect findings from real human reviewers, issues inferred from author follow-up commits, deterministic analysis tools, and multiple frontier LLMs across model families.
- Semantically deduplicate overlapping findings. We merge findings that identify the same underlying issue, broadening coverage without allowing agreement across producers to artificially inflate the golden set or making it dependent on any one source’s blind spots.
- Validate findings under a shared rubric. The source of a finding does not determine whether it is correct: a finding counts as a true positive only if it is true, relevant, and non-trivial. We use Claude Sonnet 5 as the LLM grader, applying a consistent evaluation rubric across all submissions. For transparency and reproducibility, we publish both the evaluation rubric and the judge used to apply it.
3. Metrics that measure both known and newly discovered issues
Most benchmarks report precision and recall against a fixed golden set. ReviewBench reports six metrics in two families:
- Grounded precision, recall, and F1 score use only the existing gold-set labels. They provide the strict, apples-to-apples comparison: of the issues we already know about, how many did the agent find, and what share of its findings matched a known issue?
- Augmented precision, recall and F1 score also evaluate findings that do not match anything in the golden set. The judge independently determines whether those unmatched findings are true or false positives, allowing a reviewer to receive credit for valid issues that no producer in the golden set surfaced
That distinction becomes more important as review agents become more capable. A fixed golden set inevitably becomes incomplete as systems discover issues its creators did not anticipate. Augmented metrics let ReviewBench recognize that behavior rather than automatically penalizing it. Because augmented recall expands the denominator based on what each agent discovers, we use grounded recall as the headline cross-system comparison and augmented metrics as an additional per-system diagnostic.
4. Configurable evaluation for different review preferences
There is no single universally optimal review experience. Some developers may want to focus only on critical issues, while others also value lower-severity, non-breaking findings. Some prefer broader coverage, while others prioritize precision and minimal noise. Others may have specialized needs, such as security- or privacy-focused review.
ReviewBench lets results be sliced by severity and category, while precision and recall capture different operating preferences. Users can also adjust β in the Fβ score to place more weight on recall for broader coverage or precision for lower noise. As these preferences change, the leaderboard is re-ranked accordingly, helping users identify the systems that best match their review priorities.
5. Internally audited and reproducibly evaluated
Before release, we asked senior engineers who had not participated in building the benchmark dataset to independently re-label every ground-truth finding from scratch. Their true/false-positive judgments agreed with ReviewBench 96.6% of the time. We version the benchmark dataset, judge, and matcher used in every evaluation, so results can be compared under the same benchmark configuration and revalidated when the benchmark changes. We also publish the validation methodology, agreement measurements, and known threats to validity, so readers can see how benchmark quality is assessed and where uncertainty remains.
Explore ReviewBench
ReviewBench’s research preview version is now available through the ReviewBench website, where you can explore the full benchmark, compare code review agents, and bring your own agent to evaluate and iterate.
With ReviewBench, you can:
- Explore the full benchmark dataset. The complete ReviewBench dataset is publicly available, including the pull requests, findings, labels, severity and category annotations. This allows you to inspect exactly what systems are evaluated on and reproduce benchmark results.
- Compare systems on the leaderboard. Results from evaluated code review agents using the full benchmark data are published on a common leaderboard, with views across overall performance, severity, category, and different precision–recall preferences.
- Bring your own agent and hill-climb. The full benchmark dataset, evaluation methodology, LLM judge prompt, judge model configuration, and self-serve runner are publicly available, so you can evaluate your own code review agent, inspect its strengths and gaps, and iterate against the same benchmark configuration.
How we’ve used ReviewBench
We have used ReviewBench to evaluate Copilot code review (CCR) across successive iterations, giving us a consistent way to measure progress, catch regressions, and prioritize promising changes. Over time, this has helped us improve the product. One of the most valuable benefits of ReviewBench is that it provides an early offline signal of how a change to the product is likely to perform in production. Across experiments evaluated with ReviewBench before A/B testing, offline changes have consistently pointed in the same direction as what we see later in production.
A recent lite-tier experiment provides a concrete example of this broader pattern. We introduced a multi-model ensemble review that combines several independent model runs into a single review rather than relying on a single run. ReviewBench predicted higher precision, recall, and comment volume, along with lower cost per review.
To compare offline and production results, we use corresponding online signals. Addressed rate, our online counterpart to precision, is the percentage of CCR comments that an LLM determines prompted a developer to make a corresponding code change, based on the diff, thread, reactions, resolution state, and post-review code. For recall, we measure how much additional human review is still needed.
The online A/B test moved in the same direction as ReviewBench predicted: addressed rate (precision) rose 8.0%, recall rose 13.6%, and comment volume rose 61%, while cost per review fell 8.0%, all relative to the production control.
.ghchart {
/* Theme tokens */
–ghc-text: #1f2328;
–ghc-muted: #656d76;
–ghc-grid: #d0d7de;
–ghc-axis: #59636e;
–ghc-card-bg: #ffffff;
–ghc-card-border: #d0d7de;
–ghc-canvas: #f2f5f3;
–ghc-divider: #b6bfb8;
–ghc-guide: #afb8c1;
/* GitHub brand font — matches the github.blog stack (Mona Sans, with
the blog’s metric fallback) so it renders identically in-context. */
font-family: “Mona Sans”, “Mona Sans Fallback”, -apple-system, “system-ui”,
“Segoe UI”, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
position: relative;
box-sizing: border-box;
width: 100%;
max-width: 699px;
margin: 0 auto;
color: var(–ghc-text);
line-height: 1.4;
}
.ghchart *,
.ghchart *::before,
.ghchart *::after { box-sizing: border-box; }
/* —————————————————————— */
/* Header + legend */
/* —————————————————————— */
.ghchart__header {
display: flex;
justify-content: flex-end;
align-items: center;
min-height: 24px;
margin-bottom: 4px;
}
.ghchart__legend {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
justify-content: flex-end;
}
.ghchart__legend-item {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: clamp(14px, 1.9vw, 17px);
font-weight: 600;
color: var(–ghc-muted);
white-space: nowrap;
}
.ghchart__swatch {
width: 14px;
height: 14px;
border-radius: 4px;
flex: 0 0 auto;
}
/* —————————————————————— */
/* Plot (SVG) */
/* —————————————————————— */
.ghchart__plot {
display: block;
width: 100%;
height: auto;
overflow: visible;
touch-action: pan-y;
}
.ghchart__gridline {
stroke: var(–ghc-grid);
stroke-width: 1;
stroke-dasharray: 2 6;
stroke-linecap: round;
opacity: 0.9;
}
.ghchart__axislabel {
fill: var(–ghc-axis);
font-size: 22px;
font-weight: 500;
}
.ghchart__axislabel–x { font-weight: 600; }
.ghchart__line {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Trailing partial/incomplete segment of a line (e.g. a month still in
progress) — dashed so it reads as “not final yet” without a legend. */
.ghchart__line–dashed {
stroke-dasharray: 7 6;
}
.ghchart__dot { transition: r 0.12s ease; }
/* Dashed threshold line (e.g. a target/reference level) + floating label.
Stroke color is set inline per chart; dash + label styling live here. */
.ghchart__threshold {
stroke-width: 2;
stroke-dasharray: 7 6;
stroke-linecap: round;
opacity: 0.9;
}
.ghchart__threshold-label {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Hatched band marking a partial / projected trailing region. */
.ghchart__projection { pointer-events: none; }
.ghchart__guide {
stroke: var(–ghc-guide);
stroke-width: 1.5;
stroke-dasharray: 4 4;
opacity: 0;
transition: opacity 0.12s ease;
}
.ghchart__hit { fill: transparent; }
/* —————————————————————— */
/* Scatter plot */
/* —————————————————————— */
.ghchart__gridline–v { /* vertical gridlines reuse the dashed grid style */ }
.ghchart__axistitle {
fill: var(–ghc-muted);
font-size: 20px;
font-weight: 600;
letter-spacing: -0.01em;
}
/* Dotted reference crosshair through the “reference” point. Stroke color
is set inline (tracks the reference series color). */
.ghchart__crosshair {
stroke-width: 2;
stroke-dasharray: 2 7;
stroke-linecap: round;
opacity: 0.85;
pointer-events: none;
}
.ghchart__halo { opacity: 0.18; pointer-events: none; }
.ghchart__pt { transition: r 0.12s ease; }
.ghchart__pt-label {
fill: var(–ghc-muted);
font-size: 19px;
font-weight: 600;
letter-spacing: -0.01em;
pointer-events: none;
}
.ghchart__pt-label–strong { font-size: 21px; font-weight: 700; }
.ghchart__pthit { fill: transparent; cursor: default; outline: none; }
.ghchart__pthit:focus-visible {
outline: 2px solid var(–ghc-axis);
outline-offset: 1px;
}
.ghchart__swatch–dot { border-radius: 50%; }
/* —————————————————————— */
/* Trend chart (dated time-series with narrative headline) */
/* —————————————————————— */
.ghchart__headline {
font-size: clamp(17px, 2.3vw, 20px);
line-height: 1.32;
font-weight: 700;
letter-spacing: -0.01em;
color: var(–ghc-text);
margin: 0 0 8px;
}
/* Trend legend sits left-aligned under the headline/subtitle. */
.ghchart__legend–trend {
justify-content: flex-start;
margin: 2px 0 14px;
}
.ghchart__glyph { flex: 0 0 auto; }
.ghchart__glyph–square {
width: 13px; height: 13px; border-radius: 2px;
}
.ghchart__glyph–diamond {
width: 12px; height: 12px; border: 1.5px solid; transform: rotate(45deg);
}
.ghchart__glyph–line {
width: 20px; height: 0; border-top: 2px dashed; border-radius: 0;
}
/* Phase background columns + separators + top labels. */
.ghchart__phase { fill: #f6f8fa; opacity: 0.55; }
.ghchart__phase–alt { fill: #ffffff; opacity: 0; }
.ghchart__phase-sep {
stroke: var(–ghc-grid); stroke-width: 1; stroke-dasharray: 2 5; opacity: 0.8;
}
.ghchart__phase-label {
fill: var(–ghc-muted);
font-size: 15px;
font-weight: 700;
letter-spacing: 0.06em;
}
.ghchart__phase-avg {
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Dashed trend line + ± noise band + slope label. */
.ghchart__trendband { opacity: 0.12; pointer-events: none; }
.ghchart__trendline {
stroke-width: 2.5;
stroke-dasharray: 8 6;
stroke-linecap: round;
fill: none;
}
.ghchart__slope {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Horizontal reference line + label. */
.ghchart__refline {
stroke-width: 2;
stroke-dasharray: 7 6;
stroke-linecap: round;
opacity: 0.85;
}
.ghchart__reflabel {
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Data squares — values reveal on hover only. */
.ghchart__square { transition: width 0.12s ease, height 0.12s ease, x 0.12s ease, y 0.12s ease; }
.ghchart__squarehit { fill: transparent; cursor: default; outline: none; }
.ghchart__squarehit:focus-visible {
outline: 2px solid var(–ghc-axis);
outline-offset: 1px;
}
/* Invalid-run markers + leader-line annotations. */
.ghchart__invalid { stroke-width: 2; }
.ghchart__invalid-x { stroke-width: 2; stroke-linecap: round; }
.ghchart__leader { stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.8; }
.ghchart__leader–callout { stroke-dasharray: none; opacity: 0.6; }
.ghchart__anno-title {
font-size: 15px;
font-weight: 700;
letter-spacing: -0.01em;
}
.ghchart__anno-sub {
fill: var(–ghc-muted);
font-size: 13px;
font-weight: 500;
}
/* Final-callout labels read a touch larger than the invalid-run notes. */
.ghchart__anno-title–callout { font-size: 17px; }
.ghchart__anno-sub–callout { font-size: 14px; }
/* Bracket span annotation. */
.ghchart__bracket { stroke: var(–ghc-axis); stroke-width: 1.5; opacity: 0.75; }
.ghchart__bracket-label {
fill: var(–ghc-muted);
font-size: 13px;
font-weight: 700;
letter-spacing: 0.04em;
}
/* Final callout ring. */
.ghchart__callout-ring { fill: none; stroke-width: 2; }
/* —————————————————————— */
/* Title + divider */
/* —————————————————————— */
.ghchart__title {
/* Matches the github.blog H2 (.wp-block-heading) exactly */
display: block;
font-size: 32px;
line-height: 40px;
font-weight: 700;
letter-spacing: -0.18px;
color: var(–ghc-text);
}
.ghchart__rule {
height: 1.5px;
border-radius: 1.5px;
background: var(–ghc-divider);
margin: 10px 0 18px;
}
/* —————————————————————— */
/* Bar chart: subtitle, panels, bars, caption */
/* —————————————————————— */
.ghchart__subtitle {
margin: -6px 0 20px;
font-size: clamp(13px, 1.6vw, 15px);
line-height: 1.5;
color: var(–ghc-muted);
}
.ghchart__panel { margin-top: 30px; }
.ghchart__panel:first-of-type { margin-top: 6px; }
.ghchart__panel-title {
margin: 0 0 6px;
font-size: clamp(15px, 1.9vw, 18px);
font-weight: 700;
letter-spacing: -0.01em;
color: var(–ghc-text);
}
.ghchart__plot–bar { touch-action: pan-y; }
.ghchart__axisline {
stroke: var(–ghc-axis);
stroke-width: 1.25;
opacity: 0.35;
}
.ghchart__bar { transition: opacity 0.12s ease; }
.ghchart__barhit { fill: transparent; }
/* Always-on value labels above selected bars. */
.ghchart__barvalue {
fill: var(–ghc-text);
font-size: 20px;
font-weight: 700;
font-variant-numeric: tabular-nums;
pointer-events: none;
}
/* Anchor annotation: dotted reference line + floating pill. */
.ghchart__anno-line {
stroke: #8b949e;
stroke-width: 1.5;
stroke-dasharray: 4 3;
stroke-linecap: round;
pointer-events: none;
}
.ghchart__anno-chip {
fill: #ffebe9;
stroke: #ffcecb;
stroke-width: 1;
pointer-events: none;
}
.ghchart__anno-chip-text {
fill: #a0111f;
font-weight: 700;
pointer-events: none;
}
.ghchart__caption {
margin-top: 22px;
padding-top: 12px;
border-top: 1px solid var(–ghc-card-border);
font-size: 12.5px;
line-height: 1.55;
color: var(–ghc-muted);
}
.ghchart__caption b { color: var(–ghc-text); font-weight: 700; }
.ghchart__caption code {
font-family: ui-monospace, SFMono-Regular, “SF Mono”, Menlo, Consolas, monospace;
font-size: 0.92em;
padding: 1px 5px;
background: #f6f8fa;
border: 1px solid var(–ghc-card-border);
border-radius: 5px;
}
.ghchart__tag {
display: inline-block;
font-size: 10px;
font-weight: 650;
letter-spacing: 0.03em;
text-transform: uppercase;
padding: 2px 7px;
border-radius: 999px;
border: 1px solid #aceebb;
color: #0a5223;
background: #dafbe1;
white-space: nowrap;
vertical-align: middle;
}
/* —————————————————————— */
/* Distribution dashboard (segmented mix bar + ranked bar panels) */
/* —————————————————————— */
.ghchart__dist-section { margin-top: 28px; }
.ghchart__dist-section:first-of-type { margin-top: 12px; }
.ghchart–panel-cards { padding: 24px; background: var(–ghc-canvas); }
.ghchart–panel-cards .ghchart__dist-cols { gap: 24px; }
.ghchart–panel-cards .ghchart__dist-cols .ghchart__dist-section {
min-width: 0;
margin: 0;
padding: 24px;
border: 1px solid var(–ghc-card-border);
border-radius: 8px;
background: var(–ghc-card-bg);
}
@media (max-width: 480px) {
.ghchart–panel-cards,
.ghchart–panel-cards .ghchart__dist-cols .ghchart__dist-section { padding: 16px; }
}
/* Segmented proportional “mix” bar: name inside, count/percent below.
Separators are 2px inside borders (box-sizing:border-box) rather than a
flex gap, so the three segments sum to exactly 100% with no overflow. */
.ghchart__seg {
display: flex;
width: 100%;
height: 42px;
margin-top: 2px;
border-radius: 7px;
overflow: hidden;
}
.ghchart__seg-cell {
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
color: #ffffff;
font-size: 15px;
font-weight: 700;
letter-spacing: -0.01em;
white-space: nowrap;
overflow: hidden;
}
.ghchart__seg-cell + .ghchart__seg-cell { border-left: 2px solid var(–ghc-card-bg); }
.ghchart__seg-note-row {
display: flex;
width: 100%;
margin-top: 8px;
}
.ghchart__seg-note {
min-width: 0;
text-align: center;
font-size: 12.5px;
color: var(–ghc-muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* Keep the outer notes from spilling past the chart edges. */
.ghchart__seg-note:first-child { text-align: left; }
.ghchart__seg-note:last-child { text-align: right; }
.ghchart__seg-note b { color: var(–ghc-text); font-weight: 700; }
/* Ranked horizontal bar list: [category | bar track | value].
The list is the grid and rows use display:contents, so the category,
bar, and value columns line up across every row in a panel. */
.ghchart__dist-list {
display: grid;
grid-template-columns: max-content 1fr max-content;
align-items: center;
column-gap: 12px;
row-gap: 10px;
margin-top: 4px;
}
.ghchart__dist-row { display: contents; }
.ghchart__dist-cat {
text-align: right;
font-size: 13px;
line-height: 1.25;
color: var(–ghc-text);
white-space: nowrap;
}
.ghchart__dist-track { width: 100%; }
.ghchart__dist-bar {
height: 15px;
min-width: 2px;
border-radius: 3px;
}
.ghchart__dist-val {
font-size: 12.5px;
color: var(–ghc-muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.ghchart__dist-val b { color: var(–ghc-text); font-weight: 700; }
.ghchart__dist-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 16px; margin: 0; }
.ghchart__dist-stat { display: flex; flex-direction: column; gap: 4px; }
.ghchart__dist-stat dt { color: var(–ghc-muted); font-size: 13px; }
.ghchart__dist-stat dd { order: -1; margin: 0; font-size: 30px; font-weight: 800; letter-spacing: -0.02em; }
.ghchart__comparison-row { display: grid; grid-template-columns: 145px minmax(0, 1fr); gap: 20px; padding: 14px 0; }
.ghchart__comparison-label { align-self: center; font-size: 15px; font-weight: 600; }
.ghchart__comparison-plot { position: relative; padding: 6px 0; display: grid; gap: 8px; }
.ghchart__comparison-grid {
position: absolute; top: 0; bottom: 0; border-left: 1px dashed var(–ghc-grid);
transform: translateX(-50%); pointer-events: none;
}
.ghchart__comparison-zero { border-left: 2px solid var(–ghc-axis); z-index: 1; }
.ghchart__comparison-track { position: relative; height: 28px; }
.ghchart__comparison-bar {
position: absolute; top: 0; height: 28px;
border-radius: 0 2px 2px 0;
pointer-events: none;
}
.ghchart__comparison-bar–negative { border-radius: 2px 0 0 2px; }
.ghchart__comparison-value {
position: absolute;
top: 4px;
z-index: 2;
width: max-content;
color: var(–ghc-text);
font-size: 13px;
font-weight: 600;
line-height: 20px;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.ghchart__comparison-track–stacked .ghchart__comparison-value {
position: relative;
top: 0;
width: 100%;
white-space: normal;
overflow-wrap: anywhere;
background: var(–ghc-card-bg);
}
.ghchart__comparison-axis { position: relative; height: 24px; }
.ghchart__comparison-tick { position: absolute; transform: translateX(-50%); white-space: nowrap; font-size: 12px; color: var(–ghc-axis); }
@media (max-width: 480px) {
.ghchart__comparison-row { grid-template-columns: minmax(0, 1fr); gap: 8px; }
.ghchart__comparison-tick { font-size: 11px; }
}
/* Two-up panel grid (collapses to one column on narrow screens).
minmax(0,1fr) lets columns shrink so long labels never force overflow. */
.ghchart__dist-cols {
margin-top: 26px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px 30px;
}
.ghchart__dist-cols .ghchart__dist-section { margin-top: 0; }
.ghchart__dist-cols–stack { grid-template-columns: 1fr; }
.ghchart__dist-cols–stack .ghchart__dist-section { margin-top: 28px; }
.ghchart__dist-cols–stack .ghchart__dist-section:first-child { margin-top: 0; }
/* Tighten the label/value type a touch in the 2-up layout so the bars
keep room at the 699px blog width. */
.ghchart__dist-cols:not(.ghchart__dist-cols–stack) .ghchart__dist-cat { font-size: 12px; }
.ghchart__dist-cols:not(.ghchart__dist-cols–stack) .ghchart__dist-val { font-size: 11.5px; }
.ghchart__dist-cols:not(.ghchart__dist-cols–stack) .ghchart__dist-list { column-gap: 9px; }
@media (max-width: 600px) {
.ghchart__dist-cols { grid-template-columns: 1fr; gap: 26px; }
.ghchart__dist-cols .ghchart__dist-section ~ .ghchart__dist-section { margin-top: 2px; }
}
/* Let long category labels wrap on very narrow (mobile) widths. */
@media (max-width: 480px) {
.ghchart__dist-cat { white-space: normal; }
}
/* —————————————————————— */
/* Timeline / staged-duration chart (request lifecycle, before/after) */
/* —————————————————————— */
.ghchart__tl-eyebrow {
font-size: clamp(12px, 1.5vw, 13px);
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(–ghc-muted);
margin: 0 0 6px;
}
.ghchart__tl-headline { margin-bottom: 14px; }
/* Before -> after summary card. */
.ghchart__tl-stat {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 16px 24px;
padding: 16px 22px;
border: 1px solid var(–ghc-card-border);
border-radius: 12px;
/* Greenish grey to match the AI gateway diagram canvas (–ghd-canvas). */
background: #f2f5f3;
margin: 0 0 18px;
}
.ghchart__tl-stat-main { display: flex; align-items: center; gap: 20px; }
.ghchart__tl-stat-col { display: flex; flex-direction: column; gap: 2px; }
.ghchart__tl-stat-k {
font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
text-transform: uppercase; color: var(–ghc-muted);
}
.ghchart__tl-stat-v {
font-size: clamp(28px, 4.4vw, 40px); font-weight: 800;
letter-spacing: -0.02em; line-height: 1; color: var(–ghc-text);
}
.ghchart__tl-stat-v–muted { color: var(–ghc-muted); font-weight: 700; }
.ghchart__tl-stat-arrow { font-size: 26px; color: var(–ghc-muted); }
.ghchart__tl-stat-delta { text-align: right; }
.ghchart__tl-stat-delta-v {
font-size: clamp(26px, 4vw, 38px); font-weight: 800;
letter-spacing: -0.02em; line-height: 1; color: #1a7f37;
}
.ghchart__tl-stat-delta-k { font-size: 13px; color: var(–ghc-muted); margin-top: 4px; }
/* Stage legend (2-up, monospace-free, stacks on mobile). */
.ghchart__tl-legend {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 28px;
margin: 0 0 20px;
}
.ghchart__tl-legend-item { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
.ghchart__tl-swatch { width: 13px; height: 13px; border-radius: 3px; flex: 0 0 auto; }
.ghchart__tl-legend-text {
font-size: clamp(13px, 1.7vw, 15px); color: var(–ghc-text);
font-variant-numeric: tabular-nums;
}
.ghchart__tl-rule {
height: 1.5px; border-radius: 1.5px; background: var(–ghc-divider); margin: 4px 0 18px;
}
/* SVG plot text. */
.ghchart__tl-rowlabel { fill: var(–ghc-text); font-size: 22px; font-weight: 700; letter-spacing: 0.02em; }
.ghchart__tl-rowsub { fill: var(–ghc-muted); font-size: 18px; font-weight: 500; }
.ghchart__tl-seg-label { fill: #ffffff; font-size: 23px; font-weight: 700; letter-spacing: -0.01em; }
.ghchart__tl-callout-line { stroke: var(–ghc-guide); stroke-width: 1.5; fill: none; }
.ghchart__tl-callout-label { font-size: 21px; font-weight: 600; letter-spacing: -0.01em; }
/* Segments + ribbons ease their opacity for the before/after hover emphasis. */
.ghchart__tl-seg { transition: opacity 0.14s ease; }
.ghchart__tl-seg[data-stage] { cursor: pointer; }
.ghchart__tl-seg[data-stage]:focus-visible { outline: 2px solid var(–ghc-axis); outline-offset: 1px; }
.ghchart__tl-flow { opacity: 0.16; transition: opacity 0.14s ease; }
/* Timeline millisecond gridlines: thicker + larger dashes so the axis marks
read clearly behind the bars (scoped so scatter gridlines are unaffected). */
.ghchart__plot–timeline .ghchart__gridline–v {
stroke-width: 1.5;
stroke-dasharray: 5 5;
opacity: 1;
}
/* Timeline bracket labels read larger than the trend bracket note (they are
structural spans, not a small secondary annotation). */
.ghchart__tl-bracket-label { font-size: 20px; }
/* Wrapper so DOM labels can sit over the SVG at viewBox-proportional %s. */
.ghchart__tl-plotwrap { position: relative; }
/* Right-bracket label as a DOM node: a fixed, readable size that does NOT
scale down with the plot on small windows (unlike SVG text). */
.ghchart__tl-bracket-label-dom {
position: absolute;
transform: translateY(-50%);
font-size: clamp(12px, 1.5vw, 14px);
font-weight: 700;
letter-spacing: 0.02em;
color: var(–ghc-muted);
white-space: nowrap;
pointer-events: none;
}
.ghchart__tl-tick { fill: var(–ghc-axis); }
/* Axis title rendered as a DOM caption below the plot; matches the legend
labels’ size/weight/color instead of scaling with the SVG. */
.ghchart__tl-axis-caption {
font-size: clamp(13px, 1.7vw, 15px);
font-weight: 400;
color: var(–ghc-text);
text-align: center;
margin-top: 8px;
}
@media (max-width: 560px) {
.ghchart__tl-legend { grid-template-columns: 1fr; }
.ghchart__tl-stat { justify-content: flex-start; }
.ghchart__tl-stat-delta { text-align: left; }
}
/* —————————————————————— */
/* Calendar heatmap (GitHub contribution-graph style) */
/* —————————————————————— */
.ghheat__eyebrow { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; }
.ghheat__eyedot {
width: 9px; height: 9px; border-radius: 50%;
background: #cf222e; box-shadow: 0 0 0 3px rgba(207, 34, 46, 0.14);
flex: 0 0 auto;
}
.ghheat__eyetext {
font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
text-transform: uppercase; color: #cf222e;
}
/* Subtitle here is a prominent lede, not the muted variant. */
.ghheat__subtitle {
color: var(–ghc-text);
font-size: clamp(14px, 1.7vw, 16px);
/* 24px top gap matches the github.blog H2 → paragraph block spacing. */
margin: 24px 0 8px;
}
.ghheat__subtitle b { font-weight: 700; }
.ghheat__panel { margin-top: 36px; }
.ghheat__phead {
display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
margin-bottom: 16px;
}
.ghheat__badge {
display: inline-flex; align-items: center; justify-content: center;
width: 22px; height: 22px; border-radius: 50%;
color: #fff; font-size: 12px; font-weight: 700;
flex: 0 0 auto; align-self: center;
}
.ghheat__ptitle {
font-size: clamp(16px, 2vw, 19px); font-weight: 700;
letter-spacing: -0.01em; color: var(–ghc-text);
}
.ghheat__pdesc { font-size: 14px; color: var(–ghc-muted); }
.ghheat__body { display: flex; gap: 30px; align-items: flex-start; flex-wrap: wrap; }
.ghheat__gridwrap { flex: 1 1 340px; min-width: 260px; }
.ghheat__grid { display: block; width: 100%; height: auto; overflow: visible; }
.ghheat__cell {
stroke: rgba(27, 31, 36, 0.06);
stroke-width: 1;
transition: stroke 0.1s ease;
}
.ghheat__cell:hover { stroke: #24292f; stroke-width: 1.4; }
.ghheat__cell–pad { opacity: 0; pointer-events: none; }
.ghheat__wd, .ghheat__month {
fill: var(–ghc-muted); font-size: 11px; font-weight: 600;
}
.ghheat__side { flex: 1 1 250px; min-width: 230px; }
.ghheat__stat { display: flex; align-items: baseline; gap: 7px; }
.ghheat__statbig {
font-size: clamp(26px, 4vw, 34px); font-weight: 800; letter-spacing: -0.02em;
line-height: 1;
}
.ghheat__statsmall { font-size: 15px; font-weight: 600; color: var(–ghc-muted); }
.ghheat__note {
margin: 10px 0 16px; font-size: 14px; line-height: 1.55; color: var(–ghc-muted);
}
.ghheat__note b { color: var(–ghc-text); font-weight: 700; }
.ghheat__legend {
display: flex; align-items: center; gap: 5px;
font-size: 12.5px; color: var(–ghc-muted); flex-wrap: wrap;
}
.ghheat__sw { width: 12px; height: 12px; border-radius: 3px; display: inline-block; flex: 0 0 auto; }
.ghheat__sw–empty { border: 1px solid #d0d7de; }
.ghheat__legunit { margin-left: 2px; }
.ghheat__extra {
display: flex; align-items: flex-start; gap: 7px; margin-top: 14px;
font-size: 12px; line-height: 1.5; color: var(–ghc-muted); max-width: 330px;
}
.ghheat__extra .ghheat__sw { margin-top: 2px; }
/* —————————————————————— */
/* Tooltip */
/* —————————————————————— */
.ghchart__tooltip {
position: absolute;
z-index: 5;
top: 0;
left: 0;
/* `width: max-content` keeps the box sized to its own content instead of
the browser’s absolute-position “shrink-to-fit” formula, which derives
available width from how far `left` sits from the containing block’s
edge — near the right edge that shrinks toward zero and forces the
row text to wrap awkwardly even though the box is later clamped into
view. max-content is unaffected by `left`, so wrapping only happens if
content truly exceeds max-width. */
width: max-content;
min-width: 128px;
max-width: 260px;
padding: 10px 12px;
background: var(–ghc-card-bg);
border: 1px solid var(–ghc-card-border);
border-radius: 12px;
/* Layered drop shadow from Primer Brand’s ActionMenu (.ActionMenu__menu) */
box-shadow:
0px 100px 80px rgba(0, 0, 0, 0.01),
0px 41px 33px rgba(0, 0, 0, 0.02),
0px 22px 17px rgba(0, 0, 0, 0.02),
0px 12px 10px rgba(0, 0, 0, 0.03),
0px 6px 5px rgba(0, 0, 0, 0.04),
0px 2px 2px rgba(0, 0, 0, 0.07);
pointer-events: none;
opacity: 0;
transform: translate(-50%, -100%);
/* Fade in on show and out on hide; quickly ease left/top when tracking
from one point to the next. The position ease is suppressed on the
first appearance (see chart.js) so the tooltip fades in place rather
than sliding in from its previous spot. */
transition: opacity 0.12s ease,
left 0.1s cubic-bezier(0.4, 0, 0.2, 1),
top 0.1s cubic-bezier(0.4, 0, 0.2, 1);
will-change: opacity, transform, left, top;
}
.ghchart__tooltip[data-visible=”true”] { opacity: 1; }
.ghchart__tt-title {
font-size: 13px;
font-weight: 700;
color: var(–ghc-text);
margin-bottom: 6px;
}
.ghchart__tt-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
line-height: 1.6;
color: var(–ghc-muted);
}
.ghchart__tt-swatch { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.ghchart__tt-name { flex: 1 1 auto; }
.ghchart__tt-value { font-weight: 700; color: var(–ghc-text); font-variant-numeric: tabular-nums; }
/* Optional “partial data” note, shown only for a hovered point past a
line’s config.partial.fromIndex (see .ghchart__line–dashed). */
.ghchart__tt-note {
margin-top: 6px;
padding-top: 6px;
border-top: 1px solid var(–ghc-card-border);
font-size: 12px;
font-style: italic;
color: var(–ghc-muted);
}
/* Bar tooltip body: a title line + free-form text lines (no swatches). */
.ghchart__tt-line {
font-size: 13px;
line-height: 1.5;
color: var(–ghc-muted);
}
.ghchart__tt-line:first-child { color: var(–ghc-text); font-weight: 600; }
.ghchart__tt-line + .ghchart__tt-line { margin-top: 2px; }
/* Timeline before/after tooltip: only the title is bold; both value lines stay
regular weight and the same color (no emphasised first line). */
.ghchart__tt-body–plain .ghchart__tt-line,
.ghchart__tt-body–plain .ghchart__tt-line:first-child {
font-weight: 400;
color: var(–ghc-text);
}
@media (prefers-reduced-motion: reduce) {
.ghchart__dot,
.ghchart__guide,
.ghchart__bar,
.ghchart__pt,
.ghchart__square,
.ghheat__cell,
.ghchart__tl-seg,
.ghchart__tl-flow,
.ghchart__tooltip { transition: none; }
}
/*
Shared chart engine — reusable renderer for the blog-charts library.
——————————————————————
Exposes window.GHChartEngine = { renderers, render(config, mountEl?), destroy(mountEl) }.
Renderers are keyed by config.type (currently “line”); add a new viz type
by registering GHChartEngine.renderers[] = function (root, config) {…}.
This same file is embedded verbatim at the top of each exported WordPress
chart.js, followed by that chart’s CONFIG + a small auto-boot. It has no
external dependencies and is safe to include more than once on a page.
*/
(function (global) {
“use strict”;
var SVGNS = “http://www.w3.org/2000/svg”;
function el(tag, attrs) {
var node = document.createElementNS(SVGNS, tag);
if (attrs) {
for (var k in attrs) {
if (Object.prototype.hasOwnProperty.call(attrs, k)) {
node.setAttribute(k, attrs[k]);
}
}
}
return node;
}
function html(tag, cls) {
var node = document.createElement(tag);
if (cls) node.className = cls;
return node;
}
function clamp(v, lo, hi) { return v hi ? hi : v; }
var WHITE = [255, 255, 255], BLACK = [0, 0, 0];
function hexToRgb(h) {
h = String(h).replace(“#”, “”);
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
}
function rgbToHex(c) {
return “#” + c.map(function (x) {
x = Math.max(0, Math.min(255, Math.round(x)));
return (x < 16 ? "0" : "") + x.toString(16);
}).join("");
}
function mix(a, b, t) {
return [a[0] + (b[0] – a[0]) * t, a[1] + (b[1] – a[1]) * t, a[2] + (b[2] – a[2]) * t];
}
function rampFrom(baseHex) {
var b = hexToRgb(baseHex);
return [
rgbToHex(mix(b, WHITE, 0.80)),
rgbToHex(mix(b, WHITE, 0.52)),
rgbToHex(mix(b, WHITE, 0.20)),
baseHex,
rgbToHex(mix(b, BLACK, 0.30))
];
}
function strongOf(baseHex) { return rampFrom(baseHex)[4]; }
function labelColorOn(hex) {
function luminance(color) {
var c = hexToRgb(color).map(function (channel) {
channel /= 255;
return channel = 4.5) return “#1f2328”;
return 1.05 / (background + 0.05) >= 4.5 ? “#ffffff” : “#000000”;
}
var renderers = {};
var renderCleanups = (global.GHChartEngine && global.GHChartEngine._renderCleanups) || new WeakMap();
renderers.comparison = function (root, config) {
var axis = config.xAxis, series = config.series, rows = config.rows;
if (!axis || !Number.isFinite(axis.min) || !Number.isFinite(axis.max) ||
axis.min > 0 || axis.max < 0 || axis.max <= axis.min ||
!Array.isArray(axis.ticks) || !Array.isArray(series) || !series.length ||
series.some(function (s) { return !s || typeof s.color !== "string" || !/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(s.color); }) ||
!Array.isArray(rows) || !rows.length ||
axis.ticks.some(function (v) { return !Number.isFinite(v) || v axis.max; }) ||
rows.some(function (row) {
return !Array.isArray(row.values) || row.values.length !== series.length ||
row.values.some(function (v) { return !Number.isFinite(v) || v axis.max; }) ||
!Array.isArray(row.decimals) || row.decimals.length !== series.length ||
row.decimals.some(function (n) { return !Number.isInteger(n) || n 10; });
})) throw new Error(“Comparison chart requires a zero-inclusive axis, hex series colors, and matching finite values/decimal precision for each series.”);
root.textContent = “”;
root.setAttribute(“role”, “figure”);
root.setAttribute(“aria-label”, config.title);
var heading = html(“div”, “ghchart__headline”);
heading.textContent = config.title;
root.appendChild(heading);
if (config.subtitle) {
var subtitle = html(“p”, “ghchart__subtitle”);
subtitle.textContent = config.subtitle;
root.appendChild(subtitle);
}
var legend = html(“div”, “ghchart__legend ghchart__legend–trend”);
series.forEach(function (s) {
var item = html(“span”, “ghchart__legend-item”);
var swatch = html(“span”, “ghchart__swatch”);
swatch.style.background = s.color;
item.appendChild(swatch);
item.appendChild(document.createTextNode(s.name));
legend.appendChild(item);
});
root.appendChild(legend);
var marks = [];
function at(value) { return (value – axis.min) / (axis.max – axis.min) * 100; }
function signed(value, decimals) { return (value > 0 ? “+” : “”) + value.toFixed(decimals) + “%”; }
rows.forEach(function (row) {
var group = html(“div”, “ghchart__comparison-row”);
group.setAttribute(“role”, “group”);
group.setAttribute(“aria-label”, row.label);
var label = html(“div”, “ghchart__comparison-label”);
label.textContent = row.label;
label.setAttribute(“aria-hidden”, “true”);
group.appendChild(label);
var plot = html(“div”, “ghchart__comparison-plot”);
axis.ticks.forEach(function (value) {
var line = html(“span”, “ghchart__comparison-grid”);
line.style.left = at(value) + “%”;
line.setAttribute(“aria-hidden”, “true”);
plot.appendChild(line);
});
var zero = html(“span”, “ghchart__comparison-grid ghchart__comparison-zero”);
zero.style.left = at(0) + “%”;
zero.setAttribute(“aria-hidden”, “true”);
plot.appendChild(zero);
series.forEach(function (s, index) {
var value = row.values[index], formatted = signed(value, row.decimals[index]);
var track = html(“div”, “ghchart__comparison-track”);
track.setAttribute(“role”, “group”);
track.setAttribute(“aria-label”, s.name);
var bar = html(“span”, “ghchart__comparison-bar”);
// Anchor the zero-facing edge, without fractional transform snapping.
if (value < 0) {
bar.style.right = "calc(100% – " + at(0) + "%)";
bar.classList.add("ghchart__comparison-bar–negative");
} else bar.style.left = at(0) + "%";
bar.style.width = Math.abs(at(value) – at(0)) + "%";
bar.style.background = s.color;
bar.setAttribute("aria-hidden", "true");
var valueLabel = html("span", "ghchart__comparison-value");
valueLabel.textContent = formatted;
track.appendChild(bar);
track.appendChild(valueLabel);
marks.push({ track: track, bar: bar, label: valueLabel, value: value, color: s.color });
plot.appendChild(track);
});
group.appendChild(plot);
root.appendChild(group);
});
var axisRow = html("div", "ghchart__comparison-row");
axisRow.appendChild(html("span"));
var ticks = html("div", "ghchart__comparison-axis");
axis.ticks.forEach(function (value) {
var tick = html("span", "ghchart__comparison-tick");
tick.style.left = at(value) + "%";
if (value === axis.min) tick.style.transform = "none";
if (value === axis.max) tick.style.transform = "translateX(-100%)";
tick.textContent = value === 0 ? "0" : signed(value, 0);
ticks.appendChild(tick);
});
axisRow.appendChild(ticks);
root.appendChild(axisRow);
var active = true, frame = 0, observer;
function positionLabel(mark, side, coordinate, placement) {
mark.label.style.left = side === "left" ? "calc(" + at(coordinate) + "% + 8px)" : "auto";
mark.label.style.right = side === "right" ? "calc(100% – " + at(coordinate) + "% + 8px)" : "auto";
mark.label.style.color = placement === "inside" ? labelColorOn(mark.color) : "";
mark.label.setAttribute("data-placement", placement);
}
function layoutValues() {
if (!active || !root.clientWidth) return;
marks.forEach(function (mark) {
mark.track.classList.remove("ghchart__comparison-track–stacked");
mark.track.style.height = "";
mark.bar.style.top = "";
mark.label.style.left = "0px";
mark.label.style.right = "auto";
mark.label.style.color = "";
var width = mark.track.clientWidth, textWidth = mark.label.offsetWidth + 1;
var zero = at(0) * width / 100, end = at(mark.value) * width / 100;
var negative = mark.value = textWidth + 8) {
positionLabel(mark, negative ? “right” : “left”, mark.value, “outside”);
} else if (Math.abs(end – zero) >= textWidth + 16) {
positionLabel(mark, negative ? “left” : “right”, mark.value, “inside”);
} else if (opposite >= textWidth + 8) {
positionLabel(mark, negative ? “left” : “right”, 0, “opposite”);
} else {
// Very narrow plots or long precision strings get a separate label line.
mark.track.classList.add(“ghchart__comparison-track–stacked”);
mark.label.setAttribute(“data-placement”, “stacked”);
var top = mark.label.offsetHeight + 6;
mark.bar.style.top = top + “px”;
mark.track.style.height = top + 28 + “px”;
}
});
}
function schedule() {
if (!active) return;
cancelAnimationFrame(frame);
frame = requestAnimationFrame(layoutValues);
}
layoutValues();
if (global.ResizeObserver) {
observer = new ResizeObserver(schedule);
observer.observe(root);
} else global.addEventListener(“resize”, schedule);
if (document.fonts) {
document.fonts.ready.then(schedule);
document.fonts.addEventListener(“loadingdone”, schedule);
}
return function () {
active = false;
cancelAnimationFrame(frame);
if (observer) observer.disconnect();
global.removeEventListener(“resize”, schedule);
if (document.fonts) document.fonts.removeEventListener(“loadingdone”, schedule);
};
};
/* ================================================================
Line chart renderer
================================================================ */
renderers.line = function (root, config) {
// viewBox geometry (design units; scales fluidly to any width).
var VB = { w: 1000, h: 560 };
var M = { top: 30, right: 34, bottom: 56, left: 86 };
// Inset the plotted points slightly from the axis so the first/last
// months (e.g. “Jan”) don’t crowd the y-axis labels (e.g. “50”).
var PAD_X = 18;
// Reset (in case of re-render) and use a unique id prefix so multiple
// charts on one page never share gradient ids.
root.textContent = “”;
var uid = config.mountId;
var n = config.labels.length;
var yMin = config.yAxis.min;
var yMax = config.yAxis.max;
var step = config.yAxis.step;
// Y-axis tick labels can format differently from the tooltip values
// (e.g. a plain “40” on the axis but “40%” in the tooltip).
var fmtAxis = config.formatAxis || config.formatValue;
// Widen the left gutter when the formatted tick labels need it (a unit
// suffix like “2,000M” is far wider than a bare “40”). Labels are real
// SVG we can’t measure before layout, so estimate from the
// glyph mix at the 22-unit axis font size.
var AXIS_FONT = 22;
var Y_LABEL_GAP = 22;
function estimateTextW(s) {
var w = 0;
for (var i = 0; i < s.length; i++) {
var c = s.charAt(i);
if (c === "," || c === "." || c === " ") w += 0.28;
else if (/[0-9]/.test(c)) w += 0.58;
else if (/[A-Z]/.test(c)) w += 0.72;
else w += 0.55;
}
return w * AXIS_FONT;
}
var widestY = 0;
for (var tv = yMin; tv <= yMax + 0.0001; tv += step) {
widestY = Math.max(widestY, estimateTextW(String(fmtAxis(tv))));
}
M.left = Math.max(M.left, Math.ceil(widestY + Y_LABEL_GAP + 8));
var innerW = VB.w – M.left – M.right;
var innerH = VB.h – M.top – M.bottom;
var plotW = innerW – 2 * PAD_X;
var stepX = n <= 1 ? 0 : plotW / (n – 1);
function xAt(i) { return n <= 1 ? M.left + innerW / 2 : M.left + PAD_X + stepX * i; }
function yAt(v) { return M.top + innerH * (1 – (v – yMin) / (yMax – yMin)); }
var baseY = yAt(yMin);
/* —- Title + divider (top) ———————————- */
var title = html("span", "ghchart__title");
title.textContent = config.title;
root.appendChild(title);
root.appendChild(html("div", "ghchart__rule"));
/* —- Header + legend —————————————- */
if (!config.hideLegend) {
var header = html("div", "ghchart__header");
var legend = html("div", "ghchart__legend");
config.series.forEach(function (s) {
var item = html("span", "ghchart__legend-item");
var sw = html("span", "ghchart__swatch");
sw.style.background = s.color;
item.appendChild(sw);
item.appendChild(document.createTextNode(s.name));
legend.appendChild(item);
});
header.appendChild(legend);
root.appendChild(header);
}
/* —- SVG plot ———————————————– */
var svg = el("svg", {
"class": "ghchart__plot",
viewBox: "0 0 " + VB.w + " " + VB.h,
preserveAspectRatio: "xMidYMid meet",
role: "img",
"aria-label": config.title + " line chart"
});
// Gradient defs (one per area series).
var defs = el("defs");
config.series.forEach(function (s, si) {
if (!s.area) return;
var g = el("linearGradient", {
id: uid + "-grad-" + si, x1: "0", y1: "0", x2: "0", y2: "1"
});
g.appendChild(el("stop", { offset: "0", "stop-color": s.color, "stop-opacity": "0.28" }));
g.appendChild(el("stop", { offset: "0.55", "stop-color": s.color, "stop-opacity": "0.08" }));
g.appendChild(el("stop", { offset: "1", "stop-color": s.color, "stop-opacity": "0" }));
defs.appendChild(g);
});
// Diagonal-hatch pattern for an optional "partial / projected" band.
if (config.projection) {
var pat = el("pattern", {
id: uid + "-hatch", width: 7, height: 7,
patternUnits: "userSpaceOnUse", patternTransform: "rotate(45)"
});
pat.appendChild(el("line", {
x1: 0, y1: 0, x2: 0, y2: 7,
stroke: config.projection.color || "#8c959f",
"stroke-width": 1.1, "stroke-opacity": 0.5
}));
defs.appendChild(pat);
}
svg.appendChild(defs);
// Gridlines + y-axis labels.
for (var v = yMin; v cubic Bezier) path builder.
function smooth(points) {
if (!points.length) return “”;
if (points.length < 2) return "M" + points[0].x + "," + points[0].y;
var d = "M " + points[0].x + "," + points[0].y;
for (var i = 0; i < points.length – 1; i++) {
var p0 = points[i – 1] || points[i];
var p1 = points[i];
var p2 = points[i + 1];
var p3 = points[i + 2] || p2;
var t = 1 / 6;
var c1x = p1.x + (p2.x – p0.x) * t;
var c1y = p1.y + (p2.y – p0.y) * t;
var c2x = p2.x – (p3.x – p1.x) * t;
var c2y = p2.y – (p3.y – p1.y) * t;
d += " C " + c1x + "," + c1y + " " + c2x + "," + c2y + " " + p2.x + "," + p2.y;
}
return d;
}
// Area fills (drawn first, behind lines). When config.partial marks a
// trailing partial/incomplete range (e.g. a month still in progress),
// the fill stops at the last complete point instead of extending under
// the partial segment — visually distinguishing "real" coverage from
// data that isn't final yet.
var partialIdx = config.partial ? config.partial.fromIndex : null;
config.series.forEach(function (s, si) {
if (!s.area) return;
var p = pts[si];
var fillP = partialIdx == null ? p : p.slice(0, partialIdx + 1);
if (fillP.length < 2) return;
var d = smooth(fillP) + " L " + fillP[fillP.length – 1].x + "," + baseY +
" L " + fillP[0].x + "," + baseY + " Z";
svg.appendChild(el("path", { d: d, fill: "url(#" + uid + "-grad-" + si + ")" }));
});
// Vertical hover guide (hidden until hover).
var guide = el("line", {
"class": "ghchart__guide",
x1: xAt(0), y1: M.top – 6, x2: xAt(0), y2: baseY
});
svg.appendChild(guide);
// Lines (draw in reverse so the first series sits on top). The trailing
// partial segment (from the last complete point onward) renders as its
// own dashed path so the "not final yet" range reads clearly without
// a legend entry — mirroring the projected-region convention above.
config.series.slice().reverse().forEach(function (s) {
var si = config.series.indexOf(s);
var p = pts[si];
var solidP = partialIdx == null ? p : p.slice(0, partialIdx + 1);
svg.appendChild(el("path", {
"class": "ghchart__line",
d: smooth(solidP),
stroke: s.color,
"stroke-width": s.width || (si === 0 ? 4 : 3.25)
}));
if (partialIdx != null && partialIdx < p.length – 1) {
// Straight (not curve-fit) dashed segment: fitting a Catmull-Rom
// curve to just this short tail would use different neighbor
// context than the solid curve and create a visible kink at the
// junction point; a straight dashed line reads as "projected" and
// joins the solid curve's end point cleanly.
var dashedP = p.slice(partialIdx);
var dd = "M " + dashedP[0].x + "," + dashedP[0].y;
var segLen = 0;
for (var di = 1; di 0 && segLen / PERIOD partialIdx) ? “” : “none”;
}
// Anchor above the highest point in this column.
var topSi = 0, topY = Infinity;
config.series.forEach(function (s, si) {
var yy = yAt(s.values[i]);
if (yy < topY) { topY = yy; topSi = si; }
});
var anchor = dotEls[topSi][i].getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
// On the first appearance, fade in exactly at the point: suppress the
// position ease so it doesn't slide in from its previous resting spot.
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = "opacity 0.12s ease";
tip.setAttribute("data-visible", "true");
// Reset left before measuring: a browser sizes an absolutely
// positioned auto/max-content-width box using the *current* `left`
// to derive available space, so measuring while `left` still holds
// the previous (possibly near-edge) position creates a feedback loop
// that keeps squeezing the tooltip narrower and wrapping its rows.
// Setting left to 0 first guarantees a true, unconstrained natural
// width; this happens synchronously before the next paint, so
// there's no visible flash.
tip.style.left = "0px";
// Clamp horizontally within the container.
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
// Flip below the point if there's no room above.
if (top – tip.offsetHeight < 0) {
top = anchor.bottom – rootRect.top + 12;
tip.style.transform = "translate(-50%, 0)";
} else {
tip.style.transform = "translate(-50%, -100%)";
}
tip.style.left = left + "px";
tip.style.top = top + "px";
// Commit that position without animating, then restore the CSS
// transition so subsequent point-to-point moves ease into place.
if (suppress) {
void tip.offsetWidth;
tip.style.transition = "";
}
}
function hide() {
activeIndex = -1;
guide.style.opacity = "0";
tip.setAttribute("data-visible", "false");
dotEls.forEach(function (col, si) {
col.forEach(function (c) { c.setAttribute("r", baseR[si]); });
});
}
hit.addEventListener("pointermove", function (e) { show(indexFromClientX(e.clientX)); });
hit.addEventListener("pointerdown", function (e) { show(indexFromClientX(e.clientX)); });
hit.addEventListener("pointerleave", hide);
// Keyboard access.
svg.setAttribute("tabindex", "0");
svg.addEventListener("keydown", function (e) {
if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
var start = activeIndex { title, lines:[…] } }
No printed value labels — values surface on hover (consistent with
the line chart). Bars use a single tunable color with rounded tops.
================================================================ */
renderers.bar = function (root, config) {
root.textContent = “”;
var barColor = (config.series && config.series[0] && config.series[0].color) || “#ff7b72”;
var barRadius = config.barRadius != null ? config.barRadius : 8;
// Per-bar fill: a bar may point at a different series (e.g. a muted
// “preliminary” color) via panel.barSeries[index]; otherwise series[0].
function barColorFor(panel, j) {
var si = (panel.barSeries && panel.barSeries[j] != null) ? panel.barSeries[j] : 0;
var s = config.series && config.series[si];
return (s && s.color) || barColor;
}
var motionMQ = window.matchMedia
? window.matchMedia(“(prefers-reduced-motion: reduce)”)
: null;
function reduceMotion() { return !!(motionMQ && motionMQ.matches); }
/* —- Title + divider (+ optional subtitle) —————— */
var title = html(“span”, “ghchart__title”);
title.textContent = config.title;
root.appendChild(title);
root.appendChild(html(“div”, “ghchart__rule”));
if (config.subtitle) {
var sub = html(“p”, “ghchart__subtitle”);
sub.textContent = config.subtitle;
root.appendChild(sub);
}
/* —- One shared tooltip for all panels ———————- */
var tip = html(“div”, “ghchart__tooltip”);
tip.setAttribute(“role”, “status”);
var tipTitle = html(“div”, “ghchart__tt-title”);
var tipBody = html(“div”, “ghchart__tt-body”);
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var allBars = []; // { el, pi, bi }
var activeKey = null; // “pi:bi” of the hovered bar
function hideTip() {
activeKey = null;
tip.setAttribute(“data-visible”, “false”);
allBars.forEach(function (b) { b.el.style.opacity = “1”; });
}
// Hoisted: referenced by the per-column pointer handlers below.
function showTip(pi, bi, panel, panelBars) {
var reappearing = activeKey === null;
activeKey = pi + “:” + bi;
allBars.forEach(function (b) {
b.el.style.opacity = (b.pi === pi && b.bi === bi) ? “1” : “0.42”;
});
var content = panel.tooltip
? panel.tooltip(bi, panel)
: { title: (panel.labels || [])[bi],
lines: [ (panel.formatValue || String)(panel.values[bi]) ] };
tipTitle.textContent = content.title != null ? content.title : “”;
tipBody.textContent = “”;
(content.lines || []).forEach(function (ln) {
var row = html(“div”, “ghchart__tt-line”);
row.textContent = ln;
tipBody.appendChild(row);
});
var anchor = panelBars[bi].el.getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = “opacity 0.12s ease”;
tip.setAttribute(“data-visible”, “true”);
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight 0) {
for (var v = yMin; v <= yMax + 1e-9; v += step) {
var gy = yAt(v);
svg.appendChild(el("line", {
"class": "ghchart__gridline", x1: M.left, y1: gy, x2: VB.w – M.right, y2: gy
}));
var yl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–y",
x: M.left – 18, y: gy, dy: "0.32em", "text-anchor": "end"
});
yl.textContent = fmt(v);
svg.appendChild(yl);
}
}
// Solid baseline.
svg.appendChild(el("line", {
"class": "ghchart__axisline", x1: M.left, y1: baseY, x2: VB.w – M.right, y2: baseY
}));
// x-axis labels.
for (var i = 0; i < nb; i++) {
var xl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–x",
x: xCenter(i), y: VB.h – M.bottom + 30, "text-anchor": "middle"
});
if (panel.xLabelSize) xl.setAttribute("style", "font-size:" + panel.xLabelSize + "px");
xl.textContent = labels[i];
svg.appendChild(xl);
}
// Bars (rounded top corners only) + full-height hover columns.
var panelBars = [];
for (var j = 0; j dark ramp is derived from a single tunable base color.
================================================================ */
renderers.heatmap = function (root, config) {
root.textContent = “”;
function trim0(s) { return s.replace(/.0$/, “”); }
function fmtM(v) {
if (v >= 1000) { var g = v / 1000; return (g >= 10 ? Math.round(g) : trim0(g.toFixed(1))) + “B”; }
if (v >= 1) return trim0(v.toFixed(1)) + “M”;
return Math.round(v * 1000) + “K”;
}
var MON = [“Jan”, “Feb”, “Mar”, “Apr”, “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”];
var MON_FULL = [“January”, “February”, “March”, “April”, “May”, “June”,
“July”, “August”, “September”, “October”, “November”, “December”];
var WD = [“Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”];
var year = config.year || 2026;
var startM = config.startMonth != null ? config.startMonth : 0;
var endM = config.endMonth != null ? config.endMonth : 5;
var emptyColor = config.emptyColor || “#ebedf0”;
function daysInMonth(y, m) { return new Date(y, m + 1, 0).getDate(); }
function parseISO(s) { var p = s.split(“-“); return new Date(+p[0], +p[1] – 1, +p[2]); }
function dayStart(dt) { return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate()); }
/* —- Eyebrow + title + subtitle —————————– */
if (config.eyebrow) {
var eb = html(“div”, “ghheat__eyebrow”);
eb.appendChild(html(“span”, “ghheat__eyedot”));
var ebt = html(“span”, “ghheat__eyetext”);
ebt.textContent = config.eyebrow;
eb.appendChild(ebt);
root.appendChild(eb);
}
var title = html(“span”, “ghchart__title”);
title.textContent = config.title;
root.appendChild(title);
if (config.subtitle) {
var sub = html(“p”, “ghchart__subtitle ghheat__subtitle”);
sub.innerHTML = config.subtitle;
root.appendChild(sub);
}
/* —- One shared tooltip ————————————– */
var tip = html(“div”, “ghchart__tooltip”);
tip.setAttribute(“role”, “status”);
var tipTitle = html(“div”, “ghchart__tt-title”);
var tipBody = html(“div”, “ghchart__tt-body”);
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var motionMQ = window.matchMedia ? window.matchMedia(“(prefers-reduced-motion: reduce)”) : null;
function reduceMotion() { return !!(motionMQ && motionMQ.matches); }
var activeCell = null;
function hideTip() { activeCell = null; tip.setAttribute(“data-visible”, “false”); }
function showTip(rectEl, titleText, lines) {
var reappearing = activeCell === null;
activeCell = rectEl;
tipTitle.textContent = titleText;
tipBody.textContent = “”;
lines.forEach(function (ln) {
var r = html(“div”, “ghchart__tt-line”);
r.innerHTML = ln;
tipBody.appendChild(r);
});
var a = rectEl.getBoundingClientRect(), rr = root.getBoundingClientRect();
var left = a.left + a.width / 2 – rr.left, top = a.top – rr.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = “opacity 0.12s ease”;
tip.setAttribute(“data-visible”, “true”);
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight < 0) {
top = a.bottom – rr.top + 12;
tip.style.transform = "translate(-50%, 0)";
} else {
tip.style.transform = "translate(-50%, -100%)";
}
tip.style.left = left + "px";
tip.style.top = top + "px";
if (suppress) { void tip.offsetWidth; tip.style.transition = ""; }
}
/* —- Panels ————————————————– */
(config.panels || []).forEach(function (panel, pi) {
var base = (config.series && config.series[pi] && config.series[pi].color) || panel.base || "#3094ff";
var ramp = rampFrom(base);
var strong = ramp[4];
var totals = panel.months || [];
var activeVals = [];
for (var mi = startM; mi <= endM; mi++) { if (totals[mi] != null) activeVals.push(totals[mi]); }
var vmin = activeVals.length ? Math.min.apply(null, activeVals) : 0;
var vmax = activeVals.length ? Math.max.apply(null, activeVals) : 1;
function levelFor(t) {
if (t == null) return -1;
if (vmax === vmin) return 4;
return Math.round((t – vmin) / (vmax – vmin) * 4);
}
var af = panel.activeFrom ? parseISO(panel.activeFrom) : new Date(year, startM, 1);
var at = panel.activeTo ? parseISO(panel.activeTo) : new Date(year, endM, daysInMonth(year, endM));
var wrap = html("div", "ghheat__panel");
var head = html("div", "ghheat__phead");
var badge = html("span", "ghheat__badge");
badge.textContent = panel.badge != null ? panel.badge : (pi + 1);
badge.style.background = strong;
head.appendChild(badge);
var pt = html("span", "ghheat__ptitle");
pt.textContent = panel.title;
head.appendChild(pt);
if (panel.descriptor) {
var pd = html("span", "ghheat__pdesc");
pd.textContent = panel.descriptor;
head.appendChild(pd);
}
wrap.appendChild(head);
var body = html("div", "ghheat__body");
/* —- Calendar grid (SVG) —- */
var gridStart = new Date(year, startM, 1);
var gs = new Date(gridStart);
gs.setDate(gs.getDate() – gs.getDay()); // back to Sunday
var gridEnd = new Date(year, endM, daysInMonth(year, endM));
var ge = new Date(gridEnd);
ge.setDate(ge.getDate() + (6 – ge.getDay())); // forward to Saturday
var nCols = Math.round((ge – gs) / (7 * 86400000)) + 1;
var cell = 13, sq = 11, rad = 2.5, labL = 34, labT = 18;
var W = labL + nCols * cell, H = labT + 7 * cell;
var svg = el("svg", {
"class": "ghheat__grid",
viewBox: "0 0 " + W + " " + H,
preserveAspectRatio: "xMinYMin meet",
role: "img",
"aria-label": panel.title + " calendar heatmap"
});
[1, 3, 5].forEach(function (r) {
var t = el("text", {
"class": "ghheat__wd", x: labL – 6, y: labT + r * cell + sq / 2,
dy: "0.32em", "text-anchor": "end"
});
t.textContent = WD[r];
svg.appendChild(t);
});
// Month labels above the column where each month begins.
for (var lm = startM; lm <= endM; lm++) {
var first = new Date(year, lm, 1);
var colOf = Math.floor((dayStart(first) – gs) / (7 * 86400000));
var mlab = el("text", { "class": "ghheat__month", x: labL + colOf * cell, y: 12, "text-anchor": "start" });
mlab.textContent = MON[lm];
svg.appendChild(mlab);
}
var afS = dayStart(af), atS = dayStart(at);
var d = new Date(gs);
for (var i = 0; i = startM && m = afS && ds = 0 ? ramp[lvl] : emptyColor
});
if (inRange) {
(function (dt, mm, has) {
var dateStr = WD[dt.getDay()] + ” u00b7 ” + MON[mm] + ” ” + dt.getDate() + “, ” + year;
var lines;
if (has) {
var daily = totals[mm] / daysInMonth(year, mm);
lines = [
“” + fmtM(daily) + “ ” + panel.legendLabel,
MON_FULL[mm] + ” u00b7 ” + fmtM(totals[mm]) + ” ” + panel.unitTotal
];
} else {
lines = [panel.emptyNote || “No vetted data”];
}
cellEl.addEventListener(“pointermove”, function () { showTip(cellEl, dateStr, lines); });
cellEl.addEventListener(“pointerdown”, function () { showTip(cellEl, dateStr, lines); });
cellEl.addEventListener(“pointerleave”, hideTip);
})(new Date(d), m, hasData);
} else {
cellEl.setAttribute(“class”, “ghheat__cell ghheat__cell–pad”);
}
svg.appendChild(cellEl);
d.setDate(d.getDate() + 1);
}
var gridwrap = html(“div”, “ghheat__gridwrap”);
gridwrap.appendChild(svg);
body.appendChild(gridwrap);
/* —- Side column: stat + note + legend + extra —- */
var side = html(“div”, “ghheat__side”);
if (panel.stat) {
var stat = html(“div”, “ghheat__stat”);
var big = html(“span”, “ghheat__statbig”);
big.textContent = panel.stat.big;
big.style.color = strong;
stat.appendChild(big);
if (panel.stat.small) {
var small = html(“span”, “ghheat__statsmall”);
small.textContent = panel.stat.small;
stat.appendChild(small);
}
side.appendChild(stat);
}
if (panel.note) {
var note = html(“p”, “ghheat__note”);
note.innerHTML = panel.note;
side.appendChild(note);
}
var leg = html(“div”, “ghheat__legend”);
var lw1 = html(“span”, “ghheat__legword”); lw1.textContent = “Fewer”; leg.appendChild(lw1);
ramp.forEach(function (c) {
var s = html(“span”, “ghheat__sw”); s.style.background = c; leg.appendChild(s);
});
var lw2 = html(“span”, “ghheat__legword”); lw2.textContent = “More”; leg.appendChild(lw2);
if (panel.legendLabel) {
var lu = html(“span”, “ghheat__legunit”); lu.textContent = “u00b7 ” + panel.legendLabel; leg.appendChild(lu);
}
side.appendChild(leg);
if (panel.extraNote) {
var ex = html(“div”, “ghheat__extra”);
var exsw = html(“span”, “ghheat__sw ghheat__sw–empty”);
exsw.style.background = emptyColor;
ex.appendChild(exsw);
var ext = html(“span”, “ghheat__extratext”);
ext.innerHTML = panel.extraNote;
ex.appendChild(ext);
side.appendChild(ex);
}
body.appendChild(side);
wrap.appendChild(body);
root.appendChild(wrap);
});
if (config.caption) {
var cap = html(“div”, “ghchart__caption”);
cap.innerHTML = config.caption;
root.appendChild(cap);
}
};
/* ================================================================
Scatter renderer — X/Y numeric plot with categorical points.
Used for cost-vs-quality benchmark charts: a highlighted “primary”
point (glow halo), a “reference” point with a dotted crosshair, and
muted “other” points. Values never print on the marks — a point’s
name shows for identity and the exact (y, x) reveals on hover.
================================================================ */
renderers.scatter = function (root, config) {
root.textContent = “”;
var uid = config.mountId;
var VB = { w: 1000, h: 600 };
var M = { top: 40, right: 30, bottom: 74, left: 96 };
var innerW = VB.w – M.left – M.right;
var innerH = VB.h – M.top – M.bottom;
var xa = config.xAxis, ya = config.yAxis;
var fmtX = xa.format || function (v) { return String(v); };
var fmtY = ya.format || function (v) { return String(v); };
var tipSuffix = config.tipSuffix || “”;
function xAt(v) { return M.left + innerW * (v – xa.min) / (xa.max – xa.min); }
function yAt(v) { return M.top + innerH * (1 – (v – ya.min) / (ya.max – ya.min)); }
var baseY = yAt(ya.min);
var motionMQ = window.matchMedia
? window.matchMedia(“(prefers-reduced-motion: reduce)”)
: null;
function reduceMotion() { return !!(motionMQ && motionMQ.matches); }
function colorOf(pt) {
var s = config.series && config.series[pt.series || 0];
return (s && s.color) || “#8c959f”;
}
/* —- Title (compact narrative headline, no rule) ———— */
var title = html(“div”, “ghchart__headline”);
title.textContent = config.title;
root.appendChild(title);
/* —- Header + legend (from series) ————————– */
if (!config.hideLegend) {
var header = html(“div”, “ghchart__header”);
var legend = html(“div”, “ghchart__legend”);
(config.series || []).forEach(function (s) {
var item = html(“span”, “ghchart__legend-item”);
var sw = html(“span”, “ghchart__swatch ghchart__swatch–dot”);
sw.style.background = s.color;
item.appendChild(sw);
item.appendChild(document.createTextNode(s.name));
legend.appendChild(item);
});
header.appendChild(legend);
root.appendChild(header);
}
/* —- SVG plot ———————————————– */
var svg = el(“svg”, {
“class”: “ghchart__plot ghchart__plot–scatter”,
viewBox: “0 0 ” + VB.w + ” ” + VB.h,
preserveAspectRatio: “xMidYMid meet”,
role: “img”,
“aria-label”: config.title + ” scatter plot”
});
// Gridlines (both directions) + tick labels.
var eps = 1e-6;
for (var yv = ya.min; yv <= ya.max + eps; yv += ya.step) {
var gy = yAt(yv);
svg.appendChild(el("line", {
"class": "ghchart__gridline",
x1: M.left, y1: gy, x2: VB.w – M.right, y2: gy
}));
var yl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–y",
x: M.left – 16, y: gy, dy: "0.32em", "text-anchor": "end"
});
yl.textContent = fmtY(yv);
svg.appendChild(yl);
}
for (var xv = xa.min; xv <= xa.max + eps; xv += xa.step) {
var gx = xAt(xv);
svg.appendChild(el("line", {
"class": "ghchart__gridline ghchart__gridline–v",
x1: gx, y1: M.top, x2: gx, y2: baseY
}));
var isFirst = Math.abs(xv – xa.min) < eps;
var isLast = Math.abs(xv – xa.max) < eps;
var xl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–x",
x: gx, y: baseY + 34,
"text-anchor": isFirst ? "start" : (isLast ? "end" : "middle")
});
xl.textContent = fmtX(xv);
svg.appendChild(xl);
}
// Axis titles.
if (xa.title) {
var xt = el("text", {
"class": "ghchart__axistitle",
x: M.left + innerW / 2, y: VB.h – 12, "text-anchor": "middle"
});
xt.textContent = xa.title;
svg.appendChild(xt);
}
if (ya.title) {
var ytX = 24, ytY = M.top + innerH / 2;
var yt = el("text", {
"class": "ghchart__axistitle",
x: ytX, y: ytY, "text-anchor": "middle",
transform: "rotate(-90 " + ytX + " " + ytY + ")"
});
yt.textContent = ya.title;
svg.appendChild(yt);
}
// Reference crosshair through the flagged point (dual dotted lines).
var refPt = (config.points || []).filter(function (p) { return p.reference; })[0];
if (refPt) {
var rColor = colorOf(refPt);
var rx = xAt(refPt.x), ry = yAt(refPt.y);
svg.appendChild(el("line", {
"class": "ghchart__crosshair",
x1: rx, y1: M.top, x2: rx, y2: baseY, stroke: rColor
}));
svg.appendChild(el("line", {
"class": "ghchart__crosshair",
x1: M.left, y1: ry, x2: VB.w – M.right, y2: ry, stroke: rColor
}));
}
// Points: halo (highlight/reference) + dot + name label.
var dotEls = [], hitEls = [];
(config.points || []).forEach(function (pt, i) {
var cx = xAt(pt.x), cy = yAt(pt.y), c = colorOf(pt);
var big = pt.highlight || pt.reference;
if (big) {
svg.appendChild(el("circle", {
"class": "ghchart__halo", cx: cx, cy: cy, r: 17, fill: c
}));
}
var dot = el("circle", {
"class": "ghchart__pt", cx: cx, cy: cy,
r: big ? 9 : 5.5, fill: c
});
svg.appendChild(dot);
dotEls.push({ el: dot, baseR: big ? 9 : 5.5 });
// Name label — bold+colored for the story points, muted for the rest.
var pos = pt.labelPos || (pt.highlight ? "below" : pt.reference ? "above" : "right");
var dx = 0, dy = 0, anchor = "middle";
var gap = big ? 15 : 9;
if (pos === "right") { dx = gap + 4; dy = 5; anchor = "start"; }
else if (pos === "left") { dx = -(gap + 4); dy = 5; anchor = "end"; }
else if (pos === "above") { dy = -(gap + 6); anchor = "middle"; }
else { dy = gap + 16; anchor = "middle"; } // below
var lbl = el("text", {
"class": "ghchart__pt-label" + (big ? " ghchart__pt-label–strong" : ""),
x: cx + dx, y: cy + dy, "text-anchor": anchor,
fill: big ? c : null
});
lbl.textContent = pt.label;
svg.appendChild(lbl);
// Transparent hit target for hover/focus.
var hit = el("circle", {
"class": "ghchart__pthit", cx: cx, cy: cy, r: 20,
tabindex: "0", role: "img",
"aria-label": pt.label + ": " + fmtY(pt.y) + ", " + fmtX(pt.x) + tipSuffix
});
svg.appendChild(hit);
hitEls.push(hit);
});
root.appendChild(svg);
/* —- Tooltip (title + free lines) ————————— */
var tip = html("div", "ghchart__tooltip");
tip.setAttribute("role", "status");
var tipTitle = html("div", "ghchart__tt-title");
var tipBody = html("div", "ghchart__tt-body");
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var activeIndex = -1;
function show(i) {
var pt = config.points[i];
if (!pt) return;
var reappearing = activeIndex === -1;
activeIndex = i;
dotEls.forEach(function (d, di) {
d.el.setAttribute("r", di === i ? d.baseR + 2.5 : d.baseR);
});
tipTitle.textContent = pt.label;
tipBody.textContent = "";
var line = html("div", "ghchart__tt-line");
line.textContent = fmtY(pt.y) + " · " + fmtX(pt.x) + tipSuffix;
tipBody.appendChild(line);
var anchor = dotEls[i].el.getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = "opacity 0.12s ease";
tip.setAttribute("data-visible", "true");
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight 0) {
svg.appendChild(el(“line”, {
“class”: “ghchart__phase-sep”, x1: x0, y1: M.top, x2: x0, y2: baseY
}));
}
var pl = el(“text”, {
“class”: “ghchart__phase-label”, x: (x0 + x1) / 2, y: M.top – 40,
“text-anchor”: “middle”
});
pl.textContent = ph.label;
svg.appendChild(pl);
if (ph.avg) {
var pa = el(“text”, {
“class”: “ghchart__phase-avg”, x: (x0 + x1) / 2, y: M.top + 24,
“text-anchor”: “middle”, fill: cPoint
});
pa.textContent = ph.avg;
svg.appendChild(pa);
}
});
// Horizontal gridlines + y tick labels (skip a gridline at the reference y).
var refY = config.reference ? config.reference.y : null;
(ya.ticks || []).forEach(function (tv) {
var gy = yAt(tv);
if (refY === null || Math.abs(tv – refY) > 1e-9) {
svg.appendChild(el(“line”, {
“class”: “ghchart__gridline”, x1: M.left, y1: gy, x2: VB.w – M.right, y2: gy
}));
}
var yl = el(“text”, {
“class”: “ghchart__axislabel ghchart__axislabel–y”,
x: M.left – 16, y: gy, dy: “0.32em”, “text-anchor”: “end”
});
yl.textContent = fmtY(tv);
svg.appendChild(yl);
});
// X tick labels (dated).
(xa.ticks || []).forEach(function (t) {
var gx = xAt(t.at);
var first = Math.abs(t.at – xa.min) < 1e-9, last = Math.abs(t.at – xa.max) < 1e-9;
var xl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–x",
x: gx, y: baseY + 30, "text-anchor": first ? "start" : (last ? "end" : "middle")
});
xl.textContent = t.text;
svg.appendChild(xl);
});
// Axis titles.
if (ya.title) {
var ytX = 22, ytY = M.top + innerH / 2;
var yt = el("text", {
"class": "ghchart__axistitle", x: ytX, y: ytY, "text-anchor": "middle",
transform: "rotate(-90 " + ytX + " " + ytY + ")"
});
yt.textContent = ya.title;
svg.appendChild(yt);
}
// Trend: optional ±band (linear only), then the dashed line + slope label.
var tr = config.trend;
if (tr) {
function trendY(x) {
return tr.from.y + (tr.to.y – tr.from.y) * (x – tr.from.x) / (tr.to.x – tr.from.x);
}
if (tr.band && !isLog) {
var p = tr.band;
var pts = [
[xAt(tr.from.x), yAt(trendY(tr.from.x) + p)],
[xAt(tr.to.x), yAt(trendY(tr.to.x) + p)],
[xAt(tr.to.x), yAt(trendY(tr.to.x) – p)],
[xAt(tr.from.x), yAt(trendY(tr.from.x) – p)]
].map(function (a) { return a[0] + "," + a[1]; }).join(" ");
svg.appendChild(el("polygon", {
"class": "ghchart__trendband", points: pts, fill: cTrend
}));
}
svg.appendChild(el("line", {
"class": "ghchart__trendline",
x1: xAt(tr.from.x), y1: yAt(tr.from.y),
x2: xAt(tr.to.x), y2: yAt(tr.to.y), stroke: cTrend
}));
if (tr.label) {
var lx, lyy;
if (tr.labelAt) { lx = xAt(tr.labelAt.x); lyy = yAt(tr.labelAt.y); }
else {
lx = xAt(tr.to.x) + (tr.labelDX == null ? -6 : tr.labelDX);
lyy = yAt(tr.to.y) + (tr.labelDY == null ? -12 : tr.labelDY);
}
var sl = el("text", {
"class": "ghchart__slope", x: lx, y: lyy,
"text-anchor": tr.labelAnchor || "end", fill: cTrend
});
sl.textContent = tr.label;
svg.appendChild(sl);
}
}
// Reference line (horizontal dashed) + right-aligned label.
if (config.reference) {
var rc = config.reference.color || "#24292f";
var ryy = yAt(config.reference.y);
svg.appendChild(el("line", {
"class": "ghchart__refline", x1: M.left, y1: ryy, x2: VB.w – M.right, y2: ryy,
stroke: rc
}));
if (config.reference.label) {
var rl = el("text", {
"class": "ghchart__reflabel", x: VB.w – M.right, y: ryy – 8,
"text-anchor": "end", fill: rc
});
rl.textContent = config.reference.label;
svg.appendChild(rl);
}
}
// Data squares (values reveal on hover only).
var side = 13, hSide = side / 2;
var sqEls = [], hitEls = [];
(config.points || []).forEach(function (pt, i) {
var cx = xAt(pt.x), cy = yAt(pt.y);
var sq = el("rect", {
"class": "ghchart__square", x: cx – hSide, y: cy – hSide,
width: side, height: side, rx: 2, fill: cPoint
});
svg.appendChild(sq);
sqEls.push({ el: sq, cx: cx, cy: cy });
// Hover/focus hit target — omitted when hover is disabled so the
// (illustrative) point values never surface, on screen or to a11y.
if (!config.disableHover) {
var hit = el("rect", {
"class": "ghchart__squarehit", x: cx – 13, y: cy – 13,
width: 26, height: 26, tabindex: "0", role: "img",
"aria-label": (pt.d || "") + " " + fmtY(pt.y)
});
svg.appendChild(hit);
hitEls.push(hit);
}
});
// Invalid-run markers: red diamond + X, leader line, annotation text.
(config.invalids || []).forEach(function (iv) {
var cx = xAt(iv.x), cy = yAt(iv.y);
svg.appendChild(el("rect", {
"class": "ghchart__invalid", x: cx – hSide, y: cy – hSide,
width: side, height: side, rx: 1.5, fill: "none", stroke: cInvalid,
transform: "rotate(45 " + cx + " " + cy + ")"
}));
var q = 3.4;
svg.appendChild(el("line", {
"class": "ghchart__invalid-x", x1: cx – q, y1: cy – q, x2: cx + q, y2: cy + q,
stroke: cInvalid
}));
svg.appendChild(el("line", {
"class": "ghchart__invalid-x", x1: cx – q, y1: cy + q, x2: cx + q, y2: cy – q,
stroke: cInvalid
}));
if (iv.title) {
var tx = cx + (iv.labelDX == null ? 16 : iv.labelDX);
var ty = cy + (iv.labelDY == null ? -26 : iv.labelDY);
svg.appendChild(el("line", {
"class": "ghchart__leader", x1: cx, y1: cy, x2: tx, y2: ty, stroke: cInvalid
}));
var anchor = iv.labelAnchor || "start";
var at = el("text", {
"class": "ghchart__anno-title", x: tx, y: ty – 4, "text-anchor": anchor, fill: cInvalid
});
at.textContent = iv.title;
svg.appendChild(at);
if (iv.sub) {
var asu = el("text", {
"class": "ghchart__anno-sub", x: tx, y: ty + 11, "text-anchor": anchor
});
asu.textContent = iv.sub;
svg.appendChild(asu);
}
}
});
// Bracket annotation (span with end ticks + centered label above).
(config.brackets || []).forEach(function (b) {
var x0 = xAt(b.fromX), x1 = xAt(b.toX), by = yAt(b.y);
svg.appendChild(el("line", {
"class": "ghchart__bracket", x1: x0, y1: by, x2: x1, y2: by
}));
svg.appendChild(el("line", { "class": "ghchart__bracket", x1: x0, y1: by, x2: x0, y2: by + 7 }));
svg.appendChild(el("line", { "class": "ghchart__bracket", x1: x1, y1: by, x2: x1, y2: by + 7 }));
var bl = el("text", {
"class": "ghchart__bracket-label", x: (x0 + x1) / 2, y: by – 8, "text-anchor": "middle"
});
bl.textContent = b.text;
svg.appendChild(bl);
});
// Final callout: ringed point + leader + title/sub placed inside the plot.
if (config.callout) {
var co = config.callout;
var ccx = xAt(co.x), ccy = yAt(co.y);
svg.appendChild(el("circle", {
"class": "ghchart__callout-ring", cx: ccx, cy: ccy, r: 12, stroke: cPoint
}));
var ctx = ccx + (co.labelDX == null ? -16 : co.labelDX);
var cty = ccy + (co.labelDY == null ? -30 : co.labelDY);
svg.appendChild(el("line", {
"class": "ghchart__leader ghchart__leader–callout", x1: ccx, y1: ccy, x2: ctx, y2: cty,
stroke: cPoint
}));
var can = co.labelAnchor || "end";
var ct = el("text", {
"class": "ghchart__anno-title ghchart__anno-title–callout", x: ctx, y: cty – 4, "text-anchor": can, fill: cPoint
});
ct.textContent = co.title;
svg.appendChild(ct);
if (co.sub) {
var cs = el("text", {
"class": "ghchart__anno-sub ghchart__anno-sub–callout", x: ctx, y: cty + 12, "text-anchor": can
});
cs.textContent = co.sub;
svg.appendChild(cs);
}
}
root.appendChild(svg);
if (config.caption) {
var cap = html("div", "ghchart__caption");
cap.textContent = config.caption;
root.appendChild(cap);
}
/* —- Tooltip + hover interaction (skipped when disabled) —— */
if (!config.disableHover) {
var tip = html("div", "ghchart__tooltip");
tip.setAttribute("role", "status");
var tipTitle = html("div", "ghchart__tt-title");
var tipBody = html("div", "ghchart__tt-body");
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var activeIndex = -1;
function show(i) {
var pt = config.points[i];
if (!pt) return;
var reappearing = activeIndex === -1;
activeIndex = i;
sqEls.forEach(function (s, si) {
var big = si === i;
var w = big ? side + 5 : side;
s.el.setAttribute("width", w);
s.el.setAttribute("height", w);
s.el.setAttribute("x", s.cx – w / 2);
s.el.setAttribute("y", s.cy – w / 2);
});
var tipInfo = config.pointTip
? config.pointTip(pt)
: { title: pt.d || "", line: fmtY(pt.y) };
tipTitle.textContent = tipInfo.title;
tipBody.textContent = "";
var line = html("div", "ghchart__tt-line");
line.textContent = tipInfo.line;
tipBody.appendChild(line);
var anchor = sqEls[i].el.getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = "opacity 0.12s ease";
tip.setAttribute("data-visible", "true");
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight < 0) {
top = anchor.bottom – rootRect.top + 12;
tip.style.transform = "translate(-50%, 0)";
} else {
tip.style.transform = "translate(-50%, -100%)";
}
tip.style.left = left + "px";
tip.style.top = top + "px";
if (suppress) { void tip.offsetWidth; tip.style.transition = ""; }
}
function hide() {
activeIndex = -1;
tip.setAttribute("data-visible", "false");
sqEls.forEach(function (s) {
s.el.setAttribute("width", side);
s.el.setAttribute("height", side);
s.el.setAttribute("x", s.cx – hSide);
s.el.setAttribute("y", s.cy – hSide);
});
}
hitEls.forEach(function (hit, i) {
hit.addEventListener("pointerenter", function () { show(i); });
hit.addEventListener("pointermove", function () { show(i); });
hit.addEventListener("pointerleave", hide);
hit.addEventListener("focus", function () { show(i); });
hit.addEventListener("blur", hide);
});
}
};
/* ================================================================
Distribution dashboard renderer
—————————————————————-
A composition figure (no hover — every value is printed as text, so
the marks are inherently screen-reader accessible):
• a compact narrative headline + muted subtitle;
• an optional segmented "mix" bar — proportional colored segments,
each with its name inside and count/percent below;
• one or more panels of ranked horizontal bars, each row printing
its category (left), a bar, and its count/percent (right).
Colors come from config.series so the harness can tune them live:
series[0] = bar fill (single base hex);
series[1..] = segment fills, in the order of config.segments.items.
================================================================ */
renderers.distribution = function (root, config) {
root.textContent = "";
root.setAttribute("role", "figure");
if (config.title) root.setAttribute("aria-label", config.title);
var series = config.series || [];
var barColor = (series[0] && series[0].color) || "#8250df";
var total = config.total ||
(config.segments && config.segments.items
? config.segments.items.reduce(function (a, b) { return a + b.value; }, 0)
: 0);
function pctText(v) {
if (!total) return "";
return (Math.round((v / total) * 1000) / 10).toFixed(1) + "%";
}
/* Label format is config-driven so a chart can print counts, percents,
or both without a renderer change: config.valueFormat =
"both" (default) | "percent" | "count". Counts stay in the data either
way so the percentages are always derived from them. */
var valueFormat = config.valueFormat || "both";
function valueHTML(v) {
var p = pctText(v);
if (valueFormat === "percent") return p;
if (valueFormat === "count") return "” + v + ““;
return “” + v + “” + (p ? ” (” + p + “)” : “”);
}
/* —- Headline + subtitle ————————————- */
if (config.title) {
var head = html(“div”, “ghchart__headline”);
head.textContent = config.title;
root.appendChild(head);
}
if (config.subtitle) {
var sub = html(“p”, “ghchart__subtitle”);
sub.textContent = config.subtitle;
root.appendChild(sub);
}
/* —- Segmented “mix” bar ————————————– */
var seg = config.segments;
if (seg && seg.items && seg.items.length) {
var segSec = html(“div”, “ghchart__dist-section”);
if (seg.title) {
var segTitle = html(“div”, “ghchart__panel-title”);
segTitle.textContent = seg.title;
segSec.appendChild(segTitle);
}
var segTotal = seg.items.reduce(function (a, b) { return a + b.value; }, 0) || 1;
var bar = html(“div”, “ghchart__seg”);
var notes = html(“div”, “ghchart__seg-note-row”);
seg.items.forEach(function (it, i) {
var basis = (it.value / segTotal) * 100;
var col = (series[i + 1] && series[i + 1].color) || it.color || barColor;
var cell = html(“div”, “ghchart__seg-cell”);
cell.style.flex = “0 0 ” + basis + “%”;
cell.style.background = col;
cell.textContent = it.label;
bar.appendChild(cell);
var note = html(“div”, “ghchart__seg-note”);
note.style.flex = “0 0 ” + basis + “%”;
note.innerHTML = valueHTML(it.value);
notes.appendChild(note);
});
segSec.appendChild(bar);
segSec.appendChild(notes);
root.appendChild(segSec);
}
/* —- Ranked bar panels (grid: 2-up, stacking on narrow) —— */
var panels = config.panels || [];
if (panels.length) {
var cols = html(“div”, “ghchart__dist-cols”);
if (config.columns === 1 || panels.length === 1) {
cols.classList.add(“ghchart__dist-cols–stack”);
}
panels.forEach(function (panel) {
var sec = html(“div”, “ghchart__dist-section”);
if (panel.title) {
var pt = html(“div”, “ghchart__panel-title”);
pt.textContent = panel.title;
sec.appendChild(pt);
}
if (panel.subtitle) {
var descriptor = html(“p”, “ghchart__subtitle”);
descriptor.textContent = panel.subtitle;
sec.appendChild(descriptor);
}
if (panel.stats) {
var stats = html(“dl”, “ghchart__dist-stats”);
panel.stats.forEach(function (stat) {
var pair = html(“div”, “ghchart__dist-stat”);
var term = html(“dt”);
term.textContent = stat.label;
var value = html(“dd”);
value.textContent = stat.value;
value.style.color = strongOf((series[panel.series || 0] || series[0]).color);
pair.appendChild(term);
pair.appendChild(value);
stats.appendChild(pair);
});
sec.appendChild(stats);
cols.appendChild(sec);
return;
}
var list = html(“div”, “ghchart__dist-list”);
var pmax = (panel.items || []).reduce(function (m, it) {
return it.value > m ? it.value : m;
}, 0) || 1;
(panel.items || []).forEach(function (it) {
var row = html(“div”, “ghchart__dist-row”);
var cat = html(“div”, “ghchart__dist-cat”);
cat.textContent = it.label;
row.appendChild(cat);
var track = html(“div”, “ghchart__dist-track”);
var b = html(“div”, “ghchart__dist-bar”);
b.style.width = Math.max((it.value / pmax) * 100, 1.2) + “%”;
b.style.background = it.color || barColor;
track.appendChild(b);
row.appendChild(track);
var val = html(“div”, “ghchart__dist-val”);
val.innerHTML = valueHTML(it.value);
row.appendChild(val);
list.appendChild(row);
});
sec.appendChild(list);
cols.appendChild(sec);
});
root.appendChild(cols);
}
/* —- Caption / footnote ————————————– */
if (config.caption) {
var cap = html(“div”, “ghchart__caption”);
cap.innerHTML = config.caption;
root.appendChild(cap);
}
};
/* ================================================================
Timeline / staged-duration renderer
—————————————————————-
A horizontal time axis broken into labeled stage segments. Composes
only the pieces the data needs (like the trend renderer):
• eyebrow kicker + narrative headline + optional subtitle;
• an optional before/after stat card and a stage legend;
• one or more rows of segmented duration bars on a shared time
scale — a row is either sequential (segments end to end) or
parallel (a full-height “gate” segment then concurrent lanes);
• optional flow ribbons linking matching stages between the first
two rows (the sequential -> parallel “collapse”);
• optional elbow-leader callouts above narrow segments;
• optional span brackets (below a row, or right of a parallel group);
• an optional numeric axis (ticks + gridlines + title).
No hover — every label is printed, so it reads on paper and to screen
readers. Stage colors come from config.series so the harness tunes
them live: each entry is { id, name, color }.
================================================================ */
renderers.timeline = function (root, config) {
root.textContent = “”;
root.setAttribute(“role”, “img”);
if (config.title) root.setAttribute(“aria-label”, config.title);
var stages = config.series || [];
var stageById = {};
stages.forEach(function (s) { if (s && s.id) stageById[s.id] = s; });
function colorOf(seg) {
return seg.color || (stageById[seg.id] && stageById[seg.id].color) || “#8250df”;
}
function nameOf(seg) {
if (seg.label != null) return seg.label;
return seg.name || (stageById[seg.id] && stageById[seg.id].name) || “”;
}
/* —- DOM header: eyebrow + headline + subtitle ————— */
if (config.eyebrow) {
var eb = html(“div”, “ghchart__tl-eyebrow”);
eb.textContent = config.eyebrow;
root.appendChild(eb);
}
if (config.title) {
var hl = html(“div”, “ghchart__headline ghchart__tl-headline”);
hl.textContent = config.title;
root.appendChild(hl);
}
if (config.subtitle) {
var sbt = html(“p”, “ghchart__subtitle”);
sbt.textContent = config.subtitle;
root.appendChild(sbt);
}
/* —- Stat card (before -> after + delta) ——————– */
if (config.stat) {
var st = config.stat;
var card = html(“div”, “ghchart__tl-stat”);
var main = html(“div”, “ghchart__tl-stat-main”);
function statCol(k, v, muted) {
var col = html(“div”, “ghchart__tl-stat-col”);
var kk = html(“div”, “ghchart__tl-stat-k”); kk.textContent = k; col.appendChild(kk);
var vv = html(“div”, “ghchart__tl-stat-v” + (muted ? ” ghchart__tl-stat-v–muted” : “”));
vv.textContent = v; col.appendChild(vv);
return col;
}
main.appendChild(statCol(st.beforeLabel, st.beforeValue, true));
var arrow = html(“div”, “ghchart__tl-stat-arrow”); arrow.textContent = “u2192”;
main.appendChild(arrow);
main.appendChild(statCol(st.afterLabel, st.afterValue, false));
card.appendChild(main);
if (st.delta) {
var dl = html(“div”, “ghchart__tl-stat-delta”);
var dv = html(“div”, “ghchart__tl-stat-delta-v”); dv.textContent = st.delta; dl.appendChild(dv);
if (st.deltaLabel) {
var dk = html(“div”, “ghchart__tl-stat-delta-k”); dk.textContent = st.deltaLabel; dl.appendChild(dk);
}
card.appendChild(dl);
}
root.appendChild(card);
}
/* —- Legend ————————————————– */
if (config.legend && config.legend.length) {
var lg = html(“div”, “ghchart__tl-legend”);
config.legend.forEach(function (item) {
var s = stageById[item.id] || {};
var it = html(“span”, “ghchart__tl-legend-item”);
var sw = html(“span”, “ghchart__tl-swatch”);
sw.style.background = item.color || s.color || “#8250df”;
it.appendChild(sw);
var tx = html(“span”, “ghchart__tl-legend-text”);
tx.textContent = (item.name || s.name || “”) + (item.value ? ” u00b7 ” + item.value : “”);
it.appendChild(tx);
lg.appendChild(it);
});
root.appendChild(lg);
}
if (config.rule) root.appendChild(html(“div”, “ghchart__tl-rule”));
/* —- Plot geometry ——————————————- */
var rows = config.rows || [];
function rowTotal(r) {
if (r.parallel) {
var g = r.gate ? r.gate.value : 0, mx = 0;
r.parallel.forEach(function (p) { if (p.value > mx) mx = p.value; });
return g + mx;
}
var t = 0; (r.segments || []).forEach(function (s) { t += s.value; }); return t;
}
var maxTotal = 0;
rows.forEach(function (r) { var t = rowTotal(r); if (t > maxTotal) maxTotal = t; });
var axis = config.axis;
var scaleMax = config.scaleMax || (axis && axis.max) || maxTotal || 1;
var VB_W = 1000;
var hasRowLabels = rows.some(function (r) { return r.label; });
var M = { left: hasRowLabels ? 150 : 44, right: 46 };
var innerW = VB_W – M.left – M.right;
function xAt(v) { return M.left + innerW * v / scaleMax; }
function wAt(v) { return innerW * v / scaleMax; }
var SEQ_H = 56, LANE_H = 13, LANE_GAP = 6, SEG_R = 4;
function rowBandH(r) {
if (r.parallel) {
var n = r.parallel.length;
return Math.max(SEQ_H, n * LANE_H + (n – 1) * LANE_GAP);
}
return SEQ_H;
}
var flows = !!config.flows && rows.length >= 2;
var FLOW_GAP = config.flowGap != null ? config.flowGap : 250, ROW_GAP = 48;
// Reserve vertical space for above-callouts (tallest tier wins).
var anyAbove = rows.some(function (r) {
return (r.segments || []).some(function (s) { return s.labelPos === “above”; });
});
var maxTier = 0;
rows.forEach(function (r) {
(r.segments || []).forEach(function (s) {
if (s.labelPos === “above” && (s.tier || 0) > maxTier) maxTier = s.tier || 0;
});
});
var CALLOUT_TIER_H = 40;
var topPad = anyAbove ? (18 + (maxTier + 1) * CALLOUT_TIER_H) : 16;
// Assign each row a vertical band.
var cursor = topPad;
var rowLayout = rows.map(function (r, i) {
var top = cursor, h = rowBandH(r);
cursor = top + h;
if (i row1), matched by stage id (drawn under bars).
if (flows) {
var r0 = rows[0], l0 = rowLayout[0], r1 = rows[1], l1 = rowLayout[1];
var beforeSpan = {}, acc0 = 0;
(r0.segments || []).forEach(function (s) {
beforeSpan[s.id] = { x0: xAt(acc0), x1: xAt(acc0 + s.value) }; acc0 += s.value;
});
var afterSpan = {}, gv1 = r1.gate ? r1.gate.value : 0;
if (r1.gate) afterSpan[r1.gate.id] = { x0: xAt(0), x1: xAt(gv1), y: l1.top };
(r1.parallel || []).forEach(function (p, k) {
// Land each ribbon on its companion lane’s actual depth, not the row top.
afterSpan[p.id] = {
x0: xAt(gv1), x1: xAt(gv1 + p.value), y: l1.top + k * (LANE_H + LANE_GAP)
};
});
var by = l0.bottom;
Object.keys(beforeSpan).forEach(function (id) {
var b = beforeSpan[id], a = afterSpan[id];
if (!a) return;
var ay = a.y, ddy = (ay – by) * 0.5;
var d = “M ” + b.x0 + ” ” + by +
” C ” + b.x0 + ” ” + (by + ddy) + ” ” + a.x0 + ” ” + (ay – ddy) + ” ” + a.x0 + ” ” + ay +
” L ” + a.x1 + ” ” + ay +
” C ” + a.x1 + ” ” + (ay – ddy) + ” ” + b.x1 + ” ” + (by + ddy) + ” ” + b.x1 + ” ” + by + ” Z”;
var flowPath = el(“path”, {
“class”: “ghchart__tl-flow”, d: d, fill: colorOf({ id: id }), “data-stage”: id
});
svg.appendChild(flowPath);
hoverSlot(id).flowEl = flowPath;
});
}
// Rows: segmented bars.
rows.forEach(function (r, ri) {
var L = rowLayout[ri];
if (r.label) {
var rl = el(“text”, {
“class”: “ghchart__tl-rowlabel”, x: M.left – 20, y: L.top + L.h / 2 – 3, “text-anchor”: “end”
});
rl.textContent = r.label; svg.appendChild(rl);
if (r.sublabel) {
var rs = el(“text”, {
“class”: “ghchart__tl-rowsub”, x: M.left – 20, y: L.top + L.h / 2 + 18, “text-anchor”: “end”
});
rs.textContent = r.sublabel; svg.appendChild(rs);
}
}
if (r.parallel) {
var gv = r.gate ? r.gate.value : 0;
if (r.gate) {
var gateEl = el(“rect”, {
“class”: “ghchart__tl-seg”, x: xAt(0), y: L.top,
width: Math.max(0, wAt(gv)), height: L.h, rx: SEG_R, fill: colorOf(r.gate),
“data-stage”: r.gate.id
});
svg.appendChild(gateEl);
var gslot = hoverSlot(r.gate.id);
gslot.afterEl = gateEl; gslot.after = r.gate.value;
gslot.name = gslot.name || nameOf(r.gate);
}
r.parallel.forEach(function (p, k) {
var ly = L.top + k * (LANE_H + LANE_GAP);
var laneEl = el(“rect”, {
“class”: “ghchart__tl-seg”, x: xAt(gv), y: ly,
width: Math.max(1, wAt(p.value)), height: LANE_H, rx: 3, fill: colorOf(p),
“data-stage”: p.id
});
svg.appendChild(laneEl);
var pslot = hoverSlot(p.id);
pslot.afterEl = laneEl; pslot.after = p.value;
pslot.name = pslot.name || nameOf(p);
});
} else {
var acc = 0;
(r.segments || []).forEach(function (s) {
var x = xAt(acc), w = Math.max(1, wAt(s.value));
var segEl = el(“rect”, {
“class”: “ghchart__tl-seg”, x: x, y: L.top, width: w, height: L.h, rx: SEG_R,
fill: colorOf(s), “data-stage”: s.id
});
svg.appendChild(segEl);
var slot = hoverSlot(s.id);
slot.name = slot.name || nameOf(s);
if (ri === 0) { slot.beforeEl = segEl; slot.before = s.value; }
else { slot.afterEl = segEl; slot.after = s.value; }
if (s.labelPos === “inside”) {
var ilAttrs = {
“class”: “ghchart__tl-seg-label”, x: x + w / 2, y: L.top + L.h / 2,
dy: “0.34em”, “text-anchor”: “middle”
};
// Inline style (not a fill attribute) so it overrides the class’s
// default white fill — lets a bright segment take dark label text.
if (s.labelColor) ilAttrs.style = “fill:” + s.labelColor;
var il = el(“text”, ilAttrs);
il.textContent = nameOf(s); svg.appendChild(il);
}
acc += s.value;
});
}
});
// Elbow-leader callouts above narrow segments.
rows.forEach(function (r, ri) {
if (r.parallel) return;
var L = rowLayout[ri], acc = 0;
(r.segments || []).forEach(function (s) {
var x = xAt(acc), w = wAt(s.value), cx = x + w / 2;
if (s.labelPos === “above”) {
var tier = s.tier || 0;
var tierY = topPad – 14 – tier * CALLOUT_TIER_H;
// labelX pins the label to an absolute data-x (so callouts can be
// left-aligned into a stack); labelDX is a relative nudge otherwise.
var lx = s.labelX != null ? xAt(s.labelX) : cx + (s.labelDX || 0);
var anchor = s.labelAnchor || “start”;
svg.appendChild(el(“path”, {
“class”: “ghchart__tl-callout-line”,
d: “M ” + cx + ” ” + L.top + ” L ” + cx + ” ” + tierY + ” L ” + lx + ” ” + tierY, fill: “none”
}));
var pad = anchor === “start” ? 6 : (anchor === “end” ? -6 : 0);
var cl = el(“text”, {
“class”: “ghchart__tl-callout-label”, x: lx + pad, y: tierY,
dy: “0.34em”, “text-anchor”: anchor, fill: s.labelColor || colorOf(s)
});
cl.textContent = nameOf(s); svg.appendChild(cl);
}
acc += s.value;
});
});
// Below brackets (span with up-ticks + label beneath).
var belowY = (rowLayout.length ? rowLayout[rowLayout.length – 1].bottom : topPad) + 14;
belowBrackets.forEach(function (b, bi) {
var x0 = xAt(b.from), x1 = xAt(b.to), yy = belowY + bi * 46;
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: x0, y1: yy, x2: x1, y2: yy }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: x0, y1: yy, x2: x0, y2: yy – 7 }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: x1, y1: yy, x2: x1, y2: yy – 7 }));
var bl = el(“text”, {
“class”: “ghchart__bracket-label ghchart__tl-bracket-label”, x: (x0 + x1) / 2, y: yy + 22, “text-anchor”: “middle”
});
bl.textContent = b.text; svg.appendChild(bl);
});
// Right brackets (vertical span beside a parallel group + label).
rightBrackets.forEach(function (b) {
var L = rowLayout[b.row || 0];
var bx = xAt(b.at != null ? b.at : rowTotal(rows[b.row || 0])) + 14;
var y0 = L.top, y1 = L.bottom;
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: bx, y1: y0, x2: bx, y2: y1 }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: bx, y1: y0, x2: bx – 7, y2: y0 }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: bx, y1: y1, x2: bx – 7, y2: y1 }));
// Label as a DOM node (not SVG text) so it keeps a fixed, readable size
// instead of scaling down with the plot on small windows.
var rlabel = html(“div”, “ghchart__tl-bracket-label-dom”);
rlabel.textContent = b.text;
rlabel.style.left = ((bx + 12) / VB_W * 100) + “%”;
rlabel.style.top = (((y0 + y1) / 2) / VB_H * 100) + “%”;
plotwrap.appendChild(rlabel);
});
// Numeric axis: tick labels + title.
if (axis && axis.ticks) {
axis.ticks.forEach(function (tv, i) {
var gx = xAt(tv);
var first = i === 0, last = i === axis.ticks.length – 1;
var xl = el(“text”, {
“class”: “ghchart__axislabel ghchart__axislabel–x ghchart__tl-tick”,
x: gx, y: axisY + 26, “text-anchor”: first ? “start” : (last ? “end” : “middle”)
});
xl.textContent = axis.format ? axis.format(tv) : String(tv);
svg.appendChild(xl);
});
}
/* —- Hover interaction (before/after chart) ——————
Emphasise a stage’s whole connection (its before block, after block
and ribbon) and show a tooltip with the before/after values. Only
when there are flows to connect and hover isn’t disabled. */
if (flows && config.hover !== false) {
var unit = config.unit ? ” ” + config.unit : “”;
function fmtHover(v) { return v == null ? “u2013” : v + unit; }
var tip = html(“div”, “ghchart__tooltip”);
tip.setAttribute(“role”, “status”);
var tipTitle = html(“div”, “ghchart__tt-title”);
var tipBody = html(“div”, “ghchart__tt-body ghchart__tt-body–plain”);
tip.appendChild(tipTitle); tip.appendChild(tipBody);
root.appendChild(tip);
var focused = null;
function focusStage(id) {
focused = id;
Object.keys(stageHover).forEach(function (k) {
var s = stageHover[k], on = (k === id);
if (s.beforeEl) s.beforeEl.style.opacity = on ? “1” : “0.16”;
if (s.afterEl) s.afterEl.style.opacity = on ? “1” : “0.16”;
if (s.flowEl) s.flowEl.style.opacity = on ? “0.5” : “0.04”;
});
var st = stageHover[id];
tipTitle.textContent = st.name || id;
tipBody.textContent = “”;
var lb = html(“div”, “ghchart__tt-line”); lb.textContent = “Before ” + fmtHover(st.before);
var la = html(“div”, “ghchart__tt-line”); la.textContent = “After ” + fmtHover(st.after);
tipBody.appendChild(lb); tipBody.appendChild(la);
}
function clearStage() {
focused = null;
Object.keys(stageHover).forEach(function (k) {
var s = stageHover[k];
if (s.beforeEl) s.beforeEl.style.opacity = “”;
if (s.afterEl) s.afterEl.style.opacity = “”;
if (s.flowEl) s.flowEl.style.opacity = “”;
});
tip.setAttribute(“data-visible”, “false”);
}
function placeTip(clientX, clientY) {
var rr = root.getBoundingClientRect();
var left = clientX – rr.left, top = clientY – rr.top – 14;
tip.setAttribute(“data-visible”, “true”);
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight existing.version) {
/* This bundle is newer. Every export embeds the COMPLETE engine, so this
bundle’s `renderers` is a self-contained, up-to-date set. Adopt it as
the shared registry (upgrading older same-named renderers in place),
while defensively preserving any renderer types only the older engine
happened to have. Then take over render(). */
for (var ek in existing.renderers) {
if (Object.prototype.hasOwnProperty.call(existing.renderers, ek) && !renderers[ek]) {
renderers[ek] = existing.renderers[ek];
}
}
existing.renderers = renderers;
existing.render = render;
existing.destroy = destroy;
existing.version = ENGINE_VERSION;
existing._renderCleanups = renderCleanups;
} else {
/* Same or older version already present: only contribute renderer types
the shared registry is missing (don’t clobber an equal/newer engine). */
for (var rk in renderers) {
if (Object.prototype.hasOwnProperty.call(renderers, rk) && !existing.renderers[rk]) {
existing.renderers[rk] = renderers[rk];
}
}
}
} else {
/* No engine yet, or a pre-versioning (old-style) engine loaded first.
Take over, preserving any renderers the old-style engine registered. */
if (existing && existing.renderers) {
for (var ok in existing.renderers) {
if (Object.prototype.hasOwnProperty.call(existing.renderers, ok) && !renderers[ok]) {
renderers[ok] = existing.renderers[ok];
}
}
}
global.GHChartEngine = { renderers: renderers, render: render, destroy: destroy, version: ENGINE_VERSION, _renderCleanups: renderCleanups };
}
})(typeof window !== “undefined” ? window : this);
;(function () {
“use strict”;
var CONFIG = {
mountId: “gh-chart-reviewbench-online-impact”,
type: “comparison”,
title: “Offline gains on ReviewBench translated into online impact”,
subtitle: “Lite-tier ensemble review compared with the production single-reviewer control”,
series: [
{
name: “ReviewBench offline”,
color: “#8da5db”
},
{
name: “Online A/B”,
color: “#499d6f”
}
],
xAxis: {
min: -30,
max: 70,
ticks: [-20, 0, 20, 40, 60]
},
rows: [
{
label: “Precision”,
values: [4.45, 8],
decimals: [2, 2]
},
{
label: “Recall”,
values: [12.88, 13.58],
decimals: [2, 2]
},
{
label: “Comment volume”,
values: [40, 25],
decimals: [1, 1]
},
{
label: “Cost / review”,
values: [-23.7, -8],
decimals: [1, 2]
}
]
};
function boot() {
var root = document.getElementById(CONFIG.mountId);
if (!root || root.getAttribute(“data-gh-rendered”)) return;
root.setAttribute(“data-gh-rendered”, “1”);
window.GHChartEngine.render(CONFIG);
}
if (document.readyState === “loading”) {
document.addEventListener(“DOMContentLoaded”, boot);
} else {
boot();
}
})();
Comment volume alone, however, does not capture comment quality. More critical findings mean something very different from low-severity nits. ReviewBench’s severity-level evaluation captured this too: it predicted a 227% increase in critical comments, compared with 262% online, along with the same broader shift toward more moderate comments and fewer nits.
.ghchart {
/* Theme tokens */
–ghc-text: #1f2328;
–ghc-muted: #656d76;
–ghc-grid: #d0d7de;
–ghc-axis: #59636e;
–ghc-card-bg: #ffffff;
–ghc-card-border: #d0d7de;
–ghc-canvas: #f2f5f3;
–ghc-divider: #b6bfb8;
–ghc-guide: #afb8c1;
/* GitHub brand font — matches the github.blog stack (Mona Sans, with
the blog’s metric fallback) so it renders identically in-context. */
font-family: “Mona Sans”, “Mona Sans Fallback”, -apple-system, “system-ui”,
“Segoe UI”, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
position: relative;
box-sizing: border-box;
width: 100%;
max-width: 699px;
margin: 0 auto;
color: var(–ghc-text);
line-height: 1.4;
}
.ghchart *,
.ghchart *::before,
.ghchart *::after { box-sizing: border-box; }
/* —————————————————————— */
/* Header + legend */
/* —————————————————————— */
.ghchart__header {
display: flex;
justify-content: flex-end;
align-items: center;
min-height: 24px;
margin-bottom: 4px;
}
.ghchart__legend {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
justify-content: flex-end;
}
.ghchart__legend-item {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: clamp(14px, 1.9vw, 17px);
font-weight: 600;
color: var(–ghc-muted);
white-space: nowrap;
}
.ghchart__swatch {
width: 14px;
height: 14px;
border-radius: 4px;
flex: 0 0 auto;
}
/* —————————————————————— */
/* Plot (SVG) */
/* —————————————————————— */
.ghchart__plot {
display: block;
width: 100%;
height: auto;
overflow: visible;
touch-action: pan-y;
}
.ghchart__gridline {
stroke: var(–ghc-grid);
stroke-width: 1;
stroke-dasharray: 2 6;
stroke-linecap: round;
opacity: 0.9;
}
.ghchart__axislabel {
fill: var(–ghc-axis);
font-size: 22px;
font-weight: 500;
}
.ghchart__axislabel–x { font-weight: 600; }
.ghchart__line {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Trailing partial/incomplete segment of a line (e.g. a month still in
progress) — dashed so it reads as “not final yet” without a legend. */
.ghchart__line–dashed {
stroke-dasharray: 7 6;
}
.ghchart__dot { transition: r 0.12s ease; }
/* Dashed threshold line (e.g. a target/reference level) + floating label.
Stroke color is set inline per chart; dash + label styling live here. */
.ghchart__threshold {
stroke-width: 2;
stroke-dasharray: 7 6;
stroke-linecap: round;
opacity: 0.9;
}
.ghchart__threshold-label {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Hatched band marking a partial / projected trailing region. */
.ghchart__projection { pointer-events: none; }
.ghchart__guide {
stroke: var(–ghc-guide);
stroke-width: 1.5;
stroke-dasharray: 4 4;
opacity: 0;
transition: opacity 0.12s ease;
}
.ghchart__hit { fill: transparent; }
/* —————————————————————— */
/* Scatter plot */
/* —————————————————————— */
.ghchart__gridline–v { /* vertical gridlines reuse the dashed grid style */ }
.ghchart__axistitle {
fill: var(–ghc-muted);
font-size: 20px;
font-weight: 600;
letter-spacing: -0.01em;
}
/* Dotted reference crosshair through the “reference” point. Stroke color
is set inline (tracks the reference series color). */
.ghchart__crosshair {
stroke-width: 2;
stroke-dasharray: 2 7;
stroke-linecap: round;
opacity: 0.85;
pointer-events: none;
}
.ghchart__halo { opacity: 0.18; pointer-events: none; }
.ghchart__pt { transition: r 0.12s ease; }
.ghchart__pt-label {
fill: var(–ghc-muted);
font-size: 19px;
font-weight: 600;
letter-spacing: -0.01em;
pointer-events: none;
}
.ghchart__pt-label–strong { font-size: 21px; font-weight: 700; }
.ghchart__pthit { fill: transparent; cursor: default; outline: none; }
.ghchart__pthit:focus-visible {
outline: 2px solid var(–ghc-axis);
outline-offset: 1px;
}
.ghchart__swatch–dot { border-radius: 50%; }
/* —————————————————————— */
/* Trend chart (dated time-series with narrative headline) */
/* —————————————————————— */
.ghchart__headline {
font-size: clamp(17px, 2.3vw, 20px);
line-height: 1.32;
font-weight: 700;
letter-spacing: -0.01em;
color: var(–ghc-text);
margin: 0 0 8px;
}
/* Trend legend sits left-aligned under the headline/subtitle. */
.ghchart__legend–trend {
justify-content: flex-start;
margin: 2px 0 14px;
}
.ghchart__glyph { flex: 0 0 auto; }
.ghchart__glyph–square {
width: 13px; height: 13px; border-radius: 2px;
}
.ghchart__glyph–diamond {
width: 12px; height: 12px; border: 1.5px solid; transform: rotate(45deg);
}
.ghchart__glyph–line {
width: 20px; height: 0; border-top: 2px dashed; border-radius: 0;
}
/* Phase background columns + separators + top labels. */
.ghchart__phase { fill: #f6f8fa; opacity: 0.55; }
.ghchart__phase–alt { fill: #ffffff; opacity: 0; }
.ghchart__phase-sep {
stroke: var(–ghc-grid); stroke-width: 1; stroke-dasharray: 2 5; opacity: 0.8;
}
.ghchart__phase-label {
fill: var(–ghc-muted);
font-size: 15px;
font-weight: 700;
letter-spacing: 0.06em;
}
.ghchart__phase-avg {
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Dashed trend line + ± noise band + slope label. */
.ghchart__trendband { opacity: 0.12; pointer-events: none; }
.ghchart__trendline {
stroke-width: 2.5;
stroke-dasharray: 8 6;
stroke-linecap: round;
fill: none;
}
.ghchart__slope {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Horizontal reference line + label. */
.ghchart__refline {
stroke-width: 2;
stroke-dasharray: 7 6;
stroke-linecap: round;
opacity: 0.85;
}
.ghchart__reflabel {
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
}
/* Data squares — values reveal on hover only. */
.ghchart__square { transition: width 0.12s ease, height 0.12s ease, x 0.12s ease, y 0.12s ease; }
.ghchart__squarehit { fill: transparent; cursor: default; outline: none; }
.ghchart__squarehit:focus-visible {
outline: 2px solid var(–ghc-axis);
outline-offset: 1px;
}
/* Invalid-run markers + leader-line annotations. */
.ghchart__invalid { stroke-width: 2; }
.ghchart__invalid-x { stroke-width: 2; stroke-linecap: round; }
.ghchart__leader { stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.8; }
.ghchart__leader–callout { stroke-dasharray: none; opacity: 0.6; }
.ghchart__anno-title {
font-size: 15px;
font-weight: 700;
letter-spacing: -0.01em;
}
.ghchart__anno-sub {
fill: var(–ghc-muted);
font-size: 13px;
font-weight: 500;
}
/* Final-callout labels read a touch larger than the invalid-run notes. */
.ghchart__anno-title–callout { font-size: 17px; }
.ghchart__anno-sub–callout { font-size: 14px; }
/* Bracket span annotation. */
.ghchart__bracket { stroke: var(–ghc-axis); stroke-width: 1.5; opacity: 0.75; }
.ghchart__bracket-label {
fill: var(–ghc-muted);
font-size: 13px;
font-weight: 700;
letter-spacing: 0.04em;
}
/* Final callout ring. */
.ghchart__callout-ring { fill: none; stroke-width: 2; }
/* —————————————————————— */
/* Title + divider */
/* —————————————————————— */
.ghchart__title {
/* Matches the github.blog H2 (.wp-block-heading) exactly */
display: block;
font-size: 32px;
line-height: 40px;
font-weight: 700;
letter-spacing: -0.18px;
color: var(–ghc-text);
}
.ghchart__rule {
height: 1.5px;
border-radius: 1.5px;
background: var(–ghc-divider);
margin: 10px 0 18px;
}
/* —————————————————————— */
/* Bar chart: subtitle, panels, bars, caption */
/* —————————————————————— */
.ghchart__subtitle {
margin: -6px 0 20px;
font-size: clamp(13px, 1.6vw, 15px);
line-height: 1.5;
color: var(–ghc-muted);
}
.ghchart__panel { margin-top: 30px; }
.ghchart__panel:first-of-type { margin-top: 6px; }
.ghchart__panel-title {
margin: 0 0 6px;
font-size: clamp(15px, 1.9vw, 18px);
font-weight: 700;
letter-spacing: -0.01em;
color: var(–ghc-text);
}
.ghchart__plot–bar { touch-action: pan-y; }
.ghchart__axisline {
stroke: var(–ghc-axis);
stroke-width: 1.25;
opacity: 0.35;
}
.ghchart__bar { transition: opacity 0.12s ease; }
.ghchart__barhit { fill: transparent; }
/* Always-on value labels above selected bars. */
.ghchart__barvalue {
fill: var(–ghc-text);
font-size: 20px;
font-weight: 700;
font-variant-numeric: tabular-nums;
pointer-events: none;
}
/* Anchor annotation: dotted reference line + floating pill. */
.ghchart__anno-line {
stroke: #8b949e;
stroke-width: 1.5;
stroke-dasharray: 4 3;
stroke-linecap: round;
pointer-events: none;
}
.ghchart__anno-chip {
fill: #ffebe9;
stroke: #ffcecb;
stroke-width: 1;
pointer-events: none;
}
.ghchart__anno-chip-text {
fill: #a0111f;
font-weight: 700;
pointer-events: none;
}
.ghchart__caption {
margin-top: 22px;
padding-top: 12px;
border-top: 1px solid var(–ghc-card-border);
font-size: 12.5px;
line-height: 1.55;
color: var(–ghc-muted);
}
.ghchart__caption b { color: var(–ghc-text); font-weight: 700; }
.ghchart__caption code {
font-family: ui-monospace, SFMono-Regular, “SF Mono”, Menlo, Consolas, monospace;
font-size: 0.92em;
padding: 1px 5px;
background: #f6f8fa;
border: 1px solid var(–ghc-card-border);
border-radius: 5px;
}
.ghchart__tag {
display: inline-block;
font-size: 10px;
font-weight: 650;
letter-spacing: 0.03em;
text-transform: uppercase;
padding: 2px 7px;
border-radius: 999px;
border: 1px solid #aceebb;
color: #0a5223;
background: #dafbe1;
white-space: nowrap;
vertical-align: middle;
}
/* —————————————————————— */
/* Distribution dashboard (segmented mix bar + ranked bar panels) */
/* —————————————————————— */
.ghchart__dist-section { margin-top: 28px; }
.ghchart__dist-section:first-of-type { margin-top: 12px; }
.ghchart–panel-cards { padding: 24px; background: var(–ghc-canvas); }
.ghchart–panel-cards .ghchart__dist-cols { gap: 24px; }
.ghchart–panel-cards .ghchart__dist-cols .ghchart__dist-section {
min-width: 0;
margin: 0;
padding: 24px;
border: 1px solid var(–ghc-card-border);
border-radius: 8px;
background: var(–ghc-card-bg);
}
@media (max-width: 480px) {
.ghchart–panel-cards,
.ghchart–panel-cards .ghchart__dist-cols .ghchart__dist-section { padding: 16px; }
}
/* Segmented proportional “mix” bar: name inside, count/percent below.
Separators are 2px inside borders (box-sizing:border-box) rather than a
flex gap, so the three segments sum to exactly 100% with no overflow. */
.ghchart__seg {
display: flex;
width: 100%;
height: 42px;
margin-top: 2px;
border-radius: 7px;
overflow: hidden;
}
.ghchart__seg-cell {
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
color: #ffffff;
font-size: 15px;
font-weight: 700;
letter-spacing: -0.01em;
white-space: nowrap;
overflow: hidden;
}
.ghchart__seg-cell + .ghchart__seg-cell { border-left: 2px solid var(–ghc-card-bg); }
.ghchart__seg-note-row {
display: flex;
width: 100%;
margin-top: 8px;
}
.ghchart__seg-note {
min-width: 0;
text-align: center;
font-size: 12.5px;
color: var(–ghc-muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* Keep the outer notes from spilling past the chart edges. */
.ghchart__seg-note:first-child { text-align: left; }
.ghchart__seg-note:last-child { text-align: right; }
.ghchart__seg-note b { color: var(–ghc-text); font-weight: 700; }
/* Ranked horizontal bar list: [category | bar track | value].
The list is the grid and rows use display:contents, so the category,
bar, and value columns line up across every row in a panel. */
.ghchart__dist-list {
display: grid;
grid-template-columns: max-content 1fr max-content;
align-items: center;
column-gap: 12px;
row-gap: 10px;
margin-top: 4px;
}
.ghchart__dist-row { display: contents; }
.ghchart__dist-cat {
text-align: right;
font-size: 13px;
line-height: 1.25;
color: var(–ghc-text);
white-space: nowrap;
}
.ghchart__dist-track { width: 100%; }
.ghchart__dist-bar {
height: 15px;
min-width: 2px;
border-radius: 3px;
}
.ghchart__dist-val {
font-size: 12.5px;
color: var(–ghc-muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.ghchart__dist-val b { color: var(–ghc-text); font-weight: 700; }
.ghchart__dist-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 16px; margin: 0; }
.ghchart__dist-stat { display: flex; flex-direction: column; gap: 4px; }
.ghchart__dist-stat dt { color: var(–ghc-muted); font-size: 13px; }
.ghchart__dist-stat dd { order: -1; margin: 0; font-size: 30px; font-weight: 800; letter-spacing: -0.02em; }
.ghchart__comparison-row { display: grid; grid-template-columns: 145px minmax(0, 1fr); gap: 20px; padding: 14px 0; }
.ghchart__comparison-label { align-self: center; font-size: 15px; font-weight: 600; }
.ghchart__comparison-plot { position: relative; padding: 6px 0; display: grid; gap: 8px; }
.ghchart__comparison-grid {
position: absolute; top: 0; bottom: 0; border-left: 1px dashed var(–ghc-grid);
transform: translateX(-50%); pointer-events: none;
}
.ghchart__comparison-zero { border-left: 2px solid var(–ghc-axis); z-index: 1; }
.ghchart__comparison-track { position: relative; height: 28px; }
.ghchart__comparison-bar {
position: absolute; top: 0; height: 28px;
border-radius: 0 2px 2px 0;
pointer-events: none;
}
.ghchart__comparison-bar–negative { border-radius: 2px 0 0 2px; }
.ghchart__comparison-value {
position: absolute;
top: 4px;
z-index: 2;
width: max-content;
color: var(–ghc-text);
font-size: 13px;
font-weight: 600;
line-height: 20px;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.ghchart__comparison-track–stacked .ghchart__comparison-value {
position: relative;
top: 0;
width: 100%;
white-space: normal;
overflow-wrap: anywhere;
background: var(–ghc-card-bg);
}
.ghchart__comparison-axis { position: relative; height: 24px; }
.ghchart__comparison-tick { position: absolute; transform: translateX(-50%); white-space: nowrap; font-size: 12px; color: var(–ghc-axis); }
@media (max-width: 480px) {
.ghchart__comparison-row { grid-template-columns: minmax(0, 1fr); gap: 8px; }
.ghchart__comparison-tick { font-size: 11px; }
}
/* Two-up panel grid (collapses to one column on narrow screens).
minmax(0,1fr) lets columns shrink so long labels never force overflow. */
.ghchart__dist-cols {
margin-top: 26px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px 30px;
}
.ghchart__dist-cols .ghchart__dist-section { margin-top: 0; }
.ghchart__dist-cols–stack { grid-template-columns: 1fr; }
.ghchart__dist-cols–stack .ghchart__dist-section { margin-top: 28px; }
.ghchart__dist-cols–stack .ghchart__dist-section:first-child { margin-top: 0; }
/* Tighten the label/value type a touch in the 2-up layout so the bars
keep room at the 699px blog width. */
.ghchart__dist-cols:not(.ghchart__dist-cols–stack) .ghchart__dist-cat { font-size: 12px; }
.ghchart__dist-cols:not(.ghchart__dist-cols–stack) .ghchart__dist-val { font-size: 11.5px; }
.ghchart__dist-cols:not(.ghchart__dist-cols–stack) .ghchart__dist-list { column-gap: 9px; }
@media (max-width: 600px) {
.ghchart__dist-cols { grid-template-columns: 1fr; gap: 26px; }
.ghchart__dist-cols .ghchart__dist-section ~ .ghchart__dist-section { margin-top: 2px; }
}
/* Let long category labels wrap on very narrow (mobile) widths. */
@media (max-width: 480px) {
.ghchart__dist-cat { white-space: normal; }
}
/* —————————————————————— */
/* Timeline / staged-duration chart (request lifecycle, before/after) */
/* —————————————————————— */
.ghchart__tl-eyebrow {
font-size: clamp(12px, 1.5vw, 13px);
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(–ghc-muted);
margin: 0 0 6px;
}
.ghchart__tl-headline { margin-bottom: 14px; }
/* Before -> after summary card. */
.ghchart__tl-stat {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 16px 24px;
padding: 16px 22px;
border: 1px solid var(–ghc-card-border);
border-radius: 12px;
/* Greenish grey to match the AI gateway diagram canvas (–ghd-canvas). */
background: #f2f5f3;
margin: 0 0 18px;
}
.ghchart__tl-stat-main { display: flex; align-items: center; gap: 20px; }
.ghchart__tl-stat-col { display: flex; flex-direction: column; gap: 2px; }
.ghchart__tl-stat-k {
font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
text-transform: uppercase; color: var(–ghc-muted);
}
.ghchart__tl-stat-v {
font-size: clamp(28px, 4.4vw, 40px); font-weight: 800;
letter-spacing: -0.02em; line-height: 1; color: var(–ghc-text);
}
.ghchart__tl-stat-v–muted { color: var(–ghc-muted); font-weight: 700; }
.ghchart__tl-stat-arrow { font-size: 26px; color: var(–ghc-muted); }
.ghchart__tl-stat-delta { text-align: right; }
.ghchart__tl-stat-delta-v {
font-size: clamp(26px, 4vw, 38px); font-weight: 800;
letter-spacing: -0.02em; line-height: 1; color: #1a7f37;
}
.ghchart__tl-stat-delta-k { font-size: 13px; color: var(–ghc-muted); margin-top: 4px; }
/* Stage legend (2-up, monospace-free, stacks on mobile). */
.ghchart__tl-legend {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 28px;
margin: 0 0 20px;
}
.ghchart__tl-legend-item { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
.ghchart__tl-swatch { width: 13px; height: 13px; border-radius: 3px; flex: 0 0 auto; }
.ghchart__tl-legend-text {
font-size: clamp(13px, 1.7vw, 15px); color: var(–ghc-text);
font-variant-numeric: tabular-nums;
}
.ghchart__tl-rule {
height: 1.5px; border-radius: 1.5px; background: var(–ghc-divider); margin: 4px 0 18px;
}
/* SVG plot text. */
.ghchart__tl-rowlabel { fill: var(–ghc-text); font-size: 22px; font-weight: 700; letter-spacing: 0.02em; }
.ghchart__tl-rowsub { fill: var(–ghc-muted); font-size: 18px; font-weight: 500; }
.ghchart__tl-seg-label { fill: #ffffff; font-size: 23px; font-weight: 700; letter-spacing: -0.01em; }
.ghchart__tl-callout-line { stroke: var(–ghc-guide); stroke-width: 1.5; fill: none; }
.ghchart__tl-callout-label { font-size: 21px; font-weight: 600; letter-spacing: -0.01em; }
/* Segments + ribbons ease their opacity for the before/after hover emphasis. */
.ghchart__tl-seg { transition: opacity 0.14s ease; }
.ghchart__tl-seg[data-stage] { cursor: pointer; }
.ghchart__tl-seg[data-stage]:focus-visible { outline: 2px solid var(–ghc-axis); outline-offset: 1px; }
.ghchart__tl-flow { opacity: 0.16; transition: opacity 0.14s ease; }
/* Timeline millisecond gridlines: thicker + larger dashes so the axis marks
read clearly behind the bars (scoped so scatter gridlines are unaffected). */
.ghchart__plot–timeline .ghchart__gridline–v {
stroke-width: 1.5;
stroke-dasharray: 5 5;
opacity: 1;
}
/* Timeline bracket labels read larger than the trend bracket note (they are
structural spans, not a small secondary annotation). */
.ghchart__tl-bracket-label { font-size: 20px; }
/* Wrapper so DOM labels can sit over the SVG at viewBox-proportional %s. */
.ghchart__tl-plotwrap { position: relative; }
/* Right-bracket label as a DOM node: a fixed, readable size that does NOT
scale down with the plot on small windows (unlike SVG text). */
.ghchart__tl-bracket-label-dom {
position: absolute;
transform: translateY(-50%);
font-size: clamp(12px, 1.5vw, 14px);
font-weight: 700;
letter-spacing: 0.02em;
color: var(–ghc-muted);
white-space: nowrap;
pointer-events: none;
}
.ghchart__tl-tick { fill: var(–ghc-axis); }
/* Axis title rendered as a DOM caption below the plot; matches the legend
labels’ size/weight/color instead of scaling with the SVG. */
.ghchart__tl-axis-caption {
font-size: clamp(13px, 1.7vw, 15px);
font-weight: 400;
color: var(–ghc-text);
text-align: center;
margin-top: 8px;
}
@media (max-width: 560px) {
.ghchart__tl-legend { grid-template-columns: 1fr; }
.ghchart__tl-stat { justify-content: flex-start; }
.ghchart__tl-stat-delta { text-align: left; }
}
/* —————————————————————— */
/* Calendar heatmap (GitHub contribution-graph style) */
/* —————————————————————— */
.ghheat__eyebrow { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; }
.ghheat__eyedot {
width: 9px; height: 9px; border-radius: 50%;
background: #cf222e; box-shadow: 0 0 0 3px rgba(207, 34, 46, 0.14);
flex: 0 0 auto;
}
.ghheat__eyetext {
font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
text-transform: uppercase; color: #cf222e;
}
/* Subtitle here is a prominent lede, not the muted variant. */
.ghheat__subtitle {
color: var(–ghc-text);
font-size: clamp(14px, 1.7vw, 16px);
/* 24px top gap matches the github.blog H2 → paragraph block spacing. */
margin: 24px 0 8px;
}
.ghheat__subtitle b { font-weight: 700; }
.ghheat__panel { margin-top: 36px; }
.ghheat__phead {
display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
margin-bottom: 16px;
}
.ghheat__badge {
display: inline-flex; align-items: center; justify-content: center;
width: 22px; height: 22px; border-radius: 50%;
color: #fff; font-size: 12px; font-weight: 700;
flex: 0 0 auto; align-self: center;
}
.ghheat__ptitle {
font-size: clamp(16px, 2vw, 19px); font-weight: 700;
letter-spacing: -0.01em; color: var(–ghc-text);
}
.ghheat__pdesc { font-size: 14px; color: var(–ghc-muted); }
.ghheat__body { display: flex; gap: 30px; align-items: flex-start; flex-wrap: wrap; }
.ghheat__gridwrap { flex: 1 1 340px; min-width: 260px; }
.ghheat__grid { display: block; width: 100%; height: auto; overflow: visible; }
.ghheat__cell {
stroke: rgba(27, 31, 36, 0.06);
stroke-width: 1;
transition: stroke 0.1s ease;
}
.ghheat__cell:hover { stroke: #24292f; stroke-width: 1.4; }
.ghheat__cell–pad { opacity: 0; pointer-events: none; }
.ghheat__wd, .ghheat__month {
fill: var(–ghc-muted); font-size: 11px; font-weight: 600;
}
.ghheat__side { flex: 1 1 250px; min-width: 230px; }
.ghheat__stat { display: flex; align-items: baseline; gap: 7px; }
.ghheat__statbig {
font-size: clamp(26px, 4vw, 34px); font-weight: 800; letter-spacing: -0.02em;
line-height: 1;
}
.ghheat__statsmall { font-size: 15px; font-weight: 600; color: var(–ghc-muted); }
.ghheat__note {
margin: 10px 0 16px; font-size: 14px; line-height: 1.55; color: var(–ghc-muted);
}
.ghheat__note b { color: var(–ghc-text); font-weight: 700; }
.ghheat__legend {
display: flex; align-items: center; gap: 5px;
font-size: 12.5px; color: var(–ghc-muted); flex-wrap: wrap;
}
.ghheat__sw { width: 12px; height: 12px; border-radius: 3px; display: inline-block; flex: 0 0 auto; }
.ghheat__sw–empty { border: 1px solid #d0d7de; }
.ghheat__legunit { margin-left: 2px; }
.ghheat__extra {
display: flex; align-items: flex-start; gap: 7px; margin-top: 14px;
font-size: 12px; line-height: 1.5; color: var(–ghc-muted); max-width: 330px;
}
.ghheat__extra .ghheat__sw { margin-top: 2px; }
/* —————————————————————— */
/* Tooltip */
/* —————————————————————— */
.ghchart__tooltip {
position: absolute;
z-index: 5;
top: 0;
left: 0;
/* `width: max-content` keeps the box sized to its own content instead of
the browser’s absolute-position “shrink-to-fit” formula, which derives
available width from how far `left` sits from the containing block’s
edge — near the right edge that shrinks toward zero and forces the
row text to wrap awkwardly even though the box is later clamped into
view. max-content is unaffected by `left`, so wrapping only happens if
content truly exceeds max-width. */
width: max-content;
min-width: 128px;
max-width: 260px;
padding: 10px 12px;
background: var(–ghc-card-bg);
border: 1px solid var(–ghc-card-border);
border-radius: 12px;
/* Layered drop shadow from Primer Brand’s ActionMenu (.ActionMenu__menu) */
box-shadow:
0px 100px 80px rgba(0, 0, 0, 0.01),
0px 41px 33px rgba(0, 0, 0, 0.02),
0px 22px 17px rgba(0, 0, 0, 0.02),
0px 12px 10px rgba(0, 0, 0, 0.03),
0px 6px 5px rgba(0, 0, 0, 0.04),
0px 2px 2px rgba(0, 0, 0, 0.07);
pointer-events: none;
opacity: 0;
transform: translate(-50%, -100%);
/* Fade in on show and out on hide; quickly ease left/top when tracking
from one point to the next. The position ease is suppressed on the
first appearance (see chart.js) so the tooltip fades in place rather
than sliding in from its previous spot. */
transition: opacity 0.12s ease,
left 0.1s cubic-bezier(0.4, 0, 0.2, 1),
top 0.1s cubic-bezier(0.4, 0, 0.2, 1);
will-change: opacity, transform, left, top;
}
.ghchart__tooltip[data-visible=”true”] { opacity: 1; }
.ghchart__tt-title {
font-size: 13px;
font-weight: 700;
color: var(–ghc-text);
margin-bottom: 6px;
}
.ghchart__tt-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
line-height: 1.6;
color: var(–ghc-muted);
}
.ghchart__tt-swatch { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.ghchart__tt-name { flex: 1 1 auto; }
.ghchart__tt-value { font-weight: 700; color: var(–ghc-text); font-variant-numeric: tabular-nums; }
/* Optional “partial data” note, shown only for a hovered point past a
line’s config.partial.fromIndex (see .ghchart__line–dashed). */
.ghchart__tt-note {
margin-top: 6px;
padding-top: 6px;
border-top: 1px solid var(–ghc-card-border);
font-size: 12px;
font-style: italic;
color: var(–ghc-muted);
}
/* Bar tooltip body: a title line + free-form text lines (no swatches). */
.ghchart__tt-line {
font-size: 13px;
line-height: 1.5;
color: var(–ghc-muted);
}
.ghchart__tt-line:first-child { color: var(–ghc-text); font-weight: 600; }
.ghchart__tt-line + .ghchart__tt-line { margin-top: 2px; }
/* Timeline before/after tooltip: only the title is bold; both value lines stay
regular weight and the same color (no emphasised first line). */
.ghchart__tt-body–plain .ghchart__tt-line,
.ghchart__tt-body–plain .ghchart__tt-line:first-child {
font-weight: 400;
color: var(–ghc-text);
}
@media (prefers-reduced-motion: reduce) {
.ghchart__dot,
.ghchart__guide,
.ghchart__bar,
.ghchart__pt,
.ghchart__square,
.ghheat__cell,
.ghchart__tl-seg,
.ghchart__tl-flow,
.ghchart__tooltip { transition: none; }
}
/*
Shared chart engine — reusable renderer for the blog-charts library.
——————————————————————
Exposes window.GHChartEngine = { renderers, render(config, mountEl?), destroy(mountEl) }.
Renderers are keyed by config.type (currently “line”); add a new viz type
by registering GHChartEngine.renderers[] = function (root, config) {…}.
This same file is embedded verbatim at the top of each exported WordPress
chart.js, followed by that chart’s CONFIG + a small auto-boot. It has no
external dependencies and is safe to include more than once on a page.
*/
(function (global) {
“use strict”;
var SVGNS = “http://www.w3.org/2000/svg”;
function el(tag, attrs) {
var node = document.createElementNS(SVGNS, tag);
if (attrs) {
for (var k in attrs) {
if (Object.prototype.hasOwnProperty.call(attrs, k)) {
node.setAttribute(k, attrs[k]);
}
}
}
return node;
}
function html(tag, cls) {
var node = document.createElement(tag);
if (cls) node.className = cls;
return node;
}
function clamp(v, lo, hi) { return v hi ? hi : v; }
var WHITE = [255, 255, 255], BLACK = [0, 0, 0];
function hexToRgb(h) {
h = String(h).replace(“#”, “”);
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
}
function rgbToHex(c) {
return “#” + c.map(function (x) {
x = Math.max(0, Math.min(255, Math.round(x)));
return (x < 16 ? "0" : "") + x.toString(16);
}).join("");
}
function mix(a, b, t) {
return [a[0] + (b[0] – a[0]) * t, a[1] + (b[1] – a[1]) * t, a[2] + (b[2] – a[2]) * t];
}
function rampFrom(baseHex) {
var b = hexToRgb(baseHex);
return [
rgbToHex(mix(b, WHITE, 0.80)),
rgbToHex(mix(b, WHITE, 0.52)),
rgbToHex(mix(b, WHITE, 0.20)),
baseHex,
rgbToHex(mix(b, BLACK, 0.30))
];
}
function strongOf(baseHex) { return rampFrom(baseHex)[4]; }
function labelColorOn(hex) {
function luminance(color) {
var c = hexToRgb(color).map(function (channel) {
channel /= 255;
return channel = 4.5) return “#1f2328”;
return 1.05 / (background + 0.05) >= 4.5 ? “#ffffff” : “#000000”;
}
var renderers = {};
var renderCleanups = (global.GHChartEngine && global.GHChartEngine._renderCleanups) || new WeakMap();
renderers.comparison = function (root, config) {
var axis = config.xAxis, series = config.series, rows = config.rows;
if (!axis || !Number.isFinite(axis.min) || !Number.isFinite(axis.max) ||
axis.min > 0 || axis.max < 0 || axis.max <= axis.min ||
!Array.isArray(axis.ticks) || !Array.isArray(series) || !series.length ||
series.some(function (s) { return !s || typeof s.color !== "string" || !/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(s.color); }) ||
!Array.isArray(rows) || !rows.length ||
axis.ticks.some(function (v) { return !Number.isFinite(v) || v axis.max; }) ||
rows.some(function (row) {
return !Array.isArray(row.values) || row.values.length !== series.length ||
row.values.some(function (v) { return !Number.isFinite(v) || v axis.max; }) ||
!Array.isArray(row.decimals) || row.decimals.length !== series.length ||
row.decimals.some(function (n) { return !Number.isInteger(n) || n 10; });
})) throw new Error(“Comparison chart requires a zero-inclusive axis, hex series colors, and matching finite values/decimal precision for each series.”);
root.textContent = “”;
root.setAttribute(“role”, “figure”);
root.setAttribute(“aria-label”, config.title);
var heading = html(“div”, “ghchart__headline”);
heading.textContent = config.title;
root.appendChild(heading);
if (config.subtitle) {
var subtitle = html(“p”, “ghchart__subtitle”);
subtitle.textContent = config.subtitle;
root.appendChild(subtitle);
}
var legend = html(“div”, “ghchart__legend ghchart__legend–trend”);
series.forEach(function (s) {
var item = html(“span”, “ghchart__legend-item”);
var swatch = html(“span”, “ghchart__swatch”);
swatch.style.background = s.color;
item.appendChild(swatch);
item.appendChild(document.createTextNode(s.name));
legend.appendChild(item);
});
root.appendChild(legend);
var marks = [];
function at(value) { return (value – axis.min) / (axis.max – axis.min) * 100; }
function signed(value, decimals) { return (value > 0 ? “+” : “”) + value.toFixed(decimals) + “%”; }
rows.forEach(function (row) {
var group = html(“div”, “ghchart__comparison-row”);
group.setAttribute(“role”, “group”);
group.setAttribute(“aria-label”, row.label);
var label = html(“div”, “ghchart__comparison-label”);
label.textContent = row.label;
label.setAttribute(“aria-hidden”, “true”);
group.appendChild(label);
var plot = html(“div”, “ghchart__comparison-plot”);
axis.ticks.forEach(function (value) {
var line = html(“span”, “ghchart__comparison-grid”);
line.style.left = at(value) + “%”;
line.setAttribute(“aria-hidden”, “true”);
plot.appendChild(line);
});
var zero = html(“span”, “ghchart__comparison-grid ghchart__comparison-zero”);
zero.style.left = at(0) + “%”;
zero.setAttribute(“aria-hidden”, “true”);
plot.appendChild(zero);
series.forEach(function (s, index) {
var value = row.values[index], formatted = signed(value, row.decimals[index]);
var track = html(“div”, “ghchart__comparison-track”);
track.setAttribute(“role”, “group”);
track.setAttribute(“aria-label”, s.name);
var bar = html(“span”, “ghchart__comparison-bar”);
// Anchor the zero-facing edge, without fractional transform snapping.
if (value < 0) {
bar.style.right = "calc(100% – " + at(0) + "%)";
bar.classList.add("ghchart__comparison-bar–negative");
} else bar.style.left = at(0) + "%";
bar.style.width = Math.abs(at(value) – at(0)) + "%";
bar.style.background = s.color;
bar.setAttribute("aria-hidden", "true");
var valueLabel = html("span", "ghchart__comparison-value");
valueLabel.textContent = formatted;
track.appendChild(bar);
track.appendChild(valueLabel);
marks.push({ track: track, bar: bar, label: valueLabel, value: value, color: s.color });
plot.appendChild(track);
});
group.appendChild(plot);
root.appendChild(group);
});
var axisRow = html("div", "ghchart__comparison-row");
axisRow.appendChild(html("span"));
var ticks = html("div", "ghchart__comparison-axis");
axis.ticks.forEach(function (value) {
var tick = html("span", "ghchart__comparison-tick");
tick.style.left = at(value) + "%";
if (value === axis.min) tick.style.transform = "none";
if (value === axis.max) tick.style.transform = "translateX(-100%)";
tick.textContent = value === 0 ? "0" : signed(value, 0);
ticks.appendChild(tick);
});
axisRow.appendChild(ticks);
root.appendChild(axisRow);
var active = true, frame = 0, observer;
function positionLabel(mark, side, coordinate, placement) {
mark.label.style.left = side === "left" ? "calc(" + at(coordinate) + "% + 8px)" : "auto";
mark.label.style.right = side === "right" ? "calc(100% – " + at(coordinate) + "% + 8px)" : "auto";
mark.label.style.color = placement === "inside" ? labelColorOn(mark.color) : "";
mark.label.setAttribute("data-placement", placement);
}
function layoutValues() {
if (!active || !root.clientWidth) return;
marks.forEach(function (mark) {
mark.track.classList.remove("ghchart__comparison-track–stacked");
mark.track.style.height = "";
mark.bar.style.top = "";
mark.label.style.left = "0px";
mark.label.style.right = "auto";
mark.label.style.color = "";
var width = mark.track.clientWidth, textWidth = mark.label.offsetWidth + 1;
var zero = at(0) * width / 100, end = at(mark.value) * width / 100;
var negative = mark.value = textWidth + 8) {
positionLabel(mark, negative ? “right” : “left”, mark.value, “outside”);
} else if (Math.abs(end – zero) >= textWidth + 16) {
positionLabel(mark, negative ? “left” : “right”, mark.value, “inside”);
} else if (opposite >= textWidth + 8) {
positionLabel(mark, negative ? “left” : “right”, 0, “opposite”);
} else {
// Very narrow plots or long precision strings get a separate label line.
mark.track.classList.add(“ghchart__comparison-track–stacked”);
mark.label.setAttribute(“data-placement”, “stacked”);
var top = mark.label.offsetHeight + 6;
mark.bar.style.top = top + “px”;
mark.track.style.height = top + 28 + “px”;
}
});
}
function schedule() {
if (!active) return;
cancelAnimationFrame(frame);
frame = requestAnimationFrame(layoutValues);
}
layoutValues();
if (global.ResizeObserver) {
observer = new ResizeObserver(schedule);
observer.observe(root);
} else global.addEventListener(“resize”, schedule);
if (document.fonts) {
document.fonts.ready.then(schedule);
document.fonts.addEventListener(“loadingdone”, schedule);
}
return function () {
active = false;
cancelAnimationFrame(frame);
if (observer) observer.disconnect();
global.removeEventListener(“resize”, schedule);
if (document.fonts) document.fonts.removeEventListener(“loadingdone”, schedule);
};
};
/* ================================================================
Line chart renderer
================================================================ */
renderers.line = function (root, config) {
// viewBox geometry (design units; scales fluidly to any width).
var VB = { w: 1000, h: 560 };
var M = { top: 30, right: 34, bottom: 56, left: 86 };
// Inset the plotted points slightly from the axis so the first/last
// months (e.g. “Jan”) don’t crowd the y-axis labels (e.g. “50”).
var PAD_X = 18;
// Reset (in case of re-render) and use a unique id prefix so multiple
// charts on one page never share gradient ids.
root.textContent = “”;
var uid = config.mountId;
var n = config.labels.length;
var yMin = config.yAxis.min;
var yMax = config.yAxis.max;
var step = config.yAxis.step;
// Y-axis tick labels can format differently from the tooltip values
// (e.g. a plain “40” on the axis but “40%” in the tooltip).
var fmtAxis = config.formatAxis || config.formatValue;
// Widen the left gutter when the formatted tick labels need it (a unit
// suffix like “2,000M” is far wider than a bare “40”). Labels are real
// SVG we can’t measure before layout, so estimate from the
// glyph mix at the 22-unit axis font size.
var AXIS_FONT = 22;
var Y_LABEL_GAP = 22;
function estimateTextW(s) {
var w = 0;
for (var i = 0; i < s.length; i++) {
var c = s.charAt(i);
if (c === "," || c === "." || c === " ") w += 0.28;
else if (/[0-9]/.test(c)) w += 0.58;
else if (/[A-Z]/.test(c)) w += 0.72;
else w += 0.55;
}
return w * AXIS_FONT;
}
var widestY = 0;
for (var tv = yMin; tv <= yMax + 0.0001; tv += step) {
widestY = Math.max(widestY, estimateTextW(String(fmtAxis(tv))));
}
M.left = Math.max(M.left, Math.ceil(widestY + Y_LABEL_GAP + 8));
var innerW = VB.w – M.left – M.right;
var innerH = VB.h – M.top – M.bottom;
var plotW = innerW – 2 * PAD_X;
var stepX = n <= 1 ? 0 : plotW / (n – 1);
function xAt(i) { return n <= 1 ? M.left + innerW / 2 : M.left + PAD_X + stepX * i; }
function yAt(v) { return M.top + innerH * (1 – (v – yMin) / (yMax – yMin)); }
var baseY = yAt(yMin);
/* —- Title + divider (top) ———————————- */
var title = html("span", "ghchart__title");
title.textContent = config.title;
root.appendChild(title);
root.appendChild(html("div", "ghchart__rule"));
/* —- Header + legend —————————————- */
if (!config.hideLegend) {
var header = html("div", "ghchart__header");
var legend = html("div", "ghchart__legend");
config.series.forEach(function (s) {
var item = html("span", "ghchart__legend-item");
var sw = html("span", "ghchart__swatch");
sw.style.background = s.color;
item.appendChild(sw);
item.appendChild(document.createTextNode(s.name));
legend.appendChild(item);
});
header.appendChild(legend);
root.appendChild(header);
}
/* —- SVG plot ———————————————– */
var svg = el("svg", {
"class": "ghchart__plot",
viewBox: "0 0 " + VB.w + " " + VB.h,
preserveAspectRatio: "xMidYMid meet",
role: "img",
"aria-label": config.title + " line chart"
});
// Gradient defs (one per area series).
var defs = el("defs");
config.series.forEach(function (s, si) {
if (!s.area) return;
var g = el("linearGradient", {
id: uid + "-grad-" + si, x1: "0", y1: "0", x2: "0", y2: "1"
});
g.appendChild(el("stop", { offset: "0", "stop-color": s.color, "stop-opacity": "0.28" }));
g.appendChild(el("stop", { offset: "0.55", "stop-color": s.color, "stop-opacity": "0.08" }));
g.appendChild(el("stop", { offset: "1", "stop-color": s.color, "stop-opacity": "0" }));
defs.appendChild(g);
});
// Diagonal-hatch pattern for an optional "partial / projected" band.
if (config.projection) {
var pat = el("pattern", {
id: uid + "-hatch", width: 7, height: 7,
patternUnits: "userSpaceOnUse", patternTransform: "rotate(45)"
});
pat.appendChild(el("line", {
x1: 0, y1: 0, x2: 0, y2: 7,
stroke: config.projection.color || "#8c959f",
"stroke-width": 1.1, "stroke-opacity": 0.5
}));
defs.appendChild(pat);
}
svg.appendChild(defs);
// Gridlines + y-axis labels.
for (var v = yMin; v cubic Bezier) path builder.
function smooth(points) {
if (!points.length) return “”;
if (points.length < 2) return "M" + points[0].x + "," + points[0].y;
var d = "M " + points[0].x + "," + points[0].y;
for (var i = 0; i < points.length – 1; i++) {
var p0 = points[i – 1] || points[i];
var p1 = points[i];
var p2 = points[i + 1];
var p3 = points[i + 2] || p2;
var t = 1 / 6;
var c1x = p1.x + (p2.x – p0.x) * t;
var c1y = p1.y + (p2.y – p0.y) * t;
var c2x = p2.x – (p3.x – p1.x) * t;
var c2y = p2.y – (p3.y – p1.y) * t;
d += " C " + c1x + "," + c1y + " " + c2x + "," + c2y + " " + p2.x + "," + p2.y;
}
return d;
}
// Area fills (drawn first, behind lines). When config.partial marks a
// trailing partial/incomplete range (e.g. a month still in progress),
// the fill stops at the last complete point instead of extending under
// the partial segment — visually distinguishing "real" coverage from
// data that isn't final yet.
var partialIdx = config.partial ? config.partial.fromIndex : null;
config.series.forEach(function (s, si) {
if (!s.area) return;
var p = pts[si];
var fillP = partialIdx == null ? p : p.slice(0, partialIdx + 1);
if (fillP.length < 2) return;
var d = smooth(fillP) + " L " + fillP[fillP.length – 1].x + "," + baseY +
" L " + fillP[0].x + "," + baseY + " Z";
svg.appendChild(el("path", { d: d, fill: "url(#" + uid + "-grad-" + si + ")" }));
});
// Vertical hover guide (hidden until hover).
var guide = el("line", {
"class": "ghchart__guide",
x1: xAt(0), y1: M.top – 6, x2: xAt(0), y2: baseY
});
svg.appendChild(guide);
// Lines (draw in reverse so the first series sits on top). The trailing
// partial segment (from the last complete point onward) renders as its
// own dashed path so the "not final yet" range reads clearly without
// a legend entry — mirroring the projected-region convention above.
config.series.slice().reverse().forEach(function (s) {
var si = config.series.indexOf(s);
var p = pts[si];
var solidP = partialIdx == null ? p : p.slice(0, partialIdx + 1);
svg.appendChild(el("path", {
"class": "ghchart__line",
d: smooth(solidP),
stroke: s.color,
"stroke-width": s.width || (si === 0 ? 4 : 3.25)
}));
if (partialIdx != null && partialIdx < p.length – 1) {
// Straight (not curve-fit) dashed segment: fitting a Catmull-Rom
// curve to just this short tail would use different neighbor
// context than the solid curve and create a visible kink at the
// junction point; a straight dashed line reads as "projected" and
// joins the solid curve's end point cleanly.
var dashedP = p.slice(partialIdx);
var dd = "M " + dashedP[0].x + "," + dashedP[0].y;
var segLen = 0;
for (var di = 1; di 0 && segLen / PERIOD partialIdx) ? “” : “none”;
}
// Anchor above the highest point in this column.
var topSi = 0, topY = Infinity;
config.series.forEach(function (s, si) {
var yy = yAt(s.values[i]);
if (yy < topY) { topY = yy; topSi = si; }
});
var anchor = dotEls[topSi][i].getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
// On the first appearance, fade in exactly at the point: suppress the
// position ease so it doesn't slide in from its previous resting spot.
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = "opacity 0.12s ease";
tip.setAttribute("data-visible", "true");
// Reset left before measuring: a browser sizes an absolutely
// positioned auto/max-content-width box using the *current* `left`
// to derive available space, so measuring while `left` still holds
// the previous (possibly near-edge) position creates a feedback loop
// that keeps squeezing the tooltip narrower and wrapping its rows.
// Setting left to 0 first guarantees a true, unconstrained natural
// width; this happens synchronously before the next paint, so
// there's no visible flash.
tip.style.left = "0px";
// Clamp horizontally within the container.
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
// Flip below the point if there's no room above.
if (top – tip.offsetHeight < 0) {
top = anchor.bottom – rootRect.top + 12;
tip.style.transform = "translate(-50%, 0)";
} else {
tip.style.transform = "translate(-50%, -100%)";
}
tip.style.left = left + "px";
tip.style.top = top + "px";
// Commit that position without animating, then restore the CSS
// transition so subsequent point-to-point moves ease into place.
if (suppress) {
void tip.offsetWidth;
tip.style.transition = "";
}
}
function hide() {
activeIndex = -1;
guide.style.opacity = "0";
tip.setAttribute("data-visible", "false");
dotEls.forEach(function (col, si) {
col.forEach(function (c) { c.setAttribute("r", baseR[si]); });
});
}
hit.addEventListener("pointermove", function (e) { show(indexFromClientX(e.clientX)); });
hit.addEventListener("pointerdown", function (e) { show(indexFromClientX(e.clientX)); });
hit.addEventListener("pointerleave", hide);
// Keyboard access.
svg.setAttribute("tabindex", "0");
svg.addEventListener("keydown", function (e) {
if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
var start = activeIndex { title, lines:[…] } }
No printed value labels — values surface on hover (consistent with
the line chart). Bars use a single tunable color with rounded tops.
================================================================ */
renderers.bar = function (root, config) {
root.textContent = “”;
var barColor = (config.series && config.series[0] && config.series[0].color) || “#ff7b72”;
var barRadius = config.barRadius != null ? config.barRadius : 8;
// Per-bar fill: a bar may point at a different series (e.g. a muted
// “preliminary” color) via panel.barSeries[index]; otherwise series[0].
function barColorFor(panel, j) {
var si = (panel.barSeries && panel.barSeries[j] != null) ? panel.barSeries[j] : 0;
var s = config.series && config.series[si];
return (s && s.color) || barColor;
}
var motionMQ = window.matchMedia
? window.matchMedia(“(prefers-reduced-motion: reduce)”)
: null;
function reduceMotion() { return !!(motionMQ && motionMQ.matches); }
/* —- Title + divider (+ optional subtitle) —————— */
var title = html(“span”, “ghchart__title”);
title.textContent = config.title;
root.appendChild(title);
root.appendChild(html(“div”, “ghchart__rule”));
if (config.subtitle) {
var sub = html(“p”, “ghchart__subtitle”);
sub.textContent = config.subtitle;
root.appendChild(sub);
}
/* —- One shared tooltip for all panels ———————- */
var tip = html(“div”, “ghchart__tooltip”);
tip.setAttribute(“role”, “status”);
var tipTitle = html(“div”, “ghchart__tt-title”);
var tipBody = html(“div”, “ghchart__tt-body”);
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var allBars = []; // { el, pi, bi }
var activeKey = null; // “pi:bi” of the hovered bar
function hideTip() {
activeKey = null;
tip.setAttribute(“data-visible”, “false”);
allBars.forEach(function (b) { b.el.style.opacity = “1”; });
}
// Hoisted: referenced by the per-column pointer handlers below.
function showTip(pi, bi, panel, panelBars) {
var reappearing = activeKey === null;
activeKey = pi + “:” + bi;
allBars.forEach(function (b) {
b.el.style.opacity = (b.pi === pi && b.bi === bi) ? “1” : “0.42”;
});
var content = panel.tooltip
? panel.tooltip(bi, panel)
: { title: (panel.labels || [])[bi],
lines: [ (panel.formatValue || String)(panel.values[bi]) ] };
tipTitle.textContent = content.title != null ? content.title : “”;
tipBody.textContent = “”;
(content.lines || []).forEach(function (ln) {
var row = html(“div”, “ghchart__tt-line”);
row.textContent = ln;
tipBody.appendChild(row);
});
var anchor = panelBars[bi].el.getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = “opacity 0.12s ease”;
tip.setAttribute(“data-visible”, “true”);
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight 0) {
for (var v = yMin; v <= yMax + 1e-9; v += step) {
var gy = yAt(v);
svg.appendChild(el("line", {
"class": "ghchart__gridline", x1: M.left, y1: gy, x2: VB.w – M.right, y2: gy
}));
var yl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–y",
x: M.left – 18, y: gy, dy: "0.32em", "text-anchor": "end"
});
yl.textContent = fmt(v);
svg.appendChild(yl);
}
}
// Solid baseline.
svg.appendChild(el("line", {
"class": "ghchart__axisline", x1: M.left, y1: baseY, x2: VB.w – M.right, y2: baseY
}));
// x-axis labels.
for (var i = 0; i < nb; i++) {
var xl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–x",
x: xCenter(i), y: VB.h – M.bottom + 30, "text-anchor": "middle"
});
if (panel.xLabelSize) xl.setAttribute("style", "font-size:" + panel.xLabelSize + "px");
xl.textContent = labels[i];
svg.appendChild(xl);
}
// Bars (rounded top corners only) + full-height hover columns.
var panelBars = [];
for (var j = 0; j dark ramp is derived from a single tunable base color.
================================================================ */
renderers.heatmap = function (root, config) {
root.textContent = “”;
function trim0(s) { return s.replace(/.0$/, “”); }
function fmtM(v) {
if (v >= 1000) { var g = v / 1000; return (g >= 10 ? Math.round(g) : trim0(g.toFixed(1))) + “B”; }
if (v >= 1) return trim0(v.toFixed(1)) + “M”;
return Math.round(v * 1000) + “K”;
}
var MON = [“Jan”, “Feb”, “Mar”, “Apr”, “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”];
var MON_FULL = [“January”, “February”, “March”, “April”, “May”, “June”,
“July”, “August”, “September”, “October”, “November”, “December”];
var WD = [“Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”];
var year = config.year || 2026;
var startM = config.startMonth != null ? config.startMonth : 0;
var endM = config.endMonth != null ? config.endMonth : 5;
var emptyColor = config.emptyColor || “#ebedf0”;
function daysInMonth(y, m) { return new Date(y, m + 1, 0).getDate(); }
function parseISO(s) { var p = s.split(“-“); return new Date(+p[0], +p[1] – 1, +p[2]); }
function dayStart(dt) { return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate()); }
/* —- Eyebrow + title + subtitle —————————– */
if (config.eyebrow) {
var eb = html(“div”, “ghheat__eyebrow”);
eb.appendChild(html(“span”, “ghheat__eyedot”));
var ebt = html(“span”, “ghheat__eyetext”);
ebt.textContent = config.eyebrow;
eb.appendChild(ebt);
root.appendChild(eb);
}
var title = html(“span”, “ghchart__title”);
title.textContent = config.title;
root.appendChild(title);
if (config.subtitle) {
var sub = html(“p”, “ghchart__subtitle ghheat__subtitle”);
sub.innerHTML = config.subtitle;
root.appendChild(sub);
}
/* —- One shared tooltip ————————————– */
var tip = html(“div”, “ghchart__tooltip”);
tip.setAttribute(“role”, “status”);
var tipTitle = html(“div”, “ghchart__tt-title”);
var tipBody = html(“div”, “ghchart__tt-body”);
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var motionMQ = window.matchMedia ? window.matchMedia(“(prefers-reduced-motion: reduce)”) : null;
function reduceMotion() { return !!(motionMQ && motionMQ.matches); }
var activeCell = null;
function hideTip() { activeCell = null; tip.setAttribute(“data-visible”, “false”); }
function showTip(rectEl, titleText, lines) {
var reappearing = activeCell === null;
activeCell = rectEl;
tipTitle.textContent = titleText;
tipBody.textContent = “”;
lines.forEach(function (ln) {
var r = html(“div”, “ghchart__tt-line”);
r.innerHTML = ln;
tipBody.appendChild(r);
});
var a = rectEl.getBoundingClientRect(), rr = root.getBoundingClientRect();
var left = a.left + a.width / 2 – rr.left, top = a.top – rr.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = “opacity 0.12s ease”;
tip.setAttribute(“data-visible”, “true”);
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight < 0) {
top = a.bottom – rr.top + 12;
tip.style.transform = "translate(-50%, 0)";
} else {
tip.style.transform = "translate(-50%, -100%)";
}
tip.style.left = left + "px";
tip.style.top = top + "px";
if (suppress) { void tip.offsetWidth; tip.style.transition = ""; }
}
/* —- Panels ————————————————– */
(config.panels || []).forEach(function (panel, pi) {
var base = (config.series && config.series[pi] && config.series[pi].color) || panel.base || "#3094ff";
var ramp = rampFrom(base);
var strong = ramp[4];
var totals = panel.months || [];
var activeVals = [];
for (var mi = startM; mi <= endM; mi++) { if (totals[mi] != null) activeVals.push(totals[mi]); }
var vmin = activeVals.length ? Math.min.apply(null, activeVals) : 0;
var vmax = activeVals.length ? Math.max.apply(null, activeVals) : 1;
function levelFor(t) {
if (t == null) return -1;
if (vmax === vmin) return 4;
return Math.round((t – vmin) / (vmax – vmin) * 4);
}
var af = panel.activeFrom ? parseISO(panel.activeFrom) : new Date(year, startM, 1);
var at = panel.activeTo ? parseISO(panel.activeTo) : new Date(year, endM, daysInMonth(year, endM));
var wrap = html("div", "ghheat__panel");
var head = html("div", "ghheat__phead");
var badge = html("span", "ghheat__badge");
badge.textContent = panel.badge != null ? panel.badge : (pi + 1);
badge.style.background = strong;
head.appendChild(badge);
var pt = html("span", "ghheat__ptitle");
pt.textContent = panel.title;
head.appendChild(pt);
if (panel.descriptor) {
var pd = html("span", "ghheat__pdesc");
pd.textContent = panel.descriptor;
head.appendChild(pd);
}
wrap.appendChild(head);
var body = html("div", "ghheat__body");
/* —- Calendar grid (SVG) —- */
var gridStart = new Date(year, startM, 1);
var gs = new Date(gridStart);
gs.setDate(gs.getDate() – gs.getDay()); // back to Sunday
var gridEnd = new Date(year, endM, daysInMonth(year, endM));
var ge = new Date(gridEnd);
ge.setDate(ge.getDate() + (6 – ge.getDay())); // forward to Saturday
var nCols = Math.round((ge – gs) / (7 * 86400000)) + 1;
var cell = 13, sq = 11, rad = 2.5, labL = 34, labT = 18;
var W = labL + nCols * cell, H = labT + 7 * cell;
var svg = el("svg", {
"class": "ghheat__grid",
viewBox: "0 0 " + W + " " + H,
preserveAspectRatio: "xMinYMin meet",
role: "img",
"aria-label": panel.title + " calendar heatmap"
});
[1, 3, 5].forEach(function (r) {
var t = el("text", {
"class": "ghheat__wd", x: labL – 6, y: labT + r * cell + sq / 2,
dy: "0.32em", "text-anchor": "end"
});
t.textContent = WD[r];
svg.appendChild(t);
});
// Month labels above the column where each month begins.
for (var lm = startM; lm <= endM; lm++) {
var first = new Date(year, lm, 1);
var colOf = Math.floor((dayStart(first) – gs) / (7 * 86400000));
var mlab = el("text", { "class": "ghheat__month", x: labL + colOf * cell, y: 12, "text-anchor": "start" });
mlab.textContent = MON[lm];
svg.appendChild(mlab);
}
var afS = dayStart(af), atS = dayStart(at);
var d = new Date(gs);
for (var i = 0; i = startM && m = afS && ds = 0 ? ramp[lvl] : emptyColor
});
if (inRange) {
(function (dt, mm, has) {
var dateStr = WD[dt.getDay()] + ” u00b7 ” + MON[mm] + ” ” + dt.getDate() + “, ” + year;
var lines;
if (has) {
var daily = totals[mm] / daysInMonth(year, mm);
lines = [
“” + fmtM(daily) + “ ” + panel.legendLabel,
MON_FULL[mm] + ” u00b7 ” + fmtM(totals[mm]) + ” ” + panel.unitTotal
];
} else {
lines = [panel.emptyNote || “No vetted data”];
}
cellEl.addEventListener(“pointermove”, function () { showTip(cellEl, dateStr, lines); });
cellEl.addEventListener(“pointerdown”, function () { showTip(cellEl, dateStr, lines); });
cellEl.addEventListener(“pointerleave”, hideTip);
})(new Date(d), m, hasData);
} else {
cellEl.setAttribute(“class”, “ghheat__cell ghheat__cell–pad”);
}
svg.appendChild(cellEl);
d.setDate(d.getDate() + 1);
}
var gridwrap = html(“div”, “ghheat__gridwrap”);
gridwrap.appendChild(svg);
body.appendChild(gridwrap);
/* —- Side column: stat + note + legend + extra —- */
var side = html(“div”, “ghheat__side”);
if (panel.stat) {
var stat = html(“div”, “ghheat__stat”);
var big = html(“span”, “ghheat__statbig”);
big.textContent = panel.stat.big;
big.style.color = strong;
stat.appendChild(big);
if (panel.stat.small) {
var small = html(“span”, “ghheat__statsmall”);
small.textContent = panel.stat.small;
stat.appendChild(small);
}
side.appendChild(stat);
}
if (panel.note) {
var note = html(“p”, “ghheat__note”);
note.innerHTML = panel.note;
side.appendChild(note);
}
var leg = html(“div”, “ghheat__legend”);
var lw1 = html(“span”, “ghheat__legword”); lw1.textContent = “Fewer”; leg.appendChild(lw1);
ramp.forEach(function (c) {
var s = html(“span”, “ghheat__sw”); s.style.background = c; leg.appendChild(s);
});
var lw2 = html(“span”, “ghheat__legword”); lw2.textContent = “More”; leg.appendChild(lw2);
if (panel.legendLabel) {
var lu = html(“span”, “ghheat__legunit”); lu.textContent = “u00b7 ” + panel.legendLabel; leg.appendChild(lu);
}
side.appendChild(leg);
if (panel.extraNote) {
var ex = html(“div”, “ghheat__extra”);
var exsw = html(“span”, “ghheat__sw ghheat__sw–empty”);
exsw.style.background = emptyColor;
ex.appendChild(exsw);
var ext = html(“span”, “ghheat__extratext”);
ext.innerHTML = panel.extraNote;
ex.appendChild(ext);
side.appendChild(ex);
}
body.appendChild(side);
wrap.appendChild(body);
root.appendChild(wrap);
});
if (config.caption) {
var cap = html(“div”, “ghchart__caption”);
cap.innerHTML = config.caption;
root.appendChild(cap);
}
};
/* ================================================================
Scatter renderer — X/Y numeric plot with categorical points.
Used for cost-vs-quality benchmark charts: a highlighted “primary”
point (glow halo), a “reference” point with a dotted crosshair, and
muted “other” points. Values never print on the marks — a point’s
name shows for identity and the exact (y, x) reveals on hover.
================================================================ */
renderers.scatter = function (root, config) {
root.textContent = “”;
var uid = config.mountId;
var VB = { w: 1000, h: 600 };
var M = { top: 40, right: 30, bottom: 74, left: 96 };
var innerW = VB.w – M.left – M.right;
var innerH = VB.h – M.top – M.bottom;
var xa = config.xAxis, ya = config.yAxis;
var fmtX = xa.format || function (v) { return String(v); };
var fmtY = ya.format || function (v) { return String(v); };
var tipSuffix = config.tipSuffix || “”;
function xAt(v) { return M.left + innerW * (v – xa.min) / (xa.max – xa.min); }
function yAt(v) { return M.top + innerH * (1 – (v – ya.min) / (ya.max – ya.min)); }
var baseY = yAt(ya.min);
var motionMQ = window.matchMedia
? window.matchMedia(“(prefers-reduced-motion: reduce)”)
: null;
function reduceMotion() { return !!(motionMQ && motionMQ.matches); }
function colorOf(pt) {
var s = config.series && config.series[pt.series || 0];
return (s && s.color) || “#8c959f”;
}
/* —- Title (compact narrative headline, no rule) ———— */
var title = html(“div”, “ghchart__headline”);
title.textContent = config.title;
root.appendChild(title);
/* —- Header + legend (from series) ————————– */
if (!config.hideLegend) {
var header = html(“div”, “ghchart__header”);
var legend = html(“div”, “ghchart__legend”);
(config.series || []).forEach(function (s) {
var item = html(“span”, “ghchart__legend-item”);
var sw = html(“span”, “ghchart__swatch ghchart__swatch–dot”);
sw.style.background = s.color;
item.appendChild(sw);
item.appendChild(document.createTextNode(s.name));
legend.appendChild(item);
});
header.appendChild(legend);
root.appendChild(header);
}
/* —- SVG plot ———————————————– */
var svg = el(“svg”, {
“class”: “ghchart__plot ghchart__plot–scatter”,
viewBox: “0 0 ” + VB.w + ” ” + VB.h,
preserveAspectRatio: “xMidYMid meet”,
role: “img”,
“aria-label”: config.title + ” scatter plot”
});
// Gridlines (both directions) + tick labels.
var eps = 1e-6;
for (var yv = ya.min; yv <= ya.max + eps; yv += ya.step) {
var gy = yAt(yv);
svg.appendChild(el("line", {
"class": "ghchart__gridline",
x1: M.left, y1: gy, x2: VB.w – M.right, y2: gy
}));
var yl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–y",
x: M.left – 16, y: gy, dy: "0.32em", "text-anchor": "end"
});
yl.textContent = fmtY(yv);
svg.appendChild(yl);
}
for (var xv = xa.min; xv <= xa.max + eps; xv += xa.step) {
var gx = xAt(xv);
svg.appendChild(el("line", {
"class": "ghchart__gridline ghchart__gridline–v",
x1: gx, y1: M.top, x2: gx, y2: baseY
}));
var isFirst = Math.abs(xv – xa.min) < eps;
var isLast = Math.abs(xv – xa.max) < eps;
var xl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–x",
x: gx, y: baseY + 34,
"text-anchor": isFirst ? "start" : (isLast ? "end" : "middle")
});
xl.textContent = fmtX(xv);
svg.appendChild(xl);
}
// Axis titles.
if (xa.title) {
var xt = el("text", {
"class": "ghchart__axistitle",
x: M.left + innerW / 2, y: VB.h – 12, "text-anchor": "middle"
});
xt.textContent = xa.title;
svg.appendChild(xt);
}
if (ya.title) {
var ytX = 24, ytY = M.top + innerH / 2;
var yt = el("text", {
"class": "ghchart__axistitle",
x: ytX, y: ytY, "text-anchor": "middle",
transform: "rotate(-90 " + ytX + " " + ytY + ")"
});
yt.textContent = ya.title;
svg.appendChild(yt);
}
// Reference crosshair through the flagged point (dual dotted lines).
var refPt = (config.points || []).filter(function (p) { return p.reference; })[0];
if (refPt) {
var rColor = colorOf(refPt);
var rx = xAt(refPt.x), ry = yAt(refPt.y);
svg.appendChild(el("line", {
"class": "ghchart__crosshair",
x1: rx, y1: M.top, x2: rx, y2: baseY, stroke: rColor
}));
svg.appendChild(el("line", {
"class": "ghchart__crosshair",
x1: M.left, y1: ry, x2: VB.w – M.right, y2: ry, stroke: rColor
}));
}
// Points: halo (highlight/reference) + dot + name label.
var dotEls = [], hitEls = [];
(config.points || []).forEach(function (pt, i) {
var cx = xAt(pt.x), cy = yAt(pt.y), c = colorOf(pt);
var big = pt.highlight || pt.reference;
if (big) {
svg.appendChild(el("circle", {
"class": "ghchart__halo", cx: cx, cy: cy, r: 17, fill: c
}));
}
var dot = el("circle", {
"class": "ghchart__pt", cx: cx, cy: cy,
r: big ? 9 : 5.5, fill: c
});
svg.appendChild(dot);
dotEls.push({ el: dot, baseR: big ? 9 : 5.5 });
// Name label — bold+colored for the story points, muted for the rest.
var pos = pt.labelPos || (pt.highlight ? "below" : pt.reference ? "above" : "right");
var dx = 0, dy = 0, anchor = "middle";
var gap = big ? 15 : 9;
if (pos === "right") { dx = gap + 4; dy = 5; anchor = "start"; }
else if (pos === "left") { dx = -(gap + 4); dy = 5; anchor = "end"; }
else if (pos === "above") { dy = -(gap + 6); anchor = "middle"; }
else { dy = gap + 16; anchor = "middle"; } // below
var lbl = el("text", {
"class": "ghchart__pt-label" + (big ? " ghchart__pt-label–strong" : ""),
x: cx + dx, y: cy + dy, "text-anchor": anchor,
fill: big ? c : null
});
lbl.textContent = pt.label;
svg.appendChild(lbl);
// Transparent hit target for hover/focus.
var hit = el("circle", {
"class": "ghchart__pthit", cx: cx, cy: cy, r: 20,
tabindex: "0", role: "img",
"aria-label": pt.label + ": " + fmtY(pt.y) + ", " + fmtX(pt.x) + tipSuffix
});
svg.appendChild(hit);
hitEls.push(hit);
});
root.appendChild(svg);
/* —- Tooltip (title + free lines) ————————— */
var tip = html("div", "ghchart__tooltip");
tip.setAttribute("role", "status");
var tipTitle = html("div", "ghchart__tt-title");
var tipBody = html("div", "ghchart__tt-body");
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var activeIndex = -1;
function show(i) {
var pt = config.points[i];
if (!pt) return;
var reappearing = activeIndex === -1;
activeIndex = i;
dotEls.forEach(function (d, di) {
d.el.setAttribute("r", di === i ? d.baseR + 2.5 : d.baseR);
});
tipTitle.textContent = pt.label;
tipBody.textContent = "";
var line = html("div", "ghchart__tt-line");
line.textContent = fmtY(pt.y) + " · " + fmtX(pt.x) + tipSuffix;
tipBody.appendChild(line);
var anchor = dotEls[i].el.getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = "opacity 0.12s ease";
tip.setAttribute("data-visible", "true");
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight 0) {
svg.appendChild(el(“line”, {
“class”: “ghchart__phase-sep”, x1: x0, y1: M.top, x2: x0, y2: baseY
}));
}
var pl = el(“text”, {
“class”: “ghchart__phase-label”, x: (x0 + x1) / 2, y: M.top – 40,
“text-anchor”: “middle”
});
pl.textContent = ph.label;
svg.appendChild(pl);
if (ph.avg) {
var pa = el(“text”, {
“class”: “ghchart__phase-avg”, x: (x0 + x1) / 2, y: M.top + 24,
“text-anchor”: “middle”, fill: cPoint
});
pa.textContent = ph.avg;
svg.appendChild(pa);
}
});
// Horizontal gridlines + y tick labels (skip a gridline at the reference y).
var refY = config.reference ? config.reference.y : null;
(ya.ticks || []).forEach(function (tv) {
var gy = yAt(tv);
if (refY === null || Math.abs(tv – refY) > 1e-9) {
svg.appendChild(el(“line”, {
“class”: “ghchart__gridline”, x1: M.left, y1: gy, x2: VB.w – M.right, y2: gy
}));
}
var yl = el(“text”, {
“class”: “ghchart__axislabel ghchart__axislabel–y”,
x: M.left – 16, y: gy, dy: “0.32em”, “text-anchor”: “end”
});
yl.textContent = fmtY(tv);
svg.appendChild(yl);
});
// X tick labels (dated).
(xa.ticks || []).forEach(function (t) {
var gx = xAt(t.at);
var first = Math.abs(t.at – xa.min) < 1e-9, last = Math.abs(t.at – xa.max) < 1e-9;
var xl = el("text", {
"class": "ghchart__axislabel ghchart__axislabel–x",
x: gx, y: baseY + 30, "text-anchor": first ? "start" : (last ? "end" : "middle")
});
xl.textContent = t.text;
svg.appendChild(xl);
});
// Axis titles.
if (ya.title) {
var ytX = 22, ytY = M.top + innerH / 2;
var yt = el("text", {
"class": "ghchart__axistitle", x: ytX, y: ytY, "text-anchor": "middle",
transform: "rotate(-90 " + ytX + " " + ytY + ")"
});
yt.textContent = ya.title;
svg.appendChild(yt);
}
// Trend: optional ±band (linear only), then the dashed line + slope label.
var tr = config.trend;
if (tr) {
function trendY(x) {
return tr.from.y + (tr.to.y – tr.from.y) * (x – tr.from.x) / (tr.to.x – tr.from.x);
}
if (tr.band && !isLog) {
var p = tr.band;
var pts = [
[xAt(tr.from.x), yAt(trendY(tr.from.x) + p)],
[xAt(tr.to.x), yAt(trendY(tr.to.x) + p)],
[xAt(tr.to.x), yAt(trendY(tr.to.x) – p)],
[xAt(tr.from.x), yAt(trendY(tr.from.x) – p)]
].map(function (a) { return a[0] + "," + a[1]; }).join(" ");
svg.appendChild(el("polygon", {
"class": "ghchart__trendband", points: pts, fill: cTrend
}));
}
svg.appendChild(el("line", {
"class": "ghchart__trendline",
x1: xAt(tr.from.x), y1: yAt(tr.from.y),
x2: xAt(tr.to.x), y2: yAt(tr.to.y), stroke: cTrend
}));
if (tr.label) {
var lx, lyy;
if (tr.labelAt) { lx = xAt(tr.labelAt.x); lyy = yAt(tr.labelAt.y); }
else {
lx = xAt(tr.to.x) + (tr.labelDX == null ? -6 : tr.labelDX);
lyy = yAt(tr.to.y) + (tr.labelDY == null ? -12 : tr.labelDY);
}
var sl = el("text", {
"class": "ghchart__slope", x: lx, y: lyy,
"text-anchor": tr.labelAnchor || "end", fill: cTrend
});
sl.textContent = tr.label;
svg.appendChild(sl);
}
}
// Reference line (horizontal dashed) + right-aligned label.
if (config.reference) {
var rc = config.reference.color || "#24292f";
var ryy = yAt(config.reference.y);
svg.appendChild(el("line", {
"class": "ghchart__refline", x1: M.left, y1: ryy, x2: VB.w – M.right, y2: ryy,
stroke: rc
}));
if (config.reference.label) {
var rl = el("text", {
"class": "ghchart__reflabel", x: VB.w – M.right, y: ryy – 8,
"text-anchor": "end", fill: rc
});
rl.textContent = config.reference.label;
svg.appendChild(rl);
}
}
// Data squares (values reveal on hover only).
var side = 13, hSide = side / 2;
var sqEls = [], hitEls = [];
(config.points || []).forEach(function (pt, i) {
var cx = xAt(pt.x), cy = yAt(pt.y);
var sq = el("rect", {
"class": "ghchart__square", x: cx – hSide, y: cy – hSide,
width: side, height: side, rx: 2, fill: cPoint
});
svg.appendChild(sq);
sqEls.push({ el: sq, cx: cx, cy: cy });
// Hover/focus hit target — omitted when hover is disabled so the
// (illustrative) point values never surface, on screen or to a11y.
if (!config.disableHover) {
var hit = el("rect", {
"class": "ghchart__squarehit", x: cx – 13, y: cy – 13,
width: 26, height: 26, tabindex: "0", role: "img",
"aria-label": (pt.d || "") + " " + fmtY(pt.y)
});
svg.appendChild(hit);
hitEls.push(hit);
}
});
// Invalid-run markers: red diamond + X, leader line, annotation text.
(config.invalids || []).forEach(function (iv) {
var cx = xAt(iv.x), cy = yAt(iv.y);
svg.appendChild(el("rect", {
"class": "ghchart__invalid", x: cx – hSide, y: cy – hSide,
width: side, height: side, rx: 1.5, fill: "none", stroke: cInvalid,
transform: "rotate(45 " + cx + " " + cy + ")"
}));
var q = 3.4;
svg.appendChild(el("line", {
"class": "ghchart__invalid-x", x1: cx – q, y1: cy – q, x2: cx + q, y2: cy + q,
stroke: cInvalid
}));
svg.appendChild(el("line", {
"class": "ghchart__invalid-x", x1: cx – q, y1: cy + q, x2: cx + q, y2: cy – q,
stroke: cInvalid
}));
if (iv.title) {
var tx = cx + (iv.labelDX == null ? 16 : iv.labelDX);
var ty = cy + (iv.labelDY == null ? -26 : iv.labelDY);
svg.appendChild(el("line", {
"class": "ghchart__leader", x1: cx, y1: cy, x2: tx, y2: ty, stroke: cInvalid
}));
var anchor = iv.labelAnchor || "start";
var at = el("text", {
"class": "ghchart__anno-title", x: tx, y: ty – 4, "text-anchor": anchor, fill: cInvalid
});
at.textContent = iv.title;
svg.appendChild(at);
if (iv.sub) {
var asu = el("text", {
"class": "ghchart__anno-sub", x: tx, y: ty + 11, "text-anchor": anchor
});
asu.textContent = iv.sub;
svg.appendChild(asu);
}
}
});
// Bracket annotation (span with end ticks + centered label above).
(config.brackets || []).forEach(function (b) {
var x0 = xAt(b.fromX), x1 = xAt(b.toX), by = yAt(b.y);
svg.appendChild(el("line", {
"class": "ghchart__bracket", x1: x0, y1: by, x2: x1, y2: by
}));
svg.appendChild(el("line", { "class": "ghchart__bracket", x1: x0, y1: by, x2: x0, y2: by + 7 }));
svg.appendChild(el("line", { "class": "ghchart__bracket", x1: x1, y1: by, x2: x1, y2: by + 7 }));
var bl = el("text", {
"class": "ghchart__bracket-label", x: (x0 + x1) / 2, y: by – 8, "text-anchor": "middle"
});
bl.textContent = b.text;
svg.appendChild(bl);
});
// Final callout: ringed point + leader + title/sub placed inside the plot.
if (config.callout) {
var co = config.callout;
var ccx = xAt(co.x), ccy = yAt(co.y);
svg.appendChild(el("circle", {
"class": "ghchart__callout-ring", cx: ccx, cy: ccy, r: 12, stroke: cPoint
}));
var ctx = ccx + (co.labelDX == null ? -16 : co.labelDX);
var cty = ccy + (co.labelDY == null ? -30 : co.labelDY);
svg.appendChild(el("line", {
"class": "ghchart__leader ghchart__leader–callout", x1: ccx, y1: ccy, x2: ctx, y2: cty,
stroke: cPoint
}));
var can = co.labelAnchor || "end";
var ct = el("text", {
"class": "ghchart__anno-title ghchart__anno-title–callout", x: ctx, y: cty – 4, "text-anchor": can, fill: cPoint
});
ct.textContent = co.title;
svg.appendChild(ct);
if (co.sub) {
var cs = el("text", {
"class": "ghchart__anno-sub ghchart__anno-sub–callout", x: ctx, y: cty + 12, "text-anchor": can
});
cs.textContent = co.sub;
svg.appendChild(cs);
}
}
root.appendChild(svg);
if (config.caption) {
var cap = html("div", "ghchart__caption");
cap.textContent = config.caption;
root.appendChild(cap);
}
/* —- Tooltip + hover interaction (skipped when disabled) —— */
if (!config.disableHover) {
var tip = html("div", "ghchart__tooltip");
tip.setAttribute("role", "status");
var tipTitle = html("div", "ghchart__tt-title");
var tipBody = html("div", "ghchart__tt-body");
tip.appendChild(tipTitle);
tip.appendChild(tipBody);
root.appendChild(tip);
var activeIndex = -1;
function show(i) {
var pt = config.points[i];
if (!pt) return;
var reappearing = activeIndex === -1;
activeIndex = i;
sqEls.forEach(function (s, si) {
var big = si === i;
var w = big ? side + 5 : side;
s.el.setAttribute("width", w);
s.el.setAttribute("height", w);
s.el.setAttribute("x", s.cx – w / 2);
s.el.setAttribute("y", s.cy – w / 2);
});
var tipInfo = config.pointTip
? config.pointTip(pt)
: { title: pt.d || "", line: fmtY(pt.y) };
tipTitle.textContent = tipInfo.title;
tipBody.textContent = "";
var line = html("div", "ghchart__tt-line");
line.textContent = tipInfo.line;
tipBody.appendChild(line);
var anchor = sqEls[i].el.getBoundingClientRect();
var rootRect = root.getBoundingClientRect();
var left = anchor.left + anchor.width / 2 – rootRect.left;
var top = anchor.top – rootRect.top – 12;
var suppress = reappearing && !reduceMotion();
if (suppress) tip.style.transition = "opacity 0.12s ease";
tip.setAttribute("data-visible", "true");
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight < 0) {
top = anchor.bottom – rootRect.top + 12;
tip.style.transform = "translate(-50%, 0)";
} else {
tip.style.transform = "translate(-50%, -100%)";
}
tip.style.left = left + "px";
tip.style.top = top + "px";
if (suppress) { void tip.offsetWidth; tip.style.transition = ""; }
}
function hide() {
activeIndex = -1;
tip.setAttribute("data-visible", "false");
sqEls.forEach(function (s) {
s.el.setAttribute("width", side);
s.el.setAttribute("height", side);
s.el.setAttribute("x", s.cx – hSide);
s.el.setAttribute("y", s.cy – hSide);
});
}
hitEls.forEach(function (hit, i) {
hit.addEventListener("pointerenter", function () { show(i); });
hit.addEventListener("pointermove", function () { show(i); });
hit.addEventListener("pointerleave", hide);
hit.addEventListener("focus", function () { show(i); });
hit.addEventListener("blur", hide);
});
}
};
/* ================================================================
Distribution dashboard renderer
—————————————————————-
A composition figure (no hover — every value is printed as text, so
the marks are inherently screen-reader accessible):
• a compact narrative headline + muted subtitle;
• an optional segmented "mix" bar — proportional colored segments,
each with its name inside and count/percent below;
• one or more panels of ranked horizontal bars, each row printing
its category (left), a bar, and its count/percent (right).
Colors come from config.series so the harness can tune them live:
series[0] = bar fill (single base hex);
series[1..] = segment fills, in the order of config.segments.items.
================================================================ */
renderers.distribution = function (root, config) {
root.textContent = "";
root.setAttribute("role", "figure");
if (config.title) root.setAttribute("aria-label", config.title);
var series = config.series || [];
var barColor = (series[0] && series[0].color) || "#8250df";
var total = config.total ||
(config.segments && config.segments.items
? config.segments.items.reduce(function (a, b) { return a + b.value; }, 0)
: 0);
function pctText(v) {
if (!total) return "";
return (Math.round((v / total) * 1000) / 10).toFixed(1) + "%";
}
/* Label format is config-driven so a chart can print counts, percents,
or both without a renderer change: config.valueFormat =
"both" (default) | "percent" | "count". Counts stay in the data either
way so the percentages are always derived from them. */
var valueFormat = config.valueFormat || "both";
function valueHTML(v) {
var p = pctText(v);
if (valueFormat === "percent") return p;
if (valueFormat === "count") return "” + v + ““;
return “” + v + “” + (p ? ” (” + p + “)” : “”);
}
/* —- Headline + subtitle ————————————- */
if (config.title) {
var head = html(“div”, “ghchart__headline”);
head.textContent = config.title;
root.appendChild(head);
}
if (config.subtitle) {
var sub = html(“p”, “ghchart__subtitle”);
sub.textContent = config.subtitle;
root.appendChild(sub);
}
/* —- Segmented “mix” bar ————————————– */
var seg = config.segments;
if (seg && seg.items && seg.items.length) {
var segSec = html(“div”, “ghchart__dist-section”);
if (seg.title) {
var segTitle = html(“div”, “ghchart__panel-title”);
segTitle.textContent = seg.title;
segSec.appendChild(segTitle);
}
var segTotal = seg.items.reduce(function (a, b) { return a + b.value; }, 0) || 1;
var bar = html(“div”, “ghchart__seg”);
var notes = html(“div”, “ghchart__seg-note-row”);
seg.items.forEach(function (it, i) {
var basis = (it.value / segTotal) * 100;
var col = (series[i + 1] && series[i + 1].color) || it.color || barColor;
var cell = html(“div”, “ghchart__seg-cell”);
cell.style.flex = “0 0 ” + basis + “%”;
cell.style.background = col;
cell.textContent = it.label;
bar.appendChild(cell);
var note = html(“div”, “ghchart__seg-note”);
note.style.flex = “0 0 ” + basis + “%”;
note.innerHTML = valueHTML(it.value);
notes.appendChild(note);
});
segSec.appendChild(bar);
segSec.appendChild(notes);
root.appendChild(segSec);
}
/* —- Ranked bar panels (grid: 2-up, stacking on narrow) —— */
var panels = config.panels || [];
if (panels.length) {
var cols = html(“div”, “ghchart__dist-cols”);
if (config.columns === 1 || panels.length === 1) {
cols.classList.add(“ghchart__dist-cols–stack”);
}
panels.forEach(function (panel) {
var sec = html(“div”, “ghchart__dist-section”);
if (panel.title) {
var pt = html(“div”, “ghchart__panel-title”);
pt.textContent = panel.title;
sec.appendChild(pt);
}
if (panel.subtitle) {
var descriptor = html(“p”, “ghchart__subtitle”);
descriptor.textContent = panel.subtitle;
sec.appendChild(descriptor);
}
if (panel.stats) {
var stats = html(“dl”, “ghchart__dist-stats”);
panel.stats.forEach(function (stat) {
var pair = html(“div”, “ghchart__dist-stat”);
var term = html(“dt”);
term.textContent = stat.label;
var value = html(“dd”);
value.textContent = stat.value;
value.style.color = strongOf((series[panel.series || 0] || series[0]).color);
pair.appendChild(term);
pair.appendChild(value);
stats.appendChild(pair);
});
sec.appendChild(stats);
cols.appendChild(sec);
return;
}
var list = html(“div”, “ghchart__dist-list”);
var pmax = (panel.items || []).reduce(function (m, it) {
return it.value > m ? it.value : m;
}, 0) || 1;
(panel.items || []).forEach(function (it) {
var row = html(“div”, “ghchart__dist-row”);
var cat = html(“div”, “ghchart__dist-cat”);
cat.textContent = it.label;
row.appendChild(cat);
var track = html(“div”, “ghchart__dist-track”);
var b = html(“div”, “ghchart__dist-bar”);
b.style.width = Math.max((it.value / pmax) * 100, 1.2) + “%”;
b.style.background = it.color || barColor;
track.appendChild(b);
row.appendChild(track);
var val = html(“div”, “ghchart__dist-val”);
val.innerHTML = valueHTML(it.value);
row.appendChild(val);
list.appendChild(row);
});
sec.appendChild(list);
cols.appendChild(sec);
});
root.appendChild(cols);
}
/* —- Caption / footnote ————————————– */
if (config.caption) {
var cap = html(“div”, “ghchart__caption”);
cap.innerHTML = config.caption;
root.appendChild(cap);
}
};
/* ================================================================
Timeline / staged-duration renderer
—————————————————————-
A horizontal time axis broken into labeled stage segments. Composes
only the pieces the data needs (like the trend renderer):
• eyebrow kicker + narrative headline + optional subtitle;
• an optional before/after stat card and a stage legend;
• one or more rows of segmented duration bars on a shared time
scale — a row is either sequential (segments end to end) or
parallel (a full-height “gate” segment then concurrent lanes);
• optional flow ribbons linking matching stages between the first
two rows (the sequential -> parallel “collapse”);
• optional elbow-leader callouts above narrow segments;
• optional span brackets (below a row, or right of a parallel group);
• an optional numeric axis (ticks + gridlines + title).
No hover — every label is printed, so it reads on paper and to screen
readers. Stage colors come from config.series so the harness tunes
them live: each entry is { id, name, color }.
================================================================ */
renderers.timeline = function (root, config) {
root.textContent = “”;
root.setAttribute(“role”, “img”);
if (config.title) root.setAttribute(“aria-label”, config.title);
var stages = config.series || [];
var stageById = {};
stages.forEach(function (s) { if (s && s.id) stageById[s.id] = s; });
function colorOf(seg) {
return seg.color || (stageById[seg.id] && stageById[seg.id].color) || “#8250df”;
}
function nameOf(seg) {
if (seg.label != null) return seg.label;
return seg.name || (stageById[seg.id] && stageById[seg.id].name) || “”;
}
/* —- DOM header: eyebrow + headline + subtitle ————— */
if (config.eyebrow) {
var eb = html(“div”, “ghchart__tl-eyebrow”);
eb.textContent = config.eyebrow;
root.appendChild(eb);
}
if (config.title) {
var hl = html(“div”, “ghchart__headline ghchart__tl-headline”);
hl.textContent = config.title;
root.appendChild(hl);
}
if (config.subtitle) {
var sbt = html(“p”, “ghchart__subtitle”);
sbt.textContent = config.subtitle;
root.appendChild(sbt);
}
/* —- Stat card (before -> after + delta) ——————– */
if (config.stat) {
var st = config.stat;
var card = html(“div”, “ghchart__tl-stat”);
var main = html(“div”, “ghchart__tl-stat-main”);
function statCol(k, v, muted) {
var col = html(“div”, “ghchart__tl-stat-col”);
var kk = html(“div”, “ghchart__tl-stat-k”); kk.textContent = k; col.appendChild(kk);
var vv = html(“div”, “ghchart__tl-stat-v” + (muted ? ” ghchart__tl-stat-v–muted” : “”));
vv.textContent = v; col.appendChild(vv);
return col;
}
main.appendChild(statCol(st.beforeLabel, st.beforeValue, true));
var arrow = html(“div”, “ghchart__tl-stat-arrow”); arrow.textContent = “u2192”;
main.appendChild(arrow);
main.appendChild(statCol(st.afterLabel, st.afterValue, false));
card.appendChild(main);
if (st.delta) {
var dl = html(“div”, “ghchart__tl-stat-delta”);
var dv = html(“div”, “ghchart__tl-stat-delta-v”); dv.textContent = st.delta; dl.appendChild(dv);
if (st.deltaLabel) {
var dk = html(“div”, “ghchart__tl-stat-delta-k”); dk.textContent = st.deltaLabel; dl.appendChild(dk);
}
card.appendChild(dl);
}
root.appendChild(card);
}
/* —- Legend ————————————————– */
if (config.legend && config.legend.length) {
var lg = html(“div”, “ghchart__tl-legend”);
config.legend.forEach(function (item) {
var s = stageById[item.id] || {};
var it = html(“span”, “ghchart__tl-legend-item”);
var sw = html(“span”, “ghchart__tl-swatch”);
sw.style.background = item.color || s.color || “#8250df”;
it.appendChild(sw);
var tx = html(“span”, “ghchart__tl-legend-text”);
tx.textContent = (item.name || s.name || “”) + (item.value ? ” u00b7 ” + item.value : “”);
it.appendChild(tx);
lg.appendChild(it);
});
root.appendChild(lg);
}
if (config.rule) root.appendChild(html(“div”, “ghchart__tl-rule”));
/* —- Plot geometry ——————————————- */
var rows = config.rows || [];
function rowTotal(r) {
if (r.parallel) {
var g = r.gate ? r.gate.value : 0, mx = 0;
r.parallel.forEach(function (p) { if (p.value > mx) mx = p.value; });
return g + mx;
}
var t = 0; (r.segments || []).forEach(function (s) { t += s.value; }); return t;
}
var maxTotal = 0;
rows.forEach(function (r) { var t = rowTotal(r); if (t > maxTotal) maxTotal = t; });
var axis = config.axis;
var scaleMax = config.scaleMax || (axis && axis.max) || maxTotal || 1;
var VB_W = 1000;
var hasRowLabels = rows.some(function (r) { return r.label; });
var M = { left: hasRowLabels ? 150 : 44, right: 46 };
var innerW = VB_W – M.left – M.right;
function xAt(v) { return M.left + innerW * v / scaleMax; }
function wAt(v) { return innerW * v / scaleMax; }
var SEQ_H = 56, LANE_H = 13, LANE_GAP = 6, SEG_R = 4;
function rowBandH(r) {
if (r.parallel) {
var n = r.parallel.length;
return Math.max(SEQ_H, n * LANE_H + (n – 1) * LANE_GAP);
}
return SEQ_H;
}
var flows = !!config.flows && rows.length >= 2;
var FLOW_GAP = config.flowGap != null ? config.flowGap : 250, ROW_GAP = 48;
// Reserve vertical space for above-callouts (tallest tier wins).
var anyAbove = rows.some(function (r) {
return (r.segments || []).some(function (s) { return s.labelPos === “above”; });
});
var maxTier = 0;
rows.forEach(function (r) {
(r.segments || []).forEach(function (s) {
if (s.labelPos === “above” && (s.tier || 0) > maxTier) maxTier = s.tier || 0;
});
});
var CALLOUT_TIER_H = 40;
var topPad = anyAbove ? (18 + (maxTier + 1) * CALLOUT_TIER_H) : 16;
// Assign each row a vertical band.
var cursor = topPad;
var rowLayout = rows.map(function (r, i) {
var top = cursor, h = rowBandH(r);
cursor = top + h;
if (i row1), matched by stage id (drawn under bars).
if (flows) {
var r0 = rows[0], l0 = rowLayout[0], r1 = rows[1], l1 = rowLayout[1];
var beforeSpan = {}, acc0 = 0;
(r0.segments || []).forEach(function (s) {
beforeSpan[s.id] = { x0: xAt(acc0), x1: xAt(acc0 + s.value) }; acc0 += s.value;
});
var afterSpan = {}, gv1 = r1.gate ? r1.gate.value : 0;
if (r1.gate) afterSpan[r1.gate.id] = { x0: xAt(0), x1: xAt(gv1), y: l1.top };
(r1.parallel || []).forEach(function (p, k) {
// Land each ribbon on its companion lane’s actual depth, not the row top.
afterSpan[p.id] = {
x0: xAt(gv1), x1: xAt(gv1 + p.value), y: l1.top + k * (LANE_H + LANE_GAP)
};
});
var by = l0.bottom;
Object.keys(beforeSpan).forEach(function (id) {
var b = beforeSpan[id], a = afterSpan[id];
if (!a) return;
var ay = a.y, ddy = (ay – by) * 0.5;
var d = “M ” + b.x0 + ” ” + by +
” C ” + b.x0 + ” ” + (by + ddy) + ” ” + a.x0 + ” ” + (ay – ddy) + ” ” + a.x0 + ” ” + ay +
” L ” + a.x1 + ” ” + ay +
” C ” + a.x1 + ” ” + (ay – ddy) + ” ” + b.x1 + ” ” + (by + ddy) + ” ” + b.x1 + ” ” + by + ” Z”;
var flowPath = el(“path”, {
“class”: “ghchart__tl-flow”, d: d, fill: colorOf({ id: id }), “data-stage”: id
});
svg.appendChild(flowPath);
hoverSlot(id).flowEl = flowPath;
});
}
// Rows: segmented bars.
rows.forEach(function (r, ri) {
var L = rowLayout[ri];
if (r.label) {
var rl = el(“text”, {
“class”: “ghchart__tl-rowlabel”, x: M.left – 20, y: L.top + L.h / 2 – 3, “text-anchor”: “end”
});
rl.textContent = r.label; svg.appendChild(rl);
if (r.sublabel) {
var rs = el(“text”, {
“class”: “ghchart__tl-rowsub”, x: M.left – 20, y: L.top + L.h / 2 + 18, “text-anchor”: “end”
});
rs.textContent = r.sublabel; svg.appendChild(rs);
}
}
if (r.parallel) {
var gv = r.gate ? r.gate.value : 0;
if (r.gate) {
var gateEl = el(“rect”, {
“class”: “ghchart__tl-seg”, x: xAt(0), y: L.top,
width: Math.max(0, wAt(gv)), height: L.h, rx: SEG_R, fill: colorOf(r.gate),
“data-stage”: r.gate.id
});
svg.appendChild(gateEl);
var gslot = hoverSlot(r.gate.id);
gslot.afterEl = gateEl; gslot.after = r.gate.value;
gslot.name = gslot.name || nameOf(r.gate);
}
r.parallel.forEach(function (p, k) {
var ly = L.top + k * (LANE_H + LANE_GAP);
var laneEl = el(“rect”, {
“class”: “ghchart__tl-seg”, x: xAt(gv), y: ly,
width: Math.max(1, wAt(p.value)), height: LANE_H, rx: 3, fill: colorOf(p),
“data-stage”: p.id
});
svg.appendChild(laneEl);
var pslot = hoverSlot(p.id);
pslot.afterEl = laneEl; pslot.after = p.value;
pslot.name = pslot.name || nameOf(p);
});
} else {
var acc = 0;
(r.segments || []).forEach(function (s) {
var x = xAt(acc), w = Math.max(1, wAt(s.value));
var segEl = el(“rect”, {
“class”: “ghchart__tl-seg”, x: x, y: L.top, width: w, height: L.h, rx: SEG_R,
fill: colorOf(s), “data-stage”: s.id
});
svg.appendChild(segEl);
var slot = hoverSlot(s.id);
slot.name = slot.name || nameOf(s);
if (ri === 0) { slot.beforeEl = segEl; slot.before = s.value; }
else { slot.afterEl = segEl; slot.after = s.value; }
if (s.labelPos === “inside”) {
var ilAttrs = {
“class”: “ghchart__tl-seg-label”, x: x + w / 2, y: L.top + L.h / 2,
dy: “0.34em”, “text-anchor”: “middle”
};
// Inline style (not a fill attribute) so it overrides the class’s
// default white fill — lets a bright segment take dark label text.
if (s.labelColor) ilAttrs.style = “fill:” + s.labelColor;
var il = el(“text”, ilAttrs);
il.textContent = nameOf(s); svg.appendChild(il);
}
acc += s.value;
});
}
});
// Elbow-leader callouts above narrow segments.
rows.forEach(function (r, ri) {
if (r.parallel) return;
var L = rowLayout[ri], acc = 0;
(r.segments || []).forEach(function (s) {
var x = xAt(acc), w = wAt(s.value), cx = x + w / 2;
if (s.labelPos === “above”) {
var tier = s.tier || 0;
var tierY = topPad – 14 – tier * CALLOUT_TIER_H;
// labelX pins the label to an absolute data-x (so callouts can be
// left-aligned into a stack); labelDX is a relative nudge otherwise.
var lx = s.labelX != null ? xAt(s.labelX) : cx + (s.labelDX || 0);
var anchor = s.labelAnchor || “start”;
svg.appendChild(el(“path”, {
“class”: “ghchart__tl-callout-line”,
d: “M ” + cx + ” ” + L.top + ” L ” + cx + ” ” + tierY + ” L ” + lx + ” ” + tierY, fill: “none”
}));
var pad = anchor === “start” ? 6 : (anchor === “end” ? -6 : 0);
var cl = el(“text”, {
“class”: “ghchart__tl-callout-label”, x: lx + pad, y: tierY,
dy: “0.34em”, “text-anchor”: anchor, fill: s.labelColor || colorOf(s)
});
cl.textContent = nameOf(s); svg.appendChild(cl);
}
acc += s.value;
});
});
// Below brackets (span with up-ticks + label beneath).
var belowY = (rowLayout.length ? rowLayout[rowLayout.length – 1].bottom : topPad) + 14;
belowBrackets.forEach(function (b, bi) {
var x0 = xAt(b.from), x1 = xAt(b.to), yy = belowY + bi * 46;
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: x0, y1: yy, x2: x1, y2: yy }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: x0, y1: yy, x2: x0, y2: yy – 7 }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: x1, y1: yy, x2: x1, y2: yy – 7 }));
var bl = el(“text”, {
“class”: “ghchart__bracket-label ghchart__tl-bracket-label”, x: (x0 + x1) / 2, y: yy + 22, “text-anchor”: “middle”
});
bl.textContent = b.text; svg.appendChild(bl);
});
// Right brackets (vertical span beside a parallel group + label).
rightBrackets.forEach(function (b) {
var L = rowLayout[b.row || 0];
var bx = xAt(b.at != null ? b.at : rowTotal(rows[b.row || 0])) + 14;
var y0 = L.top, y1 = L.bottom;
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: bx, y1: y0, x2: bx, y2: y1 }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: bx, y1: y0, x2: bx – 7, y2: y0 }));
svg.appendChild(el(“line”, { “class”: “ghchart__bracket”, x1: bx, y1: y1, x2: bx – 7, y2: y1 }));
// Label as a DOM node (not SVG text) so it keeps a fixed, readable size
// instead of scaling down with the plot on small windows.
var rlabel = html(“div”, “ghchart__tl-bracket-label-dom”);
rlabel.textContent = b.text;
rlabel.style.left = ((bx + 12) / VB_W * 100) + “%”;
rlabel.style.top = (((y0 + y1) / 2) / VB_H * 100) + “%”;
plotwrap.appendChild(rlabel);
});
// Numeric axis: tick labels + title.
if (axis && axis.ticks) {
axis.ticks.forEach(function (tv, i) {
var gx = xAt(tv);
var first = i === 0, last = i === axis.ticks.length – 1;
var xl = el(“text”, {
“class”: “ghchart__axislabel ghchart__axislabel–x ghchart__tl-tick”,
x: gx, y: axisY + 26, “text-anchor”: first ? “start” : (last ? “end” : “middle”)
});
xl.textContent = axis.format ? axis.format(tv) : String(tv);
svg.appendChild(xl);
});
}
/* —- Hover interaction (before/after chart) ——————
Emphasise a stage’s whole connection (its before block, after block
and ribbon) and show a tooltip with the before/after values. Only
when there are flows to connect and hover isn’t disabled. */
if (flows && config.hover !== false) {
var unit = config.unit ? ” ” + config.unit : “”;
function fmtHover(v) { return v == null ? “u2013” : v + unit; }
var tip = html(“div”, “ghchart__tooltip”);
tip.setAttribute(“role”, “status”);
var tipTitle = html(“div”, “ghchart__tt-title”);
var tipBody = html(“div”, “ghchart__tt-body ghchart__tt-body–plain”);
tip.appendChild(tipTitle); tip.appendChild(tipBody);
root.appendChild(tip);
var focused = null;
function focusStage(id) {
focused = id;
Object.keys(stageHover).forEach(function (k) {
var s = stageHover[k], on = (k === id);
if (s.beforeEl) s.beforeEl.style.opacity = on ? “1” : “0.16”;
if (s.afterEl) s.afterEl.style.opacity = on ? “1” : “0.16”;
if (s.flowEl) s.flowEl.style.opacity = on ? “0.5” : “0.04”;
});
var st = stageHover[id];
tipTitle.textContent = st.name || id;
tipBody.textContent = “”;
var lb = html(“div”, “ghchart__tt-line”); lb.textContent = “Before ” + fmtHover(st.before);
var la = html(“div”, “ghchart__tt-line”); la.textContent = “After ” + fmtHover(st.after);
tipBody.appendChild(lb); tipBody.appendChild(la);
}
function clearStage() {
focused = null;
Object.keys(stageHover).forEach(function (k) {
var s = stageHover[k];
if (s.beforeEl) s.beforeEl.style.opacity = “”;
if (s.afterEl) s.afterEl.style.opacity = “”;
if (s.flowEl) s.flowEl.style.opacity = “”;
});
tip.setAttribute(“data-visible”, “false”);
}
function placeTip(clientX, clientY) {
var rr = root.getBoundingClientRect();
var left = clientX – rr.left, top = clientY – rr.top – 14;
tip.setAttribute(“data-visible”, “true”);
var halfW = tip.offsetWidth / 2;
left = clamp(left, halfW + 2, root.clientWidth – halfW – 2);
if (top – tip.offsetHeight existing.version) {
/* This bundle is newer. Every export embeds the COMPLETE engine, so this
bundle’s `renderers` is a self-contained, up-to-date set. Adopt it as
the shared registry (upgrading older same-named renderers in place),
while defensively preserving any renderer types only the older engine
happened to have. Then take over render(). */
for (var ek in existing.renderers) {
if (Object.prototype.hasOwnProperty.call(existing.renderers, ek) && !renderers[ek]) {
renderers[ek] = existing.renderers[ek];
}
}
existing.renderers = renderers;
existing.render = render;
existing.destroy = destroy;
existing.version = ENGINE_VERSION;
existing._renderCleanups = renderCleanups;
} else {
/* Same or older version already present: only contribute renderer types
the shared registry is missing (don’t clobber an equal/newer engine). */
for (var rk in renderers) {
if (Object.prototype.hasOwnProperty.call(renderers, rk) && !existing.renderers[rk]) {
existing.renderers[rk] = renderers[rk];
}
}
}
} else {
/* No engine yet, or a pre-versioning (old-style) engine loaded first.
Take over, preserving any renderers the old-style engine registered. */
if (existing && existing.renderers) {
for (var ok in existing.renderers) {
if (Object.prototype.hasOwnProperty.call(existing.renderers, ok) && !renderers[ok]) {
renderers[ok] = existing.renderers[ok];
}
}
}
global.GHChartEngine = { renderers: renderers, render: render, destroy: destroy, version: ENGINE_VERSION, _renderCleanups: renderCleanups };
}
})(typeof window !== “undefined” ? window : this);
;(function () {
“use strict”;
var CONFIG = {
mountId: “gh-chart-reviewbench-severity”,
type: “comparison”,
title: “The largest gains came from higher-severity findings”,
subtitle: “Change in displayed comments by severity versus the production control”,
series: [
{
name: “ReviewBench offline”,
color: “#8da5db”
},
{
name: “Online A/B”,
color: “#499d6f”
}
],
xAxis: {
min: -75,
max: 285,
ticks: [-50, 0, 50, 100, 150, 200, 250]
},
rows: [
{
label: “Critical comments”,
values: [227.4, 262],
decimals: [1, 1]
},
{
label: “Moderate comments”,
values: [121, 18],
decimals: [1, 2]
},
{
label: “Nit comments”,
values: [-15.69, -45],
decimals: [2, 1]
}
]
};
function boot() {
var root = document.getElementById(CONFIG.mountId);
if (!root || root.getAttribute(“data-gh-rendered”)) return;
root.setAttribute(“data-gh-rendered”, “1”);
window.GHChartEngine.render(CONFIG);
}
if (document.readyState === “loading”) {
document.addEventListener(“DOMContentLoaded”, boot);
} else {
boot();
}
})();
This gives us a fast and repeatable signal before running production experiments. Online experiments remain the ultimate measure of user impact, but ReviewBench gives us greater confidence in which changes are worth taking there.
How to submit your own run
- Sign in with GitHub on the ReviewBench website.
- Register your agent. Provide a container image, your configuration, and your own model key. We provide the judge.
- Try it on the test set. Run against a 25-PR test set with per-PR detail and repeat as you tune your configuration.
- Do a final run. When you’re ready, run the full set of 219 pull requests (three rounds), scored by the same judge as every other entry.
- Publish to the leaderboard. Your scores remain private until a maintainer reviews and approves the submission. Scores are published to the leaderboard only if they outperform the agent’s current leaderboard score, or if this is the agent’s first leaderboard entry.
We invite you to explore ReviewBench, evaluate your own system, challenge our assumptions, and help us improve the benchmark. We’re excited to collaborate with researchers and practitioners to make code review evaluation more open, reliable, and useful—and ultimately help move AI code review forward.
Acknowledgments
ReviewBench was a team effort across GitHub and Microsoft. We’re grateful to the researchers and engineers who built it: those who designed the methodology, curated the pull requests, built the golden set and the evaluation pipeline, and made the benchmark something anyone can run.
The post ReviewBench: An open benchmark for AI code review appeared first on The GitHub Blog.