i18n some tooltips

This commit is contained in:
Damien Elmes 2020-06-28 19:34:19 +10:00
parent 93b8cebf1e
commit 41b296e96c
7 changed files with 131 additions and 34 deletions

View file

@ -83,6 +83,9 @@ statistics-counts-young-cards = Young
statistics-counts-mature-cards = Mature statistics-counts-mature-cards = Mature
statistics-counts-suspended-cards = Suspended statistics-counts-suspended-cards = Suspended
statistics-counts-buried-cards = Buried statistics-counts-buried-cards = Buried
statistics-counts-early-cards = Early
statistics-counts-learning-cards = Learning
statistics-counts-relearning-cards = Relearning
statistics-counts-title = Card Counts statistics-counts-title = Card Counts
statistics-range-all-time = deck life statistics-range-all-time = deck life
statistics-range-deck = deck statistics-range-deck = deck
@ -102,3 +105,21 @@ statistics-reviews-time-subtitle = The time taken to answer the questions.
statistics-intervals-subtitle = Delays until reviews are shown again. statistics-intervals-subtitle = Delays until reviews are shown again.
statistics-answer-buttons-subtitle = The number of times you have pressed each button. statistics-answer-buttons-subtitle = The number of times you have pressed each button.
statistics-hours-subtitle = Review success rate for each hour of the day. statistics-hours-subtitle = Review success rate for each hour of the day.
statistics-reviews-time-checkbox = Time
statistics-in-days-single = { $days ->
[0] Today
[1] Tomorrow
*[other] In { $days } days
}
statistics-in-days-range = In { $daysStart }~{ $daysEnd } days
statistics-days-ago-single = { $days ->
[1] Yesterday
*[other] { $days } days ago
}
statistics-days-ago-range = { $daysStart }~{ $daysEnd } days ago
statistics-running-total = Running total
statistics-cards-due = { $cards ->
[one] 1 card due
*[other] { $cards } cards due
}
statistics-backlog-checkbox = Backlog

View file

@ -10,11 +10,10 @@ function formatNumbers(args?: Record<string, RecordVal>): void {
if (!args) { if (!args) {
return; return;
} }
for (const key of Object.keys(args)) { for (const key of Object.keys(args)) {
if (typeof args[key] === "number") { if (typeof args[key] === "number") {
args[key] = new FluentNumber(args[key] as number, { args[key] = new FluentNumber(args[key] as number, {
maximumSignificantDigits: 2, maximumFractionDigits: 2,
}); });
} }
} }

View file

@ -18,7 +18,7 @@
let graphData = null as GraphData | null; let graphData = null as GraphData | null;
let histogramData = null as HistogramData | null; let histogramData = null as HistogramData | null;
let backlog: boolean = true;
let svg = null as HTMLElement | SVGElement | null; let svg = null as HTMLElement | SVGElement | null;
let range = FutureDueRange.Month; let range = FutureDueRange.Month;
@ -27,7 +27,7 @@
} }
$: if (graphData) { $: if (graphData) {
histogramData = buildHistogram(graphData, range); histogramData = buildHistogram(graphData, range, backlog, i18n);
} }
const title = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_TITLE);
@ -36,6 +36,7 @@
const year = timeSpan(i18n, 1 * YEAR); const year = timeSpan(i18n, 1 * YEAR);
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME); const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
const subtitle = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_SUBTITLE); const subtitle = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_SUBTITLE);
const backlogLabel = i18n.tr(i18n.TR.STATISTICS_BACKLOG_CHECKBOX);
</script> </script>
{#if histogramData} {#if histogramData}
@ -44,6 +45,11 @@
<h1>{title}</h1> <h1>{title}</h1>
<div class="range-box-inner"> <div class="range-box-inner">
<label>
<input type="checkbox" bind:checked={backlog} />
{backlogLabel}
</label>
<label> <label>
<input type="radio" bind:group={range} value={FutureDueRange.Month} /> <input type="radio" bind:group={range} value={FutureDueRange.Month} />
{month} {month}

View file

@ -44,6 +44,7 @@
const month3 = timeSpan(i18n, 3 * MONTH); const month3 = timeSpan(i18n, 3 * MONTH);
const year = timeSpan(i18n, 1 * YEAR); const year = timeSpan(i18n, 1 * YEAR);
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME); const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
const time = i18n.tr(i18n.TR.STATISTICS_REVIEWS_TIME_CHECKBOX);
let subtitle: string; let subtitle: string;
$: if (showTime) { $: if (showTime) {
@ -59,7 +60,7 @@
<div class="range-box-inner"> <div class="range-box-inner">
<label> <label>
<input type="checkbox" bind:checked={showTime} /> <input type="checkbox" bind:checked={showTime} />
Time {time}
</label> </label>
{#if revlogRange >= RevlogRange.Year} {#if revlogRange >= RevlogRange.Year}

View file

@ -12,6 +12,8 @@ import { scaleLinear, scaleSequential } from "d3-scale";
import { CardQueue } from "../cards"; import { CardQueue } from "../cards";
import { HistogramData } from "./histogram-graph"; import { HistogramData } from "./histogram-graph";
import { interpolateGreens } from "d3-scale-chromatic"; import { interpolateGreens } from "d3-scale-chromatic";
import { dayLabel } from "../time";
import { I18n } from "../i18n";
export interface GraphData { export interface GraphData {
dueCounts: Map<number, number>; dueCounts: Map<number, number>;
@ -26,7 +28,7 @@ export enum FutureDueRange {
export function gatherData(data: pb.BackendProto.GraphsOut): GraphData { export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
const due = (data.cards as pb.BackendProto.Card[]) const due = (data.cards as pb.BackendProto.Card[])
.filter((c) => c.queue == CardQueue.Review && c.due >= data.daysElapsed) .filter((c) => c.queue == CardQueue.Review) // && c.due >= data.daysElapsed)
.map((c) => c.due - data.daysElapsed); .map((c) => c.due - data.daysElapsed);
const dueCounts = rollup( const dueCounts = rollup(
due, due,
@ -40,24 +42,11 @@ function binValue(d: Bin<Map<number, number>, number>): number {
return sum(d, (d) => d[1]); return sum(d, (d) => d[1]);
} }
function hoverText(
data: HistogramData,
binIdx: number,
cumulative: number,
_percent: number
): string {
const bin = data.bins[binIdx];
const interval =
bin.x1! - bin.x0! === 1 ? `${bin.x0} days` : `${bin.x0}~${bin.x1} days`;
return (
`${binValue(data.bins[binIdx] as any)} cards due in ${interval}. ` +
`<br>${cumulative} cards at or before this point.`
);
}
export function buildHistogram( export function buildHistogram(
sourceData: GraphData, sourceData: GraphData,
range: FutureDueRange range: FutureDueRange,
backlog: boolean,
i18n: I18n
): HistogramData | null { ): HistogramData | null {
// get min/max // get min/max
const data = sourceData.dueCounts; const data = sourceData.dueCounts;
@ -65,8 +54,11 @@ export function buildHistogram(
return null; return null;
} }
const [_xMinOrig, origXMax] = extent<number>(data.keys()); const [xMinOrig, origXMax] = extent<number>(data.keys());
const xMin = 0; let xMin = xMinOrig;
if (!backlog) {
xMin = 0;
}
let xMax = origXMax; let xMax = origXMax;
// cap max to selected range // cap max to selected range
@ -103,6 +95,22 @@ export function buildHistogram(
const total = sum(bins as any, binValue); const total = sum(bins as any, binValue);
function hoverText(
data: HistogramData,
binIdx: number,
cumulative: number,
_percent: number
): string {
const bin = data.bins[binIdx];
const days = dayLabel(i18n, bin.x0!, bin.x1!);
const cards = i18n.tr(i18n.TR.STATISTICS_CARDS_DUE, {
cards: binValue(data.bins[binIdx] as any),
});
const totalLabel = i18n.tr(i18n.TR.STATISTICS_RUNNING_TOTAL);
return `${days}:<br>${cards}<br>${totalLabel}: ${cumulative}`;
}
return { return {
scale: x, scale: x,
bins, bins,

View file

@ -21,7 +21,7 @@ import { showTooltip, hideTooltip } from "./tooltip";
import { GraphBounds } from "./graphs"; import { GraphBounds } from "./graphs";
import { area, curveBasis } from "d3-shape"; import { area, curveBasis } from "d3-shape";
import { min, histogram, sum, max, Bin, cumsum } from "d3-array"; import { min, histogram, sum, max, Bin, cumsum } from "d3-array";
import { timeSpan } from "../time"; import { timeSpan, dayLabel } from "../time";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
interface Reviews { interface Reviews {
@ -116,7 +116,7 @@ export function renderReviews(
showTime: boolean, showTime: boolean,
i18n: I18n i18n: I18n
): void { ): void {
const xMax = 0; const xMax = 1;
let xMin = 0; let xMin = 0;
// cap max to selected range // cap max to selected range
switch (range) { switch (range) {
@ -202,19 +202,58 @@ export function renderReviews(
x.domain() as any x.domain() as any
); );
function valueLabel(n: number): string {
if (showTime) {
return timeSpan(i18n, n / 1000);
} else {
return i18n.tr(i18n.TR.STATISTICS_CARDS, { cards: n });
}
}
function tooltipText(d: BinType, cumulative: number): string { function tooltipText(d: BinType, cumulative: number): string {
let buf = `<div>day ${d.x0}-${d.x1}</div>`; const day = dayLabel(i18n, d.x0!, d.x1!);
let buf = `<div>${day}</div>`;
const totals = totalsForBin(d); const totals = totalsForBin(d);
const lines = [ const lines = [
[darkerGreens(1), `Mature: ${totals[0]}`], [
[lighterGreens(1), `Young: ${totals[1]}`], darkerGreens(1),
[blues(1), `New/learn: ${totals[2]}`], `${i18n.tr(i18n.TR.STATISTICS_COUNTS_MATURE_CARDS)}: ${valueLabel(
[reds(1), `Relearn: ${totals[3]}`], totals[0]
[oranges(1), `Early: ${totals[4]}`], )}`,
["grey", `Total: ${cumulative}`], ],
[
lighterGreens(1),
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_YOUNG_CARDS)}: ${valueLabel(
totals[1]
)}`,
],
[
blues(1),
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_LEARNING_CARDS)}: ${valueLabel(
totals[2]
)}`,
],
[
reds(1),
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_RELEARNING_CARDS)}: ${valueLabel(
totals[3]
)}`,
],
[
oranges(1),
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_EARLY_CARDS)}: ${valueLabel(
totals[4]
)}`,
],
[
"grey",
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_TOTAL_CARDS)}: ${valueLabel(
cumulative
)}`,
],
]; ];
for (const [colour, text] of lines) { for (const [colour, text] of lines) {
buf += `<div><span style="color: ${colour}">■</span>${text}</div>`; buf += `<div><span style="color: ${colour};">■</span>${text}</div>`;
} }
return buf; return buf;
} }

View file

@ -132,3 +132,26 @@ export function timeSpan(i18n: I18n, seconds: number, precise = true): string {
return i18n.tr(key, { amount }); return i18n.tr(key, { amount });
} }
export function dayLabel(i18n: I18n, daysStart: number, daysEnd: number): string {
const larger = Math.max(Math.abs(daysStart), Math.abs(daysEnd));
const smaller = Math.min(Math.abs(daysStart), Math.abs(daysEnd));
if (larger - smaller <= 1) {
// singular
if (daysStart >= 0) {
return i18n.tr(i18n.TR.STATISTICS_IN_DAYS_SINGLE, { days: daysStart });
} else {
return i18n.tr(i18n.TR.STATISTICS_DAYS_AGO_SINGLE, { days: -daysStart });
}
} else {
// range
if (daysStart >= 0) {
return i18n.tr(i18n.TR.STATISTICS_IN_DAYS_RANGE, { daysStart, daysEnd });
} else {
return i18n.tr(i18n.TR.STATISTICS_DAYS_AGO_RANGE, {
daysStart: Math.abs(daysEnd),
daysEnd: -daysStart,
});
}
}
}