nevera
Version:
Minimal 3kb charts as web components
109 lines (95 loc) • 2.32 kB
text/typescript
import { html, svg, render } from "lit-html";
import { attachEvents } from "../utils.js";
type Slices = {
value: number;
deg: number;
fill?: string;
}[];
type Options = { total: number };
const getSvgTemplate = (slices: Slices) => html`<svg
width="100%"
height="100%"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
style="transform: rotate(-0.25turn)"
>
<circle
r="10"
cx="10"
cy="10"
class="bg"
shape-rendering="geometricPrecision"
style="fill: var(--nev-bg);"
/>
${slices.map(
(slice) => svg`<circle
r="5"
cx="10"
cy="10"
fill="transparent"
stroke="${slice.fill}"
stroke-width="10"
stroke-dasharray="${(slice.value * 31.42) / 100} 31.42"
style="transform-origin: 10px;transform: rotate(${slice.deg}deg)"
/>`
)}
</svg>`;
type Data = Array<{
key: string;
value: number;
fill?: string;
}>;
const getChartData = (data: Data, options: Options) => {
const dataTotal = data.reduce((acc, d) => acc + d.value, 0);
const total = options.total || dataTotal;
let progress = 0;
const slices = data.map((d) => {
const step = (d.value / total) * 100;
const res = {
value: step,
deg: progress * 3.6,
fill: d.fill,
};
progress += step;
return res;
});
return slices;
};
class NeveraPieChart extends HTMLElement {
static observedAttributes = ["total"];
constructor() {
super();
}
_data?: Data = undefined;
set data(val: Data | undefined) {
this._data = val;
this.renderSvg();
}
get data() {
return this._data;
}
codeEl: HTMLElement | null = null;
styleEl: HTMLStyleElement | null = null;
connectedCallback() {
this.renderSvg();
setTimeout(
() => attachEvents(this.querySelector("svg")!, "circle:not(.bg)", this.data!, this),
10
);
}
renderSvg() {
if (this.data) {
const options = {
total: Number(this.getAttribute("total")),
};
const slices = getChartData(this.data, options);
render(getSvgTemplate(slices), this);
}
}
attributeChangedCallback(oldValue: string | null, newValue: string | null) {
if (oldValue !== newValue) {
this.renderSvg();
}
}
}
if (!customElements.get("nev-piechart")) customElements.define("nev-piechart", NeveraPieChart);