From d305a3a2cce8103021cc4ff7786d9b6982a2ea11 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 4 Jul 2020 13:38:46 +1000 Subject: [PATCH] use graph for card counts --- ts/d3_missing.d.ts | 2 +- ts/src/stats/CardCounts.svelte | 50 ++++++++++-------- ts/src/stats/card-counts.ts | 97 ++++++++++++++++++++++++++++++++-- ts/src/stats/graphs.scss | 4 ++ 4 files changed, 125 insertions(+), 28 deletions(-) diff --git a/ts/d3_missing.d.ts b/ts/d3_missing.d.ts index 7d3d27f85..6633d321f 100644 --- a/ts/d3_missing.d.ts +++ b/ts/d3_missing.d.ts @@ -1,4 +1,4 @@ import "d3-array"; declare module "d3-array" { - export function cumsum(arg0: number[]): Float64Array; + export function cumsum(arg0: any[], arg1?: (any) => number): Float64Array; } diff --git a/ts/src/stats/CardCounts.svelte b/ts/src/stats/CardCounts.svelte index 50aa8c110..c4fe19ed2 100644 --- a/ts/src/stats/CardCounts.svelte +++ b/ts/src/stats/CardCounts.svelte @@ -1,37 +1,41 @@ - - -{#if cardCounts} +{#if graphData}
-

{cardCounts.title}

-
- {#each cardCounts.counts as count} -
-
- {count[0]} -
-
{count[1]}
-
- {/each} -
+

{graphData.title}

+ + + + + +
{total}: {graphData.totalCards}
+
{/if} diff --git a/ts/src/stats/card-counts.ts b/ts/src/stats/card-counts.ts index 44bf3e027..1926674dd 100644 --- a/ts/src/stats/card-counts.ts +++ b/ts/src/stats/card-counts.ts @@ -1,17 +1,31 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import pb from "../backend/proto"; +/* eslint +@typescript-eslint/no-non-null-assertion: "off", +@typescript-eslint/no-explicit-any: "off", + */ + import { CardQueue } from "../cards"; +import pb from "../backend/proto"; +import { schemeGreens, schemeBlues } from "d3-scale-chromatic"; +import "d3-transition"; +import { select, mouse } from "d3-selection"; +import { scaleLinear } from "d3-scale"; +import { showTooltip, hideTooltip } from "./tooltip"; +import { GraphBounds } from "./graphs"; +import { cumsum } from "d3-array"; import { I18n } from "../i18n"; type Count = [string, number]; -export interface CardCounts { +export interface GraphData { title: string; counts: Count[]; + totalCards: number; } -export function gatherData(data: pb.BackendProto.GraphsOut, i18n: I18n): CardCounts { +export function gatherData(data: pb.BackendProto.GraphsOut, i18n: I18n): GraphData { + // fixme: handle preview cards const totalCards = data.cards.length; let newCards = 0; let young = 0; @@ -45,7 +59,6 @@ export function gatherData(data: pb.BackendProto.GraphsOut, i18n: I18n): CardCou } const counts = [ - [i18n.tr(i18n.TR.STATISTICS_COUNTS_TOTAL_CARDS), totalCards] as Count, [i18n.tr(i18n.TR.STATISTICS_COUNTS_NEW_CARDS), newCards] as Count, [i18n.tr(i18n.TR.STATISTICS_COUNTS_YOUNG_CARDS), young] as Count, [i18n.tr(i18n.TR.STATISTICS_COUNTS_MATURE_CARDS), mature] as Count, @@ -56,5 +69,81 @@ export function gatherData(data: pb.BackendProto.GraphsOut, i18n: I18n): CardCou return { title: i18n.tr(i18n.TR.STATISTICS_COUNTS_TITLE), counts, + totalCards, }; } + +interface Reviews { + mature: number; + young: number; + learn: number; + relearn: number; + early: number; +} + +export function renderCards( + svgElem: SVGElement, + bounds: GraphBounds, + sourceData: GraphData +): void { + const summed = cumsum(sourceData.counts, (d) => d[1]); + const data = Array.from(summed).map((n, idx) => { + return { + count: sourceData.counts[idx], + idx, + total: n, + }; + }); + const xMax = summed.slice(-1)[0]; + const x = scaleLinear().domain([0, xMax]); + const svg = select(svgElem); + const trans = svg.transition().duration(600) as any; + + x.range([bounds.marginLeft, bounds.width - bounds.marginRight]); + + const tooltipText = (d: any): string => { + const pct = ((d.count[1] / xMax) * 100).toFixed(2); + return `${d.count[0]}: ${d.count[1]} (${pct}%)`; + }; + + const updateBar = (sel: any): any => { + return sel + .on("mousemove", function (this: any, d: any) { + const [x, y] = mouse(document.body); + showTooltip(tooltipText(d), x, y); + }) + .transition(trans) + .attr("width", (d) => x(d.total) - bounds.marginLeft); + }; + + data.reverse(); + svg.select("g.days") + .selectAll("rect") + .data(data) + .join( + (enter) => + enter + .append("rect") + .attr("height", 10) + .attr("x", x(0)) + .attr("y", bounds.marginTop) + .attr("fill", (d: any): any => { + switch (d.idx) { + case 0: + return schemeBlues[5][2]; + case 1: + return schemeGreens[5][2]; + case 2: + return schemeGreens[5][3]; + case 3: + return "#FFDC41"; + case 4: + return "grey"; + } + }) + .on("mouseout", hideTooltip) + + .call((d) => updateBar(d)), + (update) => update.call((d) => updateBar(d)) + ); +} diff --git a/ts/src/stats/graphs.scss b/ts/src/stats/graphs.scss index 187344f9f..3fad7d98a 100644 --- a/ts/src/stats/graphs.scss +++ b/ts/src/stats/graphs.scss @@ -139,3 +139,7 @@ body.night-mode { opacity: 0.1; } } + +.centered { + text-align: center; +}