Plot: Vertical rect on time axis

I have plot with a x-axis with type: utc/time. I would like to draw vertical rects for each day on the a-axis similar to this reference pic. It is easy with Plot.cell, but since the axis is not ordinal, I cannot use cell.

If it was a ordinal axis, I could use Plot.cell with this code.

range = d3.extent([new Date("2022-01-01"), new Date("2022-04-01")]);
extnt = d3.utcDays(...dextent);

Plot.cell(extnt,
  {
    x: (d) => d,
    // y: "name",
    fill: "#f0dddd50",
    stroke: "white"
  })

Similar for saturday and sunday

sunday = d3.utcSunday.range(new Date("2022-01-01"), new Date("2022-04-01"));
satday = d3.utcSaturday.range(new Date("2022-01-01"), new Date("2022-04-01"));
//Saturday
Plot.cell(satday,
  {
    x: (d) => d,
    fill: "#f0dddd80",
    stroke: "white"
  }),

//Sunday
Plot.cell(sunday,
  {
    x: (d) => d,
    fill: "#f0dddd80",
    stroke: "white"
  })

Thanks in advance

How’s this? Plot: vertical rectangles for each day / Toph Tucker / Observable

untitled - 2022-03-18T145921.590

If y is ordinal and x is temporal (time) then I expect you want Plot.barX (horizontal bars). You can also use the interval option as d3.utcDay if each bar is one day long.

1 Like

@tophtucker thanks for the solution. I forgot to include the sample code in my original post - Vacation Gantt / hashg / Observable. I applied your solution. Couple of issues -

  1. Top most bar is above the rectangles.
  2. The ticks needs to be in middle of rectangles as illustrated in the mockup.

True, as suggested by you, I am using Plot.barX to indicate each entry. To enhance the ux, my team wants vertical bars representing each days as depicted in the mockup.

here is my plot with @tophtucker integrated. - Vacation Gantt / hashg / Observable

1 Like

Here’s my suggestion using barX:

Plot.plot({
  x: { axis: "top", round: true },
  y: { padding: 0, grid: true },
  marks: [
    Plot.barX(
      d3.utcDay.range(start, end).filter((d) => d.getUTCDay() < 2),
      { interval: d3.utcDay, fillOpacity: 0.15 }
    ),
    Plot.barX(
      d3.utcDay.range(start, end).filter((d) => d.getUTCDay() >= 2),
      { interval: d3.utcDay, fillOpacity: 0.05 }
    ),
    Plot.barX(data, {
      x1: "startDate",
      x2: "endDate",
      y: "name",
      fill: "name",
      inset: 0.5
    })
  ]
})

If you omit the y channel then the barX will span the vertical extent of the chart.

I’m not marking the weekends correctly though… you’ll probably want to fix that. :sweat_smile:

3 Likes

Could do all the days as one mark with a variable fillOpacity:

Plot.plot({
  x: { axis: "top", round: true },
  y: { padding: 0, grid: true },
  marks: [
    Plot.barX(d3.utcDay.range(start, end), {
      interval: d3.utcDay,
      fillOpacity: (d) => ((1 + d.getUTCDay()) % 7 < 2 ? 0.15 : 0.05)
    }),
    Plot.barX(data, {
      x1: "startDate",
      x2: "endDate",
      y: "name",
      fill: "name",
      inset: 0.5
    })
  ],
  opacity: { type: "identity" }
})
1 Like

Thanks @mbostock and @Fil for investing time to answer my question, the recommendations are working.

Would it be possible to center the ticks on corresponding bar/rect instead of beginning?

You could try with the following definition for the ticks on the x axis:

  x: {
    axis: "top",
    round: true,
    ticks: {
      range(a, b) {
        return d3.utcWeek.range(a, b).map((d) => d3.utcHour.offset(d, 12));
      }
    },
    tickFormat: "%b %-d"
  },

The ticks will be the same as those generated by d3.utcWeek.range, but shifted by 12 hours. The tickFormat will show the date instead of the default 12AM. (A bit complicated, but dealing with time is always complicated.)

1 Like

Awesome it works. Thanks again.

For what it’s worth, we’re also considering some tweaks to how temporal axes are rendered that would hopefully improve the default appearance.

:face_with_monocle: I’m waiting for it. I need to display year and month in different ticks.