mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12: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]]
|
[[package]]
|
||||||
name = "fsrs"
|
name = "fsrs"
|
||||||
version = "1.4.5"
|
version = "1.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d00a50fe61228127da3faad87e871e99d4f47fcf4fd226a648300f89bb220dc1"
|
checksum = "61218a50bf5c8da66b62be02f495c5d0b43de52ec37fae036a0e68d4539ff59d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"burn",
|
"burn",
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
|
|
|
@ -35,7 +35,7 @@ git = "https://github.com/ankitects/linkcheck.git"
|
||||||
rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
|
rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
|
||||||
|
|
||||||
[workspace.dependencies.fsrs]
|
[workspace.dependencies.fsrs]
|
||||||
version = "=1.4.5"
|
version = "=1.4.7"
|
||||||
# git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
# git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
||||||
# rev = "58ca25ed2bc4bb1dc376208bbcaed7f5a501b941"
|
# rev = "58ca25ed2bc4bb1dc376208bbcaed7f5a501b941"
|
||||||
# path = "../open-spaced-repetition/fsrs-rs"
|
# path = "../open-spaced-repetition/fsrs-rs"
|
||||||
|
|
|
@ -1225,7 +1225,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fsrs",
|
"name": "fsrs",
|
||||||
"version": "1.4.5",
|
"version": "1.4.7",
|
||||||
"authors": "Open Spaced Repetition",
|
"authors": "Open Spaced Repetition",
|
||||||
"repository": "https://github.com/open-spaced-repetition/fsrs-rs",
|
"repository": "https://github.com/open-spaced-repetition/fsrs-rs",
|
||||||
"license": "BSD-3-Clause",
|
"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-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-time = Review Time/Day
|
||||||
deck-config-fsrs-simulator-y-axis-title-count = Review Count/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-fsrs-simulator-experimental = FSRS simulator (experimental)
|
||||||
deck-config-additional-new-cards-to-simulate = Additional new cards to simulate
|
deck-config-additional-new-cards-to-simulate = Additional new cards to simulate
|
||||||
deck-config-simulate = Simulate
|
deck-config-simulate = Simulate
|
||||||
deck-config-clear-last-simulate = Clear last simulation
|
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.
|
## 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
|
[one] { $reviews } review
|
||||||
*[other] { $reviews } reviews
|
*[other] { $reviews } reviews
|
||||||
}
|
}
|
||||||
|
statistics-memorized = {$memorized} memorized
|
||||||
# Shown at the bottom of the deck list, and in the statistics screen.
|
# Shown at the bottom of the deck list, and in the statistics screen.
|
||||||
# eg "Studied 3 cards in 13 seconds today (4.33s/card)."
|
# 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
|
# 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 Warning from "./Warning.svelte";
|
||||||
import ParamsInputRow from "./ParamsInputRow.svelte";
|
import ParamsInputRow from "./ParamsInputRow.svelte";
|
||||||
import ParamsSearchRow from "./ParamsSearchRow.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 Graph from "../graphs/Graph.svelte";
|
||||||
import HoverColumns from "../graphs/HoverColumns.svelte";
|
import HoverColumns from "../graphs/HoverColumns.svelte";
|
||||||
import CumulativeOverlay from "../graphs/CumulativeOverlay.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
|
| ComputeRetentionProgress
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
let showTime = false;
|
let simulateSubgraph: SimulateSubgraph = SimulateSubgraph.count;
|
||||||
|
|
||||||
const optimalRetentionRequest = new ComputeOptimalRetentionRequest({
|
const optimalRetentionRequest = new ComputeOptimalRetentionRequest({
|
||||||
daysToSimulate: 365,
|
daysToSimulate: 365,
|
||||||
|
@ -326,33 +330,46 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
resp.dailyReviewCount,
|
resp.dailyReviewCount,
|
||||||
resp.dailyNewCount,
|
resp.dailyNewCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dailyMemorizedCount = resp.accumulatedKnowledgeAcquisition;
|
||||||
|
|
||||||
points = points.concat(
|
points = points.concat(
|
||||||
resp.dailyTimeCost.map((v, i) => ({
|
resp.dailyTimeCost.map((v, i) => ({
|
||||||
x: i,
|
x: i,
|
||||||
timeCost: v,
|
timeCost: v,
|
||||||
count: dailyTotalCount[i],
|
count: dailyTotalCount[i],
|
||||||
|
memorized: dailyMemorizedCount[i],
|
||||||
label: simulationNumber,
|
label: simulationNumber,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
tableData = renderSimulationChart(
|
tableData = renderSimulationChart(
|
||||||
svg as SVGElement,
|
svg as SVGElement,
|
||||||
bounds,
|
bounds,
|
||||||
points,
|
points,
|
||||||
showTime,
|
simulateSubgraph,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: tableData = renderSimulationChart(svg as SVGElement, bounds, points, showTime);
|
$: tableData = renderSimulationChart(
|
||||||
|
svg as SVGElement,
|
||||||
|
bounds,
|
||||||
|
points,
|
||||||
|
simulateSubgraph,
|
||||||
|
);
|
||||||
|
|
||||||
function clearSimulation(): void {
|
function clearSimulation(): void {
|
||||||
points = points.filter((p) => p.label !== simulationNumber);
|
points = points.filter((p) => p.label !== simulationNumber);
|
||||||
simulationNumber = Math.max(0, simulationNumber - 1);
|
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>
|
</script>
|
||||||
|
|
||||||
<SpinBoxFloatRow
|
<SpinBoxFloatRow
|
||||||
|
@ -549,12 +566,34 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<div>{simulateProgressString}</div>
|
<div>{simulateProgressString}</div>
|
||||||
|
|
||||||
<Graph>
|
<Graph>
|
||||||
|
<div class="radio-group">
|
||||||
<InputBox>
|
<InputBox>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" bind:checked={showTime} />
|
<input
|
||||||
{label}
|
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>
|
</label>
|
||||||
</InputBox>
|
</InputBox>
|
||||||
|
</div>
|
||||||
|
|
||||||
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
|
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
|
||||||
<CumulativeOverlay />
|
<CumulativeOverlay />
|
||||||
|
@ -569,4 +608,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
div.radio-group {
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -26,14 +26,21 @@ export interface Point {
|
||||||
x: number;
|
x: number;
|
||||||
timeCost: number;
|
timeCost: number;
|
||||||
count: number;
|
count: number;
|
||||||
|
memorized: number;
|
||||||
label: number;
|
label: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SimulateSubgraph {
|
||||||
|
time,
|
||||||
|
count,
|
||||||
|
memorized,
|
||||||
|
}
|
||||||
|
|
||||||
export function renderSimulationChart(
|
export function renderSimulationChart(
|
||||||
svgElem: SVGElement,
|
svgElem: SVGElement,
|
||||||
bounds: GraphBounds,
|
bounds: GraphBounds,
|
||||||
data: Point[],
|
data: Point[],
|
||||||
showTime: boolean,
|
subgraph: SimulateSubgraph,
|
||||||
): TableDatum[] {
|
): TableDatum[] {
|
||||||
const svg = select(svgElem);
|
const svg = select(svgElem);
|
||||||
svg.selectAll(".lines").remove();
|
svg.selectAll(".lines").remove();
|
||||||
|
@ -65,10 +72,22 @@ export function renderSimulationChart(
|
||||||
// y scale
|
// y scale
|
||||||
|
|
||||||
const yTickFormat = (n: number): string => {
|
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()
|
const y = scaleLinear()
|
||||||
.range([bounds.height - bounds.marginBottom, bounds.marginTop])
|
.range([bounds.height - bounds.marginBottom, bounds.marginTop])
|
||||||
.domain([0, yMax])
|
.domain([0, yMax])
|
||||||
|
@ -95,14 +114,10 @@ export function renderSimulationChart(
|
||||||
.attr("dy", "1.1em")
|
.attr("dy", "1.1em")
|
||||||
.attr("fill", "currentColor")
|
.attr("fill", "currentColor")
|
||||||
.style("text-anchor", "middle")
|
.style("text-anchor", "middle")
|
||||||
.text(`${
|
.text(subgraph_title);
|
||||||
showTime
|
|
||||||
? tr.deckConfigFsrsSimulatorYAxisTitleTime()
|
|
||||||
: tr.deckConfigFsrsSimulatorYAxisTitleCount()
|
|
||||||
}`);
|
|
||||||
|
|
||||||
// x lines
|
// 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 groups = rollup(points, v => Object.assign(v, { z: v[0][2] }), d => d[2]);
|
||||||
|
|
||||||
const color = schemeCategory10;
|
const color = schemeCategory10;
|
||||||
|
@ -169,9 +184,18 @@ export function renderSimulationChart(
|
||||||
const days = +((date.getTime() - Date.now()) / (60 * 60 * 24 * 1000)).toFixed();
|
const days = +((date.getTime() - Date.now()) / (60 * 60 * 24 * 1000)).toFixed();
|
||||||
let tooltipContent = `Date: ${localizedDate(date)}<br>In ${days} Days<br>`;
|
let tooltipContent = `Date: ${localizedDate(date)}<br>In ${days} Days<br>`;
|
||||||
for (const [key, value] of Object.entries(groupData)) {
|
for (const [key, value] of Object.entries(groupData)) {
|
||||||
tooltipContent += `#${key}: ${
|
const path = svg.select(`path[data-group="${key}"]`);
|
||||||
showTime ? timeSpan(value) : tr.statisticsReviews({ reviews: Math.round(value) })
|
const hidden = path.classed("hidden");
|
||||||
}<br>`;
|
|
||||||
|
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);
|
showTooltip(tooltipContent, event.pageX, event.pageY);
|
||||||
|
|
Loading…
Reference in a new issue