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])
]
})