Skip to content

Bar mark

TIP

The bar mark is a variant of the rect mark for use when one dimension is ordinal and the other is quantitative. See also the cell mark.

The bar mark comes in two orientations: barY extends vertically↑ as in a vertical bar chart or column chart, while barX extends horizontally→. For example, the bar chart below shows the frequency of letters in the English language.

0.000.010.020.030.040.050.060.070.080.090.100.110.12↑ frequencyABCDEFGHIJKLMNOPQRSTUVWXYZletterFork
js
Plot.barY(alphabet, {x: "letter", y: "frequency"}).plot()

Ordinal domains are sorted naturally (alphabetically) by default. Either set the scale domain explicitly to change the order, or use the mark sort option to derive the scale domain from a channel. For example, to sort x by descending y:

0.000.010.020.030.040.050.060.070.080.090.100.110.12↑ frequencyETAOINSHRDLCUMWFGYPBVKJXQZletterFork
js
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "-y"}}).plot()

There is typically one ordinal value associated with each bar, such as a name (or above, letter), and two quantitative values defining a lower and upper bound; the lower bound is often not specified (as above) because it defaults to zero. For barY, x is ordinal and y1 & y2 are quantitative, whereas for barX, y is ordinal and x1 & x2 are quantitative.

Above, since y was specified instead of y1 and y2, the bar spans from zero to the given y value: if you only specify a single quantitative value, barY applies an implicit stackY transform and likewise barX implicitly applies stackX. The stacked horizontal bar chart below draws one bar (of unit width in x) per penguin, colored and sorted by the penguin’s body mass, and grouped by species along y.

Fork
js
Plot.plot({
  marginLeft: 60,
  x: {label: "Frequency"},
  y: {label: null},
  color: {legend: true},
  marks: [
    Plot.barX(penguins, {y: "species", x: 1, inset: 0.5, fill: "body_mass_g", sort: "body_mass_g"}),
    Plot.ruleX([0])
  ]
})

TIP

The group transform with the count reducer could be used to produce one bar per species.

You can opt-out of the implicit stack transform by specifying the bar’s extent with two quantitative values: x1 and x2 for barX, or y1 and y2 for barY. For example, here is a historical timeline of civilizations, where each has a beginning and an end.

3000 BC2000 BC1000 BC0 AD1000 AD2000 ADAegean civilizationAge of pre-colonial civilization (Christian, Islamic, and traditional kingdoms)Age of Turkic empiresAge of united CaliphateAncient Andean regionAncient ChinaAncient Steppe empiresBritish IndiaClassic age of MesoamericaColonial AfricaColonial United StatesEarly Islamic periodEarly modern ChinaEarly modern EuropeEarly Nubian civilizationEgyptian civilizationFirst Persian EmpireFormative age of MesoamericaFormative Japan (age of Yamato rule)Formative USFractured Islamic worldGreat power USGreek ageHeian ageImperial JapanIndian kingdom ageIndus civilizationInter-Persian periodKushMedieval Andean regionMedieval ChinaMedieval EuropeMesopotamian civilizationModern AfricaModern EuropeModern IndiaModern JapanMongol EmpireMughal EmpirePeak of AksumPeople's Republic of ChinaPostclassic age of MesoamericaPtolemaic EgyptRepublic of ChinaRoman > Byzantine EgyptRoman EmpireRoman RepublicSecond Persian EmpireShogunateSuperpower USVedic ageFork
js
Plot.plot({
  marginLeft: 130,
  axis: null,
  x: {
    axis: "top",
    grid: true,
    tickFormat: (x) => x < 0 ? `${-x} BC` : `${x} AD`
  },
  marks: [
    Plot.barX(civilizations, {
      x1: "start",
      x2: "end",
      y: "civilization",
      sort: {y: "x1"}
    }),
    Plot.text(civilizations, {
      x: "start",
      y: "civilization",
      text: "civilization",
      textAnchor: "end",
      dx: -3
    })
  ]
})

TIP

This uses a text mark to label the bars directly instead of a y axis. It also uses a custom tick format for the x axis to show the calendar era.

For a diverging bar chart, simply specify a negative value. The chart below shows change in population from 2010 to 2019. States whose population increased are green, while states whose population decreased are pink. (Puerto Rico’s population declined sharply after hurricanes Maria and Irma.)

−10−5+0+5+10+15← decrease · Change in population, 2010–2019 (%) · increase →Puerto RicoWest VirginiaIllinoisVermontConnecticutMississippiNew YorkRhode IslandPennsylvaniaNew JerseyMichiganMaineOhioNew MexicoKansasWisconsinMissouriLouisianaAlabamaWyomingKentuckyAlaskaNew HampshireArkansasIowaIndianaHawaiiMarylandOklahomaNebraskaCaliforniaMassachusettsMinnesotaVirginiaTennesseeMontanaDelawareSouth DakotaGeorgiaNorth CarolinaOregonSouth CarolinaWashingtonNorth DakotaArizonaIdahoNevadaFloridaColoradoTexasUtahDistrict of ColumbiaFork
js
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, {y: "State", x: (d) => (d[2019] - d[2010]) / d[2010], fill: (d) => Math.sign(d[2019] - d[2010]), sort: {y: "x"}}),
    Plot.gridX({stroke: "white", strokeOpacity: 0.5}),
    Plot.axisY({x: 0}),
    Plot.ruleX([0])
  ]
})

TIP

The percent scale option is useful for showing percentages; it applies a scale transform that multiplies associated channel values by 100.

When ordinal data is regular, such as the yearly observations of the time-series bar chart of world population below, use the interval option to enforce uniformity and show gaps for missing data. It can be set to a named interval such as hour or day, a number for numeric intervals, a d3-time interval, or a custom implementation.

01,0002,0003,0004,0005,0006,0007,000↑ population2014201520162017201820192020year →Fork
js
Plot
  .barY(timeseries, {x: "year", y: "population"})
  .plot({x: {tickFormat: "", interval: checked ? 1 : undefined}})

TIP

You can also make a time-series bar chart with a rect mark, possibly with the bin transform to bin observations at regular intervals.

A bar’s ordinal dimension is optional; if missing, the bar spans the chart along this dimension. Such bars typically also have a color encoding. For example, here are warming stripes showing the increase in average temperature globally over the last 172 years.

186018801900192019401960198020002020year →Fork
js
Plot.plot({
  x: {round: true, tickFormat: "d"},
  color: {scheme: "BuRd"},
  marks: [
    Plot.barX(hadcrut, {
      x: "year",
      fill: "anomaly",
      interval: 1, // annual observations
      inset: 0 // no gaps
    })
  ]
})

With the stack transform, a one-dimensional bar can show the proportions of each value relative to the whole, as a compact alternative to a pie or donut chart.

0102030405060708090100frequency (%) →ETAOINSHRDLCUMWFGYPBVKJXQZFork
js
Plot.plot({
  x: {percent: true},
  marks: [
    Plot.barX(alphabet, Plot.stackX({x: "frequency", fillOpacity: 0.3, inset: 0.5})),
    Plot.textX(alphabet, Plot.stackX({x: "frequency", text: "letter", inset: 0.5})),
    Plot.ruleX([0, 1])
  ]
})

TIP

Although barX applies an implicit stackX transform, textX does not; this example uses an explicit stackX transform in both cases for clarity.

For a grouped bar chart, use faceting. The chart below uses fy to partition the bar chart of penguins by island.

Fork
js
Plot.plot({
  marginLeft: 60,
  marginRight: 60,
  label: null,
  x: {label: "Frequency"},
  y: {padding: 0},
  marks: [
    Plot.barX(penguins, {fy: "island", y: "sex", x: 1, inset: 0.5}),
    Plot.ruleX([0])
  ]
})

Bar options

For required channels, see barX and barY. The bar mark supports the standard mark options, including insets and rounded corners. The stroke defaults to none. The fill defaults to currentColor if the stroke is none, and to none otherwise.

barX(data, options)

js
Plot.barX(alphabet, {y: "letter", x: "frequency"})

Returns a new horizontal→ bar with the given data and options. The following channels are required:

  • x1 - the starting horizontal position; bound to the x scale
  • x2 - the ending horizontal position; bound to the x scale

The following optional channels are supported:

  • y - the vertical position; bound to the y scale, which must be band

If neither the x1 nor x2 option is specified, the x option may be specified as shorthand to apply an implicit stackX transform; this is the typical configuration for a horizontal bar chart with bars aligned at x = 0. If the x option is not specified, it defaults to identity. If options is undefined, then it defaults to x2 as identity and y as the zero-based index [0, 1, 2, …]; this allows an array of numbers to be passed to barX to make a quick sequential bar chart. If the y channel is not specified, the bar will span the full vertical extent of the plot (or facet).

If an interval is specified, such as d3.utcDay, x1 and x2 can be derived from x: interval.floor(x) is invoked for each x to produce x1, and interval.offset(x1) is invoked for each x1 to produce x2. If the interval is specified as a number n, x1 and x2 are taken as the two consecutive multiples of n that bracket x. Named UTC intervals such as day are also supported; see scale options.

barY(data, options)

js
Plot.barY(alphabet, {x: "letter", y: "frequency"})

Returns a new vertical↑ bar with the given data and options. The following channels are required:

  • y1 - the starting vertical position; bound to the y scale
  • y2 - the ending vertical position; bound to the y scale

The following optional channels are supported:

  • x - the horizontal position; bound to the x scale, which must be band

If neither the y1 nor y2 option is specified, the y option may be specified as shorthand to apply an implicit stackY transform; this is the typical configuration for a vertical bar chart with bars aligned at y = 0. If the y option is not specified, it defaults to identity. If options is undefined, then it defaults to y2 as identity and x as the zero-based index [0, 1, 2, …]; this allows an array of numbers to be passed to barY to make a quick sequential bar chart. If the x channel is not specified, the bar will span the full horizontal extent of the plot (or facet).

If an interval is specified, such as d3.utcDay, y1 and y2 can be derived from y: interval.floor(y) is invoked for each y to produce y1, and interval.offset(y1) is invoked for each y1 to produce y2. If the interval is specified as a number n, y1 and y2 are taken as the two consecutive multiples of n that bracket y. Named UTC intervals such as day are also supported; see scale options.