---
slug: binning
title: From bars to a real histogram
tagline: How one word turns a noisy bar chart into a distribution — and where binning stops short.
---

Counting a continuous field is the most common chart that's _almost_ right. The naive
version compiles, renders, and quietly lies about your data. Walk the four stages below —
each tab is one small edit — and watch a picket fence become a distribution, then a finished
chart.

:::progression

## raw counts

A bar chart of a continuous field gives you _one bar per distinct value_ — a spiky picket
fence, not a distribution. It looks almost right, which is exactly what makes it the classic
80%.

```vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "mark": "bar",
  "encoding": {
    "x": { "field": "minutes", "type": "quantitative" },
    "y": { "aggregate": "count" }
  },
  "data": { "values": [{"minutes":12},{"minutes":14},{"minutes":15},{"minutes":16},{"minutes":18},{"minutes":19},{"minutes":20},{"minutes":21},{"minutes":22},{"minutes":23},{"minutes":24},{"minutes":25},{"minutes":26},{"minutes":28},{"minutes":30},{"minutes":31},{"minutes":33},{"minutes":35},{"minutes":37},{"minutes":40},{"minutes":43},{"minutes":46},{"minutes":50},{"minutes":55},{"minutes":61},{"minutes":68},{"minutes":77},{"minutes":86}] }
}
```

## + bin

`bin: true` is the whole fix. Vega-Lite chooses ~10 "nice" boundaries, buckets the rows, and
counts each bucket — a real histogram from a one-word edit.

```vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "mark": "bar",
  "encoding": {
    "x": { "field": "minutes", "type": "quantitative", "bin": true },
    "y": { "aggregate": "count" }
  },
  "data": { "values": [{"minutes":12},{"minutes":14},{"minutes":15},{"minutes":16},{"minutes":18},{"minutes":19},{"minutes":20},{"minutes":21},{"minutes":22},{"minutes":23},{"minutes":24},{"minutes":25},{"minutes":26},{"minutes":28},{"minutes":30},{"minutes":31},{"minutes":33},{"minutes":35},{"minutes":37},{"minutes":40},{"minutes":43},{"minutes":46},{"minutes":50},{"minutes":55},{"minutes":61},{"minutes":68},{"minutes":77},{"minutes":86}] }
}
```

## + tuned bins

Take control of the resolution: `bin: { step: 10 }` forces clean 10-minute buckets (or set
`maxbins` for a target count). Real titles turn axes from raw field names into a sentence.

```vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "mark": "bar",
  "encoding": {
    "x": { "field": "minutes", "type": "quantitative", "bin": { "step": 10 }, "title": "Delivery time (min)" },
    "y": { "aggregate": "count", "title": "Orders" }
  },
  "data": { "values": [{"minutes":12},{"minutes":14},{"minutes":15},{"minutes":16},{"minutes":18},{"minutes":19},{"minutes":20},{"minutes":21},{"minutes":22},{"minutes":23},{"minutes":24},{"minutes":25},{"minutes":26},{"minutes":28},{"minutes":30},{"minutes":31},{"minutes":33},{"minutes":35},{"minutes":37},{"minutes":40},{"minutes":43},{"minutes":46},{"minutes":50},{"minutes":55},{"minutes":61},{"minutes":68},{"minutes":77},{"minutes":86}] }
}
```

## polished

The last 20%: round the bar tops, add a tooltip that reports each bucket's range and count,
and _layer_ a mean line over the bars. Composition is where polish lives — one spec, two
marks sharing an axis.

```vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "layer": [
    {
      "mark": { "type": "bar", "cornerRadiusEnd": 2 },
      "encoding": {
        "x": { "field": "minutes", "type": "quantitative", "bin": { "step": 10 }, "title": "Delivery time (min)" },
        "y": { "aggregate": "count", "title": "Orders" },
        "tooltip": [
          { "field": "minutes", "bin": { "step": 10 }, "title": "Range (min)" },
          { "aggregate": "count", "title": "Orders" }
        ]
      }
    },
    {
      "mark": { "type": "rule", "color": "#d6336c", "size": 2 },
      "encoding": { "x": { "field": "minutes", "aggregate": "mean" } }
    }
  ],
  "data": { "values": [{"minutes":12},{"minutes":14},{"minutes":15},{"minutes":16},{"minutes":18},{"minutes":19},{"minutes":20},{"minutes":21},{"minutes":22},{"minutes":23},{"minutes":24},{"minutes":25},{"minutes":26},{"minutes":28},{"minutes":30},{"minutes":31},{"minutes":33},{"minutes":35},{"minutes":37},{"minutes":40},{"minutes":43},{"minutes":46},{"minutes":50},{"minutes":55},{"minutes":61},{"minutes":68},{"minutes":77},{"minutes":86}] }
}
```

:::

:::sharp-edge
**The sharp edge.** Tempted to wire a slider to the bin width? It won't work. In Vega-Lite,
`bin.step` and `maxbins` are fixed numbers — no parameter or expression drives them, so a
bound slider compiles fine and then does nothing. The one binning property you _can_ make
interactive is `extent`: brush an interval selection to re-bin a chosen range. That's a
lesson of its own.
:::
