Column charts

Column chart is used for value distributions. The structure of the table is quite ordinary, but its formatting is based on display: grid; and especially display: contents; to facilitate the placement of the cells — technique inspired by Hidde De Vries's article More accessible markup with display: contents, and clarified by Ire Aderinokun's post How display: contents works.

The basic principle is the same as the bar charts:

  1. the first grid row is the reserved to display value in case it reaches scale's max value, with a fixed size of 2ex — have a look to CSS units rudiments, documented in Every Layout by Andy Bell and Heydon Pickering ;
  2. the repeat() function applied with the --scale custom property enables us to handle a dynamic scale;
  3. <thead>, <tbody> and <tr> containers are neutralized in the grid structure using display: contents;
  4. each cell is placed on the grid depending its --value — its background color also depending on its value;
  5. its text value — wrapped in a <span> element — is positioned at the top of the column using the same trick as in the bar chart;
  6. and finally <colgroup> and <col> elements are used to display a light background for each column, which becomes more visible on :hover.

Switch
Allows you to disable styles on the following table.

Browser market shares in France in January 2019
Browser Chrome Firefox Safari Edge IE Others
Market shares 62% 15% 9% 5% 6% 3%
HTML
<table class="chaarts column" id="column" style="--y: 7;">
	<caption id="caption-7">[…]</caption>
	<colgroup>
		<col>
		<col>
		<col>
		<col>
		<col>
		<col>
		<col>
	</colgroup>
	<thead>
		<tr>
			<th scope="row">[…]</th>
			<th scope="col" style="--value: 62;">Chrome</th>
			<th scope="col" style="--value: 15;">Firefox</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<th scope="row">[…]</th>
			<td style="--value: 62;"><span>62%</span></td>
			<td style="--value: 15;"><span>15%</span></td>
		</tr>
	</tbody>
</table>
css
.chaarts[class*=column] {
  --gap: 0.5rem;
  --size: calc(var(--scale, 100) * 100%);
  --width: calc(64em / var(--y) - 1rem);
  display: grid;
  grid-column-gap: var(--gap);
  max-block-size: 64em;
  position: relative;
}

.chaarts[class*=column] td,
.chaarts[class*=column] th,
.chaarts[class*=column] col {
  grid-column: 1;
  margin: 0;
}

.chaarts[class*=column] tr > * + * {
  text-align: center;
}

.chaarts[class*=column] tr,
.chaarts[class*=column] tbody,
.chaarts[class*=column] thead,
.chaarts[class*=column] colgroup {
  display: contents;
}

.chaarts[class*=column] caption {
  grid-column: 1/span var(--y);
  grid-row: -1;
}

.chaarts[class*=column] td {
  --integer: calc(var(--value));
  grid-row: calc(var(--scale, 100) + 2 - var(--integer)) / -2;
  pointer-events: none;
  position: relative;
  transition: opacity 0.2s var(--move);
}

.chaarts[class*=column] td:nth-of-type(1) {
  grid-column: 2;
}

.chaarts[class*=column] td:nth-of-type(2) {
  grid-column: 3;
}

.chaarts[class*=column] td:nth-of-type(3) {
  grid-column: 4;
}

.chaarts[class*=column] td:nth-of-type(4) {
  grid-column: 5;
}

.chaarts[class*=column] td:nth-of-type(5) {
  grid-column: 6;
}

.chaarts[class*=column] td:nth-of-type(6) {
  grid-column: 7;
}

.chaarts[class*=column] td:nth-of-type(7) {
  grid-column: 8;
}

.chaarts[class*=column] td:nth-of-type(8) {
  grid-column: 9;
}

.chaarts[class*=column] td:nth-of-type(9) {
  grid-column: 10;
}

.chaarts[class*=column] td:nth-of-type(10) {
  grid-column: 11;
}

.chaarts[class*=column] span {
  background: inherit;
  background-clip: text;
  color: transparent;
  font-weight: bold;
  inset-block-end: 100%;
  inset-inline-end: 0;
  inset-inline-start: 0;
  line-height: 1.5;
  pointer-events: auto;
  position: absolute;
}

