Yeah, It’s a difficult one. Here’s a solution using an initializer that copies the coordinates (x,y) computed by the dodge transform to the (px, py) channels that drive the pointer’s location, and replaces x and y with custom values (which now solely indicate where we want to display the text).
Plot.plot({
marginLeft: 60,
height: 800,
width: 400,
x: { axis: null },
y: { tickFormat: (d, i, values) => (i == values.length - 1 ? `${d} m` : d) },
r: { range: isMobile ? [1, 16] : [1, 20] },
color: {
range: ["#53c7cb", "#f85827"],
interpolate: d3.interpolateRgb.gamma(2)
},
marks: [
Plot.ruleX([0, 400], { opacity: 0 }),
Plot.dot(
rumors,
Plot.dodgeX("left", {
y: (d) => d.Depth,
r: (d) => Math.pow(d.Radius, 2),
fill: "#f15ea3",
fill: "color(display-p3 0.945098 0.368627 0.639216 / 1.000000)"
})
),
Plot.dot(
rumors,
Plot.pointer(
Plot.dodgeX("left", {
y: (d) => d.Depth,
x: 0,
r: (d) => Math.pow(d.Radius, 2),
fill: "white",
opacity: 0.7
})
)
),
Plot.ruleY([996], { stroke: "white" }),
Plot.text(["Republic square, 996m"], {
y: 996,
fill: "white",
stroke: "#f15ea3",
stroke: "color(display-p3 0.945098 0.368627 0.639216 / 1.000000)",
dy: -8,
strokeWidth: 3,
frameAnchor: "left",
fontSize: isMobile ? 12 : 16,
fontStyle: "italic"
}),
Plot.dot([...new Set(rumors.map((d) => d.Radius).sort((a, b) => a - b))], {
x: isMobile ? 300 : 380,
y: (d, i) => (isMobile ? 1250 - i * 36 : 1250 - i * 30),
r: (d) => Math.pow(d, 2),
stroke: "#f15ea3",
stroke: "color(display-p3 0.945098 0.368627 0.639216 / 1.000000)",
strokeWidth: 1
}),
Plot.text(new Set(rumors.map((d) => d.Radius).sort((a, b) => a - b)), {
x: isMobile ? 300 : 390,
y: (d, i) => (isMobile ? 1250 - i * 36 : 1250 - i * 30),
text: (d) =>
({ 1: "House", 2: "District", 5: "City", 10: "Country" }[d] || d),
dx: 20,
textAnchor: "start",
fill: "#777"
}),
Plot.text(
rumors,
Plot.initializer(
Plot.pointer(
Plot.dodgeX("left", {
y: (d, i) => d.Depth,
r: (d) => Math.pow(d.Radius, 2),
frameAnchor: "top-right",
lineWidth: 25,
fill: "#f15ea3",
fill: "color(display-p3 0.945098 0.368627 0.639216 / 1.000000)",
stroke: "white",
text: (d) => `${d.Depth} m — ${d["Rumor"]}`
})
),
(data, facets, channels) => ({
data,
facets,
channels: {
...channels,
px: channels.x,
x: {value: Array(data.length).fill(500)},
py: channels.y,
y: {value: channels.y.value.map(d => d < 100 ? 20 : d > 700 ? 750 : 310)}
}
})
)
)
],
style: {
overflow: "visible"
}
})