Use reading log for books and pages chart when filtering by month

This commit is contained in:
Evan Fiordeliso 2025-07-04 09:56:29 -04:00
parent d9cfb3df36
commit 31cfa881c7
12 changed files with 99 additions and 53 deletions

View File

@ -11,7 +11,7 @@
plugin: BookTrackerPlugin; plugin: BookTrackerPlugin;
} }
let { plugin }: Props = $props(); const { plugin }: Props = $props();
function bookUri(book: string) { function bookUri(book: string) {
const v = encodeURIComponent(plugin.app.vault.getName()); const v = encodeURIComponent(plugin.app.vault.getName());

View File

@ -23,20 +23,32 @@
import type BookTrackerPlugin from "@src/main"; import type BookTrackerPlugin from "@src/main";
import BookCountStat from "@ui/components/stats/BookCountStat.svelte"; import BookCountStat from "@ui/components/stats/BookCountStat.svelte";
import { ALL_TIME } from "@ui/stores/date-filter.svelte"; import { ALL_TIME } from "@ui/stores/date-filter.svelte";
import {
createReadingLog,
setReadingLogContext,
} from "@ui/stores/reading-log.svelte";
interface Props { interface Props {
plugin: BookTrackerPlugin; plugin: BookTrackerPlugin;
source: string; source: string;
} }
let { plugin, source }: Props = $props(); const { plugin, source }: Props = $props();
let settingsStore = createSettings(plugin); const settingsStore = createSettings(plugin);
setSettingsContext(settingsStore); setSettingsContext(settingsStore);
let metadataStore = createMetadata(plugin); const metadataStore = createMetadata(plugin);
setMetadataContext(metadataStore); setMetadataContext(metadataStore);
const readingLogStore = createReadingLog(plugin.readingLog);
setReadingLogContext(readingLogStore);
$effect(() => {
readingLogStore.filterYear = metadataStore.filterYear;
readingLogStore.filterMonth = metadataStore.filterMonth;
});
let sections = $state<ReadingStatsSection[]>([]); let sections = $state<ReadingStatsSection[]>([]);
let error = $state<string | null>(null); let error = $state<string | null>(null);
@ -68,7 +80,10 @@
} }
}); });
onDestroy(() => metadataStore.destroy()); onDestroy(() => {
metadataStore.destroy();
readingLogStore.destroy();
});
</script> </script>
<div class="obt-reading-stats"> <div class="obt-reading-stats">

View File

@ -15,7 +15,7 @@
color?: "rainbow" | ColorName; color?: "rainbow" | ColorName;
}; };
let { const {
property, property,
horizontal, horizontal,
sortByLabel = false, sortByLabel = false,

View File

@ -2,28 +2,46 @@
import { chart } from "@ui/directives/chart"; import { chart } from "@ui/directives/chart";
import { ALL_TIME } from "@ui/stores/date-filter.svelte"; import { ALL_TIME } from "@ui/stores/date-filter.svelte";
import { getMetadataContext } from "@ui/stores/metadata.svelte"; import { getMetadataContext } from "@ui/stores/metadata.svelte";
import { getReadingLogContext } from "@ui/stores/reading-log.svelte";
import { getSettingsContext } from "@ui/stores/settings.svelte"; import { getSettingsContext } from "@ui/stores/settings.svelte";
import { Color } from "@utils/color"; import { Color } from "@utils/color";
import type { ChartConfiguration } from "chart.js"; import type { ChartConfiguration } from "chart.js";
const settings = getSettingsContext().settings; const settingsStore = getSettingsContext();
const store = getMetadataContext(); const store = getMetadataContext();
const config = $derived.by(() => { const readingLog = getReadingLogContext();
const items = store.metadata.map((f) => ({
pageCount: f.frontmatter[settings.pageCountProperty],
date: f.frontmatter[settings.endDateProperty],
}));
const isMonthly = $derived(
store.filterYear !== ALL_TIME && store.filterMonth !== ALL_TIME,
);
const items = $derived(
isMonthly
? readingLog.entries.map((entry) => ({
pageCount: entry.pagesRead,
date: entry.createdAt,
}))
: store.metadata.map((f) => ({
pageCount:
f.frontmatter[settingsStore.settings.pageCountProperty],
// @ts-expect-error Moment is provided by Obsidian
date: moment(
f.frontmatter[settingsStore.settings.endDateProperty],
),
})),
);
const config = $derived.by(() => {
const books = new Map<number, number>(); const books = new Map<number, number>();
const pages = new Map<number, number>(); const pages = new Map<number, number>();
for (const item of items) { for (const item of items) {
// @ts-expect-error Moment is provided by Obsidian
const date = moment(item.date);
let key: number; let key: number;
if (store.filterYear === ALL_TIME) { if (store.filterYear === ALL_TIME) {
key = date.year(); key = item.date.year();
} else if (store.filterMonth === ALL_TIME) {
key = item.date.month();
} else { } else {
key = date.month(); key = item.date.date();
} }
const pageCount = pages.get(key) ?? 0; const pageCount = pages.get(key) ?? 0;
@ -33,10 +51,26 @@
books.set(key, bookCount + 1); books.set(key, bookCount + 1);
} }
console.log(pages);
if (isMonthly && typeof store.filterMonth === "number") {
// @ts-expect-error Moment is provided by Obsidian
const daysInMonth = moment()
.month(store.filterMonth - 1)
.daysInMonth();
for (let i = 1; i <= daysInMonth; i++) {
if (!pages.has(i)) {
books.set(i, 0);
pages.set(i, 0);
}
}
}
console.log(pages);
const labels = Array.from(books.keys()) const labels = Array.from(books.keys())
.sort((a, b) => a - b) .sort((a, b) => a - b)
.map((key) => .map((key) =>
store.filterYear === ALL_TIME store.filterYear === ALL_TIME || isMonthly
? key ? key
: // @ts-expect-error Moment is provided by Obsidian : // @ts-expect-error Moment is provided by Obsidian
moment().month(key).format("MMM"), moment().month(key).format("MMM"),
@ -48,26 +82,30 @@
.sort((a, b) => a[0] - b[0]) .sort((a, b) => a[0] - b[0])
.map((p) => p[1]); .map((p) => p[1]);
let datasets = [
{
label: "Pages",
data: sortedPages,
borderColor: Color.fromName("blue").hex,
backgroundColor: Color.fromName("blue").alpha(0.5).rgba,
yAxisID: isMonthly ? "y" : "y1",
},
];
if (!isMonthly) {
datasets.push({
label: "Books",
data: sortedBooks,
borderColor: Color.fromName("red").hex,
backgroundColor: Color.fromName("red").alpha(0.5).rgba,
yAxisID: "y",
});
}
return { return {
type: "line", type: "line",
data: { data: {
labels, labels,
datasets: [ datasets,
{
label: "Books",
data: sortedBooks,
borderColor: Color.fromName("red").hex,
backgroundColor: Color.fromName("red").alpha(0.5).rgba,
yAxisID: "y",
},
{
label: "Pages",
data: sortedPages,
borderColor: Color.fromName("blue").hex,
backgroundColor: Color.fromName("blue").alpha(0.5).rgba,
yAxisID: "y1",
},
],
}, },
options: { options: {
scales: { scales: {
@ -75,16 +113,13 @@
type: "linear", type: "linear",
display: true, display: true,
position: "left", position: "left",
ticks: { beginAtZero: true },
}, },
y1: { y1: {
type: "linear", type: "linear",
display: true, display: !isMonthly,
position: "right", position: "right",
grid: { drawOnChartArea: false },
// grid line settings
grid: {
drawOnChartArea: false, // only want the grid lines for one axis to show up
},
}, },
}, },
}, },

View File

@ -17,7 +17,7 @@
color?: PieChartColor; color?: PieChartColor;
}; };
let { property, groups, unit, unitPlural, responsive, color }: Props = const { property, groups, unit, unitPlural, responsive, color }: Props =
$props(); $props();
const store = createPropertyStore(property); const store = createPropertyStore(property);

View File

@ -7,8 +7,7 @@
property: string; property: string;
}; };
let { label, property }: Props = $props(); const { label, property }: Props = $props();
const store = createPropertyStore(property); const store = createPropertyStore(property);
const avg = $derived.by(() => { const avg = $derived.by(() => {
if (store.propertyData.length === 0) { if (store.propertyData.length === 0) {

View File

@ -6,8 +6,7 @@
label: string; label: string;
}; };
let { label }: Props = $props(); const { label }: Props = $props();
const store = getMetadataContext(); const store = getMetadataContext();
const count = $derived(store.metadata.length); const count = $derived(store.metadata.length);
</script> </script>

View File

@ -7,8 +7,7 @@
property: string; property: string;
}; };
let { label, property }: Props = $props(); const { label, property }: Props = $props();
const store = createPropertyStore(property); const store = createPropertyStore(property);
const count = $derived(store.propertyData.length); const count = $derived(store.propertyData.length);
</script> </script>

View File

@ -1,13 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { Readable } from "svelte/store";
type Props = { type Props = {
label: string; label: string;
value: number; value: number;
}; };
let { label, value }: Props = $props(); const { label, value }: Props = $props();
const numberFormatter = new Intl.NumberFormat("en-US", { const numberFormatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 2, maximumFractionDigits: 2,
}); });

View File

@ -7,8 +7,7 @@
property: string; property: string;
}; };
let { label, property }: Props = $props(); const { label, property }: Props = $props();
const store = createPropertyStore(property); const store = createPropertyStore(property);
const total = $derived( const total = $derived(
store.propertyData.reduce((acc, f) => acc + f.value, 0), store.propertyData.reduce((acc, f) => acc + f.value, 0),

View File

@ -63,6 +63,9 @@ export function chart<
| ChartConfigurationCustomTypesPerDataset<Type, Data, Label> | ChartConfigurationCustomTypesPerDataset<Type, Data, Label>
) => { ) => {
chart.data = config.data; chart.data = config.data;
if (config.options) {
chart.options = config.options;
}
chart.update(); chart.update();
}, },
destroy: () => { destroy: () => {

View File

@ -44,7 +44,7 @@ export function createDateFilter<T>(
data().forEach((item) => { data().forEach((item) => {
years.add(selector(item).year()); years.add(selector(item).year());
}); });
return Array.from(years).sort((a, b) => a - b); return Array.from(years).sort((a, b) => b - a);
}); });
const filterMonths = $derived.by(() => { const filterMonths = $derived.by(() => {