uPlot
uPlot copied to clipboard
don't use center-aligned strokes for bars
not quite sure if this is a net positive or not, but drawing crisp bars with canvas-native strokes is a real pain in the ass with a lot of accidental edge cases. for a long time i've been trying to figure out how to do performant masking in canvas (the opposite of clipping) for uPlot's purposes. today i finally figured it out.
- the first one is just what canvas does natively.-
- the second if we add some math, but it still bleeds stroke and can end up accidentally anti-aliased (non crisp). this is what uPlot currently does.
- the last is the holy grail, where the stroke is drawn using an additional fill with a mask. the math is simple and always exact, and never bleeds.
https://jsfiddle.net/eaLfo8zn/
<canvas id="can2" width="300" height="150"></canvas>
<canvas id="can3" width="300" height="150"></canvas>
<canvas id="can1" width="300" height="150"></canvas>
canvas {
image-rendering: pixelated;
}
let strokeWidth = 9;
var c = document.getElementById("can1");
var ctx = c.getContext("2d");
let s = new Path2D();
let f = new Path2D();
s.rect(0, 0, 150, 100);
f.rect(0 + strokeWidth, 0 + strokeWidth, 150 - strokeWidth * 2, 100 - strokeWidth * 2);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.moveTo(75 + 0.5, 0);
ctx.lineTo(75 + 0.5, 100);
ctx.stroke();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.fill(f);
ctx.fillStyle = "rgba(0,0,255,0.5)";
s.addPath(f);
ctx.fill(s, 'evenodd');
var c2 = document.getElementById("can2");
var ctx2 = c2.getContext("2d");
ctx2.strokeStyle = 'black';
ctx2.lineWidth = 1;
ctx2.moveTo(75 + 0.5, 0);
ctx2.lineTo(75 + 0.5, 100);
ctx2.stroke();
let s2 = new Path2D();
s2.rect(0, 0, 150, 100);
ctx2.lineWidth = strokeWidth;
ctx2.fillStyle = "rgba(255,0,0,0.5)";
ctx2.fill(s2);
ctx2.strokeStyle = "rgba(0,0,255,0.5)";
ctx2.stroke(s2);
var c3 = document.getElementById("can3");
var ctx3 = c3.getContext("2d");
ctx3.strokeStyle = 'black';
ctx3.lineWidth = 1;
ctx3.moveTo(75 + 0.5, 0);
ctx3.lineTo(75 + 0.5, 100);
ctx3.stroke();
let s3 = new Path2D();
s3.rect(0 + strokeWidth / 2, 0 + strokeWidth / 2, 150 - strokeWidth, 100 - strokeWidth);
ctx3.lineWidth = strokeWidth;
ctx3.fillStyle = "rgba(255,0,0,0.5)";
ctx3.fill(s3);
ctx3.strokeStyle = "rgba(0,0,255,0.5)";
ctx3.stroke(s3);
made an attempt in https://github.com/leeoniya/uPlot/compare/stroke-via-fill-evenodd, mostly complete, but not for multi-path/disp bars.
works as advertised, but not sure it's pure win. the main downside is the reliance of stroking on filling. e.g if you need a stroke-only bar, you still need to create and apply the fill path, which adds all kinds of nonsense to the already tricky logic.