Ordinal scale domains are sorted naturally by default, and since “10.2 - 14.5” is a string, it is considered ahead of “5.05 - 7.7”. As you noted, if you use sun_min
instead of sun_interval
, you get the expected order because sun_min
is defined as numbers instead of strings.
Plot.plot({
marginLeft: 100,
x: { label: "days" },
marks: [
Plot.barX(
seasonsrdata.filter((d) => d.city == "City 26"),
Plot.groupY({ x: "count" }, { y: "sun_min" })
)
]
})
So, one solution is to specify the tickFormat option to control how a given sun_min
value is displayed. Here’s one way to do that by finding an arbitrary row in the data with a given sun_min
and returning the corresponding sun_interval
:
Plot.plot({
marginLeft: 100,
x: { label: "days" },
y: {
label: "sun_interval",
tickFormat: (y) => seasonsrdata.find((d) => d.sun_min === y).sun_interval
},
marks: [
Plot.barX(
seasonsrdata.filter((d) => d.city == "City 26"),
Plot.groupY({ x: "count" }, { y: "sun_min" })
)
]
})
If you had a lookup table from sun_min
to sun_interval
, you could use that instead.
Another approach is to group by sun_interval
, and then specify the y scale’s domain so that the order is what you expect instead of the default natural order.
Plot.plot({
marginLeft: 100,
x: { label: "days" },
y: { domain: ["0 - 1.3", "1.3 - 5.04", "5.04 - 7.7", "7.7 - 10.2", "10.2 - 14.5"] },
marks: [
Plot.barX(
seasonsrdata.filter((d) => d.city == "City 26"),
Plot.groupY({ x: "count" }, { y: "sun_interval" })
)
]
})
If you don’t want to hardcode the domain, you can use the mark sort option to impute it from the data. Here we say that the y scale domain should be imputed from the data, and we should pull out the first sun_min
value from each group to set the order:
Plot.plot({
marginLeft: 100,
x: { label: "days" },
marks: [
Plot.barX(
seasonsrdata.filter((d) => d.city == "City 26"),
Plot.groupY(
{ x: "count" },
{ y: "sun_interval", sort: { y: "data", reduce: ([[d]]) => d.sun_min } }
)
)
]
})
Another way we could write this is to extract the first number from the sun_interval
value already used by the y channel:
Plot.plot({
marginLeft: 100,
x: { label: "days" },
marks: [
Plot.barX(
seasonsrdata.filter((d) => d.city == "City 26"),
Plot.groupY(
{ x: "count" },
{ y: "sun_interval", sort: { y: "y", reduce: ([d]) => parseFloat(d) } }
)
)
]
})
If you had another channel such as fill which was naturally ordered, you could use that too:
Plot.plot({
marginLeft: 100,
x: { label: "days" },
marks: [
Plot.barX(
seasonsrdata.filter((d) => d.city == "City 26"),
Plot.groupY(
{ x: "count" },
{ y: "sun_interval", fill: "sun_min", sort: { y: "fill" } }
)
)
]
})
Live examples: Plot: Sorting bars / Observable | Observable