Add memorized option to FSRS simulation graph (#3655)

* Added: Memorized option to graph

* Count -> Reviews

* Added: Margin to radio button input

* Fix: Labels

* ./check

* Check errors?

* bump fsrs to 1.4.6

* ./ninja fix:minilints

* Added: Don't show hidden simulator values.

* Bump to fsrs 1.4.7
This commit is contained in:
Luc Mcgrady 2024-12-22 01:40:51 +00:00 committed by GitHub
parent 53a2e34a3f
commit c985acb9fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 99 additions and 29 deletions

4
Cargo.lock generated
View file

@ -1860,9 +1860,9 @@ dependencies = [
[[package]]
name = "fsrs"
version = "1.4.5"
version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00a50fe61228127da3faad87e871e99d4f47fcf4fd226a648300f89bb220dc1"
checksum = "61218a50bf5c8da66b62be02f495c5d0b43de52ec37fae036a0e68d4539ff59d"
dependencies = [
"burn",
"itertools 0.12.1",

View file

@ -35,7 +35,7 @@ git = "https://github.com/ankitects/linkcheck.git"
rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
[workspace.dependencies.fsrs]
version = "=1.4.5"
version = "=1.4.7"
# git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
# rev = "58ca25ed2bc4bb1dc376208bbcaed7f5a501b941"
# path = "../open-spaced-repetition/fsrs-rs"

View file

@ -1225,7 +1225,7 @@
},
{
"name": "fsrs",
"version": "1.4.5",
"version": "1.4.7",
"authors": "Open Spaced Repetition",
"repository": "https://github.com/open-spaced-repetition/fsrs-rs",
"license": "BSD-3-Clause",

View file

@ -463,10 +463,13 @@ deck-config-days-to-simulate = Days to simulate
deck-config-desired-retention-below-optimal = Your desired retention is below optimal. Increasing it is recommended.
deck-config-fsrs-simulator-y-axis-title-time = Review Time/Day
deck-config-fsrs-simulator-y-axis-title-count = Review Count/Day
deck-config-fsrs-simulator-y-axis-title-memorized = Memorized Total
deck-config-fsrs-simulator-experimental = FSRS simulator (experimental)
deck-config-additional-new-cards-to-simulate = Additional new cards to simulate
deck-config-simulate = Simulate
deck-config-clear-last-simulate = Clear last simulation
deck-config-fsrs-simulator-radio-count = Reviews
deck-config-fsrs-simulator-radio-memorized = Memorized
## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future.

View file

@ -59,6 +59,7 @@ statistics-reviews =
[one] { $reviews } review
*[other] { $reviews } reviews
}
statistics-memorized = {$memorized} memorized
# Shown at the bottom of the deck list, and in the statistics screen.
# eg "Studied 3 cards in 13 seconds today (4.33s/card)."
# The { statistics-in-time-span-seconds } part should be pasted in from the English

View file

@ -32,7 +32,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import Warning from "./Warning.svelte";
import ParamsInputRow from "./ParamsInputRow.svelte";
import ParamsSearchRow from "./ParamsSearchRow.svelte";
import { renderSimulationChart, type Point } from "../graphs/simulator";
import {
renderSimulationChart,
SimulateSubgraph,
type Point,
} from "../graphs/simulator";
import Graph from "../graphs/Graph.svelte";
import HoverColumns from "../graphs/HoverColumns.svelte";
import CumulativeOverlay from "../graphs/CumulativeOverlay.svelte";
@ -74,7 +78,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
| ComputeRetentionProgress
| undefined;
let showTime = false;
let simulateSubgraph: SimulateSubgraph = SimulateSubgraph.count;
const optimalRetentionRequest = new ComputeOptimalRetentionRequest({
daysToSimulate: 365,
@ -326,33 +330,46 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
resp.dailyReviewCount,
resp.dailyNewCount,
);
const dailyMemorizedCount = resp.accumulatedKnowledgeAcquisition;
points = points.concat(
resp.dailyTimeCost.map((v, i) => ({
x: i,
timeCost: v,
count: dailyTotalCount[i],
memorized: dailyMemorizedCount[i],
label: simulationNumber,
})),
);
tableData = renderSimulationChart(
svg as SVGElement,
bounds,
points,
showTime,
simulateSubgraph,
);
}
}
}
$: tableData = renderSimulationChart(svg as SVGElement, bounds, points, showTime);
$: tableData = renderSimulationChart(
svg as SVGElement,
bounds,
points,
simulateSubgraph,
);
function clearSimulation(): void {
points = points.filter((p) => p.label !== simulationNumber);
simulationNumber = Math.max(0, simulationNumber - 1);
tableData = renderSimulationChart(svg as SVGElement, bounds, points, showTime);
tableData = renderSimulationChart(
svg as SVGElement,
bounds,
points,
simulateSubgraph,
);
}
const label = tr.statisticsReviewsTimeCheckbox();
</script>
<SpinBoxFloatRow
@ -549,12 +566,34 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<div>{simulateProgressString}</div>
<Graph>
<InputBox>
<label>
<input type="checkbox" bind:checked={showTime} />
{label}
</label>
</InputBox>
<div class="radio-group">
<InputBox>
<label>
<input
type="radio"
value={SimulateSubgraph.count}
bind:group={simulateSubgraph}
/>
{tr.deckConfigFsrsSimulatorRadioCount()}
</label>
<label>
<input
type="radio"
value={SimulateSubgraph.time}
bind:group={simulateSubgraph}
/>
{tr.statisticsReviewsTimeCheckbox()}
</label>
<label>
<input
type="radio"
value={SimulateSubgraph.memorized}
bind:group={simulateSubgraph}
/>
{tr.deckConfigFsrsSimulatorRadioMemorized()}
</label>
</InputBox>
</div>
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
<CumulativeOverlay />
@ -569,4 +608,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</div>
<style>
div.radio-group {
margin: 0.5em;
}
</style>

