monkeytype/src/js/chart-controller.js

704 lines
17 KiB
JavaScript

import Chart from "chart.js";
import * as TestStats from "./test-stats";
import * as ThemeColors from "./theme-colors";
import * as Misc from "./misc";
export let result = new Chart($("#wpmChart"), {
type: "line",
data: {
labels: [],
datasets: [
{
label: "wpm",
data: [],
borderColor: "rgba(125, 125, 125, 1)",
borderWidth: 2,
yAxisID: "wpm",
order: 2,
radius: 2,
},
{
label: "raw",
data: [],
borderColor: "rgba(125, 125, 125, 1)",
borderWidth: 2,
yAxisID: "raw",
order: 3,
radius: 2,
},
{
label: "errors",
data: [],
borderColor: "rgba(255, 125, 125, 1)",
pointBackgroundColor: "rgba(255, 125, 125, 1)",
borderWidth: 2,
order: 1,
yAxisID: "error",
maxBarThickness: 10,
type: "scatter",
pointStyle: "crossRot",
radius: function (context) {
var index = context.dataIndex;
var value = context.dataset.data[index];
return value <= 0 ? 0 : 3;
},
pointHoverRadius: function (context) {
var index = context.dataIndex;
var value = context.dataset.data[index];
return value <= 0 ? 0 : 5;
},
},
],
},
options: {
tooltips: {
mode: "index",
intersect: false,
callbacks: {
afterLabel: function (ti) {
try {
$(".wordInputAfter").remove();
let wordsToHighlight =
TestStats.keypressPerSecond[parseInt(ti.xLabel) - 1].words;
let unique = [...new Set(wordsToHighlight)];
unique.forEach((wordIndex) => {
let wordEl = $($("#resultWordsHistory .words .word")[wordIndex]);
let input = wordEl.attr("input");
if (input != undefined)
wordEl.append(
`<div class="wordInputAfter">${input
.replace(/\t/g, "_")
.replace(/\n/g, "_")
.replace(/</g, "&lt")
.replace(/>/g, "&gt")}</div>`
);
});
} catch {}
},
},
},
legend: {
display: false,
labels: {},
},
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [
{
ticks: {
autoSkip: true,
autoSkipPadding: 40,
},
display: true,
scaleLabel: {
display: false,
labelString: "Seconds",
},
},
],
yAxes: [
{
id: "wpm",
display: true,
scaleLabel: {
display: true,
labelString: "Words per Minute",
},
ticks: {
beginAtZero: true,
min: 0,
autoSkip: true,
autoSkipPadding: 40,
},
gridLines: {
display: true,
},
},
{
id: "raw",
display: false,
scaleLabel: {
display: true,
labelString: "Raw Words per Minute",
},
ticks: {
beginAtZero: true,
min: 0,
autoSkip: true,
autoSkipPadding: 40,
},
gridLines: {
display: false,
},
},
{
id: "error",
display: true,
position: "right",
scaleLabel: {
display: true,
labelString: "Errors",
},
ticks: {
precision: 0,
beginAtZero: true,
autoSkip: true,
autoSkipPadding: 40,
},
gridLines: {
display: false,
},
},
],
},
annotation: {
annotations: [],
},
},
});
export let accountHistory = new Chart($(".pageAccount #accountHistoryChart"), {
animationSteps: 60,
type: "line",
data: {
datasets: [
{
yAxisID: "wpm",
label: "wpm",
fill: false,
data: [],
borderColor: "#f44336",
borderWidth: 2,
trendlineLinear: {
style: "rgba(255,105,180, .8)",
lineStyle: "dotted",
width: 4,
},
},
{
yAxisID: "acc",
label: "acc",
fill: false,
data: [],
borderColor: "#cccccc",
borderWidth: 2,
},
],
},
options: {
tooltips: {
// Disable the on-canvas tooltip
enabled: true,
intersect: false,
custom: function (tooltip) {
if (!tooltip) return;
// disable displaying the color box;
tooltip.displayColors = false;
},
callbacks: {
// HERE YOU CUSTOMIZE THE LABELS
title: function () {
return;
},
beforeLabel: function (tooltipItem, data) {
let resultData =
data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
if (tooltipItem.datasetIndex !== 0) {
return `error rate: ${Misc.roundTo2(
resultData.y
)}%\nacc: ${Misc.roundTo2(100 - resultData.y)}%`;
}
let label =
`${data.datasets[tooltipItem.datasetIndex].label}: ${
tooltipItem.yLabel
}` +
"\n" +
`raw: ${resultData.raw}` +
"\n" +
`acc: ${resultData.acc}` +
"\n\n" +
`mode: ${resultData.mode} `;
if (resultData.mode == "time") {
label += resultData.mode2;
} else if (resultData.mode == "words") {
label += resultData.mode2;
}
let diff = resultData.difficulty;
if (diff == undefined) {
diff = "normal";
}
label += "\n" + `difficulty: ${diff}`;
label +=
"\n" +
`punctuation: ${resultData.punctuation}` +
"\n" +
`language: ${resultData.language}` +
"\n\n" +
`date: ${moment(resultData.timestamp).format("DD MMM YYYY HH:mm")}`;
return label;
},
label: function () {
return;
},
afterLabel: function () {
return;
},
},
},
animation: {
duration: 250,
},
legend: {
display: false,
labels: {
fontColor: "#ffffff",
},
},
responsive: true,
maintainAspectRatio: false,
hover: {
mode: "nearest",
intersect: false,
},
scales: {
xAxes: [
{
ticks: {},
type: "time",
bounds: "ticks",
distribution: "series",
display: false,
offset: true,
scaleLabel: {
display: false,
labelString: "Date",
},
},
],
yAxes: [
{
id: "wpm",
ticks: {
beginAtZero: true,
min: 0,
stepSize: 10,
},
display: true,
scaleLabel: {
display: true,
labelString: "Words per Minute",
},
},
{
id: "acc",
ticks: {
beginAtZero: true,
max: 100,
},
display: true,
position: "right",
scaleLabel: {
display: true,
labelString: "Error rate (100 - accuracy)",
},
gridLines: {
display: false,
},
},
],
},
},
});
export let accountActivity = new Chart(
$(".pageAccount #accountActivityChart"),
{
animationSteps: 60,
type: "bar",
data: {
datasets: [
{
yAxisID: "count",
label: "Seconds",
data: [],
trendlineLinear: {
style: "rgba(255,105,180, .8)",
lineStyle: "dotted",
width: 2,
},
order: 3,
},
{
yAxisID: "avgWpm",
label: "Average Wpm",
data: [],
type: "line",
order: 2,
lineTension: 0,
fill: false,
},
],
},
options: {
tooltips: {
callbacks: {
// HERE YOU CUSTOMIZE THE LABELS
title: function (tooltipItem, data) {
let resultData =
data.datasets[tooltipItem[0].datasetIndex].data[
tooltipItem[0].index
];
return moment(resultData.x).format("DD MMM YYYY");
},
beforeLabel: function (tooltipItem, data) {
let resultData =
data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
if (tooltipItem.datasetIndex === 0) {
return `Time Typing: ${Misc.secondsToString(
resultData.y
)}\nTests Completed: ${resultData.amount}`;
} else if (tooltipItem.datasetIndex === 1) {
return `Average Wpm: ${Misc.roundTo2(resultData.y)}`;
}
},
label: function () {
return;
},
},
},
animation: {
duration: 250,
},
legend: {
display: false,
labels: {
fontColor: "#ffffff",
},
},
responsive: true,
maintainAspectRatio: false,
hover: {
mode: "nearest",
intersect: false,
},
scales: {
xAxes: [
{
ticks: {
autoSkip: true,
autoSkipPadding: 40,
},
type: "time",
time: {
unit: "day",
displayFormats: {
day: "D MMM",
},
},
bounds: "ticks",
distribution: "series",
display: true,
scaleLabel: {
display: false,
labelString: "Date",
},
offset: true,
},
],
yAxes: [
{
id: "count",
ticks: {
beginAtZero: true,
min: 0,
autoSkip: true,
autoSkipPadding: 40,
stepSize: 10,
},
display: true,
scaleLabel: {
display: true,
labelString: "Time Typing",
},
},
{
id: "avgWpm",
ticks: {
beginAtZero: true,
min: 0,
autoSkip: true,
autoSkipPadding: 40,
stepSize: 10,
},
display: true,
position: "right",
scaleLabel: {
display: true,
labelString: "Average Wpm",
},
gridLines: {
display: false,
},
},
],
},
},
}
);
export let miniResult = new Chart($(".pageAccount #miniResultChart"), {
type: "line",
data: {
labels: [],
datasets: [
{
label: "wpm",
data: [],
borderColor: "rgba(125, 125, 125, 1)",
borderWidth: 2,
yAxisID: "wpm",
order: 2,
radius: 2,
},
{
label: "raw",
data: [],
borderColor: "rgba(125, 125, 125, 1)",
borderWidth: 2,
yAxisID: "raw",
order: 3,
radius: 2,
},
{
label: "errors",
data: [],
borderColor: "rgba(255, 125, 125, 1)",
pointBackgroundColor: "rgba(255, 125, 125, 1)",
borderWidth: 2,
order: 1,
yAxisID: "error",
maxBarThickness: 10,
type: "scatter",
pointStyle: "crossRot",
radius: function (context) {
var index = context.dataIndex;
var value = context.dataset.data[index];
return value <= 0 ? 0 : 3;
},
pointHoverRadius: function (context) {
var index = context.dataIndex;
var value = context.dataset.data[index];
return value <= 0 ? 0 : 5;
},
},
],
},
options: {
tooltips: {
mode: "index",
intersect: false,
},
legend: {
display: false,
labels: {},
},
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [
{
ticks: {
autoSkip: true,
autoSkipPadding: 40,
},
display: true,
scaleLabel: {
display: false,
labelString: "Seconds",
},
},
],
yAxes: [
{
id: "wpm",
display: true,
scaleLabel: {
display: true,
labelString: "Words per Minute",
},
ticks: {
beginAtZero: true,
min: 0,
autoSkip: true,
autoSkipPadding: 40,
},
gridLines: {
display: true,
},
},
{
id: "raw",
display: false,
scaleLabel: {
display: true,
labelString: "Raw Words per Minute",
},
ticks: {
beginAtZero: true,
min: 0,
autoSkip: true,
autoSkipPadding: 40,
},
gridLines: {
display: false,
},
},
{
id: "error",
display: true,
position: "right",
scaleLabel: {
display: true,
labelString: "Errors",
},
ticks: {
precision: 0,
beginAtZero: true,
autoSkip: true,
autoSkipPadding: 40,
},
gridLines: {
display: false,
},
},
],
},
annotation: {
annotations: [
{
enabled: false,
type: "line",
mode: "horizontal",
scaleID: "wpm",
value: "-30",
borderColor: "red",
borderWidth: 1,
borderDash: [2, 2],
label: {
// Background color of label, default below
backgroundColor: "blue",
// Font size of text, inherits from global
fontSize: 11,
// Font style of text, default below
fontStyle: "normal",
// Font color of text, default below
fontColor: "#fff",
// Padding of label to add left/right, default below
xPadding: 6,
// Padding of label to add top/bottom, default below
yPadding: 6,
// Radius of label rectangle, default below
cornerRadius: 3,
// Anchor position of label on line, can be one of: top, bottom, left, right, center. Default below.
position: "center",
// Whether the label is enabled and should be displayed
enabled: true,
// Text to display in label - default is null. Provide an array to display values on a new line
content: "PB",
},
},
],
},
},
});
export function updateColors(chart) {
if (ThemeColors.main == "") {
ThemeColors.update();
}
chart.data.datasets[0].borderColor = ThemeColors.main;
chart.data.datasets[1].borderColor = ThemeColors.sub;
if (chart.data.datasets[0].type === undefined) {
if (chart.config.type === "line") {
chart.data.datasets[0].pointBackgroundColor = ThemeColors.main;
} else if (chart.config.type === "bar") {
chart.data.datasets[0].backgroundColor = ThemeColors.main;
}
} else if (chart.data.datasets[0].type === "bar") {
chart.data.datasets[0].backgroundColor = ThemeColors.main;
} else if (chart.data.datasets[0].type === "line") {
chart.data.datasets[0].pointBackgroundColor = ThemeColors.main;
}
if (chart.data.datasets[1].type === undefined) {
if (chart.config.type === "line") {
chart.data.datasets[1].pointBackgroundColor = ThemeColors.sub;
} else if (chart.config.type === "bar") {
chart.data.datasets[1].backgroundColor = ThemeColors.sub;
}
} else if (chart.data.datasets[1].type === "bar") {
chart.data.datasets[1].backgroundColor = ThemeColors.sub;
} else if (chart.data.datasets[1].type === "line") {
chart.data.datasets[1].pointBackgroundColor = ThemeColors.sub;
}
try {
chart.options.scales.xAxes[0].ticks.minor.fontColor = ThemeColors.sub;
chart.options.scales.xAxes[0].scaleLabel.fontColor = ThemeColors.sub;
} catch {}
try {
chart.options.scales.yAxes[0].ticks.minor.fontColor = ThemeColors.sub;
chart.options.scales.yAxes[0].scaleLabel.fontColor = ThemeColors.sub;
} catch {}
try {
chart.options.scales.yAxes[1].ticks.minor.fontColor = ThemeColors.sub;
chart.options.scales.yAxes[1].scaleLabel.fontColor = ThemeColors.sub;
} catch {}
try {
chart.options.scales.yAxes[2].ticks.minor.fontColor = ThemeColors.sub;
chart.options.scales.yAxes[2].scaleLabel.fontColor = ThemeColors.sub;
} catch {}
try {
chart.data.datasets[0].trendlineLinear.style = ThemeColors.sub;
chart.data.datasets[1].trendlineLinear.style = ThemeColors.sub;
} catch {}
chart.update();
}
Chart.prototype.updateColors = function () {
updateColors(this);
};
export function setDefaultFontFamily(font) {
Chart.defaults.global.defaultFontFamily = font.replace(/_/g, " ");
}
export function updateAllChartColors() {
ThemeColors.update();
accountHistory.updateColors();
result.updateColors();
accountActivity.updateColors();
miniResult.updateColors();
}