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

To piggyback off of this solution, is it possible to redefine other channels with Plot.initializer? I’m trying to create an effect of an enlarged image on hover over the dodge-transformed image mark, repurposing the code that you’ve written before. My guess is that width and r are not stored in data, but in channels. When I try to read them though I get a reading error TypeError: Cannot read properties of undefined (reading '0') . Is it even possible?

Plot.image(
      patents,
      Plot.initializer(
        Plot.pointer(
          Plot.dodgeY("middle", {
            x: (d) => parseDate(d.application_date),
            y: 0,
            r: 10,
            src: (d) =>
              Array.isArray(d.images)
                ? d.images[0]
                : typeof d.images === "string"
                ? d.images
                : null,
            width: 20,
            stroke: "black"
          })
        ),
        (data, facets, channels) => ({
          data,
          facets,
          channels: {
            ...channels,
            px: channels.x,
            py: channels.y,
            x: { value: Array(data.length).fill(100) },
            y: { value: Array(data.length).fill(100) },
            r: { value: Array(data.length).fill(100) },
            width: { value: Array(data.length).fill(200) }
          }
        })
      )
    )

this is from this notebook

Constant values such as r: 10 are not stored in channels. This might be the reason for it (I can’t access the notebook you linked). Note that if what you need is a simple hover effect, I’d recommend to do it in css (with a transform and a transition).

Sorry, fixed the link: https://observablehq.com/d/543f54b5a3211d50#cell-25
I ended up rescaling the enlarged image with css, thanks!