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%)
|
# 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
|
||||||
|
}
|
||||||
|
|
|
@ -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, "-");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue