Fix: quantiles, Extract: PercentageRange component

(I accidentally staged the second commit before comiting the first)
This commit is contained in:
Luc Mcgrady 2025-11-08 15:35:44 +00:00
parent 58040c6ede
commit c3f29fad0b
No known key found for this signature in database
GPG key ID: 4F3D7A0B17CC3D9C
4 changed files with 79 additions and 42 deletions

View file

@ -7,14 +7,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as tr from "@generated/ftl";
import { createEventDispatcher } from "svelte";
import { DifficultyRange, gatherData, prepareData } from "./difficulty";
import { gatherData, prepareData } from "./difficulty";
import Graph from "./Graph.svelte";
import type { GraphPrefs } from "./graph-helpers";
import type { SearchEventMap, TableDatum } from "./graph-helpers";
import type { HistogramData } from "./histogram-graph";
import HistogramGraph from "./HistogramGraph.svelte";
import TableData from "./TableData.svelte";
import InputBox from "./InputBox.svelte";
import PercentageRange from "./PercentageRange.svelte";
import { PercentageRangeEnum, PercentageRangeToQuantile } from "./percentageRange";
export let sourceData: GraphsResponse | null = null;
export let prefs: GraphPrefs;
@ -23,13 +24,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let histogramData: HistogramData | null = null;
let tableData: TableDatum[] = [];
let range = DifficultyRange.All;
$: percentile = {
[DifficultyRange.Percentile50]: 0.5,
[DifficultyRange.Percentile95]: 0.95,
[DifficultyRange.All]: 1,
}[range];
let range = PercentageRangeEnum.All;
$: if (sourceData) {
const data = gatherData(sourceData);
@ -40,7 +35,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
data,
dispatch,
$prefs.browserLinksSupported,
1 - percentile,
PercentageRangeToQuantile(range),
);
}
@ -50,28 +45,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{#if sourceData?.fsrs}
<Graph {title} {subtitle}>
<InputBox>
<label>
<input
type="radio"
bind:group={range}
value={DifficultyRange.Percentile50}
/>
50%
</label>
<label>
<input
type="radio"
bind:group={range}
value={DifficultyRange.Percentile95}
/>
95%
</label>
<label>
<input type="radio" bind:group={range} value={DifficultyRange.All} />
{tr.statisticsRangeAllTime()}
</label>
</InputBox>
<PercentageRange bind:range />
<HistogramGraph data={histogramData} />

View file

@ -0,0 +1,42 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import InputBox from "./InputBox.svelte";
import { PercentageRangeEnum } from "./percentageRange";
import * as tr from "@generated/ftl";
export let range: PercentageRangeEnum;
</script>
<InputBox>
<label>
<input
type="radio"
bind:group={range}
value={PercentageRangeEnum.Percentile95}
/>
50%
</label>
<label>
<input
type="radio"
bind:group={range}
value={PercentageRangeEnum.Percentile95}
/>
95%
</label>
<label>
<input
type="radio"
bind:group={range}
value={PercentageRangeEnum.Percentile100}
/>
100%
</label>
<label>
<input type="radio" bind:group={range} value={PercentageRangeEnum.All} />
{tr.statisticsRangeAllTime()}
</label>
</InputBox>

View file

@ -9,18 +9,12 @@ import type { GraphsResponse } from "@generated/anki/stats_pb";
import * as tr from "@generated/ftl";
import { localizedNumber } from "@tslib/i18n";
import type { Bin, ScaleLinear } from "d3";
import { bin, interpolateRdYlGn, quantile, scaleLinear, scaleSequential, sum } from "d3";
import { bin, interpolateRdYlGn, scaleLinear, scaleSequential, sum } from "d3";
import type { SearchDispatch, TableDatum } from "./graph-helpers";
import { getNumericMapBinValue, numericMap } from "./graph-helpers";
import type { HistogramData } from "./histogram-graph";
export enum DifficultyRange {
All = 0,
Percentile50 = 1,
Percentile95 = 2,
}
export interface GraphData {
eases: Map<number, number>;
average: number;
@ -63,19 +57,29 @@ function getAdjustedScaleAndTicks(
];
}
export function easeQuantile(data: Map<number, number>, quantile: number) {
let count = sum(data.values()) * quantile;
for (const [key, value] of data.entries()) {
count -= value;
if (count <= 0) {
return key;
}
}
}
export function prepareData(
data: GraphData,
dispatch: SearchDispatch,
browserLinksSupported: boolean,
lowerQuantile: number = 0
quantile?: number,
): [HistogramData | null, TableDatum[]] {
// get min/max
const allEases = data.eases;
if (!allEases.size) {
return [null, []];
}
const xMin = quantile(Array.from(allEases.keys()), lowerQuantile) ?? 0;
const xMax = 100
const xMin = quantile ? easeQuantile(allEases, 1 - quantile) ?? 0 : 0;
const xMax = quantile ? easeQuantile(allEases, quantile) ?? 0 : 100;
const desiredBars = 20;
const [scale, ticks] = getAdjustedScaleAndTicks(xMin, xMax, desiredBars);

View file

@ -0,0 +1,17 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export enum PercentageRangeEnum {
All = 0,
Percentile100 = 1,
Percentile95 = 2,
Percentile50 = 3,
}
export function PercentageRangeToQuantile(range: PercentageRangeEnum) {
return ({
[PercentageRangeEnum.Percentile100]: 1,
[PercentageRangeEnum.Percentile95]: 0.95,
[PercentageRangeEnum.Percentile50]: 0.5,
[PercentageRangeEnum.All]: undefined,
})[range];
}