createConicGradient(startAngle, x, y) sweeps colors clockwise around a center, like CSS conic-gradient(); it is Baseline widely available since October 2025. Two stops at one offset make a hard edge, so a pie chart needs no arcs. createPattern(source, repetition) tiles an image, video or canvas (repeat, repeat-x, repeat-y, no-repeat), and pattern.setTransform() turns or scales the tile.
<canvas id="c" style="width:600px; height:104px"></canvas><script src="hidpi.js"></script>
<script>
const ctx = hidpi(c); let at = 0;
const colors = ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'];
const pie = ctx.createConicGradient(-Math.PI / 2, 52, 52); // start at twelve o'clock
[312, 428, 256, 198, 344, 176].forEach((pages, i) => { pie.addColorStop(at, colors[i]);
pie.addColorStop(at += pages / 1714, colors[i]); }); // 1714 pages in all
ctx.fillStyle = pie; ctx.beginPath(); ctx.arc(52, 52, 48, 0, 2 * Math.PI); ctx.fill();
const tile = new OffscreenCanvas(8, 8), t = tile.getContext('2d');
t.fillStyle = '#e09a10'; t.fillRect(0, 0, 8, 8); t.clearRect(0, 0, 8, 3);
const hatch = ctx.createPattern(tile, 'repeat');
hatch.setTransform(new DOMMatrix().rotate(45)); // diagonal stripes
ctx.fillStyle = hatch; ctx.fillRect(140, 4, 72, 96);
</script>
The tile is an 8-pixel OffscreenCanvas (createImageBitmap); the pattern transform tilts its stripes.
<!doctype html>
<style>
body { margin: 0; padding: 8px; font: 13px system-ui, sans-serif; background: #fafaf7; }
canvas { width: 100%; max-width: 600px; height: 170px; display: block; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="c"></canvas>
<label>Stripe angle <input type="range" id="angle" min="0" max="90" value="45"></label>
<script>
function hidpi(c, r = devicePixelRatio) {
c.width = c.clientWidth * r; c.height = c.clientHeight * r;
const ctx = c.getContext('2d'); ctx.scale(r, r); return ctx;
}
const ctx = hidpi(document.getElementById('c'));
const books = [['Harbor', 312], ['Deep Web', 428], ['Saffron', 256], ['Summits', 198], ['Clockmaker', 344], ['Gardens', 176]];
const colors = ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'];
const total = books.reduce((sum, b) => sum + b[1], 0);
// An 8×8 tile drawn on an OffscreenCanvas: saffron with a clear stripe
const tile = new OffscreenCanvas(8, 8), t = tile.getContext('2d');
t.fillStyle = '#e09a10'; t.fillRect(0, 0, 8, 8); t.clearRect(0, 0, 8, 3);
const hatch = ctx.createPattern(tile, 'repeat');
function draw(angle) {
ctx.clearRect(0, 0, 600, 170);
// Pie: two stops at the same offset make a hard edge, so no arcs per slice
const pie = ctx.createConicGradient(-Math.PI / 2, 85, 85); // start at twelve o'clock
let at = 0;
books.forEach(([, pages], i) => {
pie.addColorStop(at, colors[i]);
pie.addColorStop(at += pages / total, colors[i]);
});
ctx.fillStyle = pie;
ctx.beginPath(); ctx.arc(85, 85, 75, 0, 2 * Math.PI); ctx.fill();
ctx.fillStyle = '#263238'; ctx.font = '11px sans-serif';
books.forEach(([name], i) => {
ctx.fillStyle = colors[i]; ctx.fillRect(180, 20 + i * 22, 12, 12);
ctx.fillStyle = '#263238'; ctx.fillText(`${name} (${books[i][1]} pages)`, 198, 30 + i * 22);
});
// Bars filled with the pattern; setTransform turns the stripes
hatch.setTransform(new DOMMatrix().rotate(angle));
ctx.fillStyle = hatch;
books.forEach(([, pages], i) => ctx.fillRect(360 + i * 38, 160 - pages / 3, 28, pages / 3));
ctx.strokeStyle = '#263238';
books.forEach(([, pages], i) => ctx.strokeRect(360.5 + i * 38, 160.5 - pages / 3, 28, pages / 3));
}
const slider = document.getElementById('angle');
slider.addEventListener('input', () => draw(+slider.value));
draw(45);
</script>