2025-01-12 14:19:53 +01:00

97 lines
2.4 KiB
Vue

<script setup lang="ts">
import type { ChartDataset, ChartOptions, ChartType } from 'chart.js';
const COLOURS = [
'#C71585',
'#dc3545',
'#fd7e14',
'#ffc107',
'#198754',
'#20c997',
'#0dcaf0',
'#0d6efd',
'#6610f2',
'#6f42c1',
'#d63384',
];
export type Dataset = Record<number | string, number>;
const props = withDefaults(defineProps<{
label: string;
data: Dataset[] | Dataset;
cumulative?: boolean;
type?: ChartType;
options?: ChartOptions;
}>(), {
type: 'line',
options: () => ({
responsive: true,
interaction: {
intersect: false,
mode: 'index',
},
}),
});
onMounted(async () => {
await drawChart();
});
const buildDataset = (data: Dataset, label: string, colour: string): ChartDataset => {
return {
label,
data: props.cumulative
? accumulate(Object.values(data))
: Object.values(data),
fill: false,
backgroundColor: colour,
borderColor: colour,
};
};
const isMultiDataset = (data: Dataset[] | Dataset): data is Dataset[] => {
return Object.values(data).length > 0 && typeof Object.values(data)[0] === 'object';
};
const canvas = useTemplateRef<HTMLCanvasElement>('canvas');
const drawChart = async () => {
if (!canvas.value) {
throw new Error('cannot find canvas');
}
let colourIndex = 0;
const { default: Chart } = await import('chart.js/auto');
new Chart(canvas.value, {
type: props.type,
data: {
labels: isMultiDataset(props.data)
? Array.from(Object.values(props.data).reduce((carry, item) => {
for (const key in item) {
carry.add(key);
}
return carry;
}, new Set()))
: Object.keys(props.data),
datasets: isMultiDataset(props.data)
? Object.entries(props.data).map(([key, data]) => buildDataset(data, key, COLOURS[colourIndex++ % COLOURS.length]))
: [buildDataset(props.data, props.label, COLOURS[0])],
},
options: props.options,
});
};
const accumulate = (values: number[]): number[] => {
const newValues = [];
let acc = 0;
for (const v of values) {
acc += v;
newValues.push(acc);
}
return newValues;
};
</script>
<template>
<canvas ref="canvas"></canvas>
</template>