Custom SVG functions for Plot with projection

I understand that a pattern for creating custom SVG marks in Plot is to create a function that destructures the x() and y() scaling functions to use ‘data space’ rather than screen space. For example:

x: { domain: [0, 10] },
y: { domain: [0, 10] },
marks: [
  (_, { x, y }) => htl.svg`<ellipse cx="${x(5)}" cy="${y(6)}" rx=5 />`
]

However, as soon as a projection is defined, this no longer works as x and y scales are not generated.

Is there a way of capturing the data-to-screen scaling functions for projections, and ideally one that works whether or not projections are defined?

1 Like

We should probably make this easier, but here is a version that works if you don’t use channels:

Plot.plot({
  projection: "mercator",
  marks: [
    Plot.graticule(),
    Plot.sphere(),
    (index, scales, values, dimensions, { projection }) => {
      let cx, cy;
      const stream = projection.stream({
        point(x, y) {
          cx = x;
          cy = y;
        }
      });
      stream.point(5, 6);
      return htl.svg`<ellipse cx="${cx}" cy="${cy}" rx=150 ry=50 />`;
    }
  ]
})

However the simpler method is to use channels, for example by substituting your own render function in a dot mark:

Plot.plot({
  projection: "mercator",
  marks: [
    Plot.graticule(),
    Plot.sphere(),
    Plot.dot([[5, 6]], {
      render: (index, scales, {x: X, y: Y}) => {
        return htl.svg`<g>${Array.from(
          index,
          (i) => htl.svg`<ellipse cx="${X[i]}" cy="${Y[i]}" rx=150 ry=50 />`
        )}`;
      }
    })
  ]
})
2 Likes

That’s great. I think the channels route is the one that works for me as I my custom rendering maps semantically onto one of the existing channels (text in my actual use case).

Out of interest, is there anywhere in the Plot documentation that mentions the ability to override render? This looks relatively straightforward, but I struggled to find any pointers in the docs/examples I looked through.

The documentation is lacking because the API is not finalized yet (see Finalize the design of the render API · Issue #1263 · observablehq/plot · GitHub).

We’re tracking this issue at Add a point projection function to the scales? · Issue #1808 · observablehq/plot · GitHub