mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
review graph and tooltip improvements
This commit is contained in:
parent
894e824460
commit
67bb92d2f4
7 changed files with 99 additions and 44 deletions
|
@ -7,7 +7,7 @@
|
|||
<script lang="typescript">
|
||||
import { assertUnreachable } from "../typing";
|
||||
import pb from "../backend/proto";
|
||||
import { getGraphData, GraphRange } from "./graphs";
|
||||
import { getGraphData, RevlogRange } from "./graphs";
|
||||
import IntervalsGraph from "./IntervalsGraph.svelte";
|
||||
import EaseGraph from "./EaseGraph.svelte";
|
||||
import AddedGraph from "./AddedGraph.svelte";
|
||||
|
@ -27,7 +27,7 @@
|
|||
}
|
||||
|
||||
let searchRange: SearchRange = SearchRange.Deck;
|
||||
let range: GraphRange = GraphRange.Month;
|
||||
let revlogRange: RevlogRange = RevlogRange.Month;
|
||||
let days: number = 31;
|
||||
let refreshing = false;
|
||||
|
||||
|
@ -61,14 +61,14 @@
|
|||
}
|
||||
|
||||
$: {
|
||||
switch (range as GraphRange) {
|
||||
case GraphRange.Month:
|
||||
switch (revlogRange as RevlogRange) {
|
||||
case RevlogRange.Month:
|
||||
days = 31;
|
||||
break;
|
||||
case GraphRange.Year:
|
||||
case RevlogRange.Year:
|
||||
days = 365;
|
||||
break;
|
||||
case GraphRange.All:
|
||||
case RevlogRange.All:
|
||||
days = 0;
|
||||
break;
|
||||
}
|
||||
|
@ -108,25 +108,25 @@
|
|||
<div class="range-box-inner">
|
||||
Review history:
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={GraphRange.Month} />
|
||||
<input type="radio" bind:group={revlogRange} value={RevlogRange.Month} />
|
||||
Month
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={GraphRange.Year} />
|
||||
<input type="radio" bind:group={revlogRange} value={RevlogRange.Year} />
|
||||
Year
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={GraphRange.All} />
|
||||
<input type="radio" bind:group={revlogRange} value={RevlogRange.All} />
|
||||
All
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-box-pad" />
|
||||
|
||||
<ReviewsGraph {sourceData} />
|
||||
<FutureDue {sourceData} />
|
||||
<TodayStats {sourceData} />
|
||||
<CardCounts {sourceData} />
|
||||
<FutureDue {sourceData} />
|
||||
<ReviewsGraph {sourceData} {revlogRange} />
|
||||
<IntervalsGraph {sourceData} />
|
||||
<EaseGraph {sourceData} />
|
||||
<ButtonsGraph {sourceData} />
|
||||
|
|
|
@ -2,18 +2,32 @@
|
|||
import { HistogramData, histogramGraph } from "./histogram-graph";
|
||||
import AxisLabels from "./AxisLabels.svelte";
|
||||
import AxisTicks from "./AxisTicks.svelte";
|
||||
import { defaultGraphBounds } from "./graphs";
|
||||
import { defaultGraphBounds, RevlogRange } from "./graphs";
|
||||
import { GraphData, gatherData, renderReviews, ReviewRange } from "./reviews";
|
||||
import pb from "../backend/proto";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let revlogRange: RevlogRange = RevlogRange.Month;
|
||||
|
||||
let graphData: GraphData | null = null;
|
||||
|
||||
let bounds = defaultGraphBounds();
|
||||
let svg = null as HTMLElement | SVGElement | null;
|
||||
let range = ReviewRange.Month;
|
||||
let range: ReviewRange;
|
||||
let showTime = false;
|
||||
let tooltip = null as null | HTMLDivElement;
|
||||
|
||||
$: switch (revlogRange as RevlogRange) {
|
||||
case RevlogRange.Month:
|
||||
range = ReviewRange.Month;
|
||||
break;
|
||||
case RevlogRange.Year:
|
||||
range = ReviewRange.Year;
|
||||
break;
|
||||
case RevlogRange.All:
|
||||
range = ReviewRange.AllTime;
|
||||
break;
|
||||
}
|
||||
|
||||
const xText = "";
|
||||
const yText = "Times pressed";
|
||||
|
@ -24,7 +38,7 @@
|
|||
}
|
||||
|
||||
$: if (graphData) {
|
||||
renderReviews(svg as SVGElement, bounds, graphData, range, showTime);
|
||||
renderReviews(svg as SVGElement, bounds, graphData, range, showTime, tooltip);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -37,22 +51,26 @@
|
|||
Time
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.Month} />
|
||||
Month
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.Quarter} />
|
||||
3 months
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.Year} />
|
||||
Year
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.AllTime} />
|
||||
All time
|
||||
</label>
|
||||
{#if revlogRange >= RevlogRange.Year}
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.Month} />
|
||||
Month
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.Quarter} />
|
||||
3 months
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.Year} />
|
||||
Year
|
||||
</label>
|
||||
{/if}
|
||||
{#if revlogRange === RevlogRange.All}
|
||||
<label>
|
||||
<input type="radio" bind:group={range} value={ReviewRange.AllTime} />
|
||||
All time
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
|
||||
|
@ -65,4 +83,6 @@
|
|||
<AxisLabels {bounds} {xText} {yText} />
|
||||
</svg>
|
||||
|
||||
<div bind:this={tooltip} class="tooltip-area" />
|
||||
|
||||
</div>
|
||||
|
|
|
@ -26,7 +26,7 @@ export enum FutureDueRange {
|
|||
|
||||
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)
|
||||
.filter((c) => c.queue == CardQueue.Review && c.due >= data.daysElapsed)
|
||||
.map((c) => c.due - data.daysElapsed);
|
||||
const dueCounts = rollup(
|
||||
due,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
background-color: white;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 20px;
|
||||
font-size: 15px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s;
|
||||
|
@ -48,6 +48,7 @@
|
|||
|
||||
.range-box {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 4em;
|
||||
|
@ -85,6 +86,11 @@
|
|||
pointer-events: all;
|
||||
}
|
||||
|
||||
.hoverzone rect:hover {
|
||||
fill: grey;
|
||||
opacity: 0.05;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
|
@ -108,3 +114,22 @@
|
|||
opacity: 0.5;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
.tooltip-area {
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
.tooltip-area > * {
|
||||
flex: 1 100%;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.legend-outer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.legend-outer div {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ export async function getGraphData(
|
|||
return pb.BackendProto.GraphsOut.decode(bytes);
|
||||
}
|
||||
|
||||
export enum GraphRange {
|
||||
export enum RevlogRange {
|
||||
Month = 1,
|
||||
Year = 2,
|
||||
All = 3,
|
||||
|
|
|
@ -106,19 +106,14 @@ function cumulativeBinValue(bin: BinType, idx: number): number {
|
|||
return sum(totalsForBin(bin).slice(0, idx + 1));
|
||||
}
|
||||
|
||||
function tooltipText(d: BinType, cumulative: number): string {
|
||||
return `bin: ${JSON.stringify(totalsForBin(d))}<br>cumulative: ${cumulative}`;
|
||||
}
|
||||
|
||||
export function renderReviews(
|
||||
svgElem: SVGElement,
|
||||
bounds: GraphBounds,
|
||||
sourceData: GraphData,
|
||||
range: ReviewRange,
|
||||
showTime: boolean
|
||||
showTime: boolean,
|
||||
tooltipArea: HTMLDivElement
|
||||
): void {
|
||||
console.log(sourceData);
|
||||
|
||||
const xMax = 0;
|
||||
let xMin = 0;
|
||||
// cap max to selected range
|
||||
|
@ -137,7 +132,6 @@ export function renderReviews(
|
|||
break;
|
||||
}
|
||||
const desiredBars = Math.min(70, Math.abs(xMin!));
|
||||
console.log(`xmin ${xMin}`);
|
||||
|
||||
const x = scaleLinear().domain([xMin!, xMax]);
|
||||
const sourceMap = showTime ? sourceData.reviewTime : sourceData.reviewCount;
|
||||
|
@ -159,7 +153,6 @@ export function renderReviews(
|
|||
// y scale
|
||||
|
||||
const yMax = max(bins, (b: Bin<any, any>) => cumulativeBinValue(b, 4))!;
|
||||
console.log(`ymax ${yMax}`);
|
||||
const y = scaleLinear()
|
||||
.range([bounds.height - bounds.marginBottom, bounds.marginTop])
|
||||
.domain([0, yMax]);
|
||||
|
@ -196,6 +189,23 @@ export function renderReviews(
|
|||
x.domain() as any
|
||||
);
|
||||
|
||||
function tooltipText(d: BinType, cumulative: number): string {
|
||||
let buf = `<div>day ${d.x0}-${d.x1}</div>`;
|
||||
const totals = totalsForBin(d);
|
||||
const lines = [
|
||||
[darkerGreens(1), `Mature: ${totals[0]}`],
|
||||
[lighterGreens(1), `Young: ${totals[1]}`],
|
||||
[blues(1), `New/learn: ${totals[2]}`],
|
||||
[reds(1), `Relearn: ${totals[3]}`],
|
||||
[oranges(1), `Early: ${totals[4]}`],
|
||||
["grey", `Total: ${cumulative}`],
|
||||
];
|
||||
for (const [colour, text] of lines) {
|
||||
buf += `<div><span style="color: ${colour}">■</span>${text}</div>`;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
const updateBar = (sel: any, idx: number): any => {
|
||||
return sel
|
||||
.attr("width", barWidth)
|
||||
|
|
|
@ -12,8 +12,8 @@ function showTooltipInner(msg: string, x: number, y: number): void {
|
|||
document.body.appendChild(tooltipDiv);
|
||||
}
|
||||
tooltipDiv.innerHTML = msg;
|
||||
tooltipDiv.style.left = `${x - 50}px`;
|
||||
tooltipDiv.style.top = `${y - 50}px`;
|
||||
tooltipDiv.style.right = `${document.body.clientWidth - x + 10}px`;
|
||||
tooltipDiv.style.top = `${y + 20}px`;
|
||||
|
||||
tooltipDiv.style.opacity = "1";
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue