diff --git a/src/marks/tip.js b/src/marks/tip.js index a23bedb994..68c8c56de3 100644 --- a/src/marks/tip.js +++ b/src/marks/tip.js @@ -231,9 +231,19 @@ export class Tip extends Mark { : fitTop && fitBottom ? fitLeft ? "left" - : "right" + : fitRight + ? "right" + : "bottom" : (fitLeft || fitRight) && (fitTop || fitBottom) ? `${fitBottom ? "bottom" : "top"}-${fitLeft ? "left" : "right"}` + : fitLeft + ? "left" + : fitRight + ? "right" + : fitTop + ? "top" + : fitBottom + ? "bottom" : mark.preferredAnchor; } const path = this.firstChild; // note: assumes exactly two children! diff --git a/test/output/tipAnchorOverflow.svg b/test/output/tipAnchorOverflow.svg new file mode 100644 index 0000000000..bd714cc386 --- /dev/null +++ b/test/output/tipAnchorOverflow.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + ​Lorem ipsum lorem ipsum lorem ipsum​Lorem ipsum lorem ipsum lorem ipsum​Lorem ipsum lorem ipsum lorem ipsum + + + \ No newline at end of file diff --git a/test/output/tipAnchorOverflowY.svg b/test/output/tipAnchorOverflowY.svg new file mode 100644 index 0000000000..bb3d4c1e57 --- /dev/null +++ b/test/output/tipAnchorOverflowY.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + ​Lorem​ipsum​lorem​ipsum​lorem​ipsum​Lorem​ipsum​lorem​ipsum​lorem​ipsum​Lorem​ipsum​lorem​ipsum​lorem​ipsum + + + \ No newline at end of file diff --git a/test/output/tipAnchorPositions.svg b/test/output/tipAnchorPositions.svg new file mode 100644 index 0000000000..940638ae8a --- /dev/null +++ b/test/output/tipAnchorPositions.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + ​A tip that is wide enough to test anchor​fitting behavior across positions + + + + ​A tip that is wide enough to test anchor​fitting behavior across positions + + + + ​A tip that is wide enough to test anchor​fitting behavior across positions + + + + ​A tip that is wide enough to test anchor​fitting behavior across positions + + + + ​A tip that is wide enough to test anchor​fitting behavior across positions + + + + ​A tip that is wide enough to test anchor​fitting behavior across positions + + + + ​A tip that is wide enough to test anchor​fitting behavior across positions + + + + ​A tip that is wide enough to test anchor​fitting behavior across positions + + + + ​A tip that is wide enough to test anchor​fitting behavior across positions + + + \ No newline at end of file diff --git a/test/plots/index.ts b/test/plots/index.ts index 3257fc4698..49b43e85bc 100644 --- a/test/plots/index.ts +++ b/test/plots/index.ts @@ -318,6 +318,7 @@ import "./text-overflow.js"; import "./this-is-just-to-say.js"; import "./tick-format.js"; import "./time-axis.js"; +import "./tip-anchor.js"; import "./tip-format.js"; import "./tip.js"; import "./title.js"; diff --git a/test/plots/tip-anchor.ts b/test/plots/tip-anchor.ts new file mode 100644 index 0000000000..8265cc8b3b --- /dev/null +++ b/test/plots/tip-anchor.ts @@ -0,0 +1,75 @@ +import * as Plot from "@observablehq/plot"; +import {test} from "test/plot"; + +// Test tip anchor selection near the right edge of the chart. +test(async function tipAnchorOverflow() { + const plot = Plot.rectX([1, 1, 1, 1, 1], { + x: Plot.identity, + fill: Plot.indexOf, + title: () => + "Lorem ipsum lorem ipsum lorem ipsum Lorem ipsum lorem ipsum lorem ipsum Lorem ipsum lorem ipsum lorem ipsum", + tip: true + }).plot({height: 80, marginTop: 20, axis: null}); + plot.dispatchEvent( + new PointerEvent("pointermove", { + pointerType: "mouse", + clientX: 580, + clientY: 30 + }) + ); + return Object.assign(plot, {ready: new Promise((resolve) => setTimeout(resolve, 100))}); +}); + +// Test tip anchor selection near the bottom edge of the chart. +test(async function tipAnchorOverflowY() { + const plot = Plot.rectY([1, 1, 1, 1, 1], { + y: Plot.identity, + fill: Plot.indexOf, + title: () => + "Lorem ipsum lorem ipsum lorem ipsum Lorem ipsum lorem ipsum lorem ipsum Lorem ipsum lorem ipsum lorem ipsum", + tip: {lineWidth: 5} + }).plot({width: 80, marginLeft: 20, axis: null}); + plot.dispatchEvent( + new PointerEvent("pointermove", { + pointerType: "mouse", + clientX: 30, + clientY: 20 + }) + ); + return Object.assign(plot, {ready: new Promise((resolve) => setTimeout(resolve, 100))}); +}); + +// Test tip anchor selection at all 9 positions across the chart. +test(async function tipAnchorPositions() { + const data = []; + for (const px of [40, 320, 600]) { + for (const py of [20, 100, 180]) { + data.push({px, py}); + } + } + const plot = Plot.plot({ + width: 640, + height: 200, + axis: null, + inset: 20, + marks: [ + Plot.dot(data, {x: "px", y: "py", r: 5, fill: "currentColor"}), + Plot.tip(data, { + x: "px", + y: "py", + title: () => "A tip that is wide enough to test anchor fitting behavior across positions" + }) + ] + }); + // Dispatch a pointer event to trigger all tips + for (const {px, py} of data) { + plot.dispatchEvent( + new PointerEvent("pointermove", { + pointerType: "mouse", + clientX: px, + clientY: py + }) + ); + } + return Object.assign(plot, {ready: new Promise((resolve) => setTimeout(resolve, 100))}); +});