Skip to content

Instantly share code, notes, and snippets.

@erikvullings
Created February 23, 2024 10:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erikvullings/6afaf251845bfce5d54b7074153b542b to your computer and use it in GitHub Desktop.
Save erikvullings/6afaf251845bfce5d54b7074153b542b to your computer and use it in GitHub Desktop.
Mithril donut or pie chart in TypeScript

Mithril Donut chart component

Creates a simple donut chart in mithril (v2.2.2) using TypeScript, including a legend and tooltips. A playground is provided in flems.

image

Example usage

// Example usage:
const data: DonutChartSlice[] = [
    { label: 'Future Projects', value: 23, color: '#ff7f0e' },
    { label: 'Projects started', value: 21, color: '#1f77b4' },
    { label: 'Projects closed', value: 20, color: '#2ca02c' }
];

const innerRadius = 50; // Use 0 for a pie chart.
const outerRadius = 100;

m.mount(document.body, {
    view: () => m(DonutChart, { data, innerRadius, outerRadius })
});
/* Donut Chart CSS */
.donut-chart-container {
display: flex;
align-items: center;
}
.donut-chart {
flex: 1;
}
.donut-chart-tooltip {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 255, 0.8);
padding: 10px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: none;
}
.donut-chart-tooltip.active {
display: block;
}
.donut-chart-tooltip span {
display: block;
}
.tooltip-label {
font-weight: bold;
}
.tooltip-value {
color: #888;
}
/* Legend CSS */
.legend {
margin-left: 20px;
font-size: 14px;
display: flex;
flex-direction: column;
}
.legend-item {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.legend-color {
width: 10px;
height: 10px;
margin-right: 5px;
}
.legend-label {
color: #333;
}
/* Total Value CSS */
.total-value {
font-size: 3rem;
fill: #333;
}
import m, { FactoryComponent } from 'mithril';
export interface DonutChartSlice {
label: string;
value: number;
color: string;
}
export interface DonutChartAttrs {
data: DonutChartSlice[];
innerRadius?: number;
outerRadius?: number;
}
export const DonutChart: FactoryComponent<DonutChartAttrs> = () => {
return {
view: ({ attrs }) => {
const { data, innerRadius = 50, outerRadius = 100 } = attrs;
// Calculate total value
const totalValue = data.reduce((acc, slice) => acc + slice.value, 0);
// Calculate angles
let startAngle = 0;
let endAngle = 0;
const arcs: {
d: string;
fill: string;
label: string;
value: number;
cumulativeValue: number;
}[] = [];
data.forEach((slice) => {
const angle = (slice.value / totalValue) * 360;
endAngle = startAngle + angle;
const startAngleRad = ((startAngle - 90) * Math.PI) / 180;
const endAngleRad = ((endAngle - 90) * Math.PI) / 180;
const x1 = Math.cos(startAngleRad) * outerRadius;
const y1 = Math.sin(startAngleRad) * outerRadius;
const x2 = Math.cos(endAngleRad) * outerRadius;
const y2 = Math.sin(endAngleRad) * outerRadius;
const largeArcFlag = angle <= 180 ? '0' : '1';
const pathData = [
`M ${x1} ${y1}`,
`A ${outerRadius} ${outerRadius} 0 ${largeArcFlag} 1 ${x2} ${y2}`,
`L ${Math.cos(endAngleRad) * innerRadius} ${Math.sin(endAngleRad) * innerRadius}`,
`A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} 0 ${
Math.cos(startAngleRad) * innerRadius
} ${Math.sin(startAngleRad) * innerRadius}`,
'Z',
].join(' ');
arcs.push({
d: pathData,
fill: slice.color,
label: slice.label,
value: slice.value,
cumulativeValue:
startAngle === 0 ? slice.value : arcs[arcs.length - 1].cumulativeValue + slice.value,
});
startAngle = endAngle;
});
// Legend
const legendItems = data.map((slice) =>
m('div.legend-item', [
m('div.legend-color', { style: { backgroundColor: slice.color } }),
m('div.legend-label', slice.label),
])
);
return m('.donut-chart-container', [
m(
'svg.donut-chart',
{ viewBox: `-${outerRadius} -${outerRadius} ${outerRadius * 2} ${outerRadius * 2}` },
arcs.map((arc) =>
m(
'g',
{ title: `${arc.label}: ${arc.value}` },
m('path', { d: arc.d, fill: arc.fill }),
m('title', `${arc.label}: ${arc.value}`)
)
),
m('text.total-value', { x: '0', y: '15', 'text-anchor': 'middle' }, totalValue)
),
m('.donut-chart-tooltip', [
m('span.tooltip-label', 'Label:'),
m('span.tooltip-value'),
m('br'),
m('span.tooltip-label', 'Value:'),
m('span.tooltip-value'),
]),
m('.legend', legendItems),
]);
},
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment