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

View file

@ -43,6 +43,49 @@ export enum SimulateWorkloadSubgraph {
memorized, 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( export function renderSimulationChart(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,