Skip to content

Commit c364dcc

Browse files
committed
feat: display the time-to-type a character histogram
1 parent a6cf7ca commit c364dcc

File tree

4 files changed

+101
-0
lines changed

4 files changed

+101
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { useFormatter } from "@keybr/lesson-ui";
2+
import { Range, Vector } from "@keybr/math";
3+
import { timeToSpeed } from "@keybr/result";
4+
import { Canvas, type Rect, type ShapeList, Shapes } from "@keybr/widget";
5+
import { type ReactNode } from "react";
6+
import { Chart, chartArea, type SizeProps } from "./Chart.tsx";
7+
import { withStyles } from "./decoration.ts";
8+
import { bucketize } from "./dist/util.ts";
9+
import { type TimeToType } from "./types.ts";
10+
import { type ChartStyles, useChartStyles } from "./use-chart-styles.ts";
11+
12+
export function TimeToTypeHistogram({
13+
steps,
14+
width,
15+
height,
16+
}: {
17+
readonly steps: readonly TimeToType[];
18+
} & SizeProps): ReactNode {
19+
const styles = useChartStyles();
20+
const paint = usePaint(styles, steps);
21+
return (
22+
<Chart width={width} height={height}>
23+
<Canvas paint={chartArea(styles, paint)} />
24+
</Chart>
25+
);
26+
}
27+
28+
function usePaint(styles: ChartStyles, steps: readonly TimeToType[]) {
29+
const { formatSpeed } = useFormatter();
30+
const g = withStyles(styles);
31+
32+
const histogram = buildHistogram(steps);
33+
34+
const vIndex = new Vector();
35+
const vValue = new Vector();
36+
for (let index = 0; index < histogram.length; index++) {
37+
vIndex.add(index);
38+
vValue.add(histogram[index]);
39+
}
40+
const rIndex = Range.from(vIndex);
41+
const rValue = Range.from(vValue);
42+
43+
return (box: Rect): ShapeList => {
44+
return [
45+
g.paintGrid(box, "vertical", { lines: 5 }),
46+
g.paintGrid(box, "horizontal", { lines: 5 }),
47+
paintHistogram(),
48+
g.paintAxis(box, "bottom"),
49+
g.paintAxis(box, "left"),
50+
g.paintTicks(box, rIndex, "bottom", {
51+
lines: 5,
52+
fmt: formatSpeed,
53+
style: styles.valueLabel,
54+
}),
55+
];
56+
57+
function paintHistogram(): ShapeList {
58+
return Shapes.fill(
59+
styles.speed,
60+
[...rIndex.steps()].map((index) => {
61+
const w = Math.ceil(box.width / rIndex.span);
62+
const x = Math.round(rIndex.normalize(index, 1) * box.width);
63+
const y = Math.round(rValue.normalize(vValue.at(index)) * box.height);
64+
return Shapes.rect({
65+
x: box.x + x,
66+
y: box.y + box.height - y,
67+
width: w,
68+
height: y,
69+
});
70+
}),
71+
);
72+
}
73+
};
74+
}
75+
76+
function buildHistogram(steps: readonly TimeToType[]) {
77+
const histogram = new Array(1501).fill(0);
78+
for (const { timeToType } of steps) {
79+
if (timeToType > 0) {
80+
const index = Math.round(timeToSpeed(timeToType));
81+
if (index >= 0 && index < histogram.length) {
82+
histogram[index] += 1;
83+
}
84+
}
85+
}
86+
return bucketize(histogram, 30);
87+
}

packages/keybr-chart/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export * from "./Marker.tsx";
1212
export * from "./ProgressOverviewChart.tsx";
1313
export * from "./SpeedChart.tsx";
1414
export * from "./SpeedHistogram.tsx";
15+
export * from "./TimeToTypeHistogram.tsx";
1516
export * from "./types.ts";

packages/keybr-chart/lib/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export type Threshold = {
22
readonly label: string;
33
readonly value: number;
44
};
5+
6+
export type TimeToType = { readonly timeToType: number };

packages/page-typing-test/lib/components/Report.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
makeAccuracyDistribution,
44
makeSpeedDistribution,
55
SpeedHistogram,
6+
TimeToTypeHistogram,
67
} from "@keybr/chart";
78
import { useIntlNumbers } from "@keybr/intl";
89
import { useFormatter } from "@keybr/lesson-ui";
@@ -118,6 +119,16 @@ export const Report = memo(function Report({
118119
</Name>
119120
</Para>
120121

122+
<Box alignItems="center" justifyContent="center">
123+
<TimeToTypeHistogram
124+
steps={result.steps}
125+
width="45rem"
126+
height="15rem"
127+
/>
128+
</Box>
129+
130+
<Para align="center">Time to type a character histogram.</Para>
131+
121132
<Spacer size={3} />
122133

123134
<Replay settings={settings} result={result} />

0 commit comments

Comments
 (0)