Pointer and Dodge transform conflicts

I’m having trouble connecting pointer transform to the text mark that should be used with dodged points (that was a mouthful :slight_smile: ). I have a Plot.dot that shows a distribution and I’m using Plot.dodgeX to disperse the points. What I need is to show the text not in the tip, but in the static position in the empty space in the bottom-right. So far I have not succeeded:

Plot.text(
      rumors,
      Plot.pointer(
        Plot.dodgeX("left", {
          y: (d, i) => d.Depth,
          r: (d) => Math.pow(d.Radius, 2),
          frameAnchor: "top-left",
          textAnchor: "start",
          lineWidth: 25,
          fill: "#f15ea3",
          stroke: "white",
          text: (d) => `${d.Depth} m — ${d["Rumor"]}`
        })
      )
    )

full code here: Rumor ruler for measuring cities / Namor Votilav | Observable

I looked into a solution by Fil in here: Pointer transforms with px, py on stacked bar charts but couldn’t adapt it to dodge. I’m guessing I need to somehow pass the py and px from after the dodged points have been rendered, because they are radius-dependent, but i just can’t figure it out. Please point me in the right direction :slight_smile:

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"
  }
})

oh and by the way, please move any further questions to our github discussion boards, thank you!

See 📣 Support for Plot, D3 and Framework has moved to GitHub - #3

1 Like

Wow, thank you! That works magically. And you even added a conditional to show text at different heights! Thanks a million times for your help :heart:

Is there somewhere I could read up about Plot.initializer? It feels like a thing that might be useful from time to time.

1 Like

unfortunately that’s all we have for now!

1 Like