Add zoomed version of graph

This commit is contained in:
Luc Mcgrady 2025-07-13 13:38:26 +01:00
parent 000cffc6d6
commit 17d8528afd
No known key found for this signature in database
GPG key ID: 4F3D7A0B17CC3D9C
2 changed files with 119 additions and 41 deletions

View file

@ -13,9 +13,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import TableData from "../graphs/TableData.svelte";
import InputBox from "../graphs/InputBox.svelte";
import { defaultGraphBounds, type TableDatum } from "../graphs/graph-helpers";
import { SimulateSubgraph, type Point } from "../graphs/simulator";
import {
SimulateSubgraph,
SimulateWorkloadSubgraph,
type Point,
} from "../graphs/simulator";
import * as tr from "@generated/ftl";
import { renderSimulationChart } from "../graphs/simulator";
import { renderSimulationChart, renderWorkloadChart } from "../graphs/simulator";
import {
computeOptimalRetention,
simulateFsrsReview,
@ -48,6 +52,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const config = state.currentConfig;
let simulateSubgraph: SimulateSubgraph = SimulateSubgraph.count;
let simulateWorkloadSubgraph: SimulateWorkloadSubgraph =
SimulateWorkloadSubgraph.ratio;
let workload: boolean = false;
let tableData: TableDatum[] = [];
let simulating: boolean = false;
const fsrs = state.fsrs;
@ -196,23 +203,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} finally {
simulating = false;
if (resp) {
simulationNumber += 1;
points = Object.entries(resp.memorized).map(([dr, v]) => ({
x: parseInt(dr),
timeCost: resp!.cost[dr],
memorized: v,
count: -1,
label: 1,
}));
points = points.concat(
Object.entries(resp.memorized).map(([dr, v]) => ({
x: parseInt(dr),
timeCost: resp!.cost[dr],
count: resp!.cost[dr] / v,
memorized: v,
label: simulationNumber,
})),
);
tableData = renderSimulationChart(
workload = true;
tableData = renderWorkloadChart(
svg as SVGElement,
bounds,
points,
simulateSubgraph,
simulateWorkloadSubgraph,
);
}
}
@ -266,11 +270,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
});
}
tableData = renderSimulationChart(
const render_function = workload
? renderWorkloadChart
: renderSimulationChart;
tableData = render_function(
svg as SVGElement,
bounds,
pointsToRender,
simulateSubgraph,
(workload ? simulateWorkloadSubgraph : simulateSubgraph) as any as never,
);
}
@ -519,30 +527,57 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<Graph>
<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>
{#if !workload}
<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>
{:else}
<label>
<input
type="radio"
value={SimulateWorkloadSubgraph.ratio}
bind:group={simulateWorkloadSubgraph}
/>
{"Ratio"}
</label>
<label>
<input
type="radio"
value={SimulateWorkloadSubgraph.memorized}
bind:group={simulateWorkloadSubgraph}
/>
{tr.deckConfigFsrsSimulatorRadioMemorized()}
</label>
<label>
<input
type="radio"
value={SimulateWorkloadSubgraph.time}
bind:group={simulateWorkloadSubgraph}
/>
{tr.statisticsReviewsTimeCheckbox()}
</label>
{/if}
</InputBox>
</div>

View file

@ -43,6 +43,49 @@ export enum SimulateWorkloadSubgraph {
memorized,
}
export function renderWorkloadChart(
svgElem: SVGElement,
bounds: GraphBounds,
data: Point[],
subgraph: SimulateWorkloadSubgraph,
) {
const today = new Date();
const xMin = 70;
const xMax = 99;
const x = scaleLinear()
.domain([xMin, xMax])
.range([bounds.marginLeft, bounds.width - bounds.marginRight]);
const subgraph_data = ({
[SimulateWorkloadSubgraph.ratio]: data.map(d => ({ ...d, y: d.timeCost / d.memorized })),
[SimulateWorkloadSubgraph.time]: data.map(d => ({ ...d, y: d.timeCost })),
[SimulateWorkloadSubgraph.memorized]: data.map(d => ({ ...d, y: d.memorized })),
})[subgraph];
const yTickFormat = (n: number): string => {
return subgraph == SimulateWorkloadSubgraph.time ? timeSpan(n, true) : n.toString();
};
const formatY: (value: number) => string = ({
[SimulateWorkloadSubgraph.ratio]: (value: number) => `${timeSpan(value)} time per 1 card memorized`,
[SimulateWorkloadSubgraph.time]: timeSpan,
[SimulateWorkloadSubgraph.memorized]: (value: number) =>
tr.statisticsMemorized({ memorized: Math.round(value).toFixed(0) }),
})[subgraph];
return _renderSimulationChart(
svgElem,
bounds,
subgraph_data,
x,
yTickFormat,
formatY,
(_e: MouseEvent, _d: number) => undefined,
);
}
export function renderSimulationChart(
svgElem: SVGElement,
bounds: GraphBounds,