diff --git a/src/marks/tip.js b/src/marks/tip.js index 047424593a..23efd64a93 100644 --- a/src/marks/tip.js +++ b/src/marks/tip.js @@ -125,15 +125,15 @@ export class Tip extends Mark { if (!defined(value) && channel.scale == null) continue; const color = channel.scale === "color" ? channels[key][i] : undefined; if (key === "x2" && "x1" in sources) { - yield [formatLabel(scales, channel) ?? "x", formatPair(sources.x1, channel, i)]; + yield [formatLabel(scales, channel, "x"), formatPair(sources.x1, channel, i)]; } else if (key === "y2" && "y1" in sources) { - yield [formatLabel(scales, channel) ?? "y", formatPair(sources.y1, channel, i)]; + yield [formatLabel(scales, channel, "y"), formatPair(sources.y1, channel, i)]; } else { - yield [formatLabel(scales, channel) ?? key, formatDefault(value), color]; + yield [formatLabel(scales, channel, key), formatDefault(value), color]; } } - if (index.fi != null && fx) yield [fx.label ?? "fx", formatFx(index.fx)]; - if (index.fi != null && fy) yield [fy.label ?? "fy", formatFy(index.fy)]; + if (index.fi != null && fx) yield [String(fx.label ?? "fx"), formatFx(index.fx)]; + if (index.fi != null && fy) yield [String(fy.label ?? "fy"), formatFy(index.fy)]; } // We don’t call applyChannelStyles because we only use the channels to @@ -159,7 +159,10 @@ export class Tip extends Mark { this.setAttribute("fill-opacity", 1); this.setAttribute("stroke", "none"); // iteratively render each channel value + const names = new Set(); for (const [name, value, color] of format(sources, i)) { + if (name && names.has(name)) continue; + else names.add(name); renderLine(that, name, value, color); } }) @@ -316,6 +319,6 @@ function formatPair(c1, c2, i) { : `${formatDefault(c1.value[i])}–${formatDefault(c2.value[i])}`; } -function formatLabel(scales, c) { - return scales[c.scale]?.label ?? c?.label; +function formatLabel(scales, c, defaultLabel) { + return String(scales[c.scale]?.label ?? c?.label ?? defaultLabel); } diff --git a/test/output/tipAreaStack.svg b/test/output/tipAreaStack.svg new file mode 100644 index 0000000000..768d2891e2 --- /dev/null +++ b/test/output/tipAreaStack.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + 0 + 2,000 + 4,000 + 6,000 + 8,000 + 10,000 + 12,000 + 14,000 + + + ↑ unemployed + + + + + + + + + + + 2000 + 2002 + 2004 + 2006 + 2008 + 2010 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/tip.ts b/test/plots/tip.ts index 96e4be7418..10ba8af50f 100644 --- a/test/plots/tip.ts +++ b/test/plots/tip.ts @@ -2,6 +2,11 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; import {feature, mesh} from "topojson-client"; +export async function tipAreaStack() { + const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); + return Plot.areaY(industries, {x: "date", y: "unemployed", fill: "industry", tip: true}).plot({marginLeft: 50}); +} + export async function tipBar() { const olympians = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({