Anki/ts/routes/card-info/ForgettingCurve.svelte
Jarrett Ye e096c462fa
Feat/FSRS-6 (#3929)
* Feat/FSRS-6

* update comment

* add decay to Card

* ./ninja fix:minilints

* pass check

* fix NaN in evaluation

* remove console

* decay should fallback to 0.5 when it's None.

* Update SimulatorModal.svelte

* Update a few comments

* Update FSRS decay defaults to use constants for better maintainability and clarity

* Update rslib/src/storage/card/data.rs
2025-04-25 16:44:34 +10:00

130 lines
3.9 KiB
Svelte

<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { type CardStatsResponse_StatsRevlogEntry as RevlogEntry } from "@generated/anki/stats_pb";
import * as tr from "@generated/ftl";
import Graph from "../graphs/Graph.svelte";
import NoDataOverlay from "../graphs/NoDataOverlay.svelte";
import AxisTicks from "../graphs/AxisTicks.svelte";
import { writable, type Writable } from "svelte/store";
import InputBox from "../graphs/InputBox.svelte";
import {
renderForgettingCurve,
TimeRange,
calculateMaxDays,
filterRevlog,
} from "./forgetting-curve";
import { defaultGraphBounds } from "../graphs/graph-helpers";
import HoverColumns from "../graphs/HoverColumns.svelte";
export let revlog: RevlogEntry[];
export let desiredRetention: number;
export let decay: number;
let svg: HTMLElement | SVGElement | null = null;
const bounds = defaultGraphBounds();
const title = tr.cardStatsFsrsForgettingCurveTitle();
$: filteredRevlog = filterRevlog(revlog);
$: maxDays = calculateMaxDays(filteredRevlog, TimeRange.AllTime);
let defaultTimeRange = TimeRange.Week;
const timeRange: Writable<TimeRange> = writable(defaultTimeRange);
$: if (maxDays > 365) {
defaultTimeRange = TimeRange.AllTime;
} else if (maxDays > 30) {
defaultTimeRange = TimeRange.Year;
} else if (maxDays > 7) {
defaultTimeRange = TimeRange.Month;
}
$: $timeRange = defaultTimeRange;
$: renderForgettingCurve(
filteredRevlog,
$timeRange,
svg as SVGElement,
bounds,
desiredRetention,
decay,
);
</script>
<div class="forgetting-curve">
{#if maxDays > 7}
<InputBox>
<div class="time-range-selector">
<label>
<input
type="radio"
bind:group={$timeRange}
value={TimeRange.Week}
/>
{tr.cardStatsFsrsForgettingCurveFirstWeek()}
</label>
<label>
<input
type="radio"
bind:group={$timeRange}
value={TimeRange.Month}
/>
{tr.cardStatsFsrsForgettingCurveFirstMonth()}
</label>
{#if maxDays > 30}
<label>
<input
type="radio"
bind:group={$timeRange}
value={TimeRange.Year}
/>
{tr.cardStatsFsrsForgettingCurveFirstYear()}
</label>
{/if}
{#if maxDays > 365}
<label>
<input
type="radio"
bind:group={$timeRange}
value={TimeRange.AllTime}
/>
{tr.cardStatsFsrsForgettingCurveAllTime()}
</label>
{/if}
</div>
</InputBox>
{/if}
<Graph {title}>
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
<AxisTicks {bounds} />
<HoverColumns />
<NoDataOverlay {bounds} />
</svg>
</Graph>
</div>
<style>
.forgetting-curve {
width: 100%;
max-width: 50em;
margin-bottom: 10em;
}
.time-range-selector {
display: flex;
justify-content: space-around;
margin-bottom: 1em;
width: 100%;
max-width: 50em;
}
.time-range-selector label {
display: flex;
align-items: center;
}
.time-range-selector input {
margin-right: 0.5em;
}
</style>