Skip to content

Commit 4081968

Browse files
authored
feat: Keep hovered histogram text in bounds (#38)
1 parent 5267d98 commit 4081968

File tree

1 file changed

+23
-11
lines changed

1 file changed

+23
-11
lines changed

lib/utils/CrossfilterHistogramPlot.ts

+23-11
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export function CrossfilterHistogramPlot(
101101
g.attr("class", "gray");
102102
g.selectAll(".tick text")
103103
.attr("text-anchor", (_, i) => ["start", "end", "start"][i])
104-
.attr("dx", (_, i) => i === 0 ? "-0.25em" : "0.25em");
104+
.attr("dx", (_, i) => ["-0.25em", "0.25em", "-0.25em"][i]);
105105
});
106106

107107
const hoveredTickGroup = axes.node()?.querySelectorAll(".tick")[2];
@@ -118,27 +118,30 @@ export function CrossfilterHistogramPlot(
118118
const fmt = type === "number"
119119
? d3.format(".3s")
120120
: tickFormatterForBins(type, bins);
121-
// `hovered` signal gets updated in mousemove event
121+
122+
let [xmin, xmax] = x.domain();
122123
effect(() => {
123124
hoveredTick
124-
.attr("transform", `translate(${x(hovered.value || 0)},0)`)
125+
.attr("transform", `translate(${x(hovered.value ?? xmin)},0)`)
125126
.attr("visibility", hovered.value ? "visible" : "hidden");
126127

127128
hoveredTick
128129
.selectAll("text")
129-
.text(`${fmt(hovered.value || 0)}`)
130+
.text(`${fmt(hovered.value ?? xmin)}`)
130131
.attr("visibility", hovered.value ? "visible" : "hidden");
131132

132-
const hoveredTickText = hoveredTick.select("text")
133-
.node() as SVGGraphicsElement;
133+
const hoveredTickText = hoveredTick
134+
.select("text")
135+
.node() as SVGTextElement;
134136
const bbox = hoveredTickText.getBBox();
137+
const cond = (x(hovered.value ?? xmin) + bbox.width) > x(xmax);
138+
139+
hoveredTickText.setAttribute("text-anchor", cond ? "end" : "start");
140+
hoveredTickText.setAttribute("dx", cond ? "-0.25em" : "0.25em");
135141

136142
hoverLabelBackground
137143
.attr("visibility", hovered.value ? "visible" : "hidden")
138-
.attr(
139-
"transform",
140-
`translate(-2.5,0)`,
141-
)
144+
.attr("transform", `translate(${(cond ? -bbox.width : 0) - 2.5}, 2.5)`)
142145
.attr("width", bbox.width + 5)
143146
.attr("height", bbox.height + 5);
144147
});
@@ -232,7 +235,7 @@ export function CrossfilterHistogramPlot(
232235

233236
node.addEventListener("mousemove", (event) => {
234237
const relativeX = event.clientX - node.getBoundingClientRect().left;
235-
hovered.value = x.invert(relativeX);
238+
hovered.value = clamp(x.invert(relativeX), xmin, xmax);
236239
});
237240
node.addEventListener("mouseleave", () => {
238241
hovered.value = undefined;
@@ -259,3 +262,12 @@ export function CrossfilterHistogramPlot(
259262
},
260263
});
261264
}
265+
266+
function clamp(
267+
value: number | Date,
268+
min: number | Date,
269+
max: number | Date,
270+
): number {
271+
// @ts-expect-error - value is either number or Date
272+
return Math.max(min, Math.min(max, value));
273+
}

0 commit comments

Comments
 (0)