// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html /* eslint @typescript-eslint/no-explicit-any: "off", */ import type { GraphsResponse } from "@tslib/anki/stats_pb"; import * as tr from "@tslib/ftl"; import { localizedNumber } from "@tslib/i18n"; import { arc, cumsum, interpolate, pie, scaleLinear, schemeBlues, schemeGreens, schemeOranges, schemeReds, select, sum, } from "d3"; import type { GraphBounds } from "./graph-helpers"; type Count = [string, number, boolean, string]; export interface GraphData { title: string; counts: Count[]; totalCards: string; } const barColours = [ schemeBlues[5][2], /* new */ schemeOranges[5][2], /* learn */ schemeReds[5][2], /* relearn */ schemeGreens[5][2], /* young */ schemeGreens[5][3], /* mature */ "#FFDC41", /* suspended */ "grey", /* buried */ ]; function countCards(data: GraphsResponse, separateInactive: boolean): Count[] { const countData = separateInactive ? data.cardCounts!.excludingInactive! : data.cardCounts!.includingInactive!; const extraQuery = separateInactive ? "AND -(\"is:buried\" OR \"is:suspended\")" : ""; const counts: Count[] = [ [tr.statisticsCountsNewCards(), countData.newCards, true, `"is:new"${extraQuery}`], [ tr.statisticsCountsLearningCards(), countData.learn, true, `(-"is:review" AND "is:learn")${extraQuery}`, ], [ tr.statisticsCountsRelearningCards(), countData.relearn, true, `("is:review" AND "is:learn")${extraQuery}`, ], [ tr.statisticsCountsYoungCards(), countData.young, true, `("is:review" AND -"is:learn") AND "prop:ivl<21"${extraQuery}`, ], [ tr.statisticsCountsMatureCards(), countData.mature, true, `("is:review" -"is:learn") AND "prop:ivl>=21"${extraQuery}`, ], [ tr.statisticsCountsSuspendedCards(), countData.suspended, separateInactive, "\"is:suspended\"", ], [tr.statisticsCountsBuriedCards(), countData.buried, separateInactive, "\"is:buried\""], ]; return counts; } export function gatherData( data: GraphsResponse, separateInactive: boolean, ): GraphData { const counts = countCards(data, separateInactive); const totalCards = localizedNumber(sum(counts, e => e[1])); return { title: tr.statisticsCountsTitle(), counts, totalCards, }; } export interface SummedDatum { label: string; // count of this particular item count: number; // show up in the table show: boolean; query: string; // running total total: number; } export interface TableDatum { label: string; count: string; query: string; percent: string; colour: string; } export function renderCards( svgElem: SVGElement, bounds: GraphBounds, sourceData: GraphData, ): TableDatum[] { const summed = cumsum(sourceData.counts, (d: Count) => d[1]); const data = Array.from(summed).map((n, idx) => { const count = sourceData.counts[idx]; return { label: count[0], count: count[1], show: count[2], query: count[3], idx, total: n, } as SummedDatum; }); // ensuring a non-zero range makes the percentages not break // in an empty collection const xMax = Math.max(1, summed.slice(-1)[0]); const x = scaleLinear().domain([0, xMax]); const svg = select(svgElem); const paths = svg.select(".counts"); const pieData = pie()(sourceData.counts.map((d: Count) => d[1])); const radius = bounds.height / 2 - bounds.marginTop - bounds.marginBottom; const arcGen = arc().innerRadius(0).outerRadius(radius); const trans = svg.transition().duration(600) as any; paths .attr("transform", `translate(${radius},${radius + bounds.marginTop})`) .selectAll("path") .data(pieData) .join( (enter) => enter .append("path") .attr("fill", (_d, idx) => { return barColours[idx]; }) .attr("d", arcGen as any), function(update) { return update.call((d) => d.transition(trans).attrTween("d", (d) => { const interpolator = interpolate( { startAngle: 0, endAngle: 0 }, d, ); return (t): string => arcGen(interpolator(t) as any) as string; }) ); }, ); x.range([bounds.marginLeft, bounds.width - bounds.marginRight]); const tableData = data.flatMap((d: SummedDatum, idx: number) => { const percent = localizedNumber((d.count / xMax) * 100, 2); return d.show ? ({ label: d.label, count: localizedNumber(d.count), percent: `${percent}%`, colour: barColours[idx], query: d.query, } as TableDatum) : []; }); return tableData; }