mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
add textual data to reviews graph
This commit is contained in:
parent
79536ef544
commit
a5a12e0d00
5 changed files with 127 additions and 7 deletions
|
@ -180,3 +180,11 @@ statistics-due-tomorrow = Due tomorrow
|
|||
# eg 5 of 15 (33.3%)
|
||||
statistics-amount-of-total-with-percentage = { $amount } of { $total } ({ $percent }%)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
return this.TR[msg].toLowerCase().replace(/_/g, "-");
|
||||
}
|
||||
|
|
|
@ -22,8 +22,16 @@
|
|||
graphData = gatherData(sourceData);
|
||||
}
|
||||
|
||||
let tableStrings: [string, string][] = [];
|
||||
$: 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);
|
||||
|
@ -61,4 +69,14 @@
|
|||
<NoDataOverlay {bounds} {i18n} />
|
||||
</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>
|
||||
|
|
|
@ -184,5 +184,14 @@ body.night-mode {
|
|||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.align-end {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.align-start {
|
||||
text-align: start;
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ export function renderReviews(
|
|||
range: GraphRange,
|
||||
showTime: boolean,
|
||||
i18n: I18n
|
||||
): void {
|
||||
): [string, string][] {
|
||||
const svg = select(svgElem);
|
||||
const trans = svg.transition().duration(600) as any;
|
||||
|
||||
|
@ -143,9 +143,10 @@ export function renderReviews(
|
|||
.thresholds(x.ticks(desiredBars))(sourceMap.entries() as any);
|
||||
|
||||
// empty graph?
|
||||
if (!sum(bins, (bin) => bin.length)) {
|
||||
const totalDays = sum(bins, (bin) => bin.length);
|
||||
if (!totalDays) {
|
||||
setDataAvailable(svg, false);
|
||||
return;
|
||||
return [];
|
||||
} else {
|
||||
setDataAvailable(svg, true);
|
||||
}
|
||||
|
@ -304,9 +305,10 @@ export function renderReviews(
|
|||
const areaCounts = bins.map((d: any) => cumulativeBinValue(d, 4));
|
||||
areaCounts.unshift(0);
|
||||
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")
|
||||
.datum(areaData as any)
|
||||
.attr(
|
||||
|
@ -339,4 +341,74 @@ export function renderReviews(
|
|||
showTooltip(tooltipText(d, areaData[idx + 1]), x, y);
|
||||
})
|
||||
.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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue