mirror of
https://github.com/ankitects/anki.git
synced 2025-12-10 21:36:55 -05:00
Add stability graph
This commit is contained in:
parent
072cd37b42
commit
9fd8a8bb40
8 changed files with 113 additions and 4 deletions
|
|
@ -94,6 +94,8 @@ statistics-range-collection = collection
|
||||||
statistics-range-search = Search
|
statistics-range-search = Search
|
||||||
statistics-card-ease-title = Card Ease
|
statistics-card-ease-title = Card Ease
|
||||||
statistics-card-difficulty-title = Card Difficulty
|
statistics-card-difficulty-title = Card Difficulty
|
||||||
|
statistics-card-stability-title = Card Stability
|
||||||
|
statistics-card-stability-subtitle = Combined with desired retention to determine the next interval.
|
||||||
statistics-card-retrievability-title = Card Retrievability
|
statistics-card-retrievability-title = Card Retrievability
|
||||||
statistics-card-ease-subtitle = The lower the ease, the more frequently a card will appear.
|
statistics-card-ease-subtitle = The lower the ease, the more frequently a card will appear.
|
||||||
statistics-card-difficulty-subtitle = The higher the difficulty, the harder it is to remember.
|
statistics-card-difficulty-subtitle = The higher the difficulty, the harder it is to remember.
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ message GraphsResponse {
|
||||||
uint32 rollover_hour = 10;
|
uint32 rollover_hour = 10;
|
||||||
Retrievability retrievability = 12;
|
Retrievability retrievability = 12;
|
||||||
bool fsrs = 13;
|
bool fsrs = 13;
|
||||||
|
Intervals stability = 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GraphPreferences {
|
message GraphPreferences {
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,19 @@ impl GraphsContext {
|
||||||
}
|
}
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn stability(&self) -> Intervals {
|
||||||
|
let mut data = Intervals::default();
|
||||||
|
for card in &self.cards {
|
||||||
|
if matches!(card.ctype, CardType::Review | CardType::Relearn) {
|
||||||
|
if let Some(state) = &card.memory_state {
|
||||||
|
*data
|
||||||
|
.intervals
|
||||||
|
.entry(state.stability as u32)
|
||||||
|
.or_insert_with(Default::default) += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ impl Collection {
|
||||||
reviews: Some(ctx.review_counts_and_times()),
|
reviews: Some(ctx.review_counts_and_times()),
|
||||||
future_due: Some(ctx.future_due()),
|
future_due: Some(ctx.future_due()),
|
||||||
intervals: Some(ctx.intervals()),
|
intervals: Some(ctx.intervals()),
|
||||||
|
stability: Some(ctx.stability()),
|
||||||
eases: Some(eases),
|
eases: Some(eases),
|
||||||
difficulty: Some(difficulty),
|
difficulty: Some(difficulty),
|
||||||
today: Some(ctx.today()),
|
today: Some(ctx.today()),
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let range = IntervalRange.Percentile95;
|
let range = IntervalRange.Percentile95;
|
||||||
|
|
||||||
$: if (sourceData) {
|
$: if (sourceData) {
|
||||||
intervalData = gatherIntervalData(sourceData);
|
intervalData = gatherIntervalData(sourceData.intervals!);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (intervalData) {
|
$: if (intervalData) {
|
||||||
|
|
|
||||||
87
ts/graphs/StabilityGraph.svelte
Normal file
87
ts/graphs/StabilityGraph.svelte
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import type { GraphsResponse } from "@tslib/anki/stats_pb";
|
||||||
|
import * as tr from "@tslib/ftl";
|
||||||
|
import { MONTH, timeSpan } from "@tslib/time";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
|
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 InputBox from "./InputBox.svelte";
|
||||||
|
import type { IntervalGraphData } from "./intervals";
|
||||||
|
import {
|
||||||
|
gatherIntervalData,
|
||||||
|
IntervalRange,
|
||||||
|
prepareIntervalData,
|
||||||
|
} from "./intervals";
|
||||||
|
import TableData from "./TableData.svelte";
|
||||||
|
|
||||||
|
export let sourceData: GraphsResponse | null = null;
|
||||||
|
export let prefs: GraphPrefs;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<SearchEventMap>();
|
||||||
|
|
||||||
|
let intervalData: IntervalGraphData | null = null;
|
||||||
|
let histogramData = null as HistogramData | null;
|
||||||
|
let tableData: TableDatum[] = [];
|
||||||
|
let range = IntervalRange.Percentile95;
|
||||||
|
|
||||||
|
$: if (sourceData) {
|
||||||
|
intervalData = gatherIntervalData(sourceData.stability!);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (intervalData) {
|
||||||
|
[histogramData, tableData] = prepareIntervalData(
|
||||||
|
intervalData,
|
||||||
|
range,
|
||||||
|
dispatch,
|
||||||
|
$prefs.browserLinksSupported,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = tr.statisticsCardStabilityTitle();
|
||||||
|
const subtitle = tr.statisticsCardStabilitySubtitle();
|
||||||
|
const month = timeSpan(1 * MONTH);
|
||||||
|
const all = tr.statisticsRangeAllTime();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if sourceData?.fsrs}
|
||||||
|
<Graph {title} {subtitle}>
|
||||||
|
<InputBox>
|
||||||
|
<label>
|
||||||
|
<input type="radio" bind:group={range} value={IntervalRange.Month} />
|
||||||
|
{month}
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
bind:group={range}
|
||||||
|
value={IntervalRange.Percentile50}
|
||||||
|
/>
|
||||||
|
50%
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
bind:group={range}
|
||||||
|
value={IntervalRange.Percentile95}
|
||||||
|
/>
|
||||||
|
95%
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" bind:group={range} value={IntervalRange.All} />
|
||||||
|
{all}
|
||||||
|
</label>
|
||||||
|
</InputBox>
|
||||||
|
|
||||||
|
<HistogramGraph data={histogramData} />
|
||||||
|
|
||||||
|
<TableData {tableData} />
|
||||||
|
</Graph>
|
||||||
|
{/if}
|
||||||
|
|
@ -50,6 +50,7 @@ import IntervalsGraph from "./IntervalsGraph.svelte";
|
||||||
import RangeBox from "./RangeBox.svelte";
|
import RangeBox from "./RangeBox.svelte";
|
||||||
import RetrievabilityGraph from "./RetrievabilityGraph.svelte";
|
import RetrievabilityGraph from "./RetrievabilityGraph.svelte";
|
||||||
import ReviewsGraph from "./ReviewsGraph.svelte";
|
import ReviewsGraph from "./ReviewsGraph.svelte";
|
||||||
|
import StabilityGraph from "./StabilityGraph.svelte";
|
||||||
import TodayStats from "./TodayStats.svelte";
|
import TodayStats from "./TodayStats.svelte";
|
||||||
|
|
||||||
setupGraphs(
|
setupGraphs(
|
||||||
|
|
@ -60,6 +61,7 @@ setupGraphs(
|
||||||
ReviewsGraph,
|
ReviewsGraph,
|
||||||
CardCounts,
|
CardCounts,
|
||||||
IntervalsGraph,
|
IntervalsGraph,
|
||||||
|
StabilityGraph,
|
||||||
EaseGraph,
|
EaseGraph,
|
||||||
DifficultyGraph,
|
DifficultyGraph,
|
||||||
RetrievabilityGraph,
|
RetrievabilityGraph,
|
||||||
|
|
@ -79,6 +81,7 @@ export const graphComponents = {
|
||||||
ReviewsGraph,
|
ReviewsGraph,
|
||||||
CardCounts,
|
CardCounts,
|
||||||
IntervalsGraph,
|
IntervalsGraph,
|
||||||
|
StabilityGraph,
|
||||||
EaseGraph,
|
EaseGraph,
|
||||||
DifficultyGraph,
|
DifficultyGraph,
|
||||||
RetrievabilityGraph,
|
RetrievabilityGraph,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
@typescript-eslint/no-explicit-any: "off",
|
@typescript-eslint/no-explicit-any: "off",
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { GraphsResponse } from "@tslib/anki/stats_pb";
|
import type { GraphsResponse_Intervals } from "@tslib/anki/stats_pb";
|
||||||
import * as tr from "@tslib/ftl";
|
import * as tr from "@tslib/ftl";
|
||||||
import { localizedNumber } from "@tslib/i18n";
|
import { localizedNumber } from "@tslib/i18n";
|
||||||
import { timeSpan } from "@tslib/time";
|
import { timeSpan } from "@tslib/time";
|
||||||
|
|
@ -27,11 +27,11 @@ export enum IntervalRange {
|
||||||
All = 3,
|
All = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function gatherIntervalData(data: GraphsResponse): IntervalGraphData {
|
export function gatherIntervalData(data: GraphsResponse_Intervals): IntervalGraphData {
|
||||||
// This could be made more efficient - this graph currently expects a flat list of individual intervals which it
|
// This could be made more efficient - this graph currently expects a flat list of individual intervals which it
|
||||||
// uses to calculate a percentile and then converts into a histogram, and the percentile/histogram calculations
|
// uses to calculate a percentile and then converts into a histogram, and the percentile/histogram calculations
|
||||||
// in JS are relatively slow.
|
// in JS are relatively slow.
|
||||||
const map = numericMap(data.intervals!.intervals);
|
const map = numericMap(data.intervals);
|
||||||
const totalCards = sum(map, ([_k, v]) => v);
|
const totalCards = sum(map, ([_k, v]) => v);
|
||||||
const allIntervals: number[] = Array(totalCards);
|
const allIntervals: number[] = Array(totalCards);
|
||||||
let position = 0;
|
let position = 0;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue