Skip to content

Commit b33548e

Browse files
visualizations added
1 parent 8d2db0e commit b33548e

File tree

5 files changed

+194
-17
lines changed

5 files changed

+194
-17
lines changed

Avatar.js

+88
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,94 @@ class Avatar {
133133
}
134134
}
135135

136+
// to save recent typing stats of user over time interval
137+
static async save_recent_session(intervals, interval_wpm, interval_accuracy, avatar_id){
138+
let client;
139+
try {
140+
let recent_data = [intervals, interval_wpm, interval_accuracy];
141+
console.log(recent_data);
142+
recent_data = JSON.stringify(recent_data);
143+
client = await pool.connect();
144+
const result = await client.query(
145+
'UPDATE avatar SET recent_data = $1 WHERE avatar_id = $2',
146+
[recent_data, avatar_id]
147+
);
148+
}
149+
catch(e){
150+
console.log(e);
151+
}
152+
finally {
153+
client.release();
154+
}
155+
}
156+
157+
static async plot_recent_stats(avatar_id, username){
158+
let client;
159+
try {
160+
client = await pool.connect();
161+
const result = await client.query(
162+
"SELECT recent_data FROM avatar WHERE avatar_id = $1",
163+
[avatar_id]
164+
);
165+
let recent_data = result.rows[0].recent_data;
166+
console.log(recent_data);
167+
let intervals = recent_data[0];
168+
let interval_wpm = recent_data[1];
169+
let interval_accuracy = recent_data[2];
170+
171+
const wpmData = {
172+
x: intervals,
173+
y: interval_wpm,
174+
type: 'scatter',
175+
name: 'WPM'
176+
};
177+
178+
const accuracyData = {
179+
x: intervals,
180+
y: interval_accuracy,
181+
type: 'scatter',
182+
name: 'Accuracy'
183+
};
184+
185+
let recent_layout = {
186+
title: `WPM and Accuracy of ${username} for recent typing session: `,
187+
xaxis: {
188+
title: 'Time in seconds'
189+
},
190+
yaxis: {
191+
title: 'Values'
192+
}
193+
};
194+
let recent_plotData = [wpmData, accuracyData];
195+
return {recent_plotData, recent_layout};
196+
}
197+
catch(e){
198+
console.log(e.message)
199+
}
200+
finally {
201+
client.release();
202+
}
203+
}
204+
205+
static async read_avg_stats(avatar_id){
206+
let client;
207+
try {
208+
client = await pool.connect();
209+
const result = await client.query(
210+
'SELECT avg_wpm, avg_accuracy FROM avatar WHERE avatar_id = $1',
211+
[avatar_id]
212+
);
213+
console.log(result.rows[0]);
214+
return result.rows[0];
215+
}
216+
catch(e){
217+
console.log(e.message)
218+
}
219+
finally {
220+
client.release();
221+
}
222+
}
223+
136224
}
137225

138226
module.exports = Avatar;

app.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const Avatar = require('./Avatar');
1212
const app = express();
1313

