blob: 757f59dbb54e077167232107a24322f5456e8fc5 [file] [edit]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="generator" content="Observable Framework v1.13.4">
<title>Fontc crater | Google Fonts QA Dashboard</title>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&amp;display=swap" crossorigin>
<link rel="preload" as="style" href="./_import/style.a52b67cd.css">
<link rel="preload" as="style" href="./_observablehq/stdlib/inputs.f7bf0e12.css">
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&amp;display=swap" crossorigin>
<link rel="stylesheet" type="text/css" href="./_import/style.a52b67cd.css">
<link rel="stylesheet" type="text/css" href="./_observablehq/stdlib/inputs.f7bf0e12.css">
<link rel="modulepreload" href="./_observablehq/client.329de744.js">
<link rel="modulepreload" href="./_observablehq/runtime.fa9f71c0.js">
<link rel="modulepreload" href="./_observablehq/stdlib.c4209144.js">
<link rel="modulepreload" href="./_import/components/Fontc.6f056196.js">
<link rel="modulepreload" href="./_npm/react@19.3.0/jsx-runtime.27cd96ff.js">
<link rel="modulepreload" href="./_npm/d3@7.9.0/080cf928.js">
<link rel="modulepreload" href="./_npm/htl@1.0.0/11521f02.js">
<link rel="modulepreload" href="./_observablehq/stdlib/inputs.0cc910eb.js">
<link rel="modulepreload" href="./_npm/@observablehq/plot@0.6.17/93ce672e.js">
<link rel="modulepreload" href="./_npm/react-dom@19.3.0/client.5bebe338.js">
<link rel="modulepreload" href="./_npm/isoformat@0.2.1/604cb63b.js">
<link rel="modulepreload" href="./_npm/d3-array@3.2.4/b8e751b6.js">
<link rel="modulepreload" href="./_npm/d3-axis@3.0.0/c662ea05.js">
<link rel="modulepreload" href="./_npm/d3-brush@3.0.0/d162d8c6.js">
<link rel="modulepreload" href="./_npm/d3-chord@3.0.1/5cedd0be.js">
<link rel="modulepreload" href="./_npm/d3-color@3.1.0/65d51007.js">
<link rel="modulepreload" href="./_npm/d3-contour@4.0.2/c576a91a.js">
<link rel="modulepreload" href="./_npm/d3-delaunay@6.0.4/8b43826d.js">
<link rel="modulepreload" href="./_npm/d3-dispatch@3.0.1/3fb06f14.js">
<link rel="modulepreload" href="./_npm/d3-drag@3.0.0/20b76948.js">
<link rel="modulepreload" href="./_npm/d3-dsv@3.0.1/097745a2.js">
<link rel="modulepreload" href="./_npm/d3-ease@3.0.1/8683b012.js">
<link rel="modulepreload" href="./_npm/d3-fetch@3.0.1/5c319046.js">
<link rel="modulepreload" href="./_npm/d3-force@3.0.0/33924f3e.js">
<link rel="modulepreload" href="./_npm/d3-format@3.1.2/8d67a1ae.js">
<link rel="modulepreload" href="./_npm/d3-geo@3.1.1/8037bd0b.js">
<link rel="modulepreload" href="./_npm/d3-hierarchy@3.1.2/a9ba0229.js">
<link rel="modulepreload" href="./_npm/d3-interpolate@3.0.1/e0a47f97.js">
<link rel="modulepreload" href="./_npm/d3-path@3.1.0/f9e9e0ab.js">
<link rel="modulepreload" href="./_npm/d3-polygon@3.0.1/2da8eb3b.js">
<link rel="modulepreload" href="./_npm/d3-quadtree@3.0.1/09002e01.js">
<link rel="modulepreload" href="./_npm/d3-random@3.0.1/5108797c.js">
<link rel="modulepreload" href="./_npm/d3-scale@4.0.2/c9f2571b.js">
<link rel="modulepreload" href="./_npm/d3-scale-chromatic@3.1.0/f3c78e0d.js">
<link rel="modulepreload" href="./_npm/d3-selection@3.0.0/28717d05.js">
<link rel="modulepreload" href="./_npm/d3-shape@3.2.0/7c6e2a50.js">
<link rel="modulepreload" href="./_npm/d3-time@3.1.0/3d1d57c6.js">
<link rel="modulepreload" href="./_npm/d3-time-format@4.1.0/643581a8.js">
<link rel="modulepreload" href="./_npm/d3-timer@3.0.1/e99232db.js">
<link rel="modulepreload" href="./_npm/d3-transition@3.0.1/bd07fd9a.js">
<link rel="modulepreload" href="./_npm/d3-zoom@3.0.0/d09f7cc9.js">
<link rel="modulepreload" href="./_npm/interval-tree-1d@1.0.4/f53f17b2.js">
<link rel="modulepreload" href="./_npm/scheduler@0.28.0/3d5e653e.js">
<link rel="modulepreload" href="./_npm/react@19.3.0/d4006ec2.js">
<link rel="modulepreload" href="./_npm/react-dom@19.3.0/79ce8e4b.js">
<link rel="modulepreload" href="./_npm/internmap@2.0.3/af036d7e.js">
<link rel="modulepreload" href="./_npm/delaunator@5.1.0/7b06abe7.js">
<link rel="modulepreload" href="./_npm/binary-search-bounds@2.0.5/5f640032.js">
<link rel="modulepreload" href="./_npm/robust-predicates@3.0.3/b42f30a7.js">
<script type="module">
import {define} from "./_observablehq/client.329de744.js";
import {registerFile} from "./_observablehq/stdlib.c4209144.js";
registerFile("./data/fontc.json", {"name":"./data/fontc.json","mimeType":"application/json","path":"./_file/data/fontc.075d1180.json","lastModified":1791077445185,"size":2902935});
define({id: "18f886a9", inputs: ["FileAttachment"], outputs: ["fontc","RenderFailures","RenderSuccesses"], body: async (FileAttachment) => {
const {RenderFailures, RenderSuccesses} = await import("./_import/components/Fontc.6f056196.js");
const fontc = await FileAttachment("./data/fontc.json").json();
return {fontc,RenderFailures,RenderSuccesses};
}});
define({id: "ee6caa08", inputs: ["fontc","html","display","Inputs"], outputs: ["semiFlattened","prev_identical","updown","flattened"], body: (fontc,html,display,Inputs) => {
const semiFlattened = fontc.summary.map((cur) => {
// Move the `stats` object to the top level
return {
date: new Date(cur.began),
rev: cur.fontc_rev,
targets: cur.stats.total_targets,
fontc_failed: cur.stats.fontc_failed,
fontmake_failed: cur.stats.fontmake_failed,
both_failed: cur.stats.both_failed,
other: cur.stats.other_failure,
identical: cur.stats.identical,
similarity: cur.stats.diff_perc_including_failures,
};
});
let prev_identical = undefined;
let updown = (v) => {
if (v > 0) return html`<span class="up">↑${v}</span>`;
if (v < 0) return html`<span class="down">↓${-v}</span>`;
return "";
};
semiFlattened.forEach((cur) => {
let identical_value = cur.identical;
if (prev_identical) {
let identical_change = cur.identical - prev_identical;
if (identical_change) {
cur.identical = html`${cur.identical} ${updown(identical_change)}`;
} else {
cur.identical = html`${cur.identical}`;
}
} else {
cur.identical = html`${cur.identical}`;
}
prev_identical = identical_value;
});
const flattened = fontc.summary.flatMap((cur) =>
Object.entries(cur.stats)
.filter(([status, count]) =>
["both_failed", "fontmake_failed", "identical", "produced_diff"].includes(
status
)
)
.map(([status, count]) => {
return {
began: cur.began,
status,
count,
};
})
);
display(
Inputs.table(semiFlattened, {
format: {
identical: (v) => v,
date: (v) => v.toLocaleDateString(),
similarity: (v) => `${v.toFixed(2)}%`,
},
reverse: true,
width: {
date: 90,
},
})
);
return {semiFlattened,prev_identical,updown,flattened};
}});
define({id: "92308120", inputs: ["d3","fontc","display","Plot","flattened","width"], outputs: ["y2"], body: (d3,fontc,display,Plot,flattened,width) => {
const y2 = d3.scaleLinear(
[0, 100],
[0, d3.max(fontc.summary, (d) => d.stats.total_targets)]
);
display(
Plot.plot({
color: { legend: true },
y: { axis: "left", label: "targets" },
marks: [
Plot.ruleY([0]),
Plot.axisY(y2.ticks(), {
anchor: "right",
label: "%",
color: "lightgreen",
y: y2,
tickFormat: y2.tickFormat(),
}),
Plot.areaY(flattened, {
y: "count",
x: (d) => new Date(d.began),
fill: "status",
}),
Plot.lineY(
fontc.summary,
Plot.mapY((D) => D.map(y2), {
x: (d) => new Date(d.began),
y: (d) => d.stats.diff_perc_including_failures,
stroke: "green",
strokeWidth: 2,
})
),
Plot.lineY(
fontc.summary,
Plot.mapY((D) => D.map(y2), {
x: (d) => new Date(d.began),
y: (d) => d.stats.diff_perc_excluding_failures,
stroke: "green",
strokeOpacity: 0.2,
})
),
],
width,
})
);
return {y2};
}});
define({id: "b8146c7e", mode: "jsx", inputs: ["display","RenderSuccesses","fontc"], outputs: ["jsx"], body: async (display,RenderSuccesses,fontc) => {
const {jsx} = await import("./_npm/react@19.3.0/jsx-runtime.27cd96ff.js");
display(/* @__PURE__ */ jsx(RenderSuccesses, { successes: fontc.lastRun.success }));
return {jsx};
}});
define({id: "d96478a2", mode: "jsx", inputs: ["display","RenderFailures","fontc"], outputs: ["jsx"], body: async (display,RenderFailures,fontc) => {
const {jsx} = await import("./_npm/react@19.3.0/jsx-runtime.27cd96ff.js");
display(/* @__PURE__ */ jsx(RenderFailures, { failures: fontc.lastRun.failure }));
return {jsx};
}});
</script>
</head>
<body>
<input id="observablehq-sidebar-toggle" type="checkbox" title="Toggle sidebar">
<label id="observablehq-sidebar-backdrop" for="observablehq-sidebar-toggle"></label>
<nav id="observablehq-sidebar">
<ol>
<label id="observablehq-sidebar-close" for="observablehq-sidebar-toggle"></label>
<li class="observablehq-link"><a href="./">Google Fonts QA Dashboard</a></li>
</ol>
<ol>
<li class="observablehq-link"><a href="./families.html">Family status</a></li>
<li class="observablehq-link observablehq-link-active"><a href="./fontc_crater.html">Fontc crater</a></li>
<li class="observablehq-link"><a href="./source_check.html">Fontc crater sources check</a></li>
<li class="observablehq-link"><a href="./trends.html">Fontspector QA trends</a></li>
<li class="observablehq-link"><a href="./updates.html">Server moves</a></li>
</ol>
<details open>
<summary>Standalone pages</summary>
<ol>
<li class="observablehq-link"><a href="./meta.html">meta</a></li>
<li class="observablehq-link"><a href="./familyexplorer.html">familyexplorer</a></li>
<li class="observablehq-link"><a href="./fontdiffai.html">fontdiffai</a></li>
<li class="observablehq-link"><a href="./opsz_classifier.html">opsz classifier</a></li>
<li class="observablehq-link"><a href="./opsz_classifier_v01.html">opsz classifier v01</a></li>
<li class="observablehq-link"><a href="./opsz_classifier_v02.html">opsz classifier v02</a></li>
<li class="observablehq-link"><a href="./opsz_labeller.html">opsz labeller</a></li>
<li class="observablehq-link"><a href="./opsz_prediction.html">opsz prediction</a></li>
<li class="observablehq-link"><a href="./quality-tag-review.html">quality tag review</a></li>
<li class="observablehq-link"><a href="./quality_gaps_analysis.html">quality gaps analysis</a></li>
<li class="observablehq-link"><a href="./quality_outperforming.html">quality outperforming</a></li>
<li class="observablehq-link"><a href="./quality_overview.html">quality overview</a></li>
<li class="observablehq-link"><a href="./quality_perception.html">quality perception</a></li>
<li class="observablehq-link"><a href="./quality_uplevel_analysis.html">quality uplevel analysis</a></li>
<li class="observablehq-link"><a href="./quality_usage.html">quality usage</a></li>
<li class="observablehq-link"><a href="./tags.html">tags</a></li>
<li class="observablehq-link"><a href="./vf-tag-demo-linear.html">vf tag demo linear</a></li>
<li class="observablehq-link"><a href="./vf-tag-demo2.html">vf tag demo2</a></li>
</ol>
</details>
</nav>
<script>{const e=document.querySelector("#observablehq-sidebar"),o=document.querySelector("#observablehq-sidebar-toggle"),r=sessionStorage.getItem("observablehq-sidebar");r?o.checked=r==="true":o.indeterminate=!0;for(const t of document.querySelectorAll("#observablehq-sidebar summary")){const s=t.parentElement;switch(sessionStorage.getItem(`observablehq-sidebar:${t.textContent}`)){case"true":s.open=!0;break;case"false":s.classList.contains("observablehq-section-active")||(s.open=!1);break}}addEventListener("beforeunload",()=>sessionStorage.setItem("observablehq-sidebar-scrolly",`${e.scrollTop}`));const a=sessionStorage.getItem("observablehq-sidebar-scrolly");a!=null&&(e.style.cssText="overflow: hidden;",e.scrollTop=+a,e.style.cssText="");}</script>
<div id="observablehq-center">
<aside id="observablehq-toc" data-selector="h1:not(:first-of-type)[id], h2:first-child[id], :not(h1) + h2[id]">
<nav>
<div>Contents</div>
<ol>
<li class="observablehq-secondary-link"><a href="#successes">Successes</a></li>
<li class="observablehq-secondary-link"><a href="#failures">Failures</a></li>
</ol>
</nav>
</aside>
<main id="observablehq-main" class="observablehq">
<div class="observablehq observablehq--block"><!--:18f886a9:--></div>
<div class="observablehq observablehq--block"><!--:ee6caa08:--></div>
<div class="observablehq observablehq--block"><!--:92308120:--></div>
<h2 id="successes" tabindex="-1"><a class="observablehq-header-anchor" href="#successes">Successes</a></h2>
<div class="observablehq observablehq--block"><!--:b8146c7e:--></div>
<h2 id="failures" tabindex="-1"><a class="observablehq-header-anchor" href="#failures">Failures</a></h2>
<div class="observablehq observablehq--block"><!--:d96478a2:--></div>
</main>
<footer id="observablehq-footer">
<nav><a rel="prev" href="./families.html"><span>Family status</span></a><a rel="next" href="./source_check.html"><span>Fontc crater sources check</span></a></nav>
</footer>
</div>
</body>
</html>