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