add remaining tooltip i18n

This commit is contained in:
Damien Elmes 2020-06-28 20:52:38 +10:00
parent 41b296e96c
commit 68978e7c4e
16 changed files with 146 additions and 113 deletions

View file

@ -95,9 +95,19 @@ statistics-future-due-title = Future Due
statistics-reviews-title = Reviews statistics-reviews-title = Reviews
statistics-intervals-title = Review Intervals statistics-intervals-title = Review Intervals
statistics-answer-buttons-title = Answer Buttons statistics-answer-buttons-title = Answer Buttons
# eg Button: 4
statistics-answer-buttons-button-number = Button
# eg Times pressed: 123
statistics-answer-buttons-button-pressed = Times pressed
statistics-hours-title = Hourly Breakdown statistics-hours-title = Hourly Breakdown
statistics-added-title = Added statistics-added-title = Added
statistics-card-ease-title = Card Ease statistics-card-ease-title = Card Ease
statistics-card-ease-subtitle = The lower the ease, the more frequently a card will appear.
# eg "3 cards with 150-170% ease"
statistics-card-ease-tooltip = { $cards ->
[one] 1 card with { $percent } ease
*[other] { $cards } cards with { $percent } ease
}
statistics-future-due-subtitle = The number of reviews due in the future. statistics-future-due-subtitle = The number of reviews due in the future.
statistics-added-subtitle = The number of new cards you have added. statistics-added-subtitle = The number of new cards you have added.
statistics-reviews-count-subtitle = The number of questions you have answered. statistics-reviews-count-subtitle = The number of questions you have answered.
@ -111,15 +121,26 @@ statistics-in-days-single = { $days ->
[1] Tomorrow [1] Tomorrow
*[other] In { $days } days *[other] In { $days } days
} }
statistics-in-days-range = In { $daysStart }~{ $daysEnd } days statistics-in-days-range = In { $daysStart }-{ $daysEnd } days
statistics-days-ago-single = { $days -> statistics-days-ago-single = { $days ->
[1] Yesterday [1] Yesterday
*[other] { $days } days ago *[other] { $days } days ago
} }
statistics-days-ago-range = { $daysStart }~{ $daysEnd } days ago statistics-days-ago-range = { $daysStart }-{ $daysEnd } days ago
statistics-running-total = Running total statistics-running-total = Running total
statistics-cards-due = { $cards -> statistics-cards-due = { $cards ->
[one] 1 card due [one] 1 card due
*[other] { $cards } cards due *[other] { $cards } cards due
} }
statistics-backlog-checkbox = Backlog statistics-backlog-checkbox = Backlog
statistics-intervals-day-range = { $cards ->
[one] 1 card with a { $daysStart }~{ $daysEnd } day interval
*[other] { $cards } cards with a { $daysStart }~{ $daysEnd } day interval
}
statistics-intervals-day-single = { $cards ->
[one] 1 card with a { $day } day interval
*[other] { $cards } card with a { $day } day interval
}
# hour range, eg "From 14:00-15:00"
statistics-hours-range = From { $hourStart }:00~{ $hourEnd }:00
statistics-hours-correct = { $correct }/{ $total } correct ({ $percent }%)

View file

@ -19,7 +19,7 @@
} }
$: if (addedData) { $: if (addedData) {
histogramData = buildHistogram(addedData, range); histogramData = buildHistogram(addedData, range, i18n);
} }
const title = i18n.tr(i18n.TR.STATISTICS_ADDED_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_ADDED_TITLE);

View file

@ -1,26 +0,0 @@
<script lang="typescript">
import { GraphBounds } from "./graphs";
import { I18n } from "../i18n";
export let bounds: GraphBounds;
export let xText: string;
export let yText: string;
export let i18n: I18n;
let yRotate = 180;
if (i18n.supportsVerticalText()) {
yRotate = 0;
}
</script>
<text
class="axis-label"
transform={`translate(${bounds.width / 2}, ${bounds.height - 5})`}>
{xText}
</text>
<text
class="axis-label y-axis-label"
transform={`translate(${bounds.marginLeft / 3}, ${(bounds.height - bounds.marginBottom) / 2 + bounds.marginTop})
rotate(${yRotate} 0 0)`}>
{yText}
</text>

View file

