i18n some axis labels, and support vertical CJK text

This commit is contained in:
Damien Elmes 2020-06-28 15:23:36 +10:00
parent 595c509546
commit 7e0bdb990c
14 changed files with 68 additions and 34 deletions

View file

@ -95,3 +95,6 @@ statistics-answer-buttons-title = Answer Buttons
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-axis-label-answer-count = Answers
statistics-axis-label-card-count = Cards
statistics-axis-label-review-time = Review Time

View file

@ -36,6 +36,15 @@ export class I18n {
return `missing key: ${key}`; return `missing key: ${key}`;
} }
supportsVerticalText(): boolean {
const firstLang = this.bundles[0].locales[0];
return (
firstLang.startsWith("ja") ||
firstLang.startsWith("zh") ||
firstLang.startsWith("ko")
);
}
private keyName(msg: pb.BackendProto.FluentString): string { private keyName(msg: pb.BackendProto.FluentString): string {
return this.TR[msg].toLowerCase().replace(/_/g, "-"); return this.TR[msg].toLowerCase().replace(/_/g, "-");
} }

View file

@ -15,12 +15,10 @@
let addedData: GraphData | null = null; let addedData: GraphData | null = null;
$: if (sourceData) { $: if (sourceData) {
console.log("gathering data");
addedData = gatherData(sourceData); addedData = gatherData(sourceData);
} }
$: if (addedData) { $: if (addedData) {
console.log("preparing data");
histogramData = buildHistogram(addedData, range); histogramData = buildHistogram(addedData, range);
} }
@ -29,6 +27,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 yText = i18n.tr(i18n.TR.STATISTICS_AXIS_LABEL_CARD_COUNT);
</script> </script>
{#if histogramData} {#if histogramData}
@ -54,6 +53,6 @@
</label> </label>
</div> </div>
<HistogramGraph data={histogramData} xText="Days" yText="Number of cards" /> <HistogramGraph data={histogramData} xText="Days" {yText} {i18n} />
</div> </div>
{/if} {/if}

View file

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

View file

@ -11,16 +11,15 @@
const bounds = defaultGraphBounds(); const bounds = defaultGraphBounds();
const xText = ""; const xText = "";
const yText = "Times pressed";
let svg = null as HTMLElement | SVGElement | null; let svg = null as HTMLElement | SVGElement | null;
$: if (sourceData) { $: if (sourceData) {
console.log("gathering data");
renderButtons(svg as SVGElement, bounds, gatherData(sourceData)); renderButtons(svg as SVGElement, bounds, gatherData(sourceData));
} }
const title = i18n.tr(i18n.TR.STATISTICS_ANSWER_BUTTONS_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_ANSWER_BUTTONS_TITLE);
const yText = i18n.tr(i18n.TR.STATISTICS_AXIS_LABEL_ANSWER_COUNT);
</script> </script>
<div class="graph"> <div class="graph">
@ -30,6 +29,6 @@
<g class="bars" /> <g class="bars" />
<g class="hoverzone" /> <g class="hoverzone" />
<AxisTicks {bounds} /> <AxisTicks {bounds} />
<AxisLabels {bounds} {xText} {yText} /> <AxisLabels {bounds} {xText} {yText} {i18n} />
</svg> </svg>
</div> </div>

View file

@ -8,7 +8,6 @@
let cardCounts: CardCounts | null = null; let cardCounts: CardCounts | null = null;
$: if (sourceData) { $: if (sourceData) {
console.log("gathering data");
cardCounts = gatherData(sourceData, i18n); cardCounts = gatherData(sourceData, i18n);
} }
</script> </script>

View file

@ -12,17 +12,17 @@
let histogramData = null as HistogramData | null; let histogramData = null as HistogramData | null;
$: if (sourceData) { $: if (sourceData) {
console.log("gathering data");
histogramData = prepareData(gatherData(sourceData)); histogramData = prepareData(gatherData(sourceData));
} }
const title = i18n.tr(i18n.TR.STATISTICS_CARD_EASE_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_CARD_EASE_TITLE);
const yText = i18n.tr(i18n.TR.STATISTICS_AXIS_LABEL_CARD_COUNT);
</script> </script>
{#if histogramData} {#if histogramData}
<div class="graph"> <div class="graph">
<h1>{title}</h1> <h1>{title}</h1>
<HistogramGraph data={histogramData} xText="Ease (%)" yText="Number of cards" /> <HistogramGraph data={histogramData} xText="Ease (%)" {yText} {i18n} />
</div> </div>
{/if} {/if}

View file

@ -23,12 +23,10 @@
let range = FutureDueRange.Month; let range = FutureDueRange.Month;
$: if (sourceData) { $: if (sourceData) {
console.log("gathering data");
graphData = gatherData(sourceData); graphData = gatherData(sourceData);
} }
$: if (graphData) { $: if (graphData) {
console.log("preparing data");
histogramData = buildHistogram(graphData, range); histogramData = buildHistogram(graphData, range);
} }
@ -37,6 +35,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 yText = i18n.tr(i18n.TR.STATISTICS_AXIS_LABEL_CARD_COUNT);
</script> </script>
{#if histogramData} {#if histogramData}
@ -44,6 +43,10 @@
<div class="graph"> <div class="graph">
<h1>{title}</h1> <h1>{title}</h1>
<div class="range-box-inner">
The number of cards studied each day, relative to today.
</div>
<div class="range-box-inner"> <div class="range-box-inner">
<label> <label>
<input type="radio" bind:group={range} value={FutureDueRange.Month} /> <input type="radio" bind:group={range} value={FutureDueRange.Month} />
@ -63,10 +66,7 @@
</label> </label>
</div> </div>
<HistogramGraph <HistogramGraph data={histogramData} xText="" {yText} {i18n} />
data={histogramData}
xText="Days from now"
yText="Number of cards" />
</div> </div>
{/if} {/if}

View file

@ -3,8 +3,10 @@
import AxisLabels from "./AxisLabels.svelte"; import AxisLabels from "./AxisLabels.svelte";
import AxisTicks from "./AxisTicks.svelte"; import AxisTicks from "./AxisTicks.svelte";
import { defaultGraphBounds } from "./graphs"; import { defaultGraphBounds } from "./graphs";
import { I18n } from "../i18n";
export let data: HistogramData | null = null; export let data: HistogramData | null = null;
export let i18n: I18n;
export let xText: string; export let xText: string;
export let yText: string; export let yText: string;
@ -21,5 +23,5 @@
<g class="hoverzone" /> <g class="hoverzone" />
<path class="area" /> <path class="area" />
<AxisTicks {bounds} /> <AxisTicks {bounds} />
<AxisLabels {bounds} {xText} {yText} /> <AxisLabels {bounds} {xText} {yText} {i18n} />
</svg> </svg>

View file

@ -11,16 +11,15 @@
const bounds = defaultGraphBounds(); const bounds = defaultGraphBounds();
const xText = ""; const xText = "";
const yText = "Times pressed";
let svg = null as HTMLElement | SVGElement | null; let svg = null as HTMLElement | SVGElement | null;
$: if (sourceData) { $: if (sourceData) {
console.log("gathering data");
renderHours(svg as SVGElement, bounds, gatherData(sourceData)); renderHours(svg as SVGElement, bounds, gatherData(sourceData));
} }
const title = i18n.tr(i18n.TR.STATISTICS_HOURS_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_HOURS_TITLE);
const yText = i18n.tr(i18n.TR.STATISTICS_AXIS_LABEL_ANSWER_COUNT);
</script> </script>
<div class="graph"> <div class="graph">
@ -31,6 +30,6 @@
<path class="area" /> <path class="area" />
<g class="hoverzone" /> <g class="hoverzone" />
<AxisTicks {bounds} /> <AxisTicks {bounds} />
<AxisLabels {bounds} {xText} {yText} /> <AxisLabels {bounds} {xText} {yText} {i18n} />
</svg> </svg>
</div> </div>

View file

@ -21,18 +21,17 @@
let range = IntervalRange.Percentile95; let range = IntervalRange.Percentile95;
$: if (sourceData) { $: if (sourceData) {
console.log("gathering data");
intervalData = gatherIntervalData(sourceData); intervalData = gatherIntervalData(sourceData);
} }
$: if (intervalData) { $: if (intervalData) {
console.log("preparing data");
histogramData = prepareIntervalData(intervalData, range); histogramData = prepareIntervalData(intervalData, range);
} }
const title = i18n.tr(i18n.TR.STATISTICS_INTERVALS_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_INTERVALS_TITLE);
const month = timeSpan(i18n, 1 * MONTH); const month = timeSpan(i18n, 1 * MONTH);
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME); const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
const yText = i18n.tr(i18n.TR.STATISTICS_AXIS_LABEL_CARD_COUNT);
</script> </script>
{#if histogramData} {#if histogramData}
@ -71,9 +70,6 @@
</label> </label>
</div> </div>
<HistogramGraph <HistogramGraph data={histogramData} xText="Interval (days)" {yText} {i18n} />
data={histogramData}
xText="Interval (days)"
yText="Number of cards" />
</div> </div>
{/if} {/if}

View file

@ -32,21 +32,27 @@
} }
const xText = ""; const xText = "";
const yText = "Times pressed";
$: if (sourceData) { $: if (sourceData) {
console.log("gathering data");
graphData = gatherData(sourceData); graphData = gatherData(sourceData);
} }
$: if (graphData) { $: if (graphData) {
renderReviews(svg as SVGElement, bounds, graphData, range, showTime); renderReviews(svg as SVGElement, bounds, graphData, range, showTime, i18n);
} }
const title = i18n.tr(i18n.TR.STATISTICS_REVIEWS_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_REVIEWS_TITLE);
const month = timeSpan(i18n, 1 * MONTH); const month = timeSpan(i18n, 1 * MONTH);
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);
let yText: string;
$: if (showTime) {
yText = i18n.tr(i18n.TR.STATISTICS_AXIS_LABEL_REVIEW_TIME);
} else {
yText = i18n.tr(i18n.TR.STATISTICS_AXIS_LABEL_ANSWER_COUNT);
}
</script> </script>
<div class="graph"> <div class="graph">
@ -75,7 +81,7 @@
{#if revlogRange === RevlogRange.All} {#if revlogRange === RevlogRange.All}
<label> <label>
<input type="radio" bind:group={range} value={ReviewRange.AllTime} /> <input type="radio" bind:group={range} value={ReviewRange.AllTime} />
All time {all}
</label> </label>
{/if} {/if}
</div> </div>
@ -87,7 +93,7 @@
<path class="area" /> <path class="area" />
<g class="hoverzone" /> <g class="hoverzone" />
<AxisTicks {bounds} /> <AxisTicks {bounds} />
<AxisLabels {bounds} {xText} {yText} /> <AxisLabels {bounds} {xText} {yText} {i18n} />
</svg> </svg>
</div> </div>

View file

@ -81,8 +81,7 @@
} }
.y-axis-label { .y-axis-label {
writing-mode: vertical-rl; writing-mode: vertical-lr;
rotate: 180;
} }
.hoverzone rect { .hoverzone rect {

View file

@ -21,6 +21,8 @@ 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 { I18n } from "../i18n";
interface Reviews { interface Reviews {
mature: number; mature: number;
@ -111,7 +113,8 @@ export function renderReviews(
bounds: GraphBounds, bounds: GraphBounds,
sourceData: GraphData, sourceData: GraphData,
range: ReviewRange, range: ReviewRange,
showTime: boolean showTime: boolean,
i18n: I18n
): void { ): void {
const xMax = 0; const xMax = 0;
let xMin = 0; let xMin = 0;
@ -161,6 +164,17 @@ export function renderReviews(
axisLeft(y) axisLeft(y)
.ticks(bounds.height / 80) .ticks(bounds.height / 80)
.tickSizeOuter(0) .tickSizeOuter(0)
.tickFormat(((n: number): string => {
if (showTime) {
return timeSpan(i18n, n / 1000);
} else {
if (Math.round(n) != n) {
return "";
} else {
return n.toString();
}
}
}) as any)
); );
// x bars // x bars