Interactive pyramid chart component with pure CSS and JS

Let’s say you want to create the food pyramid. You know, — eat 11 servings of whole grains, 9 servings of fruits and vegetable, 6 servings of diary and something like 2 servings of grass, oil and sweets.

Our data are:

 
 
const data = [ 
  { value: 11, title: 'Grains' }, 
  { value: 9, title: 'Fruit & Vegetables' }, 
  { value: 6, title: 'Diary & Meat' }, 
  { value: 2, title: 'Grass, Oil & Sweets' } 
] 
 

We want to see something like this:

See the Pen GEeEvY by Mycolaos (@Mycolaos) on CodePen.

The component should be:

  1. Reusable
  2. Visually a triangle with transparent background
  3. Slice proportions should be correct
  4. Responsive
  5. Easily styled
  6. Can be easily implemented with any library or framework

1) Reusable
That’s easy — we create a function that gets data and returns the HTML to be rendered.

2) Visually a triangle with transparent background
The plain HTML elements are rectangles. We will use a very handy CSS property — clip-path to create a mask over our rectangles and show triangles and trapezoids. Also, it allows us to see the background behind our pyramid.

See the Pen WOmZxM by Mycolaos (@Mycolaos) on CodePen.

We will add a clip-path to each slice of the pyramid. The reason is that it allows us to create fancy effects and interactions without broking the pyramid.

3) Slice proportions should be correct
I don’t know if that’s actually obvious, but it’s important, because we have to calculate the pyramid slices sizes.
Also, looking at some existing chart solutions like Highcharts or Amcharts, you can find that the proportions are not respected. Just see those examples with two data entries that are 50/50.

Amcharts


Highcharts

I don’t know why their charts are made this way, but for me it feels wrong to see two figures that are ¼ and ¾ representing a 50/50 relationship.

Here is a correct visualization:

See the Pen dRrVeO by Mycolaos (@Mycolaos) on CodePen.

4) Responsive
It’s easily achievable calculating percentages instead of actual pixels values. This way the pyramid can be of whatever width and height.

5) Easily styled
As the pyramid is simple HTML with minimum necessary CSS, we can easily style it the way we want. Of course, if we want to update the slices height, width or clip-path properties, we should be careful.

6) Can be easily implemented with any library or framework
The component workflow is extremely simple. The core thing it does — is just calculating plain CSS styles for each data entry.

Lets code!

Here is our component workflow

  1. Receives Data.
  2. Calculate slices style.
  3. Return the HTML to render.

As our component is a function, all we need to do is calculate the styles for entries in our data.

I would first write the template.

Template

 
const template = ( 
  `<div class="container"> 
    ${data.map(entry =>  
    `<div class="slice" 
          title=${entry.title} 
          style=${entry.style}> 
    </div>`)} 
  </div>`) 

That’s it! I know, it looks like React, but it’s not it and you can implement it the way you want.
The principle is simple — you create a list of entries with calculated style that is added inline. As said previously, you are calculating only 3 properties, and you can easily override them with !important attribute if you need to (probably wont), so don’t worry about it being inline.

Calculate the style
First of all, we want our pyramid to be scalable in both width and height. So the sizes for our slices will be calculated in percentages.

As our “pyramid” is actually a triangle, and we want to maintain the right area ratio between slices, we need a formula to calculate the correct sizes.
I’ll make it simple, — there is a formula that allows us to find the topmost slice forming a triangle:

K = √S

The S — is our slice area ratio related to the total area of the “pyramid”.
The K — is the coefficient we use to multiply the “pyramid” Base and Height values to find our triangle slice base and height.

As we consider “pyramid” to be always 100% in both width and height, the formula becomes

heightPct = basePct = 100√S

You’ll notice that the bottom slice is actually a trapezoid.
We can’t just calculate the sizes for the first slice, then for the second and so on.

We can calculate only the topmost triangle. That’s why we will accumulate slices areas and save the previous results:

    • Find the topmost slice height and base.
    • Accumulate the area.
    • Save the height result.

    • Get the accumulated area and add the current slice area.
    • Find the accumulated area height and base.
    • Subtract previous height to get the current slice height.
    • Accumulate the area.
    • Save the accumulated height result.

    • Repeat previous.

It’s simple, isn’t it?
Having the height and the base it’s easy to calculate the clip-path rule:

function trapezoidPath (height, topBase, bottomBase) {
  const
    topBaseOffset = (100 - topBase) / 2,
    bottomBaseOffset = (100 - bottomBase) / 2

  const
    topRule = topBaseOffset === 0 ? '50% 0' : `${topBaseOffset}% 0%, ${100 - topBaseOffset}% 0%`,
    bottomRule = `${100 - bottomBaseOffset}% 100%, ${bottomBaseOffset}% 100%`

  return `polygon(${topRule}, ${bottomRule})`
}

We should avoid to change our source data, so we will put styles in another array, here what it will look like:

function Pyramid(data) {
  const styles = calculateStyles(data)
  
  const containerStyle = (
    `height: 100%;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    box-sizing: border-box;`
  )
  
  const template = (
  `<div class="pyramid-chart-container" style="${containerStyle}">
    ${data.map((entry, index) => 
    `<div class="pyramid-chart-slice"
          title="${entry.title}"
          style="${styles[index]}">
    </div>`).join('')}
  </div>`)
  
  return template
}

And this is the full code with the calculations and an example pyramid.

See the Pen VWRQqp by Mycolaos (@Mycolaos) on CodePen.

This is just an example code for fun. Don’t consider this component to be a 100% solution for your needs.

The toughest part here is calculating the sizes. Calculating the clip-path is really simple.

Feel free to play with it and change it to sweet your needs.

Currently, this implementation has some limits:

  1. You can’t play with height of a single slice. You can’t set it 110% at :hover assuming it will be 110% of the slice current height, because its height is a percentage of the containers height.
    But you can play with width, because it’s always 100%.
  2. You can’t set side borders.
  3. Can’t use :before and :after pseudo-element safely, as the clip-path applies on them as well.

Those could be workarounded using more containers, clip-paths or SVG, but it breaks the current simplicity of the components HTML and CSS.

If you find some elegant solution, please let me know!

Get a look on an example of using this component with some nice features and the simplicity of adding them:

  • Show title on hover
  • Space between while hover the pyramid
  • Single slice position offset on hover
  • Remove slices on click
  • Reverse order
  • Smooth transitions
  • Responsivness

Those things are trivial and come for free with HTML and CSS. This an example using my Pyramid implemented with React, because it handles the DOM well.

Stay tuned for new articles!

See the Pen bRZjoJ by Mycolaos (@Mycolaos) on CodePen.

Do we still need vars?

Are you already using let? If you have just started to use it, you could have a question — “should I still use var somewhere?”

Let’s first catch the differences between var and let.

Scope

As we know, var is function scoped, let is block scoped.

Function scope — var is not visible outside, neither let would be:

 
function testScope() { 
  var variable = 'text of var'

  console.log(variable) // "text of var" 
} 

testScope() 
console.log(variable) // ReferenceError: variable is not defined 

Block scope — var is visible in the closest ancestor function scope, but let is not visible outside of the block

if (true) { 
  var variable = 'text of var' 
  let letiable = 'text of let' 

  console.log(variable) // "text of var" 
  console.log(letiable) // "text of let" 
} 

console.log(variable) // "text of var" 
console.log(letiable ) // ReferenceError: letiable is not defined 

Why is let better? It’s more declarative and less error prone.
If you declare a var inside an if block, it could still be used outside of it. There’s no warranty that the code below will not rely on it. Yes, your intention could be to not use it outside, but still leaving the possibility to do this leads to unexpected bugs.
Immagine that you are reading someone else code

if (flag) {  
  var msg =  "Be aware of hoisting and scope rules";  
  /* some other code */ 
} 

How possibly can you know where else that msg variable could or even should be used? With let you are sure that it cannot be used outside of the if block.

There’s an interesting bonus for using let inside for loops.
Let’s consider this example first:

var i = 0 

while (i < 5) { 
  let index = i 

  setTimeout(function () { 
    console.log(index) // will show 0, 1, 2, 3, 4 
  }) 

  i++ 
} 

It will not work with var index, the print would be “4, 4, 4 ,4, 4”.

With a for loop, you don’t need any intermediary variable.

for (let i=0; i<5; i++) { 

  setTimeout(function () { 
    console.log(i) // will show 0, 1, 2, 3, 4 
  }) 
} 

Attention, please. It works because inside the for loop the let variable in the initialization expression is redeclared at each iteration with the resulting value of the previous iteration. It’s an ES6 feature made for us to manage easier for loops.

// explaining the first iteration

for (let i=0; i<5; i++) { 
  // the lexical variable `i` equals to `0`

  setTimeout(function () { 
    console.log(i) // will show 0, 1, 2, 3, 4 
  }) 

  // after the expression statement is executed
  // the final expression `i++` is evaluated and `i` value becomes `1`
  // `let i` is redeclared assigning it the result of the final expression `let i = 1`
} 

As you expect, let is not visible outside the loop, but it can be redeclared inside it. That’s because the initialization expression is in an outer block for the statement expression.

for (let i=0; i<5; i++) { 
  let i = 'same value'

  setTimeout(function () { 
    console.log(i) // will show "same value", "same value", "same value", "same value", "same value" 
  }) 
} 
Global scope

Unlike var, the let are not attached to the window object.

var variable = 'var text' 
let letiable = 'let text' 

console.log( 
  window.variable, // "var text" 
  window.letiable // undefined 
) 

You could say, well, I can use var to save properties on window object!
That’s not a good practice! Be explicit — window.UserModule = {}. Sure, remember that we all should avoid using window object for that puproses, because it’s accessible from everywhere! Name collisions are guaranteed! Use ES6 modules instead.

Hoisting

Beside the declaration, which is hoisted for all the declarations in ES6, a varis initialized with undefined, but let is not initialized.

variable = 'This is a hoisted and initialized variable.' 
 
console.log(variable) // "This is a hoisted and initialized variable." 

/** a lot of code */ 
 
var variable; 

The var is initialized. That’s why you can access it.

Look at this example:

var variable = 'This is outer variable.' 
 
function hoistingExample () { 
  variable = 'This is inner variable.' // seems like we are changing our outer variable value, right?

  console.log(variable) // "'This is inner variable." 

  /** a lot of code */ 
 
  var variable; 
} 

hoistingExample() 
console.log(variable) // "'This is outer variable." 

As var has function scope, you will misunderstand the code. The first thing you’ll figure out is that variable in outer scope will be reassigned when you invoke the function, but it will not.

A let variable is uninitialized before the let statement. This leads us to a better and readable code.

letiable = 'This variable cannot be accessed.' // ReferenceError: letiable is not defined 
 
let letiable; 

Isn’t it great?

Temporal Dead Zone

The hoisting explaining above lead us here. The Walking Dead Zone! It’s the zone inside a code block before a let statement. You cannot access a lexical variable let before its statement is evaluated!

Here is an important example to see:

 
let str = 'outer text' 

if (true) { 
  let str= str // ReferenceError: str is not defined 
} 

The second, inner let str is hoisted. This means that it becomes the variable which is referenced inside the if scope when you try to access it. That means that we cannot more access the outer let str. The outer str is cut out for us, so let’s discuss the inner let str.

The inner str it’s not initialized yet. So, when you write let str = str you are trying to assign it itself. Remember that the assignment is evaluated right-to-left. That’s why we get the error here. The str on the right is evaluated first, and it’s not initialized yet. That’s it!

Here another example with a for...of to be aware of:

 
let a = ['lexical', 'variable', 'example'] 

for (let a of a) { // ReferenceError: a is not defined 
  /** some code */ 
} 
Declaration

A var can be redeclared, a let cannot!

 
var variable = 'var text' 
var variable = 'changed var text' 
console.log(variable) // "changed var text" 
 
let letiable = 'let text' 
let letiable = 'changed let text' // SyntaxError: Identifier 'letiable' has already been declared 

As in previous examples, more declarative, less error prone!

Style

Around the web you can find advices to keep using var for style purpose — declaring them for function scope or even global scope, keep the let for block scopes.
Well, having “lets” and still using “vars” it’s like shooting yourself in the foot! Using var means keeping dangers to buggy behaviour mainly due to its hoisting and scope implementations.
Actually, using let force us to write a better code both stylistically and less error-prone. It forces us to write more declarative, easier to understand and more safe code.

A great thing — is simplicity. Simplicity to understand and to use. Let’s safe us from all those issues and misunderstandings. We should write instructions that make things alive, and not losing time for solving bugs that make them dead!

Conclusion

If you are still in doubt, or feel some inner opposing to shift completly to using let — you should know that you’re not alone. I bet that even best and experienced programmers feel that (or did it).

We oppose to new things! It’s natural to us. But we adapt really fast, so a couple of scripts with let — and you will feel like you was using it the whole life!

Wait a moment…
And the final answer? Do we still need vars? Sure! But, I’d rather say that vars need us. In the legacy code, of course. Beside that, I don’t see a single reason you should use vars in your new modern ES6+ code. If you know any, please let us know, — below in the comments, in a blog post, or even in a youtube advertising — anything!

P.S.: Yeah, I know, there could be a discussion about using const almost everywhere and keep using let as low as possible, but that’s another topic! Stay tuned!