@ -1,7 +1,6 @@
<script lang="typescript"> <script lang="typescript">
import { defaultGraphBounds } from "./graphs"; import { defaultGraphBounds } from "./graphs";
import AxisTicks from "./AxisTicks.svelte"; import AxisTicks from "./AxisTicks.svelte";
import AxisLabels from "./AxisLabels.svelte";
import { gatherData, GraphData, renderButtons } from "./buttons"; import { gatherData, GraphData, renderButtons } from "./buttons";
import pb from "../backend/proto"; import pb from "../backend/proto";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
@ -14,7 +13,7 @@
let svg = null as HTMLElement | SVGElement | null; let svg = null as HTMLElement | SVGElement | null;
$: if (sourceData) { $: if (sourceData) {
renderButtons(svg as SVGElement, bounds, gatherData(sourceData)); renderButtons(svg as SVGElement, bounds, gatherData(sourceData), i18n);
} }
const title = i18n.tr(i18n.TR.STATISTICS_ANSWER_BUTTONS_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_ANSWER_BUTTONS_TITLE);

View file

@ -12,11 +12,11 @@
let histogramData = null as HistogramData | null; let histogramData = null as HistogramData | null;
$: if (sourceData) { $: if (sourceData) {
histogramData = prepareData(gatherData(sourceData)); histogramData = prepareData(gatherData(sourceData), i18n);
} }
const title = i18n.tr(i18n.TR.STATISTICS_CARD_EASE_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_CARD_EASE_TITLE);
const subtitle = "temp"; //i18n.tr(i18n.TR.STATISTICS_EASE_SUBTITLE); const subtitle = i18n.tr(i18n.TR.STATISTICS_CARD_EASE_SUBTITLE);
</script> </script>
{#if histogramData} {#if histogramData}

View file

@ -140,6 +140,6 @@
<ReviewsGraph {sourceData} {revlogRange} {i18n} /> <ReviewsGraph {sourceData} {revlogRange} {i18n} />
<IntervalsGraph {sourceData} {i18n} /> <IntervalsGraph {sourceData} {i18n} />
<EaseGraph {sourceData} {i18n} /> <EaseGraph {sourceData} {i18n} />
<ButtonsGraph {sourceData} {i18n} />
<HourGraph {sourceData} {i18n} /> <HourGraph {sourceData} {i18n} />
<ButtonsGraph {sourceData} {i18n} />
<AddedGraph {sourceData} {i18n} /> <AddedGraph {sourceData} {i18n} />

View file

@ -1,6 +1,5 @@
<script lang="typescript"> <script lang="typescript">
import { HistogramData, histogramGraph } from "./histogram-graph"; import { HistogramData, histogramGraph } from "./histogram-graph";
import AxisLabels from "./AxisLabels.svelte";
import AxisTicks from "./AxisTicks.svelte"; import AxisTicks from "./AxisTicks.svelte";
import { defaultGraphBounds } from "./graphs"; import { defaultGraphBounds } from "./graphs";

View file

@ -1,7 +1,6 @@
<script lang="typescript"> <script lang="typescript">
import { defaultGraphBounds } from "./graphs"; import { defaultGraphBounds } from "./graphs";
import AxisTicks from "./AxisTicks.svelte"; import AxisTicks from "./AxisTicks.svelte";
import AxisLabels from "./AxisLabels.svelte";
import { gatherData, GraphData, renderHours } from "./hours"; import { gatherData, GraphData, renderHours } from "./hours";
import pb from "../backend/proto"; import pb from "../backend/proto";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
@ -14,7 +13,7 @@
let svg = null as HTMLElement | SVGElement | null; let svg = null as HTMLElement | SVGElement | null;
$: if (sourceData) { $: if (sourceData) {
renderHours(svg as SVGElement, bounds, gatherData(sourceData)); renderHours(svg as SVGElement, bounds, gatherData(sourceData), i18n);
} }
const title = i18n.tr(i18n.TR.STATISTICS_HOURS_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_HOURS_TITLE);

View file

@ -25,7 +25,7 @@
} }
$: if (intervalData) { $: if (intervalData) {
histogramData = prepareIntervalData(intervalData, range); histogramData = prepareIntervalData(intervalData, range, i18n);
} }
const title = i18n.tr(i18n.TR.STATISTICS_INTERVALS_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_INTERVALS_TITLE);

View file

