Text marks for the sum of last n days when using Plot.windowY

I am creating a bar chart with the sum of values in the last 7 days. This is easy combining Plot.rectY and Plot.windowY but I have not been able to display the text with the sum on top of each bar:

This is the code I have tried:

Plot.plot({
  marks: [
    Plot.rectY(
      weather.slice(-28),
      Plot.windowY({
        k: 7,
        anchor: "end",
        reduce: "sum",
        x: "date",
        y: "precipitation",
        interval: d3.utcDay,
        fill: "lightgrey"
      })
    ),
    Plot.text(
      weather.slice(-28),
      Plot.windowY({
        text: "sum",
        k: 7,
        anchor: "end",
        reduce: "sum",
        x: "date",
        y: "precipitation",
        interval: d3.utcDay
      })
    ),
    Plot.ruleY([0])
  ]
})

I would appreciate hints on how to combine Plot.text with Plot.windowY

Thanks

You’ll need to use Plot.window for this. Plot.windowY is just for doing a window on the y channel, but here you also want to do it on the text channel. So, you can use Plot.window and Plot.map as the more general form. First you can define your window function:

rollsum = Plot.window({ k: 7, anchor: "end", reduce: "sum" })

Then you can apply it to your rect and text marks like so:

Plot.plot({
  marks: [
    Plot.rectY(
      weather.slice(-28),
      Plot.map(
        { y: rollsum },
        {
          x: "date",
          y: "precipitation",
          interval: d3.utcDay,
          fill: "lightgrey"
        }
      )
    ),
    Plot.text(
      weather.slice(-28),
      Plot.map(
        { y: rollsum, text: rollsum },
        {
          x: (d) => d3.utcHour.offset(d.date, 12),
          y: "precipitation",
          text: "precipitation",
          frameAnchor: "bottom",
          dy: -3
        }
      )
    ),
    Plot.ruleY([0])
  ]
})

The other tricky thing here is that Plot.text doesn’t support the interval option, so by default if you say x: "date" then your text labels will be positioned at the left edge of each rect. If you want them to be in the center of the rect, you can offset the labels in data space like I did above. Or, you could invoke the bin transform yourself (which is what the interval option does under the hood).

Plot.plot({
  marks: [
    Plot.rectY(
      weather.slice(-28),
      Plot.map(
        { y: rollsum },
        {
          x: "date",
          y: "precipitation",
          interval: d3.utcDay,
          fill: "lightgrey"
        }
      )
    ),
    Plot.text(
      weather.slice(-28),
      Plot.map(
        { y: rollsum, text: rollsum },
        Plot.binX(
          { y: "first", text: "first" },
          {
            x: "date",
            y: "precipitation",
            text: "precipitation",
            frameAnchor: "bottom",
            interval: d3.utcDay,
            dy: -3
          }
        )
      )
    ),
    Plot.ruleY([0])
  ]
})

We should maybe offer a standalone interval transform for this sort of thing…

1 Like