Label either side of origin in barchart at 0

I am trying to label either side of a bar chart that has positive and negative values depending on whether they are positive or negative but just can’t quite fully grok it. I’ve tried a few things in this notebook here: Untitled / Sam Albers | Observable and I can almost get it:

But I am just wondering if there is anyway to get that text label on the negative side to end at 0 (with the corresponding positive one already starting at 0.

Typically you’d do this by creating two marks: one for the left-aligned text, and another for the right-aligned text, and then apply a filter (or change the data) so that the two marks apply to the respective negative and positive rows in the data. Here’s an example:

That example uses d3.groups to group the data, but another approach would be to use the filter transform like so:

Plot.plot({
  label: null,
  x: {
    axis: "top",
    label: "← decrease · Change in population, 2010–2019 (%) · increase →",
    labelAnchor: "center",
    tickFormat: "+",
    percent: true
  },
  color: {
    scheme: "PiYG",
    type: "ordinal"
  },
  marks: [
    Plot.barX(statepop, {
      x: "value",
      y: "State",
      fill: (d) => d.value > 0,
      sort: { y: "x" }
    }),
    Plot.gridX({
      stroke: "white",
      strokeOpacity: 0.5
    }),
    Plot.axisY({
      filter: (y) => statepop.find((d) => d.State === y).value > 0,
      x: 0,
      tickSize: 0,
      anchor: "left"
    }),
    Plot.axisY({
      filter: (y) => statepop.find((d) => d.State === y).value <= 0,
      x: 0,
      tickSize: 0,
      anchor: "right"
    }),
    Plot.textX(statepop, {
      filter: (d) => d.value > 0,
      x: "value",
      y: "State",
      text: ((f) => (d) => f(d.value))(d3.format("+.1%")),
      textAnchor: "start",
      dx: 4
    }),
    Plot.textX(statepop, {
      filter: (d) => d.value <= 0,
      x: "value",
      y: "State",
      text: ((f) => (d) => f(d.value))(d3.format("+.1%")),
      textAnchor: "end",
      dx: -4
    }),
    Plot.ruleX([0])
  ]
})
2 Likes

Oh wow - d3.groups is perfect! Thank you for this.