I am trying to design a line chart which will show a tool tip when the user hovers over a line. Below is a minimal example showing my attempt at determining whether a point lies on a line (the line can be considered a rectangle with height 5 and width 80 in this case).
I don't understand why isPointInPath
can only find the points at exactly the start and end points of the line. How can I determine if a point lies anywhere on the line?
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.lineWidth = 5;
ctx.moveTo(20, 20);
ctx.lineTo(100, 20);
ctx.stroke();
console.log(ctx.isPointInPath(20, 20)); // beginning: true
console.log(ctx.isPointInPath(100, 20)); // end: true
console.log(ctx.isPointInPath(60, 20)); // middle: false
console.log(ctx.isPointInPath(100, 21)); // end offset: false
isPointInPath
does use the fill region to make its check. You want isPointInStroke()
which also takes the lineWidth
into consideration:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const path = new Path2D();
let x = -1;
let y = -1;
for (let i = 0; i<20; i++) {
path.lineTo(
Math.random() * canvas.width,
Math.random() * canvas.height
);
}
ctx.lineWidth = 5;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = ctx.isPointInStroke(path, x, y)
? "red"
: "green";
ctx.stroke(path);
}
draw();
addEventListener("mousemove", (evt) => {
const rect = canvas.getBoundingClientRect();
x = evt.clientX - rect.left;
y = evt.clientY - rect.top;
draw();
});
<canvas></canvas>
As to why isPointInPath()
finds the points that are explicitly set in the Path declaration and not the ones that would logically lie on it... That's quite unclear I'm afraid, the specs don't have a clear fill algorithm, and I'll open a specs issue to fix that since there is actually an interop-issue with Safari acting as you'd expect, and Firefox + Chrome ignoring the fill area entirely when it doesn't produce a pixel, but still keeping these points.