add textual data to reviews graph

This commit is contained in:
Damien Elmes 2020-08-04 14:28:46 +10:00
parent 79536ef544
commit a5a12e0d00
5 changed files with 127 additions and 7 deletions

View file

@ -180,3 +180,11 @@ statistics-due-tomorrow = Due tomorrow
# eg 5 of 15 (33.3%) # eg 5 of 15 (33.3%)
statistics-amount-of-total-with-percentage = { $amount } of { $total } ({ $percent }%) statistics-amount-of-total-with-percentage = { $amount } of { $total } ({ $percent }%)
statistics-average-over-period = If you studied every day statistics-average-over-period = If you studied every day
statistics-reviews-per-day = { $count ->
[one] { $count } review/day
*[other] { $count } reviews/day
}
statistics-minutes-per-day = { $count ->
[one] { $count } minute/day
*[other] { $count } minutes/day
}

View file

@ -46,6 +46,19 @@ export class I18n {
); );
} }
direction(): string {
const firstLang = this.bundles[0].locales[0];
if (
firstLang.startsWith("ar") ||
firstLang.startsWith("he") ||
firstLang.startsWith("fa")
) {
return "rtl";
} else {
return "ltr";
}
}
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

@ -22,8 +22,16 @@
graphData = gatherData(sourceData); graphData = gatherData(sourceData);
} }
let tableStrings: [string, string][] = [];
$: if (graphData) { $: if (graphData) {
renderReviews(svg as SVGElement, bounds, graphData, graphRange, showTime, i18n); tableStrings = renderReviews(
svg as SVGElement,
bounds,
graphData,
graphRange,
showTime,
i18n
);
} }
const title = i18n.tr(i18n.TR.STATISTICS_REVIEWS_TITLE); const title = i18n.tr(i18n.TR.STATISTICS_REVIEWS_TITLE);
@ -61,4 +69,14 @@
<NoDataOverlay {bounds} {i18n} /> <NoDataOverlay {bounds} {i18n} />
</svg> </svg>
<div class="centered">
<table dir={i18n.direction()}>
{#each tableStrings as [label, value]}
<tr>
<td class="align-end">{label}:</td>
<td class="align-start">{value}</td>
</tr>
{/each}
</table>
</div>
</div> </div>

View file

@ -184,5 +184,14 @@ body.night-mode {
} }
.centered { .centered {
text-align: center; display: flex;
justify-content: center;
}
.align-end {
text-align: end;
}
.align-start {
text-align: start;
} }

View file

@ -108,7 +108,7 @@ export function renderReviews(
range: GraphRange, range: GraphRange,
showTime: boolean, showTime: boolean,
i18n: I18n i18n: I18n
): void { ): [string, string][] {
const svg = select(svgElem); const svg = select(svgElem);
const trans = svg.transition().duration(600) as any; const trans = svg.transition().duration(600) as any;
@ -143,9 +143,10 @@ export function renderReviews(
.thresholds(x.ticks(desiredBars))(sourceMap.entries() as any); .thresholds(x.ticks(desiredBars))(sourceMap.entries() as any);
// empty graph? // empty graph?
if (!sum(bins, (bin) => bin.length)) { const totalDays = sum(bins, (bin) => bin.length);
if (!totalDays) {
setDataAvailable(svg, false); setDataAvailable(svg, false);
return; return [];
} else { } else {
setDataAvailable(svg, true); setDataAvailable(svg, true);
} }
@ -304,9 +305,10 @@ export function renderReviews(
const areaCounts = bins.map((d: any) => cumulativeBinValue(d, 4)); const areaCounts = bins.map((d: any) => cumulativeBinValue(d, 4));
areaCounts.unshift(0); areaCounts.unshift(0);
const areaData = cumsum(areaCounts); const areaData = cumsum(areaCounts);
const yAreaScale = y.copy().domain([0, areaData.slice(-1)[0]]); const yCumMax = areaData.slice(-1)[0];
const yAreaScale = y.copy().domain([0, yCumMax]);
if (areaData.slice(-1)[0]) { if (yCumMax) {
svg.select("path.area") svg.select("path.area")
.datum(areaData as any) .datum(areaData as any)
.attr( .attr(
@ -339,4 +341,74 @@ export function renderReviews(
showTooltip(tooltipText(d, areaData[idx + 1]), x, y); showTooltip(tooltipText(d, areaData[idx + 1]), x, y);
}) })
.on("mouseout", hideTooltip); .on("mouseout", hideTooltip);
const periodDays = -xMin;
const studiedDays = sum(bins, (bin) => bin.length);
const total = yCumMax;
const periodAvg = total / periodDays;
const studiedAvg = total / studiedDays;
let totalString: string,
averageForDaysStudied: string,
averageForPeriod: string,
averageAnswerTime: string,
averageAnswerTimeLabel: string;
if (showTime) {
totalString = timeSpan(i18n, total / 1000, false);
averageForDaysStudied = i18n.tr(i18n.TR.STATISTICS_MINUTES_PER_DAY, {
count: Math.round(studiedAvg / 1000 / 60),
});
averageForPeriod = i18n.tr(i18n.TR.STATISTICS_MINUTES_PER_DAY, {
count: Math.round(periodAvg / 1000 / 60),
});
averageAnswerTimeLabel = i18n.tr(i18n.TR.STATISTICS_AVERAGE_ANSWER_TIME_LABEL);
// need to get total review count to calculate average time
const countBins = histogram()
.value((m) => {
return m[0];
})
.domain(x.domain() as any)(sourceData.reviewCount.entries() as any);
const totalReviews = sum(countBins, (bin) => cumulativeBinValue(bin as any, 4));
const totalSecs = total / 1000;
console.log(`total secs ${totalSecs} total reviews ${totalReviews}`);
const avgSecs = totalSecs / totalReviews;
const cardsPerMin = (totalReviews * 60) / totalSecs;
averageAnswerTime = i18n.tr(i18n.TR.STATISTICS_AVERAGE_ANSWER_TIME, {
"average-seconds": avgSecs,
"cards-per-minute": cardsPerMin,
});
} else {
totalString = i18n.tr(i18n.TR.STATISTICS_REVIEWS, { reviews: total });
averageForDaysStudied = i18n.tr(i18n.TR.STATISTICS_REVIEWS_PER_DAY, {
count: Math.round(studiedAvg),
});
averageForPeriod = i18n.tr(i18n.TR.STATISTICS_REVIEWS_PER_DAY, {
count: Math.round(periodAvg),
});
averageAnswerTime = averageAnswerTimeLabel = "";
}
const tableData: [string, string][] = [
[
i18n.tr(i18n.TR.STATISTICS_DAYS_STUDIED),
i18n.tr(i18n.TR.STATISTICS_AMOUNT_OF_TOTAL_WITH_PERCENTAGE, {
amount: studiedDays,
total: periodDays,
percent: Math.round((studiedDays / periodDays) * 100),
}),
],
[i18n.tr(i18n.TR.STATISTICS_TOTAL), totalString],
[i18n.tr(i18n.TR.STATISTICS_AVERAGE_FOR_DAYS_STUDIED), averageForDaysStudied],
[i18n.tr(i18n.TR.STATISTICS_AVERAGE_OVER_PERIOD), averageForPeriod],
];
if (averageAnswerTime) {
tableData.push([averageAnswerTimeLabel, averageAnswerTime]);
}
return tableData;
} }