Conic and Patterns

Conic Gradients and Pattern Fills

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.

Page counts as a conic pie, and a hatched fill for sold-out Salt and SaffronHTML
<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>
Browser output of Listing 2.9
Browser output of 9

The tile is an 8-pixel OffscreenCanvas (createImageBitmap); the pattern transform tilts its stripes.

A pie chart from one conic gradient and hatched bars from a rotated patternHTMLLive
<!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>