mirror of
https://github.com/ankitects/anki.git
synced 2025-12-10 13:26:56 -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-card-ease-title = Card Ease
|
||||
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-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.
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ message GraphsResponse {
|
|||
uint32 rollover_hour = 10;
|
||||
Retrievability retrievability = 12;
|
||||
bool fsrs = 13;
|
||||
Intervals stability = 14;
|
||||
}
|
||||
|
||||
message GraphPreferences {
|
||||
|
|
|
|||
|
|
@ -19,4 +19,19 @@ impl GraphsContext {
|
|||
}
|
||||
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()),
|
||||
future_due: Some(ctx.future_due()),
|
||||
intervals: Some(ctx.intervals()),
|
||||
stability: Some(ctx.stability()),
|
||||
eases: Some(eases),
|
||||
difficulty: Some(difficulty),
|
||||
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;
|
||||
|
||||
$: if (sourceData) {
|
||||
intervalData = gatherIntervalData(sourceData);
|
||||
intervalData = gatherIntervalData(sourceData.intervals!);
|
||||
}
|
||||
|
||||
$: 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 RetrievabilityGraph from "./RetrievabilityGraph.svelte";
|
||||
import ReviewsGraph from "./ReviewsGraph.svelte";
|
||||
import StabilityGraph from "./StabilityGraph.svelte";
|
||||
import TodayStats from "./TodayStats.svelte";
|
||||
|
||||
setupGraphs(
|
||||
|
|
@ -60,6 +61,7 @@ setupGraphs(
|
|||
ReviewsGraph,
|
||||
CardCounts,
|
||||
IntervalsGraph,
|
||||
StabilityGraph,
|
||||
EaseGraph,
|
||||
DifficultyGraph,
|
||||
RetrievabilityGraph,
|
||||
|
|
@ -79,6 +81,7 @@ export const graphComponents = {
|
|||
ReviewsGraph,
|
||||
CardCounts,
|
||||
IntervalsGraph,
|
||||
StabilityGraph,
|
||||
EaseGraph,
|
||||
DifficultyGraph,
|
||||
RetrievabilityGraph,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
@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 { localizedNumber } from "@tslib/i18n";
|
||||
import { timeSpan } from "@tslib/time";
|
||||
|
|
@ -27,11 +27,11 @@ export enum IntervalRange {
|
|||
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
|
||||
// uses to calculate a percentile and then converts into a histogram, and the percentile/histogram calculations
|
||||
// in JS are relatively slow.
|
||||
const map = numericMap(data.intervals!.intervals);
|
||||
const map = numericMap(data.intervals);
|
||||
const totalCards = sum(map, ([_k, v]) => v);
|
||||
const allIntervals: number[] = Array(totalCards);
|
||||
let position = 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue