Adding custom paths in Plot (replicating Calendar in Plot)

I am using this calendar version for one of my scripts and I took the small challenge of re-writing it in Observable Plot

Plot.plot({
  height: 150,
  width,
  x: {
    axis: null,
    padding: 0,
  },
  y: {
    padding: 0,
    tickFormat: d => Plot.formatWeekday("en", "narrow")(-((d + 6) % 7)),
    tickSize: 0,
    ticks: 7
  },
  fy: {
    // reverse: true
  },
  marks: [
    Plot.rect(log, {
      x1: d => d3.timeMonday.count(d3.utcYear(d[0]), d[0]),
      x2: d => d3.timeMonday.count(d3.utcYear(d[0]), d[0]) +1,
      // i => (i + 6) % 7
      y1: d => -((d[0].getUTCDay() + 6) %7 + d[3]/d[2] - 0.5),
      y2: d => -((d[0].getUTCDay() + 6) % 7 + 1/[d[2]] + d[3]/d[2] - 0.5),
      fy: d => d[0].getUTCFullYear(),
      fill: (d, i) => d[1],
      title: (d, i) => `${i} ${d[0]} ${d[1]} ${d3.timeMonday.count(d3.utcYear(d[0]), d[0])}`,
      inset: 0.5
    }),
    Plot.text(d3.utcMonths(log[0][0], log[log.length -1][0]), {
      x: d => d3.timeMonday.count(d3.utcYear(d), d) + 0.5,
      y: d => 0,
      dy: -15,
      text: d3.utcFormat('%b')
    }),
  ]
})

This works (might be improved sure), but I now wanted to add the path that separates the months and I believe I am reaching the limit of what is possible with Observable Plot.

Any idea on how to do that?

You may want to look at this one for inspiration. I think that part is quite tricky.

Here’s my take:

I did this by implementing a custom mark:

class MonthLine extends Plot.Mark {
  static defaults = {stroke: "white", strokeWidth: 3};
  constructor(data, options = {}) {
    const {x, y} = options;
    super(data, {x: {value: x, scale: "x"}, y: {value: y, scale: "y"}}, options, MonthFrame.defaults);
  }
  render(index, {x, y}, {x: X, y: Y}, dimensions) {
    const {marginTop, marginBottom, height} = dimensions;
    const dx = x.bandwidth(), dy = y.bandwidth();
    return htl.svg`<g fill=none stroke=${this.stroke} stroke-width=${this.strokeWidth}>
      ${Array.from(index, (i) => htl.svg`<path d=${
        Y[i] > marginTop + dy 
          ? `M${X[i] + dx},${marginTop + dy}V${Y[i]}h${-dx}` 
          : `M${X[i]},${marginTop + dy}`}V${height - marginBottom}>`)}
    </g>`;
  }
}

We haven’t documented how to implement custom marks yet, but as the code above hints, it’s possible! In this case we’re using the x and y scales provided by plot to render an SVG path element.

1 Like

brilliant, this is exactly what I wished it was possible!

Thank you!

1 Like

Weird, I am trying @mbostock solution, but apparently x.bandwidth not defined. It looks like in your example the type of the x is band and in my example is linear. I will look into this.

Yes, my code assumes that x and y are band scales; if you use linear scales you’ll need to make some adjustments.