@ -1,6 +1,5 @@
<script lang="typescript"> <script lang="typescript">
import { HistogramData, histogramGraph } from "./histogram-graph"; import { HistogramData, histogramGraph } from "./histogram-graph";
import AxisLabels from "./AxisLabels.svelte";
import AxisTicks from "./AxisTicks.svelte"; import AxisTicks from "./AxisTicks.svelte";
import { defaultGraphBounds, RevlogRange } from "./graphs"; import { defaultGraphBounds, RevlogRange } from "./graphs";
import { GraphData, gatherData, renderReviews, ReviewRange } from "./reviews"; import { GraphData, gatherData, renderReviews, ReviewRange } from "./reviews";

View file

@ -11,6 +11,8 @@ import { extent, histogram } from "d3-array";
import { scaleLinear, scaleSequential } from "d3-scale"; import { scaleLinear, scaleSequential } from "d3-scale";
import { HistogramData } from "./histogram-graph"; import { HistogramData } from "./histogram-graph";
import { interpolateBlues } from "d3-scale-chromatic"; import { interpolateBlues } from "d3-scale-chromatic";
import { I18n } from "../i18n";
import { dayLabel } from "../time";
export enum AddedRange { export enum AddedRange {
Month = 0, Month = 0,
@ -31,22 +33,10 @@ export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
return { daysAdded }; return { daysAdded };
} }
function hoverText(
data: HistogramData,
binIdx: number,
cumulative: number,
_percent: number
): string {
const bin = data.bins[binIdx];
return (
`${bin.length} at ${bin.x1! - 1} days.<br>` +
` ${cumulative} cards at or below this point.`
);
}
export function buildHistogram( export function buildHistogram(
data: GraphData, data: GraphData,
range: AddedRange range: AddedRange,
i18n: I18n
): HistogramData | null { ): HistogramData | null {
// get min/max // get min/max
const total = data.daysAdded.length; const total = data.daysAdded.length;
@ -81,5 +71,19 @@ export function buildHistogram(
const colourScale = scaleSequential(interpolateBlues).domain([xMin!, xMax]); const colourScale = scaleSequential(interpolateBlues).domain([xMin!, xMax]);
function hoverText(
data: HistogramData,
binIdx: number,
cumulative: number,
_percent: number
): string {
const bin = data.bins[binIdx];
const day = dayLabel(i18n, bin.x0!, bin.x1!);
const cards = i18n.tr(i18n.TR.STATISTICS_CARDS, { cards: bin.length });
const total = i18n.tr(i18n.TR.STATISTICS_RUNNING_TOTAL);
const totalCards = i18n.tr(i18n.TR.STATISTICS_CARDS, { cards: cumulative });
return `${day}:<br>${cards}<br>${total}: ${totalCards}`;
}
return { scale, bins, total, hoverText, colourScale, showArea: true }; return { scale, bins, total, hoverText, colourScale, showArea: true };
} }

View file

@ -14,6 +14,7 @@ import { scaleLinear, scaleBand, scaleSequential } from "d3-scale";
import { axisBottom, axisLeft } from "d3-axis"; import { axisBottom, axisLeft } from "d3-axis";
import { showTooltip, hideTooltip } from "./tooltip"; import { showTooltip, hideTooltip } from "./tooltip";
import { GraphBounds } from "./graphs"; import { GraphBounds } from "./graphs";
import { I18n } from "../i18n";
type ButtonCounts = [number, number, number, number]; type ButtonCounts = [number, number, number, number];
@ -67,14 +68,11 @@ interface Datum {
count: number; count: number;
} }
function tooltipText(d: Datum): string {
return JSON.stringify(d);
}
export function renderButtons( export function renderButtons(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,
sourceData: GraphData sourceData: GraphData,
i18n: I18n
): void { ): void {
const data = [ const data = [
...sourceData.learning.map((count: number, idx: number) => { ...sourceData.learning.map((count: number, idx: number) => {
@ -108,9 +106,22 @@ export function renderButtons(
const xGroup = scaleBand() const xGroup = scaleBand()
.domain(["learning", "young", "mature"]) .domain(["learning", "young", "mature"])
.range([bounds.marginLeft, bounds.width - bounds.marginRight]); .range([bounds.marginLeft, bounds.width - bounds.marginRight]);
svg.select<SVGGElement>(".x-ticks").transition(trans).call( svg.select<SVGGElement>(".x-ticks")
.transition(trans)
.call(
axisBottom(xGroup) axisBottom(xGroup)
// .ticks() .tickFormat(((d: string) => {
switch (d) {
case "learning":
return i18n.tr(i18n.TR.STATISTICS_COUNTS_LEARNING_CARDS);
case "young":
return i18n.tr(i18n.TR.STATISTICS_COUNTS_YOUNG_CARDS);
case "mature":
return i18n.tr(i18n.TR.STATISTICS_COUNTS_MATURE_CARDS);
default:
console.log(d);
}
}) as any)
.tickSizeOuter(0) .tickSizeOuter(0)
); );
@ -177,6 +188,13 @@ export function renderButtons(
); );
// hover/tooltip // hover/tooltip
function tooltipText(d: Datum): string {
const button = i18n.tr(i18n.TR.STATISTICS_ANSWER_BUTTONS_BUTTON_NUMBER);
const timesPressed = i18n.tr(i18n.TR.STATISTICS_ANSWER_BUTTONS_BUTTON_PRESSED);
return `${button}: ${d.buttonNum}<br>${timesPressed}: ${d.count}`;
}
svg.select("g.hoverzone") svg.select("g.hoverzone")
.selectAll("rect") .selectAll("rect")
.data(data) .data(data)

View file

@ -12,6 +12,7 @@ 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 { interpolateRdYlGn } from "d3-scale-chromatic"; import { interpolateRdYlGn } from "d3-scale-chromatic";
import { I18n } from "../i18n";
export interface GraphData { export interface GraphData {
eases: number[]; eases: number[];
@ -24,16 +25,7 @@ export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
return { eases }; return { eases };
} }
function hoverText(data: HistogramData, binIdx: number, _percent: number): string { export function prepareData(data: GraphData, i18n: I18n): HistogramData | null {
const bin = data.bins[binIdx];
const minPct = Math.floor(bin.x0!);
const maxPct = Math.floor(bin.x1!);
const ease = maxPct - minPct <= 10 ? `${bin.x0}%` : `${bin.x0}%~${bin.x1}%`;
return `${bin.length} cards with ${ease} ease.`;
}
export function prepareData(data: GraphData): HistogramData | null {
// get min/max // get min/max
const allEases = data.eases; const allEases = data.eases;
if (!allEases.length) { if (!allEases.length) {
@ -54,5 +46,16 @@ export function prepareData(data: GraphData): HistogramData | null {
const colourScale = scaleSequential(interpolateRdYlGn).domain([xMin, 300]); const colourScale = scaleSequential(interpolateRdYlGn).domain([xMin, 300]);
function hoverText(data: HistogramData, binIdx: number, _percent: number): string {
const bin = data.bins[binIdx];
const minPct = Math.floor(bin.x0!);
const maxPct = Math.floor(bin.x1!);
const percent = maxPct - minPct <= 10 ? `${bin.x0}%` : `${bin.x0}%-${bin.x1}%`;
return i18n.tr(i18n.TR.STATISTICS_CARD_EASE_TOOLTIP, {
cards: bin.length,
percent,
});
}
return { scale, bins, total, hoverText, colourScale, showArea: false }; return { scale, bins, total, hoverText, colourScale, showArea: false };
} }

View file

@ -2,6 +2,10 @@
box-sizing: border-box; box-sizing: border-box;
} }
body {
font-family: Arial;
}
.graph-tooltip { .graph-tooltip {
position: absolute; position: absolute;
background-color: white; background-color: white;
@ -23,25 +27,6 @@
text-align: center; text-align: center;
} }
/* .cards-graph-grid {
display: flex;
flex-wrap: wrap;
padding-left: 0;
padding-right: 1em;
align-items: center;
}
.cards-graph-grid svg {
flex: 3 0 0;
flex-direction: column;
min-width: 500px;
}
.cards-graph-grid div.graph-description {
flex: 0 0 10em;
font-family: "Arial";
} */
.no-domain-line .domain { .no-domain-line .domain {
display: none; display: none;
} }

View file

@ -15,6 +15,7 @@ import { axisBottom, axisLeft } from "d3-axis";
import { showTooltip, hideTooltip } from "./tooltip"; 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 { I18n } from "../i18n";
type ButtonCounts = [number, number, number, number]; type ButtonCounts = [number, number, number, number];
@ -52,14 +53,11 @@ export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
return { hours }; return { hours };
} }
function tooltipText(d: Hour): string {
return JSON.stringify(d);
}
export function renderHours( export function renderHours(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,
sourceData: GraphData sourceData: GraphData,
i18n: I18n
): void { ): void {
const data = sourceData.hours; const data = sourceData.hours;
@ -141,6 +139,19 @@ export function renderHours(
}) })
); );
function tooltipText(d: Hour): string {
const hour = i18n.tr(i18n.TR.STATISTICS_HOURS_RANGE, {
hourStart: d.hour,
hourEnd: d.hour + 1,
});
const correct = i18n.tr(i18n.TR.STATISTICS_HOURS_CORRECT, {
correct: d.correctCount,
total: d.totalCount,
percent: d.totalCount ? (d.correctCount / d.totalCount) * 100 : 0,
});
return `${hour}<br>${correct}`;
}
// hover/tooltip // hover/tooltip
svg.select("g.hoverzone") svg.select("g.hoverzone")
.selectAll("rect") .selectAll("rect")

View file

@ -12,6 +12,7 @@ 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 { interpolateBlues } from "d3-scale-chromatic"; import { interpolateBlues } from "d3-scale-chromatic";
import { I18n } from "../i18n";
export interface IntervalGraphData { export interface IntervalGraphData {
intervals: number[]; intervals: number[];
@ -32,26 +33,32 @@ export function gatherIntervalData(data: pb.BackendProto.GraphsOut): IntervalGra
return { intervals }; return { intervals };
} }
function hoverText( export function intervalLabel(
data: HistogramData, i18n: I18n,
binIdx: number, daysStart: number,
_cumulative: number, daysEnd: number,
percent: number cards: number
): string { ): string {
const bin = data.bins[binIdx]; if (daysEnd - daysStart <= 1) {
const interval = // singular
bin.x1! - bin.x0! === 1 return i18n.tr(i18n.TR.STATISTICS_INTERVALS_DAY_SINGLE, {
? `${bin.x0} day interval` day: daysStart,
: `${bin.x0}~${bin.x1} day interval`; cards,
return ( });
`${bin.length} cards with ${interval}. ` + } else {
`<br>${percent.toFixed(1)}% cards at or before this point.` // range
); return i18n.tr(i18n.TR.STATISTICS_INTERVALS_DAY_RANGE, {
daysStart,
daysEnd,
cards,
});
}
} }
export function prepareIntervalData( export function prepareIntervalData(
data: IntervalGraphData, data: IntervalGraphData,
range: IntervalRange range: IntervalRange,
i18n: I18n
): HistogramData | null { ): HistogramData | null {
// get min/max // get min/max
const allIntervals = data.intervals; const allIntervals = data.intervals;
@ -60,7 +67,7 @@ export function prepareIntervalData(
} }
const total = allIntervals.length; const total = allIntervals.length;
const [xMin, origXMax] = extent(allIntervals); const [_xMinOrig, origXMax] = extent(allIntervals);
let xMax = origXMax; let xMax = origXMax;
// cap max to selected range // cap max to selected range
@ -80,6 +87,7 @@ export function prepareIntervalData(
case IntervalRange.All: case IntervalRange.All:
break; break;
} }
const xMin = 0;
xMax = xMax! + 1; xMax = xMax! + 1;
// cap bars to available range // cap bars to available range
@ -94,5 +102,18 @@ export function prepareIntervalData(
const shiftedMin = xMin! - Math.round((xMax - xMin!) / 10); const shiftedMin = xMin! - Math.round((xMax - xMin!) / 10);
const colourScale = scaleSequential(interpolateBlues).domain([shiftedMin, xMax]); const colourScale = scaleSequential(interpolateBlues).domain([shiftedMin, xMax]);
function hoverText(
data: HistogramData,
binIdx: number,
_cumulative: number,
percent: number
): string {
const bin = data.bins[binIdx];
// const day = dayLabel(i18n, bin.x0!, bin.x1!);
const interval = intervalLabel(i18n, bin.x0!, bin.x1!, bin.length);
const total = i18n.tr(i18n.TR.STATISTICS_RUNNING_TOTAL);
return `${interval}<br>${total}: ${percent.toFixed(1)}%`;
}
return { scale, bins, total, hoverText, colourScale, showArea: true }; return { scale, bins, total, hoverText, colourScale, showArea: true };
} }