Plotting bar chart with counts with specific interval and min/max values

Hello,

I am preparing a notebook that shows the scores achieved in some exams. The scores can be multiples of 2, from 0 to 100. I would like the bar chart to have a bar for each possible score (0, 2, 4, …, 100), even if there is no occurrence for that particular score. My data is not aggregated (ie they contain one row for each participant), so they must be aggregated. I tried both binX and groupX (see notebook below), each has its own problems:

binX:

The “missing” scores appear, but only if they are intermediate. So, if the achieved scores are 4, 6 and 10, the bars that will appear are 4, 6, 8 and 10. But there will be no bars for 0 and 2, and no bars for 12 and above.

Also, the scores appear in between the bars, not exactly beneath the bars:

image

groupX:

The “missing” scores don’t appear, intermediate or not. However, the scores appear at the correct point.

What is the best way to solve this?

One way to do it is to use a scale transform to subtract 1 from each value, and center the rects around the value:

Plot.plot({
  x: { transform: (d) => d - 1, domain: [0, 100] },
  y: { grid: true },
  color: { legend: true },
  marks: [
    Plot.rectY(
      filteredData.filter((d) => d.year.toString() === "2023"),
      Plot.binX({ y: "count" }, { x: "score", fill: "result", interval: 2 })
    ),
    Plot.ruleY([0])
  ]
})
1 Like

You can add interval: 2 to the x scale definition to ensure that the missing values appear. E.g.,

Plot.plot({
  marginBottom: 40,
  x: {label: "Βαθμολογία", tickRotate: 90, interval: 2},
  y: {label: "# Μαθητών", grid: true},
  marks: [
    Plot.barY(
      filteredData.filter(d => d.year.toString() === "2023"),
      Plot.groupX({y: "count"}, {x: "score", fill: "result"})
    ),
    Plot.ruleY([0])
  ]
})

If you also want to ensure that the domain is consistent with the data changes, you can compute the domain explicitly using d3.range like so:

Plot.plot({
  caption: "2023",
  marginBottom: 40,
  x: {label: "Βαθμολογία", tickRotate: 90, interval: 2, domain: d3.range(0, 101, 2)},
  y: {label: "# Μαθητών", grid: true},
  marks: [
    Plot.barY(
      filteredData.filter(d => d.year.toString() === "2023"),
      Plot.groupX({y: "count"}, {x: "score", fill: "result"})
    ),
    Plot.ruleY([0])
  ]
})
1 Like