1414
app.use(bodyParser.urlencoded({ extended: true }));
15+
app.use(bodyParser.json());
1516
app.use(express.static('public'));
1617
app.use(session({ secret: 'your-secret-key', resave: false, saveUninitialized: false }));
1718
app.set("view engine", "ejs");
@@ -48,23 +49,33 @@ app.get("/", async (req, res) => {
4849

4950
app.post('/typing_session', async function(req, res) {
5051
try{
51-
let wpm = req.body.wpm;
52-
let accuracy = req.body.accuracy;
52+
const { wpm, accuracy, intervals, interval_wpm, interval_accuracy } = req.body;
5353
console.log('ajax data of typing session:', wpm, accuracy);
5454
let result = await Avatar.save_typing_session(wpm, accuracy, req.session.avatar_id);
5555
console.log(result);
5656
result = await Avatar.update_avg_stats(req.session.avatar_id);
5757
console.log(result);
58+
console.log(intervals, interval_accuracy, interval_wpm);
59+
result = await Avatar.save_recent_session(intervals, interval_wpm, interval_accuracy, req.session.avatar_id);
60+
console.log(result);
61+
await Avatar.plot_recent_stats(req.session.avatar_id);
5862
}
5963
catch(e){
60-
console.log(e.message());
64+
console.log(e);
6165
}
6266
});
6367

6468
app.get('/plots', async function(req, res) {
6569
if(req.session.userId){
70+
let result = await Avatar.read_avg_stats(req.session.avatar_id);
71+
let wpm = result.avg_wpm;
72+
let accuracy = result.avg_accuracy;
73+
wpm = wpm.toFixed(2);
74+
accuracy = accuracy.toFixed(2);
75+
console.log(wpm, accuracy);
6676
let {plotData, layout} = await Avatar.plot_stats(req.session.avatar_id, req.session.username);
67-
res.render('plots', {plotData: plotData, layout: layout});
77+
let {recent_plotData, recent_layout} = await Avatar.plot_recent_stats(req.session.avatar_id, req.session.username);
78+
res.render('plots', {plotData: plotData, layout: layout, recent_plotData: recent_plotData, recent_layout: recent_layout, username: req.session.username, avatar_character: req.session.avatar, wpm: wpm, accuracy: accuracy});
6879
}
6980
else{
7081
res.redirect('login');

public/typingInterface.js

+32-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ const fileInput = document.getElementById("fileInput");
99
const blacklist_chars = ['Shift', 'CapsLock', 'Control', 'Alt', 'Meta', 'Backspace'];
1010
const programmingLanguages = ["plaintext", "assembly", "c", "c++", "go", "java", "javascript", "kotlin", "perl", "php", "python", "r", "ruby", "rust", "scala", "sh", "swift", "typescript"];
1111
let current_char, typed_chars, para_text, para_len, cursor_index;
12-
let isRight, isStarted, wpm, accuracy, total_errors, uncorrected_errors;
12+
let isRight, isStarted, wpm = 0, accuracy = 0, total_errors, uncorrected_errors;
1313
let initial_min, initial_sec, min, sec, minutes = 1;
14+
//to store wpm, accuracy values over defined intervals.
15+
let intervals;
16+
let interval_wpm = [];
17+
let interval_accuracy = [];
18+
interval_handling(minutes);
1419

1520
// Populate the dropdown menu with options
1621
programmingLanguages.forEach(language => {
@@ -19,8 +24,9 @@ programmingLanguages.forEach(language => {
1924
option.textContent = language;
2025
select.appendChild(option);
2126
});
22-
console.log(aa);
23-
console.log(uu);
27+
28+
29+
2430
// async code to fetch text from api
2531
// If you use the async keyword before a function definition, you can then use await within the function. When you await a promise, the function is paused in a non-blocking way until the promise settles. If the promise fulfills, you get the value back. If the promise rejects, the rejected value is thrown.
2632
// The await operator is used to wait for a Promise and get its fulfillment value.
@@ -40,6 +46,17 @@ function initialize_time(minutes){
4046
initial_sec = sec = s;
4147
}
4248

49+
function interval_handling(minutes){
50+
let sessionLength = minutes * 60; // session length in seconds
51+
let intervalLength = 20; // interval length in seconds
52+
intervals = Array.from(
53+
{ length: sessionLength / intervalLength },
54+
(_, i) => (i + 1) * intervalLength
55+
);
56+
intervals.unshift(0);
57+
console.log("interval: ", intervals);
58+
}
59+
4360
$(document).ready(function() {
4461
$("#time-slider").ionRangeSlider({
4562
type: "double",
@@ -57,6 +74,7 @@ $(document).ready(function() {
5774
onChange: function (data) {
5875
minutes = data.to - data.from;
5976
initialize_time(minutes);
77+
interval_handling(minutes);
6078
}
6179
});
6280
});
@@ -269,12 +287,12 @@ function display_modal() {
269287
}
270288

271289
// to send wpm and accuracy to server js to be able to save in db
272-
function send_stats(wpm, accuracy){
290+
function send_stats(wpm, accuracy, intervals, interval_wpm, interval_accuracy){
273291
console.log("ajax triggered");
274292
$.ajax({
275293
type: 'POST',
276294
url: '/typing_session',
277-
data: { wpm: wpm, accuracy: accuracy },
295+
data: { wpm: wpm, accuracy: accuracy, intervals: intervals, interval_wpm: interval_wpm, interval_accuracy: interval_accuracy },
278296
success: function(response) {
279297
console.log('stat send done');
280298
},
@@ -288,13 +306,21 @@ function send_stats(wpm, accuracy){
288306
function timer(min, sec) {
289307
//setInterval() will execute this arrow function snippet repeatedly after every 1000 millisec
290308
const interval_id = setInterval(() => {
309+
console.log(JSON.stringify(intervals));
310+
// for stats along typing session for definite intervals
311+
if(intervals.includes((min * 60) + sec)){
312+
console.log((min * 60) + sec);
313+
interval_accuracy.push(accuracy);
314+
interval_wpm.push(wpm);
315+
}
291316

292317
if (min == 0 && sec == 0) {
293318
console.log("timer done");
294319
document.removeEventListener("keydown", typing_handler);
295320
clearInterval(interval_id);
296321
display_modal();
297-
send_stats(wpm, accuracy)
322+
send_stats(wpm, accuracy, intervals, interval_wpm, interval_accuracy);
323+
console.log(interval_accuracy, interval_wpm);
298324
return;
299325
}
300326
else if (sec == 0) {

todos.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Todos:
2-
- stats on plots page
2+
- stats on plots page: avg, last typing stats
33
- better looking avatar, plots page
44
- font sizes
55
- add icons
@@ -77,3 +77,5 @@ CREATE TABLE typing_session (
7777

7878
npm i --save-dev nodemon
7979

80+
DELETE FROM typing_session WHERE accuracy = 'NaN';
81+

views/plots.ejs

+56-6
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,86 @@
11
<!DOCTYPE html>
22
<html lang="en">
3+
34
<head>
45
<meta charset="UTF-8">
56
<meta http-equiv="X-UA-Compatible" content="IE=edge">
67
<meta name="viewport" content="width=device-width, initial-scale=1.0">
78
<title>Typing</title>
89
<!-- bootstrap styles -->
9-
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
10+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
11+
integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
12+
<!-- bootstrap icons -->
13+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
1014
<!-- google fonts -->
1115
<link rel="preconnect" href="https://fonts.googleapis.com">
1216
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13-
<link href="https://fonts.googleapis.com/css2?family=Anonymous+Pro&family=Inconsolata&family=Source+Code+Pro&display=swap" rel="stylesheet">
17+
<link
18+
href="https://fonts.googleapis.com/css2?family=Anonymous+Pro&family=Inconsolata&family=Source+Code+Pro&display=swap"
19+
rel="stylesheet">
1420
<!-- plotly -->
1521
<script src="https://cdn.plot.ly/plotly-2.18.2.min.js"></script>
1622
<!-- css stylesheet -->
1723
<link rel="stylesheet" href="/styles.css">
1824
</head>
25+
1926
<body class="light_theme">
2027
<div class="container p-5 mt-3">
28+
<div class="row mb-5">
29+
<div class="col-8">
30+
<h3 id="heading" class="d-flex justify-content-start mt-2">
31+
<i class="bi bi-keyboard me-2" style="font-size: 2.2rem;"></i>
32+
Typesmith: A minimalistic typing solution
33+
</h3>
34+
</div>
35+
<div class="col-4">
36+
<!-- user avatar -->
37+
<div class="dropdown d-flex justify-content-end">
38+
<a class="btn dropdown-toggle" href="#" role="button" id="userDropdown" data-bs-toggle="dropdown"
39+
aria-expanded="false">
40+
<img src="../images/<%= avatar_character %>.png" alt="User Avatar" class="rounded-circle"
41+
width="50" height="50">
42+
<span><%= username %></span>
43+
</a>
44+
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
45+
<li><a class="dropdown-item" href="/">Start Typing</a></li>
46+
<li>
47+
<hr class="dropdown-divider">
48+
</li>
49+
<li><a class="dropdown-item" href="/logout">Logout</a></li>
50+
</ul>
51+
</div>
52+
</div>
53+
</div>
54+
55+
<div class="my-5 border border-success p-4 rounded-3">
56+
<p class="lead"><i class="bi bi-fast-forward-fill"></i> Average WPM: <%=wpm%> </p>
57+
<p class="lead"><i class="bi bi-bullseye"></i> Average Accuracy: <%=accuracy%>%</p>
58+
</div>
59+
60+
61+
<div id="recent_plot"></div>
62+
<div class="mt-5"></div>
2163
<div id="plot"></div>
2264
</div>
2365

2466
<!-- bootstrap js -->
25-
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" crossorigin="anonymous"></script>
26-
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-mQ93GR66B00ZXjt0YO5KlohRA5SY2XofN4zfuZxLkoj1gXtW8ANNCe9d5Y3eG5eD" crossorigin="anonymous"></script>
67+
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"
68+
integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" crossorigin="anonymous">
69+
</script>
70+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"
71+
integrity="sha384-mQ93GR66B00ZXjt0YO5KlohRA5SY2XofN4zfuZxLkoj1gXtW8ANNCe9d5Y3eG5eD" crossorigin="anonymous">
72+
</script>
2773
<!-- jquery -->
2874
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
2975
<!-- custom js for plotting charts-->
3076
<script type="text/javascript">
31-
const p = <%-JSON.stringify(plotData)%>;
32-
const l = <%-JSON.stringify(layout)%>;
77+
const p = <%-JSON.stringify(plotData) %>;
78+
const l = <%-JSON.stringify(layout) %>;
3379
Plotly.newPlot('plot', p, l);
80+
const pp = <%-JSON.stringify(recent_plotData) %>;
81+
const ll = <%-JSON.stringify(recent_layout) %>;
82+
Plotly.newPlot('recent_plot', pp, ll);
3483
</script>
3584
</body>
85+
3686
</html>

0 commit comments

Comments
 (0)