mirror of
https://github.com/ankitects/anki.git
synced 2026-01-07 02:53:54 -05:00
Added: Difficulty zoom
This commit is contained in:
parent
dac26ce671
commit
58040c6ede
4 changed files with 59 additions and 16 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
<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>
|
||||
|
||||
<HistogramGraph data={histogramData} />
|
||||
|
||||
<TableData {tableData} />
|
||||
|
|
|
|||
|
|
@ -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<number, number>;
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue