Skip to content

Commit

Permalink
add highcharts for scatter plot
Browse files Browse the repository at this point in the history
  • Loading branch information
fchabouis committed May 7, 2019
1 parent 8837b5e commit 2feb546
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 42 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"dependencies": {
"axios": "^0.18.0",
"core-js": "^2.6.5",
"highcharts": "^7.1.1",
"vue": "^2.6.10",
"vue-highcharts": "^0.1.0",
"vue-router": "^3.0.3",
"vuex": "^3.0.1"
},
Expand Down
2 changes: 2 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import VueHighcharts from 'vue-highcharts';

Vue.use(VueHighcharts);
Vue.config.productionTip = false

new Vue({
Expand Down
233 changes: 191 additions & 42 deletions src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,41 @@
<div>{{wpm}} wpm</div>
<div>{{accuracy}} accuracy</div>
<div>
{{ statsPerKey }}
{{worstAccuracy}}
</div>
<div v-for="(v,k) in statsPerKey">
{{ k }} : {{ v.sum / v.n }}
<div>
<Highcharts :options="chartOptions"/>
</div>
</div>
</template>

<script>
// @ is an alias to /src
// import HelloWorld from '@/components/HelloWorld.vue'
import axios from 'axios'
import axios from "axios";
let ls = window.localStorage;
const average = list => list.reduce((prev, curr) => prev + curr) / list.length;
const timeToWpm = time => Math.round(60. / ((time * 5) / 1000))
function randomChar(length) {
let result = '';
let characters = 'abcdefghijklmnopqrstuvwxyz';
let charactersLength = characters.length;
for ( let i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
let result = "";
let characters = "abcdefghijklmnopqrstuvwxyz";
let charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function sumObjectsByKey(...objs) {
return objs.reduce((a, b) => {
for (let k in b) {
if (b.hasOwnProperty(k)) a[k] = (a[k] || 0) + b[k];
}
return a;
}, {});
}
export default {
name: "home",
Expand All @@ -38,22 +49,36 @@ export default {
},
data() {
return {
targetText: "You have just taken your first step toward getting involved.",
targetText: "",
position: 0,
time: 0,
times: [],
errorsN: 0
errorsN: 0,
errors: {},
hasStarted: false,
typingStats: { speed: {}, errors: {}, count: {} }
};
},
watch: {
testIsDone(val) {
if (val) {
this.addTheStats();
}
}
},
mounted() {
document.addEventListener("keypress", this.onPress);
this.getWords()
if (ls.getItem("typingStats")) {
this.typingStats = JSON.parse(ls.getItem("typingStats"));
}
this.getWords();
},
methods: {
onPress(event) {
event.preventDefault();
console.log(event.key)
if (event.key === this.targetText[this.position]) {
let targetKey = this.targetText[this.position];
if (event.key === targetKey) {
this.hasStarted = true;
if (this.time) {
let date = new Date();
this.times.push({ key: event.key, time: date - this.time });
Expand All @@ -65,58 +90,128 @@ export default {
} else {
if (!this.testIsDone) {
this.errorsN++;
if (targetKey in this.errors) {
this.errors[targetKey]++;
} else {
this.errors[targetKey] = 1;
}
} else {
if (event.key === 'Enter') {
this.startNew()
if (event.key === "Enter") {
this.startNew();
}
}
}
},
startNew() {
this.position = 0
this.time = 0
this.times = []
this.errorsN = 0
this.position = 0;
this.time = 0;
this.times = [];
this.errorsN = 0;
this.errors = {};
this.getWords();
},
getWords(preferredKey) {
let vm = this
let sp = `*${randomChar(1)}*`
axios.get(`https://api.datamuse.com/words?sp=${sp}&max=10`)
.then(function (response) {
vm.targetText = response.data.map(el => el.word).join(' ')
})
.catch(function (error) {
vm.targetText = 'Oooop something is wrong with the service, sorry !'
let vm = this;
let sp = `*${this.worstAccuracy}*`;
axios
.get(`https://api.datamuse.com/words?sp=${sp}&max=300`)
.then(function(response) {
const shuffled = response.data.map(el => el.word).sort(() => 0.5 - Math.random());
let selected = shuffled.slice(0, 10);
vm.targetText = selected.join(" ");
})
.catch(function(error) {
vm.targetText = "Oooop something is wrong with the service, sorry !";
});
},
addTheStats() {
this.typingStats.speed = sumObjectsByKey(
this.typingStats.speed,
this.keysSpeed
);
this.typingStats.errors = sumObjectsByKey(
this.typingStats.errors,
this.errors
);
this.typingStats.count = sumObjectsByKey(
this.typingStats.count,
this.keysN
);
ls.setItem("typingStats", JSON.stringify(this.typingStats));
}
},
computed: {
worstAccuracy() {
let minAccuracy = 1.
let badKey = ''
for (let key of Object.keys(this.typingStats.count)) {
let accuracy = 1. - (this.typingStats.errors[key] || 0) / this.typingStats.count[key]
console.log(key)
if (accuracy < minAccuracy) {
minAccuracy = accuracy
badKey = key
}
}
return badKey
},
testIsDone() {
return this.position === this.targetText.length;
return this.position === this.targetText.length && this.hasStarted;
},
keysSpeed() {
if (this.testIsDone) {
let speedStats = this.times.reduce((acc, cv) => {
let key = cv.key;
if (key in acc) {
acc[key] += cv.time;
} else {
acc[key] = 0;
}
return acc;
}, {});
return speedStats;
} else {
return {};
}
},
statsPerKey() {
keysN() {
if (this.testIsDone) {
let stats = this.times.reduce((acc, cv) => {
let countStats = this.times.reduce((acc, cv) => {
let key = cv.key;
if (key in acc) {
acc[key].sum += cv.time;
acc[key].n++
acc[key]++;
} else {
acc[key] = {sum: cv.time, n: 1};
acc[key] = 0;
}
return acc
return acc;
}, {});
return stats
return countStats;
} else {
return {}
return {};
}
},
seriesForScatterPlot() {
return Object.keys(this.typingStats.count)
.map(key => {
return {
name: key,
color: "red",
data: [
[
// 1,1
timeToWpm(this.typingStats.speed[key] / this.typingStats.count[key]),
1. - (this.typingStats.errors[key] || 0.) / this.typingStats.count[key]
]
]
};
});
},
justTimes() {
return this.times.map(k => k.time);
},
wpm() {
if (this.testIsDone) {
return Math.round(60 / ((average(this.justTimes) * 5) / 1000));
return timeToWpm(average(this.justTimes))
}
},
accuracy() {
Expand All @@ -126,6 +221,60 @@ export default {
{ style: "percent" }
);
}
},
chartOptions() {
return {
chart: {
type: "scatter",
zoomType: "xy",
animation: {
duration: 2000
}
},
title: {
text: "speed / accuracy per key"
},
xAxis: {
title: {
enabled: true,
text: "speed (wpm)"
},
startOnTick: true,
endOnTick: true,
showLastLabel: true
},
yAxis: {
title: {
text: "accuracy"
}
},
plotOptions: {
scatter: {
marker: {
radius: 5,
symbol: 'circle',
states: {
hover: {
enabled: true,
lineColor: "rgb(100,100,100)"
}
}
},
states: {
hover: {
marker: {
enabled: false
}
}
},
tooltip: {
headerFormat: "<b>{series.name}</b><br>",
pointFormat: "{point.x} wpm, {point.y} accuracy"
}
}
},
series: this.seriesForScatterPlot
};
}
}
};
Expand Down

0 comments on commit 2feb546

Please sign in to comment.