review graph and tooltip improvements

This commit is contained in:
Damien Elmes 2020-06-27 15:35:34 +10:00
parent 894e824460
commit 67bb92d2f4
7 changed files with 99 additions and 44 deletions

View file

@ -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} />

View file

@ -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>

View file

@ -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,

View file

@ -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;
}

View file

@ -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,

View file

@ -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)

View file

@ -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";
}