obsidian-book-tracker/src/ui/components/charts/Pie.svelte

121 lines
2.8 KiB
Svelte

<script lang="ts">
import type { BookMetadata } from "@src/types";
import type {
PieChartColor,
PieGrouping,
} from "@ui/code-blocks/ReadingStatsCodeBlock";
import { chart } from "@ui/directives/chart";
import { createPropertyStore } from "@ui/stores/metadata.svelte";
import { Color, type ColorName } from "@utils/color";
import type { ChartConfiguration } from "chart.js";
type Props = {
property: keyof BookMetadata;
groups?: PieGrouping[];
unit?: string;
unitPlural?: string;
color?: PieChartColor;
};
const { property, groups, unit, unitPlural, color }: Props = $props();
const store = createPropertyStore(property);
function makeLabel(value: any) {
if (typeof value === "number") {
let label = value.toString();
if (unitPlural && value !== 1) {
label += ` ${unitPlural}`;
} else if (unit) {
label += ` ${unit}`;
if (value !== 1) {
label += "s";
}
}
return label;
} else {
return value;
}
}
const config = $derived.by(() => {
const map = new Map<any, number>();
if (groups) {
for (const group of groups) {
map.set(group.label, 0);
}
}
for (const { value } of store.propertyData) {
if (groups) {
for (const group of groups) {
if (group.min && value < group.min) {
continue;
}
if (group.max && value > group.max) {
continue;
}
const count = map.get(group.label) ?? 0;
map.set(group.label, count + 1);
}
} else if (Array.isArray(value)) {
for (const v of value) {
const count = map.get(v) ?? 0;
map.set(v, count + 1);
}
} else {
const count = map.get(value) ?? 0;
map.set(value, count + 1);
}
}
const labels = Array.from(map.keys()).map((p) => makeLabel(p));
const data = Array.from(map.values());
let backgroundColor: string[] | string | undefined = Color.scale(
data.length,
).map((c) => c.hex);
if (color) {
if (color === "rainbow") {
backgroundColor = Color.scale(data.length).map((c) => c.hex);
} else if (typeof color === "string") {
backgroundColor = Color.fromName(color).hex;
} else if (Array.isArray(color)) {
if (color.length < data.length) {
throw new Error(
"Color array must be at least as long as the data array",
);
}
if (color.every((c) => typeof c === "string")) {
backgroundColor = color;
} else {
const map = color.reduce(
(acc, c) => {
acc[c.label] = c.color;
return acc;
},
{} as Record<string, ColorName>,
);
backgroundColor = labels.map(
(label) => Color.fromName(map[label]).hex,
);
}
}
}
return {
type: "pie",
data: { labels, datasets: [{ data, backgroundColor }] },
options: { responsive: true },
} as ChartConfiguration;
});
</script>
<canvas use:chart={config}></canvas>