mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 08:46:37 -04:00
add range selectors to answer button and hour graphs
This commit is contained in:
parent
f741b05f56
commit
ec9e3646c4
13 changed files with 162 additions and 140 deletions
|
@ -1,18 +1,19 @@
|
|||
<script lang="typescript">
|
||||
import { RevlogRange } from "./graphs";
|
||||
import { RevlogRange, GraphRange } from "./graphs";
|
||||
import { timeSpan, MONTH, YEAR } from "../time";
|
||||
import { I18n } from "../i18n";
|
||||
import { HistogramData } from "./histogram-graph";
|
||||
import { gatherData, buildHistogram, GraphData, AddedRange } from "./added";
|
||||
import { gatherData, buildHistogram, GraphData } from "./added";
|
||||
import pb from "../backend/proto";
|
||||
import HistogramGraph from "./HistogramGraph.svelte";
|
||||
import GraphRangeRadios from "./GraphRangeRadios.svelte";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let i18n: I18n;
|
||||
|
||||
let svg = null as HTMLElement | SVGElement | null;
|
||||
let histogramData = null as HistogramData | null;
|
||||
let range: AddedRange = AddedRange.Month;
|
||||
let graphRange: GraphRange = GraphRange.Month;
|
||||
|
||||
let addedData: GraphData | null = null;
|
||||
$: if (sourceData) {
|
||||
|
@ -20,14 +21,10 @@
|
|||
}
|
||||
|
||||
$: if (addedData) {
|
||||
histogramData = buildHistogram(addedData, range, i18n);
|
||||
histogramData = buildHistogram(addedData, graphRange, i18n);
|
||||
}
|
||||
|
||||
const title = i18n.tr(i18n.TR.STATISTICS_ADDED_TITLE);
|
||||
const month = timeSpan(i18n, 1 * MONTH);
|
||||
const month3 = timeSpan(i18n, 3 * MONTH);
|
||||
const year = timeSpan(i18n, 1 * YEAR);
|
||||
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
|
||||
const subtitle = i18n.tr(i18n.TR.STATISTICS_ADDED_SUBTITLE);
|
||||
</script>
|
||||
|
||||
|
@ -35,22 +32,7 @@
|
|||
<h1>{title}</h1>
|
||||
|
||||
<div class="range-box-inner">
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={AddedRange.Month} />
|
||||
{month}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={AddedRange.ThreeMonths} />
|
||||
{month3}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={AddedRange.Year} />
|
||||
{year}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={AddedRange.AllTime} />
|
||||
{all}
|
||||
</label>
|
||||
<GraphRangeRadios bind:graphRange {i18n} revlogRange={RevlogRange.All} />
|
||||
</div>
|
||||
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
<script lang="typescript">
|
||||
import { defaultGraphBounds } from "./graphs";
|
||||
import { defaultGraphBounds, GraphRange, RevlogRange } from "./graphs";
|
||||
import AxisTicks from "./AxisTicks.svelte";
|
||||
import { gatherData, GraphData, renderButtons } from "./buttons";
|
||||
import { renderButtons } from "./buttons";
|
||||
import pb from "../backend/proto";
|
||||
import { I18n } from "../i18n";
|
||||
import NoDataOverlay from "./NoDataOverlay.svelte";
|
||||
import GraphRangeRadios from "./GraphRangeRadios.svelte";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let i18n: I18n;
|
||||
export let revlogRange: RevlogRange;
|
||||
|
||||
let graphRange: GraphRange = GraphRange.Year;
|
||||
|
||||
const bounds = defaultGraphBounds();
|
||||
|
||||
let svg = null as HTMLElement | SVGElement | null;
|
||||
|
||||
$: if (sourceData) {
|
||||
renderButtons(svg as SVGElement, bounds, gatherData(sourceData), i18n);
|
||||
renderButtons(svg as SVGElement, bounds, sourceData, i18n, graphRange);
|
||||
}
|
||||
|
||||
const title = i18n.tr(i18n.TR.STATISTICS_ANSWER_BUTTONS_TITLE);
|
||||
|
@ -24,6 +28,10 @@
|
|||
<div class="graph" id="graph-buttons">
|
||||
<h1>{title}</h1>
|
||||
|
||||
<div class="range-box-inner">
|
||||
<GraphRangeRadios bind:graphRange {i18n} {revlogRange} />
|
||||
</div>
|
||||
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
|
||||
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
|
||||
|
|
|
@ -2,15 +2,11 @@
|
|||
import { timeSpan, MONTH, YEAR } from "../time";
|
||||
import { I18n } from "../i18n";
|
||||
import { HistogramData } from "./histogram-graph";
|
||||
import { defaultGraphBounds } from "./graphs";
|
||||
import {
|
||||
gatherData,
|
||||
GraphData,
|
||||
FutureDueRange,
|
||||
buildHistogram,
|
||||
} from "./future-due";
|
||||
import { defaultGraphBounds, GraphRange, RevlogRange } from "./graphs";
|
||||
import { gatherData, GraphData, buildHistogram } from "./future-due";
|
||||
import pb from "../backend/proto";
|
||||
import HistogramGraph from "./HistogramGraph.svelte";
|
||||
import GraphRangeRadios from "./GraphRangeRadios.svelte";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let i18n: I18n;
|
||||
|
@ -19,21 +15,17 @@
|
|||
let histogramData = null as HistogramData | null;
|
||||
let backlog: boolean = true;
|
||||
let svg = null as HTMLElement | SVGElement | null;
|
||||
let range: FutureDueRange = FutureDueRange.Month;
|
||||
let graphRange: GraphRange = GraphRange.Month;
|
||||
|
||||
$: if (sourceData) {
|
||||
graphData = gatherData(sourceData);
|
||||
}
|
||||
|
||||
$: if (graphData) {
|
||||
histogramData = buildHistogram(graphData, range, backlog, i18n);
|
||||
histogramData = buildHistogram(graphData, graphRange, backlog, i18n);
|
||||
}
|
||||
|
||||
const title = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_TITLE);
|
||||
const month = timeSpan(i18n, 1 * MONTH);
|
||||
const month3 = timeSpan(i18n, 3 * MONTH);
|
||||
const year = timeSpan(i18n, 1 * YEAR);
|
||||
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
|
||||
const subtitle = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_SUBTITLE);
|
||||
const backlogLabel = i18n.tr(i18n.TR.STATISTICS_BACKLOG_CHECKBOX);
|
||||
</script>
|
||||
|
@ -47,22 +39,7 @@
|
|||
{backlogLabel}
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={FutureDueRange.Month} />
|
||||
{month}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={FutureDueRange.Quarter} />
|
||||
{month3}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={FutureDueRange.Year} />
|
||||
{year}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={FutureDueRange.AllTime} />
|
||||
{all}
|
||||
</label>
|
||||
<GraphRangeRadios bind:graphRange {i18n} revlogRange={RevlogRange.All} />
|
||||
</div>
|
||||
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
|
|
33
ts/src/stats/GraphRangeRadios.svelte
Normal file
33
ts/src/stats/GraphRangeRadios.svelte
Normal file
|
@ -0,0 +1,33 @@
|
|||
<script lang="typescript">
|
||||
import { I18n } from "../i18n";
|
||||
import { RevlogRange, GraphRange } from "./graphs";
|
||||
import { timeSpan, MONTH, YEAR } from "../time";
|
||||
|
||||
export let i18n: I18n;
|
||||
export let revlogRange: RevlogRange;
|
||||
export let graphRange: GraphRange;
|
||||
|
||||
const month = timeSpan(i18n, 1 * MONTH);
|
||||
const month3 = timeSpan(i18n, 3 * MONTH);
|
||||
const year = timeSpan(i18n, 1 * YEAR);
|
||||
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
|
||||
</script>
|
||||
|
||||
<label>
|
||||
<input type="radio" bind:group={graphRange} value={GraphRange.Month} />
|
||||
{month}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={graphRange} value={GraphRange.ThreeMonths} />
|
||||
{month3}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={graphRange} value={GraphRange.Year} />
|
||||
{year}
|
||||
</label>
|
||||
{#if revlogRange === RevlogRange.All}
|
||||
<label>
|
||||
<input type="radio" bind:group={graphRange} value={GraphRange.AllTime} />
|
||||
{all}
|
||||
</label>
|
||||
{/if}
|
|
@ -138,11 +138,11 @@
|
|||
<TodayStats {sourceData} {i18n} />
|
||||
<CardCounts {sourceData} {i18n} />
|
||||
<CalendarGraph {sourceData} {revlogRange} {i18n} {nightMode} />
|
||||
<FutureDue {sourceData} {revlogRange} {i18n} />
|
||||
<FutureDue {sourceData} {i18n} />
|
||||
<ReviewsGraph {sourceData} {revlogRange} {i18n} />
|
||||
<IntervalsGraph {sourceData} {i18n} />
|
||||
<EaseGraph {sourceData} {i18n} />
|
||||
<HourGraph {sourceData} {i18n} />
|
||||
<ButtonsGraph {sourceData} {i18n} />
|
||||
<AddedGraph {sourceData} {revlogRange} {i18n} />
|
||||
<HourGraph {sourceData} {revlogRange} {i18n} />
|
||||
<ButtonsGraph {sourceData} {revlogRange} {i18n} />
|
||||
<AddedGraph {sourceData} {i18n} />
|
||||
{/if}
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
<script lang="typescript">
|
||||
import { defaultGraphBounds } from "./graphs";
|
||||
import { timeSpan, MONTH, YEAR } from "../time";
|
||||
import { defaultGraphBounds, RevlogRange, GraphRange } from "./graphs";
|
||||
import AxisTicks from "./AxisTicks.svelte";
|
||||
import { gatherData, GraphData, renderHours } from "./hours";
|
||||
import { renderHours } from "./hours";
|
||||
import pb from "../backend/proto";
|
||||
import { I18n } from "../i18n";
|
||||
import NoDataOverlay from "./NoDataOverlay.svelte";
|
||||
import GraphRangeRadios from "./GraphRangeRadios.svelte";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let i18n: I18n;
|
||||
export let revlogRange: RevlogRange;
|
||||
let graphRange: GraphRange = GraphRange.Year;
|
||||
|
||||
const bounds = defaultGraphBounds();
|
||||
|
||||
let svg = null as HTMLElement | SVGElement | null;
|
||||
|
||||
$: if (sourceData) {
|
||||
renderHours(svg as SVGElement, bounds, gatherData(sourceData), i18n);
|
||||
renderHours(svg as SVGElement, bounds, sourceData, i18n, graphRange);
|
||||
}
|
||||
|
||||
const title = i18n.tr(i18n.TR.STATISTICS_HOURS_TITLE);
|
||||
|
@ -24,6 +28,10 @@
|
|||
<div class="graph" id="graph-hour">
|
||||
<h1>{title}</h1>
|
||||
|
||||
<div class="range-box-inner">
|
||||
<GraphRangeRadios bind:graphRange {i18n} {revlogRange} />
|
||||
</div>
|
||||
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
|
||||
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<script lang="typescript">
|
||||
import { HistogramData, histogramGraph } from "./histogram-graph";
|
||||
import AxisTicks from "./AxisTicks.svelte";
|
||||
import { defaultGraphBounds, RevlogRange } from "./graphs";
|
||||
import { GraphData, gatherData, renderReviews, ReviewRange } from "./reviews";
|
||||
import { defaultGraphBounds, RevlogRange, GraphRange } from "./graphs";
|
||||
import { GraphData, gatherData, renderReviews } from "./reviews";
|
||||
import pb from "../backend/proto";
|
||||
import { timeSpan, MONTH, YEAR } from "../time";
|
||||
import { I18n } from "../i18n";
|
||||
import NoDataOverlay from "./NoDataOverlay.svelte";
|
||||
import GraphRangeRadios from "./GraphRangeRadios.svelte";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let revlogRange: RevlogRange;
|
||||
|
@ -16,7 +17,7 @@
|
|||
|
||||
let bounds = defaultGraphBounds();
|
||||
let svg = null as HTMLElement | SVGElement | null;
|
||||
let range: ReviewRange = ReviewRange.Month;
|
||||
let graphRange: GraphRange = GraphRange.Month;
|
||||
let showTime = false;
|
||||
|
||||
$: if (sourceData) {
|
||||
|
@ -24,14 +25,10 @@
|
|||
}
|
||||
|
||||
$: if (graphData) {
|
||||
renderReviews(svg as SVGElement, bounds, graphData, range, showTime, i18n);
|
||||
renderReviews(svg as SVGElement, bounds, graphData, graphRange, showTime, i18n);
|
||||
}
|
||||
|
||||
const title = i18n.tr(i18n.TR.STATISTICS_REVIEWS_TITLE);
|
||||
const month = timeSpan(i18n, 1 * MONTH);
|
||||
const month3 = timeSpan(i18n, 3 * MONTH);
|
||||
const year = timeSpan(i18n, 1 * YEAR);
|
||||
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
|
||||
const time = i18n.tr(i18n.TR.STATISTICS_REVIEWS_TIME_CHECKBOX);
|
||||
|
||||
let subtitle: string;
|
||||
|
@ -51,24 +48,7 @@
|
|||
{time}
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.Month} />
|
||||
{month}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.ThreeMonths} />
|
||||
{month3}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.Year} />
|
||||
{year}
|
||||
</label>
|
||||
{#if revlogRange === RevlogRange.All}
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.AllTime} />
|
||||
{all}
|
||||
</label>
|
||||
{/if}
|
||||
<GraphRangeRadios bind:graphRange {i18n} {revlogRange} />
|
||||
</div>
|
||||
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
|
|
|
@ -13,13 +13,7 @@ import { HistogramData } from "./histogram-graph";
|
|||
import { interpolateBlues } from "d3-scale-chromatic";
|
||||
import { I18n } from "../i18n";
|
||||
import { dayLabel } from "../time";
|
||||
|
||||
export enum AddedRange {
|
||||
Month = 0,
|
||||
ThreeMonths = 1,
|
||||
Year = 2,
|
||||
AllTime = 3,
|
||||
}
|
||||
import { GraphRange } from "./graphs";
|
||||
|
||||
export interface GraphData {
|
||||
daysAdded: number[];
|
||||
|
@ -35,7 +29,7 @@ export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
|
|||
|
||||
export function buildHistogram(
|
||||
data: GraphData,
|
||||
range: AddedRange,
|
||||
range: GraphRange,
|
||||
i18n: I18n
|
||||
): HistogramData | null {
|
||||
// get min/max
|
||||
|
@ -49,16 +43,16 @@ export function buildHistogram(
|
|||
|
||||
// cap max to selected range
|
||||
switch (range) {
|
||||
case AddedRange.Month:
|
||||
case GraphRange.Month:
|
||||
xMin = -31;
|
||||
break;
|
||||
case AddedRange.ThreeMonths:
|
||||
case GraphRange.ThreeMonths:
|
||||
xMin = -90;
|
||||
break;
|
||||
case AddedRange.Year:
|
||||
case GraphRange.Year:
|
||||
xMin = -365;
|
||||
break;
|
||||
case AddedRange.AllTime:
|
||||
case GraphRange.AllTime:
|
||||
break;
|
||||
}
|
||||
const xMax = 1;
|
||||
|
|
|
@ -13,7 +13,12 @@ import { select, mouse } from "d3-selection";
|
|||
import { scaleLinear, scaleBand, scaleSequential } from "d3-scale";
|
||||
import { axisBottom, axisLeft } from "d3-axis";
|
||||
import { showTooltip, hideTooltip } from "./tooltip";
|
||||
import { GraphBounds, setDataAvailable } from "./graphs";
|
||||
import {
|
||||
GraphBounds,
|
||||
setDataAvailable,
|
||||
GraphRange,
|
||||
millisecondCutoffForRange,
|
||||
} from "./graphs";
|
||||
import { I18n } from "../i18n";
|
||||
import { sum } from "d3-array";
|
||||
|
||||
|
@ -27,12 +32,20 @@ export interface GraphData {
|
|||
|
||||
const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind;
|
||||
|
||||
export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
|
||||
export function gatherData(
|
||||
data: pb.BackendProto.GraphsOut,
|
||||
range: GraphRange
|
||||
): GraphData {
|
||||
const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs);
|
||||
const learning: ButtonCounts = [0, 0, 0, 0];
|
||||
const young: ButtonCounts = [0, 0, 0, 0];
|
||||
const mature: ButtonCounts = [0, 0, 0, 0];
|
||||
|
||||
for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) {
|
||||
if (cutoff && (review.id as number) < cutoff) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let buttonNum = review.buttonChosen;
|
||||
if (buttonNum <= 0 || buttonNum > 4) {
|
||||
continue;
|
||||
|
@ -80,9 +93,11 @@ interface TotalCorrect {
|
|||
export function renderButtons(
|
||||
svgElem: SVGElement,
|
||||
bounds: GraphBounds,
|
||||
sourceData: GraphData,
|
||||
i18n: I18n
|
||||
origData: pb.BackendProto.GraphsOut,
|
||||
i18n: I18n,
|
||||
range: GraphRange
|
||||
): void {
|
||||
const sourceData = gatherData(origData, range);
|
||||
const data = [
|
||||
...sourceData.learning.map((count: number, idx: number) => {
|
||||
return {
|
||||
|
|
|
@ -14,18 +14,12 @@ import { HistogramData } from "./histogram-graph";
|
|||
import { interpolateGreens } from "d3-scale-chromatic";
|
||||
import { dayLabel } from "../time";
|
||||
import { I18n } from "../i18n";
|
||||
import { GraphRange } from "./graphs";
|
||||
|
||||
export interface GraphData {
|
||||
dueCounts: Map<number, number>;
|
||||
}
|
||||
|
||||
export enum FutureDueRange {
|
||||
Month = 0,
|
||||
Quarter = 1,
|
||||
Year = 2,
|
||||
AllTime = 3,
|
||||
}
|
||||
|
||||
export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
|
||||
const due = (data.cards as pb.BackendProto.Card[])
|
||||
.filter((c) => c.queue == CardQueue.Review) // && c.due >= data.daysElapsed)
|
||||
|
@ -44,7 +38,7 @@ function binValue(d: Bin<Map<number, number>, number>): number {
|
|||
|
||||
export function buildHistogram(
|
||||
sourceData: GraphData,
|
||||
range: FutureDueRange,
|
||||
range: GraphRange,
|
||||
backlog: boolean,
|
||||
i18n: I18n
|
||||
): HistogramData | null {
|
||||
|
@ -63,16 +57,16 @@ export function buildHistogram(
|
|||
|
||||
// cap max to selected range
|
||||
switch (range) {
|
||||
case FutureDueRange.Month:
|
||||
case GraphRange.Month:
|
||||
xMax = 31;
|
||||
break;
|
||||
case FutureDueRange.Quarter:
|
||||
case GraphRange.ThreeMonths:
|
||||
xMax = 90;
|
||||
break;
|
||||
case FutureDueRange.Year:
|
||||
case GraphRange.Year:
|
||||
xMax = 365;
|
||||
break;
|
||||
case FutureDueRange.AllTime:
|
||||
case GraphRange.AllTime:
|
||||
break;
|
||||
}
|
||||
xMax = xMax! + 1;
|
||||
|
|
|
@ -35,11 +35,20 @@ export async function getGraphData(
|
|||
return pb.BackendProto.GraphsOut.decode(bytes);
|
||||
}
|
||||
|
||||
// amount of data to fetch from backend
|
||||
export enum RevlogRange {
|
||||
Year = 1,
|
||||
All = 2,
|
||||
}
|
||||
|
||||
// period a graph should cover
|
||||
export enum GraphRange {
|
||||
Month = 0,
|
||||
ThreeMonths = 1,
|
||||
Year = 2,
|
||||
AllTime = 3,
|
||||
}
|
||||
|
||||
export interface GraphsContext {
|
||||
cards: pb.BackendProto.Card[];
|
||||
revlog: pb.BackendProto.RevlogEntry[];
|
||||
|
@ -77,3 +86,26 @@ export function setDataAvailable(
|
|||
.duration(600)
|
||||
.attr("opacity", available ? 0 : 1);
|
||||
}
|
||||
|
||||
export function millisecondCutoffForRange(
|
||||
range: GraphRange,
|
||||
nextDayAtSecs: number
|
||||
): number {
|
||||
let days;
|
||||
switch (range) {
|
||||
case GraphRange.Month:
|
||||
days = 31;
|
||||
break;
|
||||
case GraphRange.ThreeMonths:
|
||||
days = 90;
|
||||
break;
|
||||
case GraphRange.Year:
|
||||
days = 365;
|
||||
break;
|
||||
case GraphRange.AllTime:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (nextDayAtSecs - 86400 * days) * 1000;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,12 @@ import { select, mouse } from "d3-selection";
|
|||
import { scaleLinear, scaleBand, scaleSequential } from "d3-scale";
|
||||
import { axisBottom, axisLeft } from "d3-axis";
|
||||
import { showTooltip, hideTooltip } from "./tooltip";
|
||||
import { GraphBounds, setDataAvailable } from "./graphs";
|
||||
import {
|
||||
GraphBounds,
|
||||
setDataAvailable,
|
||||
GraphRange,
|
||||
millisecondCutoffForRange,
|
||||
} from "./graphs";
|
||||
import { area, curveBasis } from "d3-shape";
|
||||
import { I18n } from "../i18n";
|
||||
|
||||
|
@ -25,21 +30,21 @@ interface Hour {
|
|||
correctCount: number;
|
||||
}
|
||||
|
||||
export interface GraphData {
|
||||
hours: Hour[];
|
||||
}
|
||||
|
||||
const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind;
|
||||
|
||||
export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
|
||||
function gatherData(data: pb.BackendProto.GraphsOut, range: GraphRange): Hour[] {
|
||||
const hours = [...Array(24)].map((_n, idx: number) => {
|
||||
return { hour: idx, totalCount: 0, correctCount: 0 } as Hour;
|
||||
});
|
||||
const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs);
|
||||
|
||||
for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) {
|
||||
if (review.reviewKind == ReviewKind.EARLY_REVIEW) {
|
||||
continue;
|
||||
}
|
||||
if (cutoff && (review.id as number) < cutoff) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const hour = Math.floor(
|
||||
(((review.id as number) / 1000 + data.localOffsetSecs) / 3600) % 24
|
||||
|
@ -50,16 +55,17 @@ export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
|
|||
}
|
||||
}
|
||||
|
||||
return { hours };
|
||||
return hours;
|
||||
}
|
||||
|
||||
export function renderHours(
|
||||
svgElem: SVGElement,
|
||||
bounds: GraphBounds,
|
||||
sourceData: GraphData,
|
||||
i18n: I18n
|
||||
origData: pb.BackendProto.GraphsOut,
|
||||
i18n: I18n,
|
||||
range: GraphRange
|
||||
): void {
|
||||
const data = sourceData.hours;
|
||||
const data = gatherData(origData, range);
|
||||
|
||||
const yMax = Math.max(...data.map((d) => d.totalCount));
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import { select, mouse } from "d3-selection";
|
|||
import { scaleLinear, scaleSequential } from "d3-scale";
|
||||
import { axisBottom, axisLeft } from "d3-axis";
|
||||
import { showTooltip, hideTooltip } from "./tooltip";
|
||||
import { GraphBounds, setDataAvailable } from "./graphs";
|
||||
import { GraphBounds, setDataAvailable, GraphRange } from "./graphs";
|
||||
import { area, curveBasis } from "d3-shape";
|
||||
import { min, histogram, sum, max, Bin, cumsum } from "d3-array";
|
||||
import { timeSpan, dayLabel } from "../time";
|
||||
|
@ -38,13 +38,6 @@ export interface GraphData {
|
|||
reviewTime: Map<number, Reviews>;
|
||||
}
|
||||
|
||||
export enum ReviewRange {
|
||||
Month = 0,
|
||||
ThreeMonths = 1,
|
||||
Year = 2,
|
||||
AllTime = 3,
|
||||
}
|
||||
|
||||
const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind;
|
||||
type BinType = Bin<Map<number, Reviews[]>, number>;
|
||||
|
||||
|
@ -112,7 +105,7 @@ export function renderReviews(
|
|||
svgElem: SVGElement,
|
||||
bounds: GraphBounds,
|
||||
sourceData: GraphData,
|
||||
range: ReviewRange,
|
||||
range: GraphRange,
|
||||
showTime: boolean,
|
||||
i18n: I18n
|
||||
): void {
|
||||
|
@ -123,16 +116,16 @@ export function renderReviews(
|
|||
let xMin = 0;
|
||||
// cap max to selected range
|
||||
switch (range) {
|
||||
case ReviewRange.Month:
|
||||
case GraphRange.Month:
|
||||
xMin = -31;
|
||||
break;
|
||||
case ReviewRange.ThreeMonths:
|
||||
case GraphRange.ThreeMonths:
|
||||
xMin = -90;
|
||||
break;
|
||||
case ReviewRange.Year:
|
||||
case GraphRange.Year:
|
||||
xMin = -365;
|
||||
break;
|
||||
case ReviewRange.AllTime:
|
||||
case GraphRange.AllTime:
|
||||
xMin = min(sourceData.reviewCount.keys())!;
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue