diff --git a/ts/routes/deck-options/FsrsOptions.svelte b/ts/routes/deck-options/FsrsOptions.svelte index 1c1574791..28401169c 100644 --- a/ts/routes/deck-options/FsrsOptions.svelte +++ b/ts/routes/deck-options/FsrsOptions.svelte @@ -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 TableData from "../graphs/TableData.svelte"; import { defaultGraphBounds, type TableDatum } from "../graphs/graph-helpers"; + import InputBox from "../graphs/InputBox.svelte"; export let state: DeckOptionsState; export let openHelpModal: (String) => void; @@ -73,6 +74,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html | ComputeRetentionProgress | undefined; + let showTime = false; + const optimalRetentionRequest = new ComputeOptimalRetentionRequest({ daysToSimulate: 365, lossAversion: 2.5, @@ -305,6 +308,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html return result; } + function addArrays(arr1: number[], arr2: number[]): number[] { + return arr1.map((value, index) => value + arr2[index]); + } + $: simulateProgressString = ""; async function simulateFsrs(): Promise { @@ -328,23 +335,37 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html resp.dailyTimeCost, Math.ceil(simulateFsrsRequest.daysToSimulate / 50), ); + const dailyReviewCount = movingAverage( + addArrays(resp.dailyReviewCount, resp.dailyNewCount), + Math.ceil(simulateFsrsRequest.daysToSimulate / 50), + ); points = points.concat( dailyTimeCost.map((v, i) => ({ x: i, - y: v, + timeCost: v, + count: dailyReviewCount[i], 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 { points = points.filter((p) => p.label !== simulationNumber); 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(); openHelpModal("simulateFsrsReview")}> - Days to simulate + {tr.deckConfigDaysToSimulate()} @@ -491,10 +512,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html bind:value={simulateFsrsRequest.newLimit} defaultValue={defaults.newPerDay} min={0} - max={1000} + max={9999} > openHelpModal("simulateFsrsReview")}> - New cards/day + {tr.schedulingNewCardsday()} @@ -502,10 +523,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html bind:value={simulateFsrsRequest.reviewLimit} defaultValue={defaults.reviewsPerDay} min={0} - max={1000} + max={9999} > openHelpModal("simulateFsrsReview")}> - Maximum reviews/day + {tr.schedulingMaximumReviewsday()} @@ -516,7 +537,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html max={36500} > openHelpModal("simulateFsrsReview")}> - Maximum interval + {tr.schedulingMaximumInterval()} @@ -538,6 +559,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{simulateProgressString}
+ + + + diff --git a/ts/routes/graphs/simulator.ts b/ts/routes/graphs/simulator.ts index 1ebd0a4ae..db26cf65c 100644 --- a/ts/routes/graphs/simulator.ts +++ b/ts/routes/graphs/simulator.ts @@ -16,6 +16,7 @@ import { select, } from "d3"; +import * as tr from "@generated/ftl"; import { timeSpan } from "@tslib/time"; import type { GraphBounds, TableDatum } from "./graph-helpers"; import { setDataAvailable } from "./graph-helpers"; @@ -23,7 +24,8 @@ import { hideTooltip, showTooltip } from "./tooltip-utils.svelte"; export interface Point { x: number; - y: number; + timeCost: number; + count: number; label: number; } @@ -31,6 +33,7 @@ export function renderSimulationChart( svgElem: SVGElement, bounds: GraphBounds, data: Point[], + showTime: boolean, ): TableDatum[] { const svg = select(svgElem); svg.selectAll(".lines").remove(); @@ -62,10 +65,10 @@ export function renderSimulationChart( // y scale 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() .range([bounds.height - bounds.marginBottom, bounds.marginTop]) .domain([0, yMax]) @@ -91,10 +94,10 @@ export function renderSimulationChart( .attr("dy", "1em") .attr("fill", "currentColor") .style("text-anchor", "middle") - .text("Review Time per day"); + .text(showTime ? "Review Time per day" : "Review Count per day"); // 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 color = schemeCategory10; @@ -161,7 +164,9 @@ export function renderSimulationChart( const days = +((date.getTime() - Date.now()) / (60 * 60 * 24 * 1000)).toFixed(); let tooltipContent = `Date: ${localizedDate(date)}
In ${days} Days
`; for (const [key, value] of Object.entries(groupData)) { - tooltipContent += `#${key}: ${timeSpan(value)}
`; + tooltipContent += `#${key}: ${ + showTime ? timeSpan(value) : tr.statisticsReviews({ reviews: Math.round(value) }) + }
`; } showTooltip(tooltipContent, event.pageX, event.pageY);