mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
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:
parent
53a2e34a3f
commit
c985acb9fe
7 changed files with 99 additions and 29 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue