What Web technologies are required to draw a Pie Chart in 2021? (spoiler alert: a standard Web Component will do) 🥧
Tags: webcomponents, webdev, svg, javascript
Published: 2021-04-16 11:41:18

---
title: What Web technologies are required to draw a Pie Chart in 2021? (spoiler alert: a standard Web Component will do) 🥧 
published: true
description: Using Web Components to draw Pie Charts with semantic HTML
tags: WebComponents,WebDev,svg,JavaScript
cover_image: https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/63359009-cca5-43fe-bc88-c928d5dbac4d/01-html-baby-opt.jpg
---

# 🥧 TL;DR; 

* **standard** Web Components can provide **semantic HTML** to _Web Writers_

* eager for code? Here is the complete JSFiddle https://jsfiddle.net/WebComponents/3kwn4f7e/ 

* Follow up post: [Using UnknownHTMLElements for better semantic HTML](https://dev.to/dannyengelman/web-components-using-unknownhtmlelements-for-better-semantic-html-5d8c)

* Live demo: https://pie-meister.github.io
This PieMeister Web Component is 999 Bytes (yes Bytes)

<hr>

# 🥧 What Web technologies are required to draw a Pie Chart in 2021?

<hr>

## 🥧 HTML, so many moons ago

Had you asked me the question, *when I first learned HTML*,  
I would have written:

```html
<pie-chart>
  <slice color="green">HTML 100%</slice>
</pie-chart>
```
![](https://i.imgur.com/15QOyf3.png)


## 🥧 Required technologies in 2021

Alas, using **any** Charting Library, the modern truth answer is more like:

```html
     <PieChart
        data={[
                { title: 'HTML', value: 10, color: 'green' },
                { title: 'JavaScript', value: 75, color: 'red' },
                { title: 'CSS', value: 15, color: 'blue' },
        ]},
        radius={PieChart.defaultProps.radius - shiftSize}
        segmentsShift={(index) => (index === 0 ? shiftSize : 0.5)}
        label={({ dataEntry }) => dataEntry.value}
        labelStyle={{
          ...defaultLabelStyle,
        }}
      />
```

![](https://i.imgur.com/1FToXC6.png)

Compared to my early Web adventures, you almost have to be a rocket-scientist to get a Pie Chart in a Web Page; not to mention all the skills and tools required to *start* with a Page in the first place; and then those build steps...


<hr>

## 🥧 HTML powered by a Web Component

Since learning HTML in 1994, I have used many _Frameworks_ and _Libraries_, and payed the price multiple times for using technologies that eventually died.

Now the WHATWG, since 2019, is [in complete control of the Web HTML standard](https://techxplore.com/news/2019-06-w3c-whatwg-agreement-version-html.html), I more and more stick to **standard** technologies only.

Using modern [W3C Standard Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) my design today in 2021 is:

```html
<pie-chart>
  <slice size="90" stroke="green">HTML</slice>
  <slice size="1"  stroke="red">JavaScript</slice>
  <slice size="9"  stroke="blue">CSS</slice>
</pie-chart>
```

![](https://i.imgur.com/MWUIQVe.png)

<hr>

## 🥧 HTML is still great!

Mind you, I am slightly biased towards HTML because JavaScript **_and_** CSS **_did not exist_** when I started with Web Development.

HTML is the primary technology that made the Web great and HUGE. 
Everyone with basic (WordPerfect) Word Processing skills could create Web pages in those days. 
My retired mum did, my 6 year old niece did.

Everyone with **basic HTML** skills **_CAN_** create a Pie Chart **in 2021**

Modern Web Development does **not** have to be all about _HTML-in-JS_ and _CSS-in-JS_; only **Developers** are comfortable with. 

We can empower a _new generation_ Web Writers with **_semantic_ HTML**, 
by creating **Web Components** for them.

<hr>

# 🥧 What Web _Developers_ will learn in this post

* Create a **static** Pie Chart with [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG) (a core browser technology)

* Create a (very basic, yet powerful) ``<pie-chart>`` Web Component to write Pie Charts with **_semantic_ HTML**

* NO Frameworks, NO Libraries required!


```html
<pie-chart>
  <slice size="90" stroke="green">HTML</slice>
  <slice size="1"  stroke="red">JavaScript</slice>
  <slice size="9"  stroke="blue">CSS</slice>
</pie-chart>
```
* I changed ``value`` to ``size`` because ``value`` is a programmers/maths term. ``size`` better expresses what the slice does

* ``color`` became ``stroke`` because that is the **stroke-color** *attribute name* for SVG Elements (see below) and I don't want to confuse users with 2 different names for the same attribute

* For demonstration purposes I have kept ``<pie-chart>`` Web Component **_functionality_** as minimal as possible

* The use of the _unknown_ element ``<slice>`` instead of ``<pie-slice>`` is shortly discussed at the bottom of this post. [It warrants its own post](https://dev.to/dannyengelman/web-components-using-unknownhtmlelements-for-better-semantic-html-5d8c), discussing pros and cons.

<hr>

### ✔️ Web Component technologies used:

* [Custom Elements API](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)
    * connectedCallback

### ❌ Web Component technologies **NOT** used:

The last section of this post describes how these technologies can **enhance** a ``<pie-chart>`` v2.0 Web Component.

* [shadowDOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM)
* [::part CSS Selector - shadowParts](https://developer.mozilla.org/en-US/docs/Web/CSS/::part)
* [slots](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots)
* [slotchange Event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/slotchange_event)
* [templates](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots)
* [observedAttributes](https://javascript.info/custom-elements#observing-attributes)
* [lifecycle callbacks](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks) - also see [this diagram](https://andyogo.github.io/custom-element-reactions-diagram/)
    * constructor
    * attributeChangedCallback
    * adoptedCallback
    * disconnectedCallback

<hr>

# 🥧 Step #1 - Designing the pie

A Pie **Slice** can easily be created with the SVG **circle** element:

```html
  <circle stroke="green" stroke-dasharray="10 90" 
          pathLength="100" 
          cx="50%" cy="50%" r="25%" fill="none" stroke-width="50%">
  </circle>
```

* Key is the ``pathLength="100"`` attribute, declaring all calculations on the SVG element consider the &lt;circle> 100 **units** in length.

* Then ``stroke-dasharray="10 90"`` says:
   * draw a green stroke for 10 units
   * add whitespace for 90 units

**Multiple slices** are drawn with an extra ``stroke-dashoffset`` for **each slice**. The ``stroke-dashoffset`` value is the **subtracted** total of all **previously drawn** slices.

{% jsfiddle https://jsfiddle.net/WebComponents/fvnuLam0/ result,html %}

Each ``stroke-dashoffset`` is **increased** by 25 units, to make the Pie Chart start drawing at the top.

{% jsfiddle https://jsfiddle.net/WebComponents/d269yu5q/ result,html %}

### All SVG required for the *static* Pie Chart is:

```html
<svg viewBox="0,0,200,200">
  <circle stroke="green" stroke-dasharray="10 90" stroke-dashoffset="25" 
          pathLength="100" 
          cx="50%" cy="50%" r="25%" fill="none" stroke-width="50%">
  </circle>
  <circle stroke="blue" stroke-dasharray="25 75" stroke-dashoffset="15" 
          pathLength="100" 
          cx="50%" cy="50%" r="25%" fill="none" stroke-width="50%">
  </circle>
  <circle stroke="red" stroke-dasharray="65 35" stroke-dashoffset="-10" 
          pathLength="100" 
          cx="50%" cy="50%" r="25%" fill="none" stroke-width="50%">
  </circle>
</svg>
```

<hr>

# 🥧 Step #2 - Creating the ``<pie-chart>`` Web Component

<hr>

### ``<pie-chart>`` SVG Helper methods

Make working with SVG easier (can be copied to any SVG project):

included in the [JSFiddle source code](https://jsfiddle.net/WebComponents/3kwn4f7e/) as **Base** ``class SVGMeisterElement extends HTMLElement``

* __createSVGElement__ ( { tag , [attributes] , [innerHTML] , [append] } ) 
creates any SVG Element in the SVG NameSpace, 
optional parameters set all attributes, innerHTML and append child Elements 
The element is returned, **not** added to the DOM  

* __createSVGCircle__ ( { configuration }) 
creates a SVG ``<circle>`` from all configuration parameters

<hr>

The custom HTML ``<pie-chart>`` is **replaced with** SVG, using the Web Components **Custom Elements API**

```html
<pie-chart>
  <slice size="90" stroke="green">HTML</slice>
  <slice size="1"  stroke="red">JavaScript</slice>
  <slice size="9"  stroke="blue">CSS</slice>
</pie-chart>
```

* Each slice provides a ``size`` and ``stroke`` and a **label**
* Each slice becomes a SVG &lt;circle>


## Web Component notes

* The ``<pie-chart>`` Web Component is created **once**, 

* an HTML _writer_ is **never** confronted with JavaScript code.

* Contrary to traditional libraries, Custom Elements can also be defined **AFTER** usage in the DOM. 
_existing_ Elements will automagically _upgrade_ once the **Custom Elements API** defines the ``<pie-chart>`` Web Component.

* If the ``<pie-chart>`` is **not** defined (yet) (or JavaScript is disabled) 
CSS creates a decent fallback: 
 
  ```css
    slice {
      display: block
    }
    slice::before {
      content: attr(size) "% "
    }
  ```
output:
  ```html
  90% HTML
  1% JavaScript
  9% CSS
  ```

<hr>

I have decided not to break up this post in two. 

Posting the second part **next week** has no benefit.

If you a are bit overwhelmed by the first part; go grab a a cup of coffee 
(or continue next week)

![](https://cdn.vortala.com/childsites/uploads/71/files/coffee-brain.jpg)


<hr>

## 🥧 The Custom Element API _bones_ of the ``<pie-chart>`` Web Component

```js
customElements.define( "pie-chart" ,
  class extends SVGMeisterElement { // see JSFiddle, contains the SVG code
    connectedCallback() { // fires on the OPENING <pie-chart> tag
      // wait till <slice> elements are available in the DOM:
      setTimeout(() => this.renderPieChart()); 
    }
    renderPieChart() {
      // ... some configuration variables here, see source code
      this.svg = this.createSVGElement({ // create <svg> Element
        tag: "svg",
        attributes: {
          viewBox: `0 0 ${this.width} ${this.height}`,
        },
        innerHTML: `...`, // default SVG innerHTML content
        append: this.createSlicesWithCircles() // append <circle>s
      });
      this.replaceWith(this.svg); // replace <pie-chart> with <svg>
      this.slices.forEach((slice) => { // loop all <cicle> elements
        const sliceMiddlePoint = slice.getPointAt(this.labelPosition);
        // ... append label
      });
    }
    createSlicesWithCircles() { // process all <slice> inside <pie-chart>
      let offset = 25;
      const slices = [...this.querySelectorAll("slice")];
      // all <slice> elements are returned as <circle>
      this.slices = slices.map((slice) => { 
        // read size from <slice size="90">
        const size = parseFloat(slice.getAttribute("size")); 
        let circle = this.createSVGCircle({ // SVG helper method
          size,
          offset,
          stroke: slice.getAttribute("stroke") // read stroke color
        });
        offset -= size; // every slice at next offset
        return circle;
      });
      return this.slices;
    }
  });
```

## Code notes:

* The standard ``connectedCallback`` method is called the moment the **opening** ``<pie-chart>`` **_tag_** is appended to the DOM

* thus ``setTimeout`` (or anything that waits till the [Event Loop](https://www.youtube.com/watch?v=8aGhZQkoFbQ&vl=en) is done) is required to **wait** till all ``<slice>`` elements are parsed by the Browser engine. (see StackOverflow post: https://stackoverflow.com/a/70952159/2520800)

* the ``renderPieChart`` method 
   * creates an ``<svg>`` 
   * reads all ``<slice>`` and adds them as ``<circle>``

* **again**: It does **not** matter **_when_** the Web Component is *defined*. 
Above code can be executed **before** or **after** page load.

### Full working code:

* No Frameworks! No Libraries! No External code!

{% jsfiddle https://jsfiddle.net/WebComponents/3kwn4f7e/ html,result,js,css %}

<hr>

# 🥧 Enhancements with **_more_** Web Component techologies

Disclaimer: Code Snippets are **not** full working code, presented to inspire you only.

## [shadowDOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM)

**Replacing** HTML is a bit crude and not flexible. With shadowDOM the **SVG** can be **displayed**, and the ``<pie-chart>`` HTML will **remain** active **but invisible** in the DOM (then called **_lightDOM_**)

The Custom Elements API code can be extended with:

```js
constructor() {
  // Documentation that says "use super first in the constructor" is wrong
  let svg = `<svg>...</svg>`; 
  super() // sets and returns this scope
    .attachShadow({mode:"open"}) // sets and returns this.shadowRoot
    .innerHTML = svg;
  this.svg = this.shadowRoot.querySelector("svg");
}
```

then the line in the ``renderPieChart`` method can be **deleted**

```js
this.replaceWith(this.svg); // replace <pie-chart> with <svg>
```

## [slots](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots)

SLOTs are placeholders for more complex user-defined content, while still keeping the Web Component in control of **how and where** the slot content is displayed. With ``title`` and ``description`` slots defined in the Web Component a ``<pie-chart>`` 2.0 could look like:

```html
<pie-chart>
  <div slot="explanation">
    ... any HTML content here
  </div>
  <h1 slot="title">Web Technologies</h1>
  <slice size="90" stroke="green">HTML</slice>
  <slice size="1"  stroke="red">JavaScript</slice>
  <slice size="9"  stroke="blue">CSS</slice>
</pie-chart>
```

See ``<template>`` below **where** the slot content is used

Related:
* [StackOverflow: (long answer) ::slotted & styling slots](https://stackoverflow.com/questions/61626493/slotted-css-selector-for-nested-children-in-shadowdom-slot/61631668#61631668)
* [slotchange Event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/slotchange_event)

## [templates](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots)

Templates are **re-usable _inert_ snippets** of HTML. Can be created in HTML or by Script. Allowing very flexible creation, styling and configuration of (multiple) Web Components:

```js
<template id="PIE-CHART">
  <style>
    /* CSS */
  </style>
  <slot name="title">A Pie Chart<!-- replaced with userdefined content --></slot>
  <svg>
    <defs>
      <filter x="0" y="0" width="1" height="1" id="label">
        <feFlood flood-color="#222" flood-opacity="0.4"/>
         <feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
      </filter>
    </defs>
  </svg>
  <slot name="description"><!-- userdefined content goes here--></slot>
</template>
```

A ``constructor`` can read Templates (in this example an **existing** DOM ``<template>``)

```js
constructor() {
  // Documentation that says "use super first in the constructor" is wrong
  let template = (id) => this.getElementById(id).content.cloneNode(true);
  super() // sets and returns this scope
    .attachShadow({mode:"open"}) // sets and returns this.shadowRoot
    .append( template( this.nodeName( this.nodeName);
  this.svg = this.shadowRoot.querySelector("svg");
}
```

## [observedAttributes](https://javascript.info/custom-elements#observing-attributes)

Normal HTML behaviour allow attribute changes to affect what the HTML does/displays.

In the Custom Elements API you can specify which attributes enforce this behaviour

```html
<pie-chart offset="10">
  <slice size="90" stroke="green">HTML</slice>
  <slice size="1"  stroke="red">JavaScript</slice>
  <slice size="9"  stroke="blue">CSS</slice>
</pie-chart>
```

```js
static get observedAttributes(){
  return ["offset"]
}

attributeChangedCallback( name, oldValue, newValue ){
  if( name=="offset"){
    this.renderPieChart()
  }
}
```

Now on every ``offset`` change the Pie Chart will be rendered with new settings

## [::part CSS Selector - shadowParts](https://developer.mozilla.org/en-US/docs/Web/CSS/::part)

Since shadowDOM is **protected** from global CSS manipulation. 
Specified **_parts_** of the Web Component shadowDOM can be exposed to the 'outside world' for global CSS configuration.

Font-styles and CSS properties **do** _cascade_ into shadowDOM; see: 

```html
<template id="PIE-CHART">
  <slot part="title" name="title">
     A Pie Chart<!-- replaced with userdefined content -->
  </slot>
</template>
```

global CSS will now style **all** titles in **all** ``<pie-chart>`` elements

```css
::part(title){
  text-transform: capitalize;
  background: beige;
  border-bottom: 2px solid green;
}
```

## [lifecycle callbacks](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks) - also see [this diagram](https://andyogo.github.io/custom-element-reactions-diagram/)


 * constructor 
 Called once
    
 * connectedCallback 
 Called on the __opening__ tag of the Web Component, and every time the Element is **moved** in the DOM (think drag-drop like situations)
    
 * attributeChangedCallback 
 Called by every update of an **observed** attribute
    
 * adoptedCallback 
 When moving Elements between multiple documents
    
 * disconnectedCallback 
 Called when the Element is removed from the DOM

<hr>

# 🥧 To ``<slice>`` or _not_ to ``<pie-slice>``, that is the question

* ``<slice>`` is not a valid HTML Element, a Linter will complain, but its a valid XML/DOM Element

* The ``<pie-chart>`` Web Component works fine with ``<slice>``.

* Nor is it a (defined) Custom Element, which always require at minimum one hyphen (-) in the tagName to distinguish it from (future) HTML Elements. 

* So ``<pie-slice>`` is also an option, but doesn't have to be a **defined** Custom Element

* For more pro and cons, see: [Unknown Elements for better semantic HTML](https://dev.to/dannyengelman/web-components-using-unknownhtmlelements-for-better-semantic-html-5d8c)

# 🥧 Some afterthoughts

* Trying to do a complete Web Components course in one Dev post is impossible

* SVG Elements (like ``<circle>`` can not (yet) be extended 

* The Custom Elements API only allows extending ``HTMLElement``. Proper name: **Autonomous Elements**

* Extending (_Customized Built-In_) HTML Elements like ``<button>`` is not supported in Safari (and won't be)

* An Element ``<pie-slice>`` would allow for ``observedAttributes`` to work; something that can otherwise only be accomplished by applying the MutationObserver API.

* I didn't go into ES ``Class`` OOP functionality. See: https://javascript.info/class

* Copy the JSFiddle, **play and learn** 
https://jsfiddle.net/WebComponents/3kwn4f7e/

<br>
<hr>
<br>

{% jsfiddle https://jsfiddle.net/WebComponents/62f1eLay result,html,js %}