Observable Plot: Best way show Histogram count labels

Hi everyone, I am appreciating observable plot. It’s really helped me understand data visualization with web technologies.

I have a question about showing value labels on a histogram

I have used a group transform in observable plot to create a histogram. The code below generates it, great!

const histFmCalc = Plot.plot({
  y: {
    label: "Innovation Count"
  },
  x: {
    label: "Years in development"
  },
  marks: [
    Plot.barY(
      innovations,
      Plot.groupX(
        {y: "count"},
        {
          x: "DEVELOPMENT_TIME",
          fill: "goldenrod"
        }
      )
    )
  ]
});

I want the count for each bar to show above it. I have managed to do this with Plot.text, but I’ve had to basically recreate the grouping function manually like so:

const innoHist = innovations.reduce((acc, curr) => {
  const newAcc = acc;
  if(newAcc[curr.DEVELOPMENT_TIME]) {
    newAcc[curr.DEVELOPMENT_TIME]++
  } else {
    newAcc[curr.DEVELOPMENT_TIME] = 1
  }
  return newAcc;
},
[]);

const innoHistIndex = innoHist.map((value, index, array) => index);

And then use these new arrays in my plot (see last line):

const histFmCalc = Plot.plot({
  y: {
    label: "Innovation Count"
  },
  x: {
    label: "Years in development"
  },
  marks: [
    Plot.barY(
      innovations,
      Plot.groupX(
        {y: "count"},
        {
          x: "DEVELOPMENT_TIME",
          fill: "goldenrod"
        },
      )
    ),
    Plot.text(innoHist, {x: innoHistIndex, y: innoHist, dy: -5})
  ]
});

I feel like there should be an easier and more efficient way to do this, is there?

I am including a screenshot of my histogram because I’m very proud of it.

You can apply the group transform to the text mark, too, the same way you did to the bar. Then, add text: "count" to the output of the group transform for the text mark. For example:

To avoid repetition, you can also write a helper function like so:

function groupBySport(outputs, options) {
  return Plot.groupY(
    { x: "count", ...outputs },
    { y: "sport", sort: { y: "x", reverse: true }, ...options }
  );
}

Then to use:

Plot.plot({
  marginLeft: 100,
  marginRight: 40,
  marks: [
    Plot.barX(olympians, groupBySport()),
    Plot.text(olympians, groupBySport({text: "count"}, {frameAnchor: "left", dx: 3}))
  ]
})
3 Likes

Fantastic, thank you. As well as solving this problem, your post as given me a deeper understanding of transforms.

For reference here’s the code I ended up with

const histFmCalc = Plot.plot({
  y: {
    label: "Innovation Count"
  },
  x: {
    label: "Years in development"
  },
  marks: [
    Plot.barY(
      innovations,
      groupByDevelopmentTime(null, {fill: "goldenrod"})
    ),
    Plot.text(
      innovations,
      groupByDevelopmentTime({text: "count"}, {dy: -5})
    )
  ]
});

const groupByDevelopmentTime = (outputs, options) => {
  return Plot.groupX(
    { y: "count", ...outputs },
    { x: "DEVELOPMENT_TIME", ...options }
  );
}
3 Likes