Replace CountMethodRadios with SeparateInactiveCheckbox method

* cards will still be mostly counted by ctype rather than queue
* if the user wants to include inactive cards buried and suspended will
  be first filtered out, before the rest is counted by ctype
This commit is contained in:
Henrik Giesel 2021-01-05 16:13:06 +01:00
parent 2ae09ae39e
commit 681d82f5cc
5 changed files with 60 additions and 114 deletions

View File

@ -1,15 +1,15 @@
<script lang="typescript">
import { CardCountMethod, defaultGraphBounds } from "./graph-helpers";
import { defaultGraphBounds } from "./graph-helpers";
import { gatherData, renderCards } from "./card-counts";
import type { GraphData, TableDatum } from "./card-counts";
import type pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import CountMethodRadios from "./CountMethodRadios.svelte";
import SeparateInactiveCheckbox from "./SeparateInactiveCheckbox.svelte";
export let sourceData: pb.BackendProto.GraphsOut;
export let i18n: I18n;
let cardCountMethod = CardCountMethod.ByType;
let separateInactive = false;
let svg = null as HTMLElement | SVGElement | null;
let bounds = defaultGraphBounds();
@ -20,7 +20,7 @@
let tableData = (null as unknown) as TableDatum[];
$: {
graphData = gatherData(sourceData, cardCountMethod, i18n);
graphData = gatherData(sourceData, separateInactive, i18n);
tableData = renderCards(svg as any, bounds, graphData);
}
@ -56,7 +56,7 @@
<h1>{graphData.title}</h1>
<div class="range-box-inner">
<CountMethodRadios bind:cardCountMethod {i18n} />
<SeparateInactiveCheckbox bind:separateInactive {i18n} />
</div>
<div class="counts-outer">

View File

@ -1,20 +0,0 @@
<script lang="typescript">
import type { I18n } from "anki/i18n";
import { CardCountMethod } from "./graph-helpers";
export let i18n: I18n;
export let cardCountMethod: CardCountMethod;
const byType = "By card type";
const byQueue = "By scheduling queue";
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
</script>
<label>
<input type="radio" bind:group={cardCountMethod} value={CardCountMethod.ByType} />
{byType}
</label>
<label>
<input type="radio" bind:group={cardCountMethod} value={CardCountMethod.ByQueue} />
{byQueue}
</label>

View File

@ -0,0 +1,14 @@
<script lang="typescript">
import type { I18n } from "anki/i18n";
export let i18n: I18n;
export let separateInactive: bool = false;
const label = "Separate suspended/buried cards";
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
</script>
<label>
<input type="checkbox" bind:checked={separateInactive} />
{label}
</label>

View File