View file

@ -26,14 +26,21 @@ export interface Point {
x: number;
timeCost: number;
count: number;
memorized: number;
label: number;
}
export enum SimulateSubgraph {
time,
count,
memorized,
}
export function renderSimulationChart(
svgElem: SVGElement,
bounds: GraphBounds,
data: Point[],
showTime: boolean,
subgraph: SimulateSubgraph,
): TableDatum[] {
const svg = select(svgElem);
svg.selectAll(".lines").remove();
@ -65,10 +72,22 @@ export function renderSimulationChart(
// y scale
const yTickFormat = (n: number): string => {
return showTime ? timeSpan(n, true) : n.toString();
return subgraph == SimulateSubgraph.time ? timeSpan(n, true) : n.toString();
};
const yMax = showTime ? max(convertedData, d => d.timeCost)! : max(convertedData, d => d.count)!;
const subgraph_data = ({
[SimulateSubgraph.count]: convertedData.map(d => ({ ...d, y: d.count })),
[SimulateSubgraph.time]: convertedData.map(d => ({ ...d, y: d.timeCost })),
[SimulateSubgraph.memorized]: convertedData.map(d => ({ ...d, y: d.memorized })),
})[subgraph];
const subgraph_title = ({
[SimulateSubgraph.count]: tr.deckConfigFsrsSimulatorYAxisTitleCount(),
[SimulateSubgraph.time]: tr.deckConfigFsrsSimulatorYAxisTitleTime(),
[SimulateSubgraph.memorized]: tr.deckConfigFsrsSimulatorYAxisTitleMemorized(),
})[subgraph];
const yMax = max(subgraph_data, d => d.y)!;
const y = scaleLinear()
.range([bounds.height - bounds.marginBottom, bounds.marginTop])
.domain([0, yMax])
@ -95,14 +114,10 @@ export function renderSimulationChart(
.attr("dy", "1.1em")
.attr("fill", "currentColor")
.style("text-anchor", "middle")
.text(`${
showTime
? tr.deckConfigFsrsSimulatorYAxisTitleTime()
: tr.deckConfigFsrsSimulatorYAxisTitleCount()
}`);
.text(subgraph_title);
// x lines
const points = convertedData.map((d) => [x(d.date), y(showTime ? d.timeCost : d.count), d.label]);
const points = subgraph_data.map((d) => [x(d.date), y(d.y), d.label]);
const groups = rollup(points, v => Object.assign(v, { z: v[0][2] }), d => d[2]);
const color = schemeCategory10;
@ -169,9 +184,18 @@ export function renderSimulationChart(
const days = +((date.getTime() - Date.now()) / (60 * 60 * 24 * 1000)).toFixed();
let tooltipContent = `Date: ${localizedDate(date)}<br>In ${days} Days<br>`;
for (const [key, value] of Object.entries(groupData)) {
tooltipContent += `#${key}: ${
showTime ? timeSpan(value) : tr.statisticsReviews({ reviews: Math.round(value) })
}<br>`;
const path = svg.select(`path[data-group="${key}"]`);
const hidden = path.classed("hidden");
if (!hidden) {
const tooltip = ({
[SimulateSubgraph.time]: timeSpan(value),
[SimulateSubgraph.count]: tr.statisticsReviews({ reviews: Math.round(value) }),
[SimulateSubgraph.memorized]: tr.statisticsMemorized({ memorized: Math.round(value).toFixed(0) }),
})[subgraph];
tooltipContent += `#${key}: ${tooltip}<br>`;
}
}
showTooltip(tooltipContent, event.pageX, event.pageY);