mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
Feat/add a toggle in the simulator to display time or review count (#3523)
Co-authored-by: Damien Elmes <dae@users.noreply.github.com>
This commit is contained in:
parent
1aa734ad28
commit
eacd5bf908
2 changed files with 48 additions and 15 deletions
|
@ -40,6 +40,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import NoDataOverlay from "../graphs/NoDataOverlay.svelte";
|
import NoDataOverlay from "../graphs/NoDataOverlay.svelte";
|
||||||
import TableData from "../graphs/TableData.svelte";
|
import TableData from "../graphs/TableData.svelte";
|
||||||
import { defaultGraphBounds, type TableDatum } from "../graphs/graph-helpers";
|
import { defaultGraphBounds, type TableDatum } from "../graphs/graph-helpers";
|
||||||
|
import InputBox from "../graphs/InputBox.svelte";
|
||||||
|
|
||||||
export let state: DeckOptionsState;
|
export let state: DeckOptionsState;
|
||||||
export let openHelpModal: (String) => void;
|
export let openHelpModal: (String) => void;
|
||||||
|
@ -73,6 +74,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
| ComputeRetentionProgress
|
| ComputeRetentionProgress
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
|
let showTime = false;
|
||||||
|
|
||||||
const optimalRetentionRequest = new ComputeOptimalRetentionRequest({
|
const optimalRetentionRequest = new ComputeOptimalRetentionRequest({
|
||||||
daysToSimulate: 365,
|
daysToSimulate: 365,
|
||||||
lossAversion: 2.5,
|
lossAversion: 2.5,
|
||||||
|
@ -305,6 +308,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addArrays(arr1: number[], arr2: number[]): number[] {
|
||||||
|
return arr1.map((value, index) => value + arr2[index]);
|
||||||
|
}
|
||||||
|
|
||||||
$: simulateProgressString = "";
|
$: simulateProgressString = "";
|
||||||
|
|
||||||
async function simulateFsrs(): Promise<void> {
|
async function simulateFsrs(): Promise<void> {
|
||||||
|
@ -328,23 +335,37 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
resp.dailyTimeCost,
|
resp.dailyTimeCost,
|
||||||
Math.ceil(simulateFsrsRequest.daysToSimulate / 50),
|
Math.ceil(simulateFsrsRequest.daysToSimulate / 50),
|
||||||
);
|
);
|
||||||
|
const dailyReviewCount = movingAverage(
|
||||||
|
addArrays(resp.dailyReviewCount, resp.dailyNewCount),
|
||||||
|
Math.ceil(simulateFsrsRequest.daysToSimulate / 50),
|
||||||
|
);
|
||||||
points = points.concat(
|
points = points.concat(
|
||||||
dailyTimeCost.map((v, i) => ({
|
dailyTimeCost.map((v, i) => ({
|
||||||
x: i,
|
x: i,
|
||||||
y: v,
|
timeCost: v,
|
||||||
|
count: dailyReviewCount[i],
|
||||||
label: simulationNumber,
|
label: simulationNumber,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
tableData = renderSimulationChart(svg as SVGElement, bounds, points);
|
tableData = renderSimulationChart(
|
||||||
|
svg as SVGElement,
|
||||||
|
bounds,
|
||||||
|
points,
|
||||||
|
showTime,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: tableData = renderSimulationChart(svg as SVGElement, bounds, points, showTime);
|
||||||
|
|
||||||
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);
|
tableData = renderSimulationChart(svg as SVGElement, bounds, points, showTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const label = tr.statisticsReviewsTimeCheckbox();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SpinBoxFloatRow
|
<SpinBoxFloatRow
|
||||||
|
@ -472,7 +493,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
max={3650}
|
max={3650}
|
||||||
>
|
>
|
||||||
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
||||||
Days to simulate
|
{tr.deckConfigDaysToSimulate()}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SpinBoxRow>
|
</SpinBoxRow>
|
||||||
|
|
||||||
|
@ -491,10 +512,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
bind:value={simulateFsrsRequest.newLimit}
|
bind:value={simulateFsrsRequest.newLimit}
|
||||||
defaultValue={defaults.newPerDay}
|
defaultValue={defaults.newPerDay}
|
||||||
min={0}
|
min={0}
|
||||||
max={1000}
|
max={9999}
|
||||||
>
|
>
|
||||||
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
||||||
New cards/day
|
{tr.schedulingNewCardsday()}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SpinBoxRow>
|
</SpinBoxRow>
|
||||||
|
|
||||||
|
@ -502,10 +523,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
bind:value={simulateFsrsRequest.reviewLimit}
|
bind:value={simulateFsrsRequest.reviewLimit}
|
||||||
defaultValue={defaults.reviewsPerDay}
|
defaultValue={defaults.reviewsPerDay}
|
||||||
min={0}
|
min={0}
|
||||||
max={1000}
|
max={9999}
|
||||||
>
|
>
|
||||||
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
||||||
Maximum reviews/day
|
{tr.schedulingMaximumReviewsday()}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SpinBoxRow>
|
</SpinBoxRow>
|
||||||
|
|
||||||
|
@ -516,7 +537,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
max={36500}
|
max={36500}
|
||||||
>
|
>
|
||||||
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
<SettingTitle on:click={() => openHelpModal("simulateFsrsReview")}>
|
||||||
Maximum interval
|
{tr.schedulingMaximumInterval()}
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
</SpinBoxRow>
|
</SpinBoxRow>
|
||||||
|
|
||||||
|
@ -538,6 +559,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<div>{simulateProgressString}</div>
|
<div>{simulateProgressString}</div>
|
||||||
|
|
||||||
<Graph {title}>
|
<Graph {title}>
|
||||||
|
<InputBox>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={showTime} />
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
</InputBox>
|
||||||
|
|
||||||
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
|
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
|
||||||
<CumulativeOverlay />
|
<CumulativeOverlay />
|
||||||
<HoverColumns />
|
<HoverColumns />
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
select,
|
select,
|
||||||
} from "d3";
|
} from "d3";
|
||||||
|
|
||||||
|
import * as tr from "@generated/ftl";
|
||||||
import { timeSpan } from "@tslib/time";
|
import { timeSpan } from "@tslib/time";
|
||||||
import type { GraphBounds, TableDatum } from "./graph-helpers";
|
import type { GraphBounds, TableDatum } from "./graph-helpers";
|
||||||
import { setDataAvailable } from "./graph-helpers";
|
import { setDataAvailable } from "./graph-helpers";
|
||||||
|
@ -23,7 +24,8 @@ import { hideTooltip, showTooltip } from "./tooltip-utils.svelte";
|
||||||
|
|
||||||
export interface Point {
|
export interface Point {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
timeCost: number;
|
||||||
|
count: number;
|
||||||
label: number;
|
label: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +33,7 @@ export function renderSimulationChart(
|
||||||
svgElem: SVGElement,
|
svgElem: SVGElement,
|
||||||
bounds: GraphBounds,
|
bounds: GraphBounds,
|
||||||
data: Point[],
|
data: Point[],
|
||||||
|
showTime: boolean,
|
||||||
): TableDatum[] {
|
): TableDatum[] {
|
||||||
const svg = select(svgElem);
|
const svg = select(svgElem);
|
||||||
svg.selectAll(".lines").remove();
|
svg.selectAll(".lines").remove();
|
||||||
|
@ -62,10 +65,10 @@ export function renderSimulationChart(
|
||||||
// y scale
|
// y scale
|
||||||
|
|
||||||
const yTickFormat = (n: number): string => {
|
const yTickFormat = (n: number): string => {
|
||||||
return timeSpan(n, true);
|
return showTime ? timeSpan(n, true) : n.toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
const yMax = max(convertedData, d => d.y)!;
|
const yMax = showTime ? max(convertedData, d => d.timeCost)! : max(convertedData, d => d.count)!;
|
||||||
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])
|
||||||
|
@ -91,10 +94,10 @@ export function renderSimulationChart(
|
||||||
.attr("dy", "1em")
|
.attr("dy", "1em")
|
||||||
.attr("fill", "currentColor")
|
.attr("fill", "currentColor")
|
||||||
.style("text-anchor", "middle")
|
.style("text-anchor", "middle")
|
||||||
.text("Review Time per day");
|
.text(showTime ? "Review Time per day" : "Review Count per day");
|
||||||
|
|
||||||
// x lines
|
// x lines
|
||||||
const points = convertedData.map((d) => [x(d.date), y(d.y), d.label]);
|
const points = convertedData.map((d) => [x(d.date), y(showTime ? d.timeCost : d.count), 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;
|
||||||
|
@ -161,7 +164,9 @@ 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}: ${timeSpan(value)}<br>`;
|
tooltipContent += `#${key}: ${
|
||||||
|
showTime ? timeSpan(value) : tr.statisticsReviews({ reviews: Math.round(value) })
|
||||||
|
}<br>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
showTooltip(tooltipContent, event.pageX, event.pageY);
|
showTooltip(tooltipContent, event.pageX, event.pageY);
|
||||||
|
|
Loading…
Reference in a new issue