diff --git a/src/interactions/brush.js b/src/interactions/brush.js
index 2b2e54eda1..dd66233cc5 100644
--- a/src/interactions/brush.js
+++ b/src/interactions/brush.js
@@ -110,6 +110,7 @@ export class Brush extends Mark {
if (selection === null) {
if (type === "end") {
+ context.interaction.brushing = false;
if (sync) {
self._syncing = true;
selectAll(nodes.filter((_, i) => i !== currentNode)).call(brush.move, null);
@@ -155,6 +156,7 @@ export class Brush extends Mark {
context.dispatchValue(value);
}
} else {
+ if (event.sourceEvent) context.interaction.brushing = true;
const [[px1, py1], [px2, py2]] = dim === "xy" ? selection
: dim === "x" ? [[selection[0]], [selection[1]]]
: [[, selection[0]], [, selection[1]]]; // prettier-ignore
diff --git a/src/interactions/pointer.js b/src/interactions/pointer.js
index f0c0f765b0..a198bebc5e 100644
--- a/src/interactions/pointer.js
+++ b/src/interactions/pointer.js
@@ -140,7 +140,9 @@ function pointerK(kx, ky, {x, y, px, py, maxRadius = 40, channels, render, ...op
// squashed, selecting primarily on the dominant dimension. Across facets,
// use unsquashed distance to determine the winner.
function pointermove(event) {
- if (state.sticky || (event.pointerType === "mouse" && event.buttons === 1)) return; // dragging
+ if (context.interaction?.brushing) { if (state.sticky) (state.sticky = false), state.renders.forEach((r) => r(null)); return; } // prettier-ignore
+ if (state.sticky) return;
+ if (event.pointerType === "mouse" && event.buttons === 1) return void update(null); // hide tip during drag
let [xp, yp] = pointof(event);
(xp -= tx), (yp -= ty); // correct for facets and band scales
const kpx = xp < dimensions.marginLeft || xp > dimensions.width - dimensions.marginRight ? 1 : kx;
@@ -166,6 +168,7 @@ function pointerK(kx, ky, {x, y, px, py, maxRadius = 40, channels, render, ...op
if (i == null) return; // not pointing
if (state.sticky && state.roots.some((r) => r?.contains(event.target))) return; // stay sticky
if (state.sticky) (state.sticky = false), state.renders.forEach((r) => r(null)); // clear all pointers
+ else if (context.interaction?.brushing) return void update(null); // cancel tip on brush start
else (state.sticky = true), render(i);
event.stopImmediatePropagation(); // suppress other pointers
}
diff --git a/test/output/brushDotTip.svg b/test/output/brushDotTip.svg
new file mode 100644
index 0000000000..abfb11e928
--- /dev/null
+++ b/test/output/brushDotTip.svg
@@ -0,0 +1,416 @@
+
\ No newline at end of file
diff --git a/test/plots/brush.ts b/test/plots/brush.ts
index 4e25c26672..f1c573b532 100644
--- a/test/plots/brush.ts
+++ b/test/plots/brush.ts
@@ -526,6 +526,22 @@ export async function brushSimple() {
return html`${plot}${textarea}`;
}
+export async function brushDotTip() {
+ const penguins = await d3.csv("data/penguins.csv", d3.autoType);
+ const brush = Plot.brush();
+ const xy = {x: "culmen_length_mm" as const, y: "culmen_depth_mm" as const};
+ const plot = Plot.plot({
+ marks: [
+ brush,
+ Plot.dot(penguins, brush.inactive({...xy, fill: "species", r: 2})),
+ Plot.dot(penguins, brush.context({...xy, fill: "#ccc", r: 2})),
+ Plot.dot(penguins, brush.focus({...xy, fill: "species", r: 3, tip: true}))
+ ]
+ });
+ brush.move({x1: 36, x2: 48, y1: 15, y2: 20});
+ return plot;
+}
+
export async function brushXLine() {
const aapl = await d3.csv("data/aapl.csv", d3.autoType);
const brush = Plot.brushX();