generated from tpl/obsidian-sample-plugin
121 lines
2.8 KiB
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>
|