🥧 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
Live demo: https://pie-meister.github.io
This PieMeister Web Component is 999 Bytes (yes Bytes)
🥧 What Web technologies are required to draw a Pie Chart in 2021?
🥧 HTML, so many moons ago
Had you asked me the question, when I first learned HTML,
I would have written:
<pie-chart>
<slice color="green">HTML 100%</slice>
</pie-chart>
🥧 Required technologies in 2021
Alas, using any Charting Library, the modern truth answer is more like:
<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,
}}
/>
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...
🥧 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, I more and more stick to standard technologies only.
Using modern W3C Standard Web Components my design today in 2021 is:
<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>
🥧 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.
🥧 What Web Developers will learn in this post
Create a static Pie Chart with SVG (a core browser technology)
Create a (very basic, yet powerful)
<pie-chart>
Web Component to write Pie Charts with semantic HTMLNO Frameworks, NO Libraries required!
<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
tosize
becausevalue
is a programmers/maths term.size
better expresses what the slice doescolor
becamestroke
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 attributeFor demonstration purposes I have kept
<pie-chart>
Web Component functionality as minimal as possibleThe use of the unknown element
<slice>
instead of<pie-slice>
is shortly discussed at the bottom of this post. It warrants its own post, discussing pros and cons.
✔️ Web Component technologies used:
-
Custom Elements API
- 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
- ::part CSS Selector - shadowParts
- slots
- slotchange Event
- templates
- observedAttributes
-
lifecycle callbacks - also see this diagram
- constructor
- attributeChangedCallback
- adoptedCallback
- disconnectedCallback
🥧 Step #1 - Designing the pie
A Pie Slice can easily be created with the SVG circle element:
<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 <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.
Each stroke-dashoffset
is increased by 25 units, to make the Pie Chart start drawing at the top.
All SVG required for the static Pie Chart is:
<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>
🥧 Step #2 - Creating the <pie-chart>
Web Component
<pie-chart>
SVG Helper methods
Make working with SVG easier (can be copied to any SVG project):
included in the JSFiddle source code 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 DOMcreateSVGCircle ( { configuration })
creates a SVG<circle>
from all configuration parameters
The custom HTML <pie-chart>
is replaced with SVG, using the Web Components Custom Elements API
<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
andstroke
and a label - Each slice becomes a SVG <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:
slice {
display: block
}
slice::before {
content: attr(size) "% "
}
output:
90% HTML
1% JavaScript
9% CSS
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)
🥧 The Custom Element API bones of the <pie-chart>
Web Component
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 DOMthus
setTimeout
(or anything that waits till the Event Loop 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>
- creates an
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!
🥧 Enhancements with more Web Component techologies
Disclaimer: Code Snippets are not full working code, presented to inspire you only.
shadowDOM
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:
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
this.replaceWith(this.svg); // replace <pie-chart> with <svg>
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:
<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:
templates
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:
<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>
)
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
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
<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>
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
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:
<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
::part(title){
text-transform: capitalize;
background: beige;
border-bottom: 2px solid green;
}
lifecycle callbacks - also see this diagram
constructor
Called onceconnectedCallback
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 attributeadoptedCallback
When moving Elements between multiple documentsdisconnectedCallback
Called when the Element is removed from the DOM
🥧 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 ElementThe
<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 ElementFor more pro and cons, see: Unknown Elements for better semantic HTML
🥧 Some afterthoughts
Trying to do a complete Web Components course in one Dev post is impossible
SVG Elements (like
<circle>
can not (yet) be extendedThe Custom Elements API only allows extending
HTMLElement
. Proper name: Autonomous ElementsExtending (Customized Built-In) HTML Elements like
<button>
is not supported in Safari (and won't be)An Element
<pie-slice>
would allow forobservedAttributes
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/classCopy the JSFiddle, play and learn
https://jsfiddle.net/WebComponents/3kwn4f7e/
Top comments (2)
Really like the use of HTML elements as "data model" here, this is something I spend a lot of time trying to get working well. For features like this, it is such a no brainer and I'm happy to see it in use over the
data="[]"
example you shared. I've been playing with the Reactive Controller paradigm proposed by the Lit team in their pending announcement to position this as something a little more reusable: webcomponents.dev/edit/F4jBbQpeMSu... would love to hear your thoughts!Side note: I was struck by how much time I spent confirming for myself that
<slice>
is not a native HTML element. I'd say, beyond the possibility of leveragingobservedAttributes
, that it's possible a custom element would reduce confusion around what's natively available vs added via your code. Was certainly cool to see the simplicity, but wonder how others with less custom element experience actually consume that data.It depends on what the team needs. If you do not have the JS team, than HTML opens up a lot of possibilities.
You can pick anyone off the street, hand them the Web Component documentation and they will be productive within a day.
THAT is what made the Web huge and great in the 90s. There were plenty of other technologies available. Before I became a Web-Developer, I was a Gopher-Developer for years.
"other technologies" is why I am reluctant to use Lit. There are still too many alternatives.
I had to ditch 500K (euros.. not dollars) of MooTools development in 2009, because my CTO predecessor had allowed two 24 year old contractors to select the technology for the CEO his pet-project.
When I asked "What about jQuery?" they answered: "What is that?"
(And when we CTO and CFO in Good Cop Bad Style put the CEO on the spot...
the CTO was fired; and the CFO quit her job the next day)
It learned me the game is not played on the board...
Lit is Google and the WHATWG is Google, Mozilla, Microsoft and Apple.
And both Ryosuke Niwa (Apple) and Anne van Kesteren (Mozilla) have shown in their responses, it is no longer the V0 party where Google throws something against the wall, hoping it sticks.
Ofcourse progress is slow with 4 parties having to agree; but its slow and sure
I have never seen co-operation like this between Internet companies in the past 31 years.
Now, I am in the position to spend that extra 10%? 15%? 20%? time to do everything with Vanilla.
I can ask clients: "Do you want software that will run for sure for the next 25 JavaScript years?"
And have the financial freedom to decline clients that don't.
Because I learned one thing in all those years:
Software needs to be maintained, and developers spent 50% trying to understand previous developers code.
That's why I plea for HTML skills, the power of the Web to everyone who can type and save a document.
But is it really 10% to 20% extra?
Guess how much time it took me to restore the Drupal blog I used 17 years ago;
because stupid me preferred a better technical solution over WordPress
I have my "Game Board" pet-project/Web Component test-project hexedland.com
that is going to run for the next 25 years without problems.
Because I am building with ZERO dependencies... well only one.. The Web
And since the W3C handed the keys to the WHATWG in 2019; that future is in the hands of 4 companies... and to date, they haven't invited Facebook.
So I am no longer wasting energy on React; not even answering React questions on StackOverflow. (I try to answer every Web Component question, because it makes me learn)
And that Drupal site with all my old blogs? I stopped trying after 2 hours.
My next post will detail on the pros and cons of UNknown Elements.
Or the risk of using (new) WHATWG defined Elements (
<progress>
is an existing HTML tag)The Base Class is the
<pie-chart>
from the first post: