Friday, 11 August 2017

More CSS Charts, with Grid & Custom Properties

I loved Robin's recent post, experimenting with CSS Grid for bar-charts. I've actually been using a similar approach on a client project, building a day-planner with CSS Grid. It's a different use-case, but the same basic technique: using grid layouts to visualize data.

(I recommend reading Robin's article first, since I'm building on top of his chart.)

Robin's approach relies on a large Sass loop to generate 100 potential class names, even though less than 12 are used in the final chart. In production, we'll want something more direct and performant, with better semantics, so I turned to definition lists and CSS Variables (aka Custom Properties) to build my charts.

Here's the final result:

See the Pen Bar chart in CSS grid + variables by Miriam Suzanne (@mirisuzanne) on CodePen.

Let's dig into it!

Markup First

Robin was proposing a conceptual experiment, so he left out many real-life data and accessibility concerns. Since I'm aiming for (fictional) production code, I want to make sure it will be semantic and accessible. I borrowed the year-axis idea from a comment on Robin's charts, and moved everything into a definition list. Each year is associated with a corresponding percentage in the list:

<dl class="chart">
  <dt class="date">2000</dt>
  <dd class="bar">45%</dd>

  <dt class="date">2001</dt> 
  <dd class="bar">100%</dd>
  
  <!-- etc… -->
</dl>

There are likely other ways to mark this up accessibly, but a dl seemed clean and clear to me – with all the data and associated pairs available as structured text. By default, this displays year/percentage pairs in a readable format. Now we have to make it beautiful.

Grid Setup

I started from Robin's grid, but my markup requires an extra row for the .date elements. I add that to the end of my grid-template-rows, and place my date/bar elements appropriately:

.chart {
  display: grid;
  grid-auto-columns: 1fr;
  grid-template-rows: repeat(100, 1fr) 1.4rem;
  grid-column-gap: 5px;
}

.date {
  /* fill the bottom row */
  grid-row-start: -2;
}

.bar {
  /* end before the bottom row */
  grid-row-end: -2;
}

Normally, I would use auto for that final row, but I needed an explicit height to make the background-grid work properly. Not worth the trade-off, probably, but I was having fun.

Passing Data to CSS

At this point, CSS has no access to the relevant numbers for styling a chart. We have no hooks for setting individual bars to different heights. Robin's solution involves individual class-names for every bar-value, with a Sass to loop to create custom classes for each value. That works, but we end up with a long list of classes we may never use. Is there a way to pass data into CSS more directly?

The most direct approach might be an inline style:

<dd class="bar" style="grid-row-start: 56">45%</dd>

The start position is the full number of grid lines (one more than the number of rows, or 101 in this case), minus the total value of the given bar: 101 - 45 = 56. That works fine, but now our markup and CSS are tightly coupled. With CSS Variables, we can pass in raw data, and let the CSS decide how it is used:

<dd class="bar" style="--start: 56">45%</dd>

In the CSS we can wire that up to grid-row-start:

.bar {
  grid-row-start: var(--start);
}

We've replaced the class-name loop, and bloated 100-class output, with a single line of dynamic CSS. Variables also remove the danger normally associated with CSS-in-HTML. While an inline property like grid-row-start will be nearly impossible to override from a CSS file, the inline variable can simply be ignored by CSS. There are no specificity/cascade issues to worry about.

Data-Driven Backgrounds

As a bonus, we can do more with the data than simply provide a grid-position – reusing it to style a fallback option, or even adjust the bar colors based on that same data:

.bar {
  background-image: linear-gradient(to right, green, yellow, orange, red);
  background-size: 1600% 100%;
  
  /* turn the start value into a percentage for position on the gradient */
  background-position: calc(var(--start) * 1%) 0;
}

I started with a horizontal background gradient from green to yellow, orange, and then red. Then I used background-size to make the gradient much wider than the bar – at least 200% per color (800%). Larger gradient-widths will make the fade less visible, so I went with 1600% to keep it subtle. Finally, using calc() to convert our start position (1-100) into a percentage, I can adjust the background position left-or-right based on the value – showing a different color depending on the percentage.

The background grid is also generated using variables and background-gradients. Sadly, subpixel rounding makes it a bit unreliable, but you can play with the --line-every value to change the level of detail. Take a look around, and see what other improvements you can make!

Adding Scale [without Firefox]

Right now, we're passing in a start position rather than a pure value ("56" for "45%"). That start position is based on an assumption that the overall scale is 100%. In order to make this a more flexible tool, I thought it would be fun to contain all the math, including the scale, inside CSS. Here's what it would look like:

<dl class="chart" style="--scale: 100">
  <dt class="date">2000</dt>
  <dd class="bar" style="--value: 45">45%</dd>

  <dt class="date">2001</dt> 
  <dd class="bar" style="--value: 100">100%</dd>
  
  <!-- etc… -->
</dl>

Then we can calculate the --start value in CSS, before applying it.

.bar {
  --start: calc(var(--scale) + 1 - var(--value));
  grid-row-start: var(--start);
}

With both the overall scale and individual values in CSS, we can manipulate either one individually. Change the scale to 200%, and watch the chart update accordingly:

See the Pen Bar Chart with Sale - no firefox by Miriam Suzanne (@mirisuzanne) on CodePen.

Both Chrome and Safari handle it beautifully, but Firefox seems unhappy about calc values in grid-positioning. I imagine they'll get it fixed eventually. For now, we'll just have to leave some calculations out of our CSS.

Sad, but we'll get used to it. 😉

There is much more we could do, providing fallbacks for older browsers – but I do think this is a viable option with potential to be accessible, semantic, performant, and beautiful. Thanks for starting that conversation, Robin!


More CSS Charts, with Grid & Custom Properties is a post from CSS-Tricks



from CSS-Tricks http://ift.tt/2vubYaF
via IFTTT

No comments:

Post a Comment

Passkeys: What the Heck and Why?

These things called  passkeys  sure are making the rounds these days. They were a main attraction at  W3C TPAC 2022 , gained support in  Saf...