diff --git a/rslib/src/stats/graphs/eases.rs b/rslib/src/stats/graphs/eases.rs index 9b8e703e6..80c1caccc 100644 --- a/rslib/src/stats/graphs/eases.rs +++ b/rslib/src/stats/graphs/eases.rs @@ -17,7 +17,7 @@ impl GraphsContext { if let Some(state) = card.memory_state { *difficulty .eases - .entry(percent_to_bin(state.difficulty() * 100.0)) + .entry(percent_to_bin(state.difficulty() * 100.0, 1)) .or_insert_with(Default::default) += 1; difficulty_values.push(state.difficulty()); } else if matches!(card.ctype, CardType::Review | CardType::Relearn) { @@ -51,11 +51,11 @@ fn median(data: &mut [f32]) -> f32 { } /// Bins the number into a bin of 0, 5, .. 95 -pub(super) fn percent_to_bin(x: f32) -> u32 { +pub(super) fn percent_to_bin(x: f32, bin_size: u32) -> u32 { if x == 100.0 { - 95 + 100 - bin_size } else { - ((x / 5.0).floor() * 5.0) as u32 + ((x / bin_size as f32).floor() * bin_size as f32) as u32 } } @@ -65,11 +65,11 @@ mod tests { #[test] fn bins() { - assert_eq!(percent_to_bin(0.0), 0); - assert_eq!(percent_to_bin(4.9), 0); - assert_eq!(percent_to_bin(5.0), 5); - assert_eq!(percent_to_bin(9.9), 5); - assert_eq!(percent_to_bin(99.9), 95); - assert_eq!(percent_to_bin(100.0), 95); + assert_eq!(percent_to_bin(0.0, 5), 0); + assert_eq!(percent_to_bin(4.9, 5), 0); + assert_eq!(percent_to_bin(5.0, 5), 5); + assert_eq!(percent_to_bin(9.9, 5), 5); + assert_eq!(percent_to_bin(99.9, 5), 95); + assert_eq!(percent_to_bin(100.0, 5), 95); } } diff --git a/rslib/src/stats/graphs/retrievability.rs b/rslib/src/stats/graphs/retrievability.rs index 6881a6062..69ac4d450 100644 --- a/rslib/src/stats/graphs/retrievability.rs +++ b/rslib/src/stats/graphs/retrievability.rs @@ -39,7 +39,7 @@ impl GraphsContext { *retrievability .retrievability - .entry(percent_to_bin(r * 100.0)) + .entry(percent_to_bin(r * 100.0, 1)) .or_insert_with(Default::default) += 1; retrievability.sum_by_card += r; card_with_retrievability_count += 1; diff --git a/ts/routes/graphs/DifficultyGraph.svelte b/ts/routes/graphs/DifficultyGraph.svelte index 4eea03727..5d05dc6fe 100644 --- a/ts/routes/graphs/DifficultyGraph.svelte +++ b/ts/routes/graphs/DifficultyGraph.svelte @@ -7,13 +7,14 @@ 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 { gatherData, prepareData } from "./difficulty"; + import { DifficultyRange, 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"; export let sourceData: GraphsResponse | null = null; export let prefs: GraphPrefs; @@ -22,12 +23,24 @@ 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]; $: if (sourceData) { + const data = gatherData(sourceData); + + console.log(data.eases); + [histogramData, tableData] = prepareData( - gatherData(sourceData), + data, dispatch, $prefs.browserLinksSupported, + 1 - percentile, ); } @@ -37,6 +50,29 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {#if sourceData?.fsrs} + + + + + + diff --git a/ts/routes/graphs/difficulty.ts b/ts/routes/graphs/difficulty.ts index 1778e2513..96aff05f9 100644 --- a/ts/routes/graphs/difficulty.ts +++ b/ts/routes/graphs/difficulty.ts @@ -9,12 +9,18 @@ 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, scaleLinear, scaleSequential, sum } from "d3"; +import { bin, interpolateRdYlGn, quantile, 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; average: number; @@ -61,14 +67,15 @@ export function prepareData( data: GraphData, dispatch: SearchDispatch, browserLinksSupported: boolean, + lowerQuantile: number = 0 ): [HistogramData | null, TableDatum[]] { // get min/max const allEases = data.eases; if (!allEases.size) { return [null, []]; } - const xMin = 0; - const xMax = 100; + const xMin = quantile(Array.from(allEases.keys()), lowerQuantile) ?? 0; + const xMax = 100 const desiredBars = 20; const [scale, ticks] = getAdjustedScaleAndTicks(xMin, xMax, desiredBars);