lineWidth is centered on the path. lineCap is butt, round or square (the last two reach half a width past the end). lineJoin is miter, round or bevel; a miter longer than miterLimit (10) half-widths becomes a bevel. setLineDash([dash, gap]) dashes, and animating lineDashOffset makes "marching ants".
<canvas id="c" style="width:600px; height:104px"></canvas><script src="hidpi.js"></script>
<script>
const ctx = hidpi(c), colors = ['#1f5f8b', '#3f7d3a', '#b5452f'];
ctx.lineWidth = 16;
['butt', 'round', 'square'].forEach((cap, i) => {
ctx.strokeStyle = colors[i]; ctx.lineCap = cap; ctx.beginPath();
ctx.moveTo(20 + i * 40, 25); ctx.lineTo(20 + i * 40, 80); ctx.stroke();
ctx.lineJoin = ['miter', 'round', 'bevel'][i]; ctx.lineCap = 'butt'; ctx.beginPath();
const x = 150 + i * 110; ctx.moveTo(x, 85); ctx.lineTo(x + 40, 20);
ctx.lineTo(x + 80, 85); ctx.stroke();
});
ctx.lineWidth = 1; ctx.strokeStyle = '#263238'; ctx.strokeRect(0, 25, 125, 55); // ends
ctx.setLineDash([12, 6]); ctx.lineWidth = 4; ctx.strokeStyle = '#e09a10';
ctx.strokeRect(480, 20, 100, 66);
</script>
Only the blue butt line ends on the guides. Use round joins for thick chart lines, or sharp data turns sprout miter spikes.
<!doctype html>
<style>
body { margin: 0; padding: 8px; background: #fafaf7; }
canvas { width: 100%; max-width: 600px; height: 150px; display: block; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="c"></canvas>
<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 colors = ['#1f5f8b', '#3f7d3a', '#b5452f'];
function draw(time) {
ctx.clearRect(0, 0, 600, 150);
ctx.setLineDash([]);
ctx.font = '11px sans-serif'; ctx.textAlign = 'center';
['butt', 'round', 'square'].forEach((cap, i) => {
// Caps: round and square reach half a line width past the end point
ctx.lineWidth = 16; ctx.strokeStyle = colors[i]; ctx.lineCap = cap;
ctx.beginPath(); ctx.moveTo(25 + i * 40, 35); ctx.lineTo(25 + i * 40, 100); ctx.stroke();
ctx.fillStyle = '#263238'; ctx.fillText(cap, 25 + i * 40, 130);
// Joins: miter (sharp), round, bevel (cut off)
ctx.lineCap = 'butt'; ctx.lineJoin = ['miter', 'round', 'bevel'][i];
const x = 160 + i * 105;
ctx.beginPath(); ctx.moveTo(x, 105); ctx.lineTo(x + 40, 30); ctx.lineTo(x + 80, 105);
ctx.stroke();
ctx.fillText(ctx.lineJoin, x + 40, 130);
});
// Guides show where the paths really end
ctx.lineWidth = 1; ctx.strokeStyle = '#263238'; ctx.strokeRect(0.5, 35, 130, 65);
// Marching ants: a dashed selection box whose dash offset moves over time
ctx.setLineDash([12, 6]); ctx.lineDashOffset = -time / 40;
ctx.lineWidth = 4; ctx.strokeStyle = '#e09a10'; ctx.lineJoin = 'miter';
ctx.strokeRect(480, 30, 100, 76);
ctx.fillText('setLineDash([12, 6])', 530, 130);
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
</script>