@ -15,81 +15,48 @@ import { scaleLinear } from "d3-scale";
import { pie, arc } from "d3-shape";
import { interpolate } from "d3-interpolate";
import type { GraphBounds } from "./graph-helpers";
import { CardCountMethod } from "./graph-helpers";
import { cumsum } from "d3-array";
import type { I18n } from "anki/i18n";
type Count = [string, number, string];
type Count = [string, number];
export interface GraphData {
title: string;
counts: Count[];
totalCards: number;
}
const barColours = {
new: schemeBlues[5][2],
review: schemeGreens[5][2],
young: schemeGreens[5][2],
mature: schemeGreens[5][3],
learn: schemeOranges[5][2],
relearn: schemeOranges[5][3],
suspended: "#FFDC41",
buried: "grey",
};
const barColours = [
schemeBlues[5][2] /* new */,
schemeOranges[5][2] /* learn */,
schemeOranges[5][3] /* relearn */,
schemeGreens[5][2] /* young */,
schemeGreens[5][3] /* mature */,
"#FFDC41" /* suspended */,
"grey" /* buried */,
];
function gatherByQueue(cards: pb.BackendProto.ICard[], i18n: I18n): Count[] {
function countCards(cards: pb.BackendProto.ICard[], separateInactive: boolean, i18n: I18n): Count[] {
let newCards = 0;
let learn = 0;
let review = 0;
let relearn = 0;
let young = 0;
let mature = 0;
let suspended = 0;
let buried = 0;
for (const card of cards as pb.BackendProto.Card[]) {
if (separateInactive) {
switch (card.queue) {
case CardQueue.New:
newCards += 1;
break;
case CardQueue.Review:
review += 1;
break;
case CardQueue.Learn:
case CardQueue.DayLearn:
case CardQueue.PreviewRepeat:
learn += 1;
break;
case CardQueue.Suspended:
suspended += 1;
break;
continue;
case CardQueue.SchedBuried:
case CardQueue.UserBuried:
buried += 1;
break;
continue;
}
}
const counts: Count[] = [
[i18n.tr(i18n.TR.STATISTICS_COUNTS_NEW_CARDS), newCards, barColours.new],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_LEARNING_CARDS), learn, barColours.learn],
["Review", review, barColours.review],
[
i18n.tr(i18n.TR.STATISTICS_COUNTS_SUSPENDED_CARDS),
suspended,
barColours.suspended,
],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_BURIED_CARDS), buried, barColours.buried],
];
return counts;
}
function gatherByCtype(cards: pb.BackendProto.ICard[], i18n: I18n): Count[] {
let newCards = 0;
let learn = 0;
let young = 0;
let mature = 0;
let relearn = 0;
for (const card of cards as pb.BackendProto.Card[]) {
switch (card.ctype) {
case CardType.New:
newCards += 1;
@ -110,31 +77,28 @@ function gatherByCtype(cards: pb.BackendProto.ICard[], i18n: I18n): Count[] {
}
}
const counts: Count[] = [
[i18n.tr(i18n.TR.STATISTICS_COUNTS_NEW_CARDS), newCards, barColours.new],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_LEARNING_CARDS), learn, barColours.learn],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_YOUNG_CARDS), young, barColours.young],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_MATURE_CARDS), mature, barColours.mature],
[
i18n.tr(i18n.TR.STATISTICS_COUNTS_RELEARNING_CARDS),
relearn,
barColours.relearn,
],
];
[i18n.tr(i18n.TR.STATISTICS_COUNTS_NEW_CARDS), newCards],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_LEARNING_CARDS), learn],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_RELEARNING_CARDS), relearn],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_YOUNG_CARDS), young],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_MATURE_CARDS), mature],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_SUSPENDED_CARDS), suspended],
[i18n.tr(i18n.TR.STATISTICS_COUNTS_BURIED_CARDS), buried],
]
return counts;
}
export function gatherData(
data: pb.BackendProto.GraphsOut,
method: CardCountMethod,
separateInactive: boolean,
i18n: I18n
): GraphData {
const totalCards = data.cards.length;
const counts =
method === CardCountMethod.ByType
? gatherByCtype(data.cards, i18n)
: gatherByQueue(data.cards, i18n);
const counts = countCards(data.cards, separateInactive, i18n)
return {
title: i18n.tr(i18n.TR.STATISTICS_COUNTS_TITLE),
@ -155,7 +119,6 @@ export interface SummedDatum {
label: string;
// count of this particular item
count: number;
colour: string;
// running total
total: number;
}
@ -178,7 +141,6 @@ export function renderCards(
return {
label: count[0],
count: count[1],
colour: count[2],
idx,
total: n,
} as SummedDatum;
@ -199,20 +161,16 @@ export function renderCards(
.selectAll("path")
.data(pieData)
.join(
(enter) =>
enter
(enter) => enter
.append("path")
.attr("fill", (_d, i) => {
return data[i].colour;
.attr("fill", (_d, idx) => {
return barColours[idx];
})
.attr("d", arcGen as any),
function (update) {
return update.call((d) =>
d
.transition(trans)
.attr("fill", (_d, i) => {
return data[i].colour;
})
.attrTween("d", (d) => {
const interpolator = interpolate(
{ startAngle: 0, endAngle: 0 },
@ -227,13 +185,13 @@ export function renderCards(
x.range([bounds.marginLeft, bounds.width - bounds.marginRight]);
const tableData = data.map((d) => {
const tableData = data.map((d, idx) => {
const percent = ((d.count / xMax) * 100).toFixed(1);
return {
label: d.label,
count: d.count,
percent: `${percent}%`,
colour: d.colour,
colour: barColours[idx],
} as TableDatum;
});

View File

@ -33,12 +33,6 @@ export enum GraphRange {
AllTime = 3,
}
// how card should be counted
export enum CardCountMethod {
ByType = 0,
ByQueue = 1,
}
export interface GraphsContext {
cards: pb.BackendProto.Card[];
revlog: pb.BackendProto.RevlogEntry[];