@media (prefers-contrast: more) {
  .chaarts[class*=column] span {
    background: var(--background-lighter);
    background-clip: unset;
    color: var(--chaarts-blue);
  }
}
.chaarts[class*=column] col {
  background: var(--background) var(--stripes);
  background-blend-mode: exclusion;
  grid-row: 1/-1;
  mix-blend-mode: color;
  opacity: 0.25;
  transition: opacity 0.3s var(--move);
}

.chaarts[class*=column] col:hover {
  opacity: 0.5;
}

.chaarts[class*=column] col:first-of-type {
  opacity: 0;
}

.chaarts[class*=column] col:nth-of-type(2),
.chaarts[class*=column] thead tr *:nth-of-type(2) {
  grid-column: 2;
}

.chaarts[class*=column] col:nth-of-type(3),
.chaarts[class*=column] thead tr *:nth-of-type(3) {
  grid-column: 3;
}

.chaarts[class*=column] col:nth-of-type(4),
.chaarts[class*=column] thead tr *:nth-of-type(4) {
  grid-column: 4;
}

.chaarts[class*=column] col:nth-of-type(5),
.chaarts[class*=column] thead tr *:nth-of-type(5) {
  grid-column: 5;
}

.chaarts[class*=column] col:nth-of-type(6),
.chaarts[class*=column] thead tr *:nth-of-type(6) {
  grid-column: 6;
}

.chaarts[class*=column] col:nth-of-type(7),
.chaarts[class*=column] thead tr *:nth-of-type(7) {
  grid-column: 7;
}

.chaarts[class*=column] col:nth-of-type(8),
.chaarts[class*=column] thead tr *:nth-of-type(8) {
  grid-column: 8;
}

.chaarts[class*=column] col:nth-of-type(9),
.chaarts[class*=column] thead tr *:nth-of-type(9) {
  grid-column: 9;
}

.chaarts[class*=column] col:nth-of-type(10),
.chaarts[class*=column] thead tr *:nth-of-type(10) {
  grid-column: 10;
}

.chaarts.column-single {
  grid-auto-columns: 1fr;
  grid-template-rows: 2ex repeat(var(--scale, 100), minmax(0, 0.25rem)) minmax(min-content, 2rem);
}

.chaarts.column-single tbody th {
  grid-row: -6/-3;
  line-height: 1;
}

.chaarts.column-single thead * {
  grid-row: -2;
}

.chaarts.column-single td {
  --position: calc(var(--integer, 0) / var(--scale, 100) * 100%);
  background: linear-gradient(to top, var(--chaarts-green), var(--chaarts-gray), var(--chaarts-blue), var(--chaarts-purple), var(--chaarts-red)) 0% var(--position) / 100% var(--size), var(--background) center/1rem;
  background-blend-mode: hard-light;
}

.chaarts.column-single td:nth-of-type(1n + 1) {
  --background: var(--checkers);
}

.chaarts.column-single td:nth-of-type(2n + 2) {
  --background: var(--hexagons);
}

.chaarts.column-single td:nth-of-type(3n + 3) {
  --background: var(--triangles);
}

.chaarts.column-single td:nth-of-type(4n + 4) {
  --background: var(--zig);
}

.chaarts.column-single td:nth-of-type(5n + 5) {
  --background: var(--stripes);
}

.chaarts.column-single td:nth-of-type(6n + 6) {
  --background: var(--dots);
}

@media (prefers-contrast: more) {
  .chaarts.column-single td {
    background-color: var(--chaarts-blue);
  }
}

Multiple columns

In order to have two values for each main column, we must also have two subheadings. Concretely speaking:

  1. we add a second row in <thead>:
    • with two column header cells <th scope="col"> for each column header cell in the first row;
    • remember to add colspan="2" on the first row's header cell to match the new table layout— and a span="2" on <col>s;
    • and finally add an identifier to each header cell to referenced them to the relevant data cells — using the headers attribute, eg. for the first cell: headers="browser chrome year chrome-2018" where each value is a header cell's identifier.
  2. Styles:
    • the first-level header cells must span two columns of the grid, as required by their colspan for the table layout. It is unfortunately impossible to use an attribute value in another property than content — otherwise we could simply write grid-column: 2 / span attr(colspan);, and that would be awesome…
    • but no! Thus, a --span custom property is added on <table>, and must match the colspan attributes values mentioned earlier: it is therefore used to extend the first level headers to the appropriate number of columns.
    • Colors and patterns are no longer applied according to each value, but according to each column — in the example, every second element (since we have two entries per column). Again, if we could use an attribute value or a custom property in a selector, that would be great. Imagine tbody td:nth-of-type(var(--span)n + var(--span)) or even tbody td:nth-of-type(attr(colspan)n + attr(colspan))!

Switch
Allows you to disable styles on the following table.

Browser market shares in France in January 2019
Browser Chrome Firefox Safari Edge IE
Year 2018 2019 2018 2019 2018 2019 2018 2019 2018 2019
Market shares 49.6% 57% 11.74% 9.59% 21.53% 18.78% 3.72% 3.5% 4.46% 3.66%
HTML
<table class="chaarts column-multiple" id="column-multiple" style="--y: 11; --span: 2;">
	<caption id="caption-8">[…]</caption><colgroup>
		<col>
		<col span="2">
		<col span="2">
		<col span="2">
		<col span="2">
		<col span="2">
	</colgroup>
	<thead>
		<tr>
			<th scope="row" id="browser">[…]</th>
			<th scope="col" colspan="2" id="chrome">Chrome</th>
		</tr>
		<tr>
			<th scope="row" id="year">[…]</th>
			<th scope="col" id="chrome-2018">2018</th>
			<th scope="col" id="chrome-2019">2019</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<th scope="row" id="parts">[…]</th>
			<td style="--value: 50;" headers="browser chrome year chrome-2018"><span>49.6%</span></td>
			<td style="--value: 57;" headers="browser chrome year chrome-2019"><span>57%</span></td>
		</tr>
	</tbody>
</table>
css
.chaarts.column-multiple {
  grid-template-columns: minmax(min-content, 14ch) repeat(calc(var(--y) - 1), 1fr);
  grid-template-rows: 2ex repeat(var(--scale, 100), minmax(0, 0.25rem)) repeat(2, minmax(min-content, 2rem));
}

.chaarts.column-multiple span {
  background-image: none;
}

.chaarts.column-multiple col:nth-child(odd) {
  opacity: 0;
}

.chaarts.column-multiple tbody th {
  grid-row: -10/span 7;
}

.chaarts.column-multiple thead tr * {
  grid-row: -2;
}

.chaarts.column-multiple col:nth-of-type(2),
.chaarts.column-multiple thead tr *:nth-of-type(2) {
  grid-column: calc(4 - var(--span))/span var(--span);
}

.chaarts.column-multiple col:nth-of-type(3),
.chaarts.column-multiple thead tr *:nth-of-type(3) {
  grid-column: calc(6 - var(--span))/span var(--span);
}

.chaarts.column-multiple col:nth-of-type(4),
.chaarts.column-multiple thead tr *:nth-of-type(4) {
  grid-column: calc(8 - var(--span))/span var(--span);
}

.chaarts.column-multiple col:nth-of-type(5),
.chaarts.column-multiple thead tr *:nth-of-type(5) {
  grid-column: calc(10 - var(--span))/span var(--span);
}

.chaarts.column-multiple col:nth-of-type(6),
.chaarts.column-multiple thead tr *:nth-of-type(6) {
  grid-column: calc(12 - var(--span))/span var(--span);
}

.chaarts.column-multiple thead tr + tr * {
  font-weight: normal;
  grid-row: -3;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(2) {
  grid-column: 2;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(3) {
  grid-column: 3;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(4) {
  grid-column: 4;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(5) {
  grid-column: 5;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(6) {
  grid-column: 6;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(7) {
  grid-column: 7;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(8) {
  grid-column: 8;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(9) {
  grid-column: 9;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(10) {
  grid-column: 10;
}

.chaarts.column-multiple thead tr + tr *:nth-of-type(11) {
  grid-column: 11;
}

.chaarts.column-multiple td {
  background-color: var(--chaarts-pink);
  background-image: var(--zig);
  grid-row-end: -3;
}

.chaarts.column-multiple td:nth-of-type(2n + 2) {
  background: var(--chaarts-blue) var(--triangles);
}