From 9fd8a8bb40997030fe7669744963731b9d162d42 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 1 Oct 2023 15:44:33 +1000 Subject: [PATCH] Add stability graph --- ftl/core/statistics.ftl | 2 + proto/anki/stats.proto | 1 + rslib/src/stats/graphs/intervals.rs | 15 +++++ rslib/src/stats/graphs/mod.rs | 1 + ts/graphs/IntervalsGraph.svelte | 2 +- ts/graphs/StabilityGraph.svelte | 87 +++++++++++++++++++++++++++++ ts/graphs/index.ts | 3 + ts/graphs/intervals.ts | 6 +- 8 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 ts/graphs/StabilityGraph.svelte diff --git a/ftl/core/statistics.ftl b/ftl/core/statistics.ftl index d33fe7f96..ce6e757f8 100644 --- a/ftl/core/statistics.ftl +++ b/ftl/core/statistics.ftl @@ -94,6 +94,8 @@ statistics-range-collection = collection statistics-range-search = Search statistics-card-ease-title = Card Ease statistics-card-difficulty-title = Card Difficulty +statistics-card-stability-title = Card Stability +statistics-card-stability-subtitle = Combined with desired retention to determine the next interval. statistics-card-retrievability-title = Card Retrievability statistics-card-ease-subtitle = The lower the ease, the more frequently a card will appear. statistics-card-difficulty-subtitle = The higher the difficulty, the harder it is to remember. diff --git a/proto/anki/stats.proto b/proto/anki/stats.proto index 3f6814954..8c14b5803 100644 --- a/proto/anki/stats.proto +++ b/proto/anki/stats.proto @@ -155,6 +155,7 @@ message GraphsResponse { uint32 rollover_hour = 10; Retrievability retrievability = 12; bool fsrs = 13; + Intervals stability = 14; } message GraphPreferences { diff --git a/rslib/src/stats/graphs/intervals.rs b/rslib/src/stats/graphs/intervals.rs index f69c33052..b8a261ae0 100644 --- a/rslib/src/stats/graphs/intervals.rs +++ b/rslib/src/stats/graphs/intervals.rs @@ -19,4 +19,19 @@ impl GraphsContext { } data } + + pub(super) fn stability(&self) -> Intervals { + let mut data = Intervals::default(); + for card in &self.cards { + if matches!(card.ctype, CardType::Review | CardType::Relearn) { + if let Some(state) = &card.memory_state { + *data + .intervals + .entry(state.stability as u32) + .or_insert_with(Default::default) += 1; + } + } + } + data + } } diff --git a/rslib/src/stats/graphs/mod.rs b/rslib/src/stats/graphs/mod.rs index 7c48666ec..0d78d9ba0 100644 --- a/rslib/src/stats/graphs/mod.rs +++ b/rslib/src/stats/graphs/mod.rs @@ -67,6 +67,7 @@ impl Collection { reviews: Some(ctx.review_counts_and_times()), future_due: Some(ctx.future_due()), intervals: Some(ctx.intervals()), + stability: Some(ctx.stability()), eases: Some(eases), difficulty: Some(difficulty), today: Some(ctx.today()), diff --git a/ts/graphs/IntervalsGraph.svelte b/ts/graphs/IntervalsGraph.svelte index d15ed0e47..4251d4f55 100644 --- a/ts/graphs/IntervalsGraph.svelte +++ b/ts/graphs/IntervalsGraph.svelte @@ -33,7 +33,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html let range = IntervalRange.Percentile95; $: if (sourceData) { - intervalData = gatherIntervalData(sourceData); + intervalData = gatherIntervalData(sourceData.intervals!); } $: if (intervalData) { diff --git a/ts/graphs/StabilityGraph.svelte b/ts/graphs/StabilityGraph.svelte new file mode 100644 index 000000000..024c821e2 --- /dev/null +++ b/ts/graphs/StabilityGraph.svelte @@ -0,0 +1,87 @@ + + + +{#if sourceData?.fsrs} + + + + + + + + + + + + +{/if} diff --git a/ts/graphs/index.ts b/ts/graphs/index.ts index 57263d856..e8a5d61ed 100644 --- a/ts/graphs/index.ts +++ b/ts/graphs/index.ts @@ -50,6 +50,7 @@ import IntervalsGraph from "./IntervalsGraph.svelte"; import RangeBox from "./RangeBox.svelte"; import RetrievabilityGraph from "./RetrievabilityGraph.svelte"; import ReviewsGraph from "./ReviewsGraph.svelte"; +import StabilityGraph from "./StabilityGraph.svelte"; import TodayStats from "./TodayStats.svelte"; setupGraphs( @@ -60,6 +61,7 @@ setupGraphs( ReviewsGraph, CardCounts, IntervalsGraph, + StabilityGraph, EaseGraph, DifficultyGraph, RetrievabilityGraph, @@ -79,6 +81,7 @@ export const graphComponents = { ReviewsGraph, CardCounts, IntervalsGraph, + StabilityGraph, EaseGraph, DifficultyGraph, RetrievabilityGraph, diff --git a/ts/graphs/intervals.ts b/ts/graphs/intervals.ts index 8bb5917a1..06335248e 100644 --- a/ts/graphs/intervals.ts +++ b/ts/graphs/intervals.ts @@ -5,7 +5,7 @@ @typescript-eslint/no-explicit-any: "off", */ -import type { GraphsResponse } from "@tslib/anki/stats_pb"; +import type { GraphsResponse_Intervals } from "@tslib/anki/stats_pb"; import * as tr from "@tslib/ftl"; import { localizedNumber } from "@tslib/i18n"; import { timeSpan } from "@tslib/time"; @@ -27,11 +27,11 @@ export enum IntervalRange { All = 3, } -export function gatherIntervalData(data: GraphsResponse): IntervalGraphData { +export function gatherIntervalData(data: GraphsResponse_Intervals): IntervalGraphData { // This could be made more efficient - this graph currently expects a flat list of individual intervals which it // uses to calculate a percentile and then converts into a histogram, and the percentile/histogram calculations // in JS are relatively slow. - const map = numericMap(data.intervals!.intervals); + const map = numericMap(data.intervals); const totalCards = sum(map, ([_k, v]) => v); const allIntervals: number[] = Array(totalCards); let position = 0;