Skip to content

Commit b1e03e7

Browse files
committed
svg tooltip
1 parent b548977 commit b1e03e7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+364
-341
lines changed

src/marks/tooltip.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export class Tooltip extends Mark {
3838
let indexes = this.indexesBySvg.get(svg);
3939
if (indexes) return void indexes.push(index);
4040
this.indexesBySvg.set(svg, (indexes = [index]));
41+
const r = 8; // padding
42+
const dx = 0; // offsetLeft
43+
const dy = 12; // offsetTop
4144
const dot = select(svg)
4245
.on("pointermove", (event) => {
4346
let i, xi, yi, fxi, fyi;
@@ -63,26 +66,46 @@ export class Tooltip extends Mark {
6366
dot.attr("display", "none");
6467
} else {
6568
dot.attr("display", "inline");
66-
dot.attr("transform", `translate(${xi},${yi})`);
69+
dot.attr("transform", `translate(${Math.round(xi)},${Math.round(yi)})`);
6770
const text = [];
6871
for (const key in channels) {
6972
const channel = channels[key];
7073
const label = scales[channel.scale]?.label ?? key;
71-
text.push(`${label} = ${formatDefault(channel.value[i])}`);
74+
text.push([label, formatDefault(channel.value[i])]);
7275
}
73-
if (fxv != null) text.push(`${fx.label ?? "fx"} = ${formatFx(fxi)}`);
74-
if (fyv != null) text.push(`${fy.label ?? "fy"} = ${formatFy(fyi)}`);
75-
title.text(text.join("\n"));
76+
if (fxv != null) text.push([fx.label ?? "fx", formatFx(fxi)]);
77+
if (fyv != null) text.push([fy.label ?? "fy", formatFy(fyi)]);
78+
content
79+
.selectChildren()
80+
.data(text)
81+
.join("tspan")
82+
.attr("x", 0)
83+
.attr("y", (d, i) => `${i + 0.9}em`)
84+
.selectChildren()
85+
.data((d) => d)
86+
.join("tspan")
87+
.attr("font-weight", (d, i) => (i ? "bold" : null))
88+
.text((d, i) => (i ? ` ${d}` : String(d)));
89+
const {width, height} = content.node().getBBox();
90+
const w = width + r * 2;
91+
const h = height + r * 2;
92+
path.attr("d", `M${dx},${dy}v${-dy}l${dy},${dy}h${w - dy}v${h}h${-w}z`);
7693
}
7794
})
7895
.on("pointerdown pointerleave", () => dot.attr("display", "none"))
7996
.append("g")
80-
.attr("display", "none")
81-
.attr("pointer-events", "all")
82-
.attr("fill", "none")
83-
.call((g) => g.append("circle").attr("r", maxRadius).attr("fill", "none"))
84-
.call((g) => g.append("circle").attr("r", 4.5).attr("stroke", "red").attr("stroke-width", 1.5));
85-
const title = dot.append("title");
97+
.attr("aria-label", "tooltip")
98+
.attr("display", "none");
99+
const path = dot
100+
.append("path")
101+
.attr("fill", "white")
102+
.attr("stroke", "black")
103+
.on("pointerdown pointermove", (event) => event.stopPropagation());
104+
const content = dot
105+
.append("text")
106+
.attr("transform", `translate(${dx + r},${dy + r})`)
107+
.attr("text-anchor", "start")
108+
.on("pointerdown pointermove", (event) => event.stopPropagation());
86109
return null;
87110
}
88111
}

src/plot.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,20 @@ export function plot(options = {}) {
234234
.call(applyInlineStyles, style)
235235
.node();
236236

237+
// Render non-faceted marks.
238+
for (const mark of marks) {
239+
if (facets !== undefined && mark.facet !== "super") continue;
240+
const {channels, values, facets: indexes} = stateByMark.get(mark);
241+
let index = null;
242+
if (indexes) {
243+
index = indexes[0];
244+
index = mark.filter(index, channels, values);
245+
if (index.length === 0) continue;
246+
}
247+
const node = mark.render(index, scales, values, superdimensions, context);
248+
if (node != null) svg.appendChild(node);
249+
}
250+
237251
// Render facets.
238252
if (facets !== undefined) {
239253
const facetDomains = {x: fx?.domain(), y: fy?.domain()};
@@ -274,20 +288,6 @@ export function plot(options = {}) {
274288
});
275289
}
276290

277-
// Render non-faceted marks.
278-
for (const mark of marks) {
279-
if (facets !== undefined && mark.facet !== "super") continue;
280-
const {channels, values, facets: indexes} = stateByMark.get(mark);
281-
let index = null;
282-
if (indexes) {
283-
index = indexes[0];
284-
index = mark.filter(index, channels, values);
285-
if (index.length === 0) continue;
286-
}
287-
const node = mark.render(index, scales, values, superdimensions, context);
288-
if (node != null) svg.appendChild(node);
289-
}
290-
291291
// Wrap the plot in a figure with a caption, if desired.
292292
let figure = svg;
293293
const legends = createLegends(scaleDescriptors, context, options);

test/output/anscombeQuartet.svg

Lines changed: 9 additions & 9 deletions
Loading

test/output/athletesBoxingHeight.svg

Lines changed: 6 additions & 6 deletions
Loading

test/output/athletesSampleFacet.svg

Lines changed: 6 additions & 6 deletions
Loading

test/output/athletesSortFacet.svg

Lines changed: 4 additions & 4 deletions
Loading

test/output/athletesSportWeight.svg

Lines changed: 6 additions & 6 deletions
Loading

test/output/autoDotFacet.svg

Lines changed: 9 additions & 9 deletions
Loading

test/output/autoDotFacet2.svg

Lines changed: 12 additions & 12 deletions
Loading

test/output/autoLineFacet.svg

Lines changed: 6 additions & 6 deletions
Loading

test/output/ballotStatusRace.svg

Lines changed: 3 additions & 3 deletions
Loading

test/output/beckerBarley.svg

Lines changed: 9 additions & 9 deletions
Loading

0 commit comments

Comments
 (0)