985 lines
55 KiB
Plaintext
Executable File
985 lines
55 KiB
Plaintext
Executable File
<!DOCTYPE html>
|
|
<html lang="de">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<script src="static/libarys/chartjs/chart.js"></script>
|
|
<script src="static/libarys/chartjs/zoom-plugin/chartjs-plugin-zoom.js"></script>
|
|
<link rel="stylesheet" href="static/style.css">
|
|
<link rel="stylesheet" href="static/libarys/bootstrap/bootstrap.min.css">
|
|
<link rel="stylesheet" href="static/libarys/leaflet/leaflet.css">
|
|
<link rel="stylesheet" href="static/libarys/jquery/jquery-ui-1.13.1.min.css">
|
|
<link rel="icon" type="image/x-icon" href="static/images/favicon/favicon.png">
|
|
<script src="static/libarys/favicon/favloader.min.js"></script>
|
|
<script src="static/libarys/favicon/parseGIF.min.js"></script>
|
|
<script type="text/javascript" src="static/libarys/jquery/jquery-3.6.0.min.js"></script>
|
|
<script type="text/javascript" src="static/libarys/jquery/jquery-ui-1.13.1.min.js"></script>
|
|
<script type="application/javascript" src="static/libarys/chartjs/resizeObserver/resizeObserver.js"></script>
|
|
|
|
<script>
|
|
let matched = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
if (matched) {
|
|
favloader.init({
|
|
gif: "static/images/favicon/Favicon2ForDarkmode.gif"
|
|
});
|
|
} else {
|
|
favloader.init({
|
|
gif: "static/images/favicon/Favicon2.gif"
|
|
});
|
|
}
|
|
favloader.start();
|
|
</script>
|
|
<title id="title">Noah's Timeline | RME</title>
|
|
</head>
|
|
|
|
<body>
|
|
<main style="margin-bottom: 30px;">
|
|
<div id="selectexercisediv"></div>
|
|
<div id="processStatus-container"></div>
|
|
<div id="info-container"></div>
|
|
|
|
<div class="d-flex flex-column flex-shrink-0 ps-3 border-end border-secondary pe-2" style="width: 280px; position: fixed;">
|
|
<p href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-decoration-none">
|
|
<svg class="bi me-2" width="40" height="32">
|
|
<use xlink:href="#bootstrap"></use>
|
|
</svg>
|
|
<span class="fs-4">Settings</span>
|
|
</p>
|
|
<hr>
|
|
<ul class="nav nav-pills flex-column mb-auto ">
|
|
<li>
|
|
<div class="container text-center">
|
|
<button class="btn btn-primary mb-2" type="button" data-bs-toggle="collapse" data-bs-target="#navbarToggleExternalContent"
|
|
aria-expanded="false" aria-controls="navbarToggleExternalContent">
|
|
Database settings
|
|
</button>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="row mb-1">
|
|
<div class="collapse" style="height: 160px;" id="navbarToggleExternalContent">
|
|
<div class="pt-2">
|
|
<div class="col"></div>
|
|
<div class="col-10 mx-auto">
|
|
<form action="/" method="POST">
|
|
<div id="" class="row mb-0">
|
|
<div class="row mt-1 mx-auto">
|
|
<div class="input-group input-group-sm mb-2">
|
|
<span class="input-group-text" id="inputGroup-sizing-sm">Host</span>
|
|
<input type="text" id="host" name="host" title="Host" value="<%- inputs['host'] %>" class="form-control" aria-label="Sizing example input"
|
|
aria-describedby="inputGroup-sizing-sm">
|
|
</div>
|
|
</div>
|
|
<div class="row mx-auto">
|
|
<div class="input-group input-group-sm mb-2">
|
|
<span class="input-group-text" id="inputGroup-sizing-sm">Username</span>
|
|
<input type="text" id="user" name="user" title="Username" value="<%- inputs['user'] %>" class="form-control" aria-label="Sizing example input"
|
|
aria-describedby="inputGroup-sizing-sm">
|
|
</div>
|
|
</div>
|
|
<div class="row mx-auto">
|
|
<div class="input-group input-group-sm mb-2">
|
|
<span class="input-group-text" id="inputGroup-sizing-sm">Password</span>
|
|
<input type="password" id="password" name="password" title="Password for user" value="<%- inputs['password'] %>" class="form-control"
|
|
aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm">
|
|
</div>
|
|
</div>
|
|
<div class="row mx-auto">
|
|
<div class="input-group input-group-sm mb-1">
|
|
<span class="input-group-text" id="inputGroup-sizing-sm">Database</span>
|
|
<input type="text" id="database" name="database" title="Databasename" value="<%- inputs['database'] %>" class="form-control"
|
|
aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm">
|
|
</div>
|
|
</div>
|
|
<div id="inputs">
|
|
<button id="connect" class="btn btn-primary mt-1" type="submit" title="Connect to database!" name="button_id" value="1">Connect</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
<hr>
|
|
<li>
|
|
<div id="mb-3">
|
|
<div id="inputs">
|
|
<button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#toggleEventTypes" aria-expanded="true"
|
|
aria-controls="toggleEventTypes">Eventtypes</button>
|
|
<!-- <button class="btn btn-primary " type="submit" id="getAllData" title="Connect to database!" name="button_id" value="3" <%
|
|
if(inputs[ "getAllData"]) { %> disabled
|
|
<% } %> >Get all data</button> -->
|
|
</div>
|
|
</div>
|
|
<form action="/" method="POST">
|
|
<div class="container p-0 show collapse scrollable" id="toggleEventTypes">
|
|
|
|
</div>
|
|
</li>
|
|
<hr>
|
|
<li class="nav-item">
|
|
<div id="allInputs" class="container mb-1">
|
|
<div id="day-select" class="row container p-0 text-center mx-auto" style="width: 100%">
|
|
|
|
</div>
|
|
</div>
|
|
<div id="inputs">
|
|
<button id="getdata" class="btn btn-success mt-3" style="width: 80%;" type="submit" title="Gets data from database and shows them!" name="button_id"
|
|
value="2">Get data</button>
|
|
</div>
|
|
|
|
</li>
|
|
</form>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="container mx-auto mt-4 window">
|
|
<div class="row">
|
|
<div class="col"></div>
|
|
<div class="col-15">
|
|
<h1 class="text-center pt-1 mb-2 mt-0">Noah's Timeline</h1>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col"></div>
|
|
<div class="col-15 mx-2 mt-0" style="max-width: 100%;">
|
|
<p class="text-muted text-center fs-5 mb-1">
|
|
<small id="info-waiting">Connect to database</small>
|
|
</p>
|
|
<div class="chart-containter">
|
|
<div class="container ">
|
|
<canvas id="myChartAll"></canvas>
|
|
</div>
|
|
<div id="reset_zoom" class="container text-end">
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row pb-2">
|
|
<div class="col">
|
|
<div class="container" style="width: 80%; margin-right: 15px;">
|
|
<canvas id="myChartDoughnut"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="col">
|
|
<div class="container mx-auto" style="padding-top: 25%;">
|
|
<canvas id="myChartBar"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- <div class="row mb-5">
|
|
<div class="col"></div>
|
|
<div class="col-10">
|
|
<div id="map"></div>
|
|
</div>
|
|
</div> -->
|
|
|
|
<div class="container" id="singleCharts">
|
|
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<script src="static/libarys/leaflet/leaflet.js"></script>
|
|
<script>
|
|
|
|
let progress = { // For progress bar
|
|
current: 0,
|
|
max: 0
|
|
}
|
|
function updateProgress(current, max){
|
|
if(current == -1 && max == -1){
|
|
progress.current = (progress.max - 4);
|
|
}else{
|
|
progress.current = current;
|
|
progress.max = max;
|
|
}
|
|
updateProgressbar()
|
|
}
|
|
|
|
function updateProgressbar(){
|
|
console.log("CURRENT: " + progress.current + "\nMAX: " + progress.max);
|
|
let greyPortion = progress.max - progress.current;
|
|
let grey = "";
|
|
for(let i = 0; i < greyPortion; i++){
|
|
grey += ", #cecece"
|
|
}
|
|
let green = "";
|
|
for(let i = 0; i < progress.current; i++){
|
|
green += " #7afe52,"
|
|
}
|
|
green += "#10ff00";
|
|
document.getElementById("processStatus").style.backgroundImage = "linear-gradient(to right," + green + grey + ")";
|
|
}
|
|
|
|
let getDataButton = document.getElementById("getdata"); // Get Data Button
|
|
getDataButton.addEventListener("click", function(e){ // Eventlistener for Get Data Button
|
|
e.preventDefault();
|
|
let eventtypesValues = [];
|
|
document.querySelectorAll("input[name=eventtypes]:checked").forEach((checkbox) => {
|
|
eventtypesValues.push(checkbox.value); // Get all checked eventtypes
|
|
});
|
|
|
|
let data = {};
|
|
data.startDay = document.getElementById("startday-select").value;
|
|
data.endDay = document.getElementById("endday-select").value;
|
|
data.eventtypes = eventtypesValues;
|
|
|
|
$.ajax({ // Getdata
|
|
url: "/getdata",
|
|
type: "GET",
|
|
data: data,
|
|
contentType: "application/json",
|
|
success: function(data){
|
|
if(data.errorcode == 0){
|
|
console.log(data);
|
|
updateCharts(data);
|
|
clearInterval(processStatusInterval);
|
|
updateProgress(-1, -1); // -> progress.current = progress.max - 4 // (Loading main linechart, Loading doughnutchart, Loading barchart, Loading all singlecharts)
|
|
}else{
|
|
info("error", data.errorcode, data.errorinfo);
|
|
}
|
|
},
|
|
error: function(jqXHR, textStatus, errorThrown) {
|
|
info("error", textStatus, errorThrown);
|
|
}
|
|
});
|
|
document.getElementById("processStatus-container").innerHTML = '<div id="processStatus" class="pt-3 text-center">Waiting for server...</div>'
|
|
let processStatusElement = document.getElementById("processStatus");
|
|
let processStatusInterval = setInterval(
|
|
() => {
|
|
$.ajax({ // Get currentstatus of server process
|
|
url: "/processstatus",
|
|
type: "GET",
|
|
contentType: "application/json",
|
|
success: function(data){ // If data successfully comes back
|
|
if(data.curElement){
|
|
processStatusElement.innerHTML = "Adding " + data.curElement + "...";
|
|
}
|
|
if(data.count.cur > 0){
|
|
updateProgress(data.count.cur, data.count.max + 4) // + 4 (Loading main linechart, Loading doughnutchart, Loading barchart, Loading all singlecharts)
|
|
}
|
|
}
|
|
})
|
|
}, 10);
|
|
});
|
|
|
|
let connectButton = document.getElementById("connect");
|
|
connectButton.addEventListener("click", function(e){
|
|
e.preventDefault();
|
|
|
|
let data = {};
|
|
data.host = document.getElementById("host").value;
|
|
data.user = document.getElementById("user").value;
|
|
data.password = document.getElementById("password").value;
|
|
data.database = document.getElementById("database").value;
|
|
|
|
$.ajax({ // Connect to database
|
|
url: "/connectdatabase",
|
|
type: "GET",
|
|
data: data,
|
|
contentType: "application/json",
|
|
success: function(data){
|
|
if(data.errorcode == 0){ // If no error occurred
|
|
function getExerciseOptions(options){
|
|
let str = "";
|
|
for(let i = 0; i < options.length; i++){
|
|
str += '<option value="' + options[i].id + '" class="text-center">' +
|
|
options[i].name + "|" + options[i].id + "|" + options[i].start_time + " - " + options[i].stop_time + '</option>';
|
|
}
|
|
return str;
|
|
}
|
|
|
|
document.getElementById("selectexercisediv").innerHTML =
|
|
'<div id="exercise" class="p-2 container mx-auto text-center position-fixed">' +
|
|
'<button id="exercise-close">X</button>' +
|
|
'<label for="day-select" class="mb-1 px-5 mx-5" style="display: block;">Select an exercise:</label>' +
|
|
'<select class="btn btn-outline-primary mb-2" name="exercise" id="exercise-select">' +
|
|
getExerciseOptions(data.allExercises) +
|
|
'</select>' +
|
|
'<div class="input-group input-group-sm my-3 px-5">' +
|
|
'<span class="input-group-text" id="day-range-sm">Day Range</span>' +
|
|
'<input type="text" id="day-range" class="form-control" aria-label="day-range-sm" aria-describedby="day-range-sm" disabled>' +
|
|
'</div>' +
|
|
'<div class="container px-3 my-2">' +
|
|
'<div id="slider-range"></div>' +
|
|
'</div>' +
|
|
'<button class="btn btn-primary d-block mx-auto mt-3" id="selectexercise">Select exercise</button>' +
|
|
'</div>';
|
|
|
|
$("#exercise").draggable();
|
|
|
|
document.getElementById("exercise-close").addEventListener("click", (e) => {
|
|
document.getElementById("selectexercisediv").innerHTML = "";
|
|
});
|
|
|
|
async function getExerciseDays(exercise){ // Get days between start and stop time
|
|
let days = [];
|
|
// Converting selected exercise start time to unix timestamp
|
|
let mintime = new Date(exercise.start_time) * 1;
|
|
// Converting selected exercise stop time to unix timestamp
|
|
let maxtime = new Date(exercise.stop_time) * 1;
|
|
|
|
// Calculate time difference
|
|
let timeDifference = maxtime - mintime;
|
|
// Calculate day difference from time difference
|
|
let dayDifference = Math.floor(timeDifference / (1000 * 3600 * 24));
|
|
days[0] = await getStringDay(mintime);
|
|
// Filling the day difference with days
|
|
for (let i = 0; i < dayDifference ; i++) {
|
|
let day = new Date(days[i])
|
|
day.setDate(day.getDate() + 1);
|
|
// Converting Unixtimestamp to readable format (YYYY-MM-DD) and inserting it
|
|
days[i + 1] = await getStringDay(parseInt(day * 1));
|
|
}
|
|
async function getStringDay(InputTime) {
|
|
let unixTimestamp = 0;
|
|
let seperator = "-";
|
|
if (InputTime > 0) {
|
|
unixTimestamp = new Date(parseInt(InputTime));
|
|
} else {
|
|
unixTimestamp = new Date(new Date().getTime())
|
|
}
|
|
return unixTimestamp.getFullYear() + seperator + ("0" + (unixTimestamp.getMonth() + 1)).substr(-2) + seperator + ("0" + unixTimestamp.getDate()).substr(-2);
|
|
}
|
|
return days;
|
|
}
|
|
|
|
let startDaySlider = 0;
|
|
let endDaySlider = 0;
|
|
|
|
let exerciseSelect = document.getElementById("exercise-select");
|
|
let exercise = data.allExercises.find(el => el.id == exerciseSelect.value); // Get current selected exercise
|
|
async function slider(){
|
|
let days = await getExerciseDays(exercise); // Get days between start and stop time
|
|
$("#slider-range").slider({
|
|
range: true,
|
|
min: 0,
|
|
max: days.length-1,
|
|
values: [ 0, 1 ],
|
|
slide: function( event, ui ) {
|
|
$("#day-range").val( days[ui.values[ 0 ]] + " - " + days[ui.values[ 1 ]]);
|
|
},
|
|
stop: function(event, ui){ // When user stops slider:
|
|
startDaySlider = ui.values[0]; // Save values
|
|
endDaySlider = ui.values[1];
|
|
}
|
|
});
|
|
$("#day-range").val( days[$("#slider-range").slider("values", 0)] +
|
|
" - " + days[$("#slider-range").slider("values", 1)] );
|
|
}
|
|
slider();
|
|
|
|
|
|
exerciseSelect.addEventListener("change", async (e) =>{ // On user change exercise
|
|
let exercise = data.allExercises.find(el => el.id == exerciseSelect.value); // Get current selected exercise
|
|
let days = await getExerciseDays(exercise); // Get days between start and stop time
|
|
$("#slider-range").slider({
|
|
range: true,
|
|
min: 0,
|
|
max: days.length - 1,
|
|
values: [ 0, 1 ],
|
|
slide: function(event, ui) {
|
|
$("#day-range").val( days[ui.values[ 0 ]] + " - " + days[ui.values[ 1 ]]); // Show date values
|
|
},
|
|
stop: function(event, ui){ // When user stops slider:
|
|
startDaySlider = ui.values[0]; // Save values
|
|
endDaySlider = ui.values[1];
|
|
}
|
|
});
|
|
$("#day-range").val( days[$("#slider-range").slider("values", 0)] +
|
|
" - " + days[$("#slider-range").slider("values", 1)] ); // Show date values
|
|
});
|
|
|
|
let selectexerciseButton = document.getElementById("selectexercise");
|
|
selectexerciseButton.addEventListener("click", function(e){ // On "Select Exercise"-Button click
|
|
e.preventDefault();
|
|
let exercise = document.getElementById("exercise-select").value; // Get exercise id
|
|
document.getElementById("selectexercisediv").innerHTML = ""; // Clear pop up
|
|
|
|
let data = {}; // Data object that gets send to server
|
|
data.startDay = startDaySlider;
|
|
data.endDay = endDaySlider;
|
|
data.id = exercise;
|
|
|
|
|
|
let gotData = false;
|
|
$.ajax({ // Select exercise
|
|
url: "/selectexercise",
|
|
type: "GET",
|
|
data: data,
|
|
contentType: "application/json",
|
|
success: function(data){ // If data successfully comes back
|
|
gotData = true;
|
|
clearInterval(processStatusInterval);
|
|
if(data.errorcode == 0){
|
|
let str =
|
|
'<div class="row">' +
|
|
'<div class="checkboxList">' +
|
|
'<input type="checkbox" name="eventtypes" id="checkbox_all" checked>' +
|
|
'<label for="checkbox_all" class="ms-1"><b>Select all</b></label>' +
|
|
'</div>' +
|
|
'</div>';
|
|
let eventtypes = data.inputs.eventtypes;
|
|
function checkChecked(eventtypeId){ // For checking if the checkbox is checked or not
|
|
if(data.inputs.selectedEvents.includes(eventtypeId.toString()))
|
|
return "checked";
|
|
}
|
|
for(let i = 0; i < eventtypes.length; i++){ // Checkbox eventtypes
|
|
let eventtype = eventtypes[i];
|
|
str +=
|
|
'<div class="row">' +
|
|
'<div class="checkboxList">' +
|
|
'<input type="checkbox" name="eventtypes" id="checkbox_' + i + '" value="' + eventtype.id + '"' + checkChecked(eventtype.id) + '>' +
|
|
'<label for="checkbox_' + i + '" class="ms-1">' +
|
|
eventtype.name + ' (id: ' + eventtype.id + ')' +
|
|
'</label>' +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
document.getElementById("toggleEventTypes").innerHTML = str;
|
|
let selectAllCheckbox = document.getElementById("checkbox_all");
|
|
selectAllCheckbox.addEventListener("click", (e) => {
|
|
let checkboxes = document.getElementsByName("eventtypes");
|
|
for(let checkbox of checkboxes){
|
|
checkbox.checked = selectAllCheckbox.checked;
|
|
}
|
|
});
|
|
str = "";
|
|
function getDayOptions(end_start_day){ // Day select options (Current start Day, Current end Day)
|
|
let days = data.inputs.days;
|
|
let str_options = "";
|
|
for(let i = 0; i < days.length; i++){ // For every day
|
|
let selected = "";
|
|
if(end_start_day == i){
|
|
selected = "selected"
|
|
}
|
|
str_options +=
|
|
'<option value="' + i + '" class="text-center"' + selected + '>' +
|
|
days[i] +
|
|
'</option>'; // All day options
|
|
}
|
|
return str_options;
|
|
}
|
|
str =
|
|
'<div class="col p-0 mt-1">' +
|
|
'<label for="startday-select">Start Day</label>' +
|
|
'<select class="btn btn-outline-primary p-1" name="startDay" id="startday-select" style="width: 100%; margin-right: 2px;">' +
|
|
'<option value="0" class="text-center">Start day</option>' +
|
|
getDayOptions(data.inputs.startDay) +
|
|
'</select>' +
|
|
'</div>' +
|
|
'<div class="col p-0 mt-1">' +
|
|
'<label for="endday-select">End Day</label>' +
|
|
'<select class="btn btn-outline-primary p-1" name="endDay" id="endday-select"style="width: 100%; margin-left: 2px;">' +
|
|
'<option value="0" class="text-center">End day</option>' +
|
|
getDayOptions(data.inputs.endDay) +
|
|
'</select>' +
|
|
'</div>';
|
|
document.getElementById("day-select").innerHTML = str;
|
|
updateProgress(-1, -1); // -> progress.current = progress.max - 4 // (Loading main linechart, Loading doughnutchart, Loading barchart, Loading all singlecharts)
|
|
|
|
updateCharts(data); // Update all charts with new data
|
|
}else{
|
|
info("error", data.errorcode, data.errorinfo);
|
|
}
|
|
},
|
|
error: function(jqXHR, textStatus, errorThrown) {
|
|
info("error", textStatus, errorThrown);
|
|
}
|
|
}); // Vlt gradient von rechts nach links in grün, wie weit der process ist...
|
|
document.getElementById("processStatus-container").innerHTML = '<div id="processStatus" class="pt-3 text-center">Waiting for server...</div>'
|
|
let processStatusElement = document.getElementById("processStatus");
|
|
let processStatusInterval = setInterval(
|
|
() => {
|
|
$.ajax({ // Get currentstatus of server process
|
|
url: "/processstatus",
|
|
type: "GET",
|
|
contentType: "application/json",
|
|
success: function(data){ // If data successfully comes back
|
|
if(data.curElement){
|
|
processStatusElement.innerHTML = "Adding " + data.curElement + "...";
|
|
}
|
|
if(data.count.cur > 0){
|
|
updateProgress(data.count.cur, data.count.max + 4) // + 4 (Loading main linechart, Loading doughnutchart, Loading barchart, Loading all singlecharts)
|
|
}
|
|
}
|
|
})
|
|
}, 10);
|
|
loadingSpinner();
|
|
});
|
|
}else{ // If error occurred
|
|
info("error", data.errorcode, data.errorinfo);
|
|
}
|
|
},
|
|
error: function(jqXHR, textStatus, errorThrown) {
|
|
info("error", textStatus, errorThrown);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ----------========================================================----------
|
|
// ----< Chartjs >----
|
|
// ----------========================================================----------
|
|
|
|
// Chart infos
|
|
let charts = {};
|
|
charts.chartAll = {}; // Main chart
|
|
charts.doughnutChart = {}; // Pie chart
|
|
charts.barChart = {}; // Bar chart
|
|
charts.singleCharts = {}; // All single charts
|
|
charts.singleCharts.chart = []; // Array for all single charts
|
|
|
|
charts.chartAll.init = true; // If chart has to be initialized
|
|
charts.doughnutChart.init = true; // "
|
|
charts.barChart.init = true; // "
|
|
|
|
function getInitColors(){
|
|
let opacity = 0.4;
|
|
const colors = ['rgba(255, 61, 61,' + opacity + ')', 'rgba(255, 145, 61,' + opacity + ')',
|
|
'rgba(255, 213, 61,' + opacity + ')', 'rgba(232, 255, 61,' + opacity + ')',
|
|
'rgba(168, 255, 61,' + opacity + ')', 'rgba(93, 255, 61,' + opacity + ')',
|
|
'rgba(61, 255, 197,' + opacity + ')', 'rgba(61, 236, 255,' + opacity + ')',
|
|
'rgba(61, 168, 255,' + opacity + ')', 'rgba(61, 67, 255,' + opacity + ')',
|
|
'rgba(158, 61, 255,' + opacity + ')', 'rgba(236, 61, 255,' + opacity + ')',
|
|
'rgba(255, 61, 210,' + opacity + ')', 'rgba(255, 61, 152,' + opacity + ')',
|
|
'rgba(255, 209, 209,' + opacity + ')', 'rgba(255, 239, 186,' + opacity + ')',
|
|
'rgba(211, 255, 161,' + opacity + ')', 'rgba(161, 255, 210,' + opacity + ')',
|
|
'rgba(161, 214, 255,' + opacity + ')', 'rgba(255, 161, 255,' + opacity + ')'
|
|
];
|
|
const colorsOutline = ['rgba(255, 61, 61, 1)', 'rgba(255, 145, 61, 1)',
|
|
'rgba(255, 213, 61, 1)', 'rgba(232, 255, 61, 1)',
|
|
'rgba(168, 255, 61, 1)', 'rgba(93, 255, 61, 1)',
|
|
'rgba(61, 255, 197, 1)', 'rgba(61, 236, 255, 1)',
|
|
'rgba(61, 168, 255, 1)', 'rgba(61, 67, 255, 1)',
|
|
'rgba(158, 61, 255, 1)', 'rgba(236, 61, 255, 1)',
|
|
'rgba(255, 61, 210, 1)', 'rgba(255, 61, 152, 1)',
|
|
'rgba(255, 209, 209, 1)', 'rgba(255, 239, 186, 1)',
|
|
'rgba(211, 255, 161, 1)', 'rgba(161, 255, 210, 1)',
|
|
'rgba(161, 214, 255, 1)', 'rgba(255, 161, 255, 1)'
|
|
];
|
|
return [colors, colorsOutline];
|
|
}
|
|
const [colors, colorsOutline] = getInitColors();
|
|
|
|
async function updateCharts(allData){ // Initialized/Update all charts with new data
|
|
let processStatusElement = document.getElementById("processStatus");
|
|
loadingSpinner(); // Loading spinner (Loading data...)
|
|
let timeout = 25;
|
|
setTimeout(() => { // Timeout so progress can load
|
|
let data = allData.data; // Data
|
|
let exercise = allData.inputs.exercise; // Exercise informations
|
|
let timeLabels = allData.time; // Time labels
|
|
|
|
/* Update or init charts with data */
|
|
let lineConfig = getLineChartConfig(data); // Get config for all line charts
|
|
processStatusElement.innerHTML = "Loading main linechart...";
|
|
|
|
setTimeout(() => { // Timeout so progress can load
|
|
|
|
myChartAll(timeLabels, getDataset(data), lineConfig); // Update or init main chart
|
|
updateProgress(progress.current+1, progress.max);
|
|
let [dataLabels, dataCount] = getDataLables_Count(data);
|
|
processStatusElement.innerHTML = "Loading doughnutchart...";
|
|
|
|
setTimeout(() => { // Timeout so progress can load
|
|
|
|
doughnutChart(dataLabels, dataCount); // Update or init doughnut/pie chart
|
|
updateProgress(progress.current+1, progress.max);
|
|
processStatusElement.innerHTML = "Loading barchart...";
|
|
|
|
setTimeout(() => { // Timeout so progress can load
|
|
|
|
barChart(dataLabels, dataCount); // Update or init bar chart
|
|
updateProgress(progress.current+1, progress.max);
|
|
processStatusElement.innerHTML = "Loading all singlecharts...";
|
|
|
|
setTimeout(() => { // Timeout so progress can load
|
|
|
|
singleCharts(timeLabels, data, lineConfig); // Update or init all single charts
|
|
updateProgress(progress.current+1, progress.max);
|
|
resetZoomButton();
|
|
/* Header with exercise information */
|
|
$("#info-waiting").text(exercise.name + ", " + exercise.id + ", " + exercise.start_time + " - " + exercise.stop_time + " | Enable/disable by clicking event names");
|
|
|
|
document.getElementById("processStatus").style.backgroundImage = "linear-gradient(to right, grey, grey)";
|
|
document.getElementById("processStatus-container").innerHTML = "";
|
|
|
|
}, timeout);
|
|
}, timeout);
|
|
}, timeout);
|
|
}, timeout);
|
|
}, timeout);
|
|
}
|
|
|
|
function loadingSpinner(){ // Loading spinner
|
|
document.getElementById("info-waiting").innerHTML =
|
|
'<div class="spinner-border" role="status">' +
|
|
'<span class="sr-only"></span>' +
|
|
'</div>' +
|
|
'<div class="container text-center my-auto">' +
|
|
' Loading data...' +
|
|
'</div>';
|
|
}
|
|
|
|
function countEvents(dataArr) { // Sum all numbers in array
|
|
return dataArr.reduce((partialSum, a) => partialSum + a, 0); // Sum
|
|
}
|
|
function getDataLables_Count(data){ // Get all labels and total count of each eventtype
|
|
let dataLabels = [];
|
|
let dataCount = [];
|
|
|
|
let counter = 0;
|
|
for (let i in data) {
|
|
dataLabels[counter] = data[i][1]; // All labels
|
|
dataCount[counter] = countEvents(data[i][0]); // Total count of event
|
|
counter++;
|
|
}
|
|
return [dataLabels, dataCount];
|
|
}
|
|
function getDataset(data){
|
|
let dataset = [];
|
|
let counter = 1;
|
|
for(let i in data){
|
|
dataset.push({
|
|
label: data[i][1], // Event Type
|
|
data: data[i][0], // Event Count
|
|
backgroundColor: [
|
|
colors[counter - 1]
|
|
],
|
|
borderColor: [
|
|
colorsOutline[counter - 1]
|
|
],
|
|
borderWidth: 1,
|
|
fill: true
|
|
});
|
|
counter++;
|
|
}
|
|
return dataset;
|
|
}
|
|
function getHighest(data){ // Get highest value
|
|
let totalHighest = 0;
|
|
let curHighest = 0;
|
|
for(let i in data){
|
|
curHighest = Math.max.apply(null, data[i][0]); // Get current max from data[eventtype]
|
|
if(curHighest > totalHighest) // If current highest if bigger than total highest
|
|
totalHighest = curHighest
|
|
}
|
|
return totalHighest;
|
|
}
|
|
function getLineChartConfig(data){ // Config for all line charts
|
|
var config = {
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
title: {
|
|
display: true,
|
|
text: "Number of events",
|
|
font: {
|
|
size: 20
|
|
}
|
|
},
|
|
max: getYHeight(getHighest(data)), // Sets the maximum and adds some ontop
|
|
stepSize: 5
|
|
},
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: "Time",
|
|
font: {
|
|
size: 15
|
|
}
|
|
},
|
|
ticks: {
|
|
//autoSkip: false, // <- Shows every label
|
|
maxRotation: 180
|
|
}
|
|
}
|
|
},
|
|
plugins: { // https://www.chartjs.org/chartjs-plugin-zoom/samples/fetch-data.html <-- Zoom und dabei Daten besser auflösen
|
|
tooltip: true,
|
|
zoom: {
|
|
pan: {
|
|
enabled: true,
|
|
mode: 'x',
|
|
modifierKey: 'ctrl'
|
|
},
|
|
zoom: {
|
|
drag: {
|
|
enabled: true
|
|
},
|
|
mode: 'x'
|
|
}
|
|
}
|
|
},
|
|
animations: {
|
|
tension: {
|
|
to: 0.3 // Corners of lines are round
|
|
}
|
|
},
|
|
maintainAspectRatio: false
|
|
};
|
|
return config;
|
|
}
|
|
function getYHeight(highestValue) { // Get Y height for charts
|
|
if (highestValue == 0) {
|
|
return 1;
|
|
}
|
|
for (let i = highestValue; i < highestValue + 200; i++) {
|
|
if (i % 100 == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
return highestValue + 1;
|
|
}
|
|
function getMaxValue(arr) { // Get max value for bar chart
|
|
return Math.max.apply(null, arr);
|
|
}
|
|
|
|
async function myChartAll(timelabel, dataset, config){ // Main big chart
|
|
if(charts.chartAll.init){
|
|
const ctx = document.getElementById('myChartAll').getContext('2d');
|
|
let myChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: timelabel,
|
|
datasets: dataset
|
|
},
|
|
options: config
|
|
});
|
|
charts.chartAll.chart = myChart;
|
|
charts.chartAll.init = false;
|
|
}else{
|
|
const chart = charts.chartAll.chart;
|
|
chart.data.labels = timelabel;
|
|
chart.data.datasets = dataset;
|
|
chart.options = config;
|
|
chart.update();
|
|
}
|
|
}
|
|
async function doughnutChart(datalabels, datacount){ // Round pie chart
|
|
if(charts.doughnutChart.init){
|
|
const ctx = document.getElementById("myChartDoughnut").getContext("2d");
|
|
const myChartDonut = new Chart(ctx, {
|
|
type: "pie",
|
|
data: {
|
|
labels: datalabels,
|
|
datasets: [{
|
|
label: "Total Event Count",
|
|
data: datacount,
|
|
backgroundColor: colors,
|
|
borderColor: colorsOutline,
|
|
borderWidth: 1,
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true
|
|
}
|
|
});
|
|
charts.doughnutChart.init = false;
|
|
charts.doughnutChart.chart = myChartDonut;
|
|
}else{
|
|
let chart = charts.doughnutChart.chart;
|
|
chart.data = {
|
|
labels: datalabels,
|
|
datasets: [{
|
|
label: "Total Event Count",
|
|
data: datacount,
|
|
backgroundColor: colors,
|
|
borderColor: colorsOutline,
|
|
borderWidth: 1,
|
|
}]
|
|
};
|
|
chart.update();
|
|
}
|
|
}
|
|
async function barChart(datalabels, datacount){ // Bar chart
|
|
if(charts.barChart.init){
|
|
const ctx3 = document.getElementById("myChartBar").getContext("2d");
|
|
const myChartBar = new Chart(ctx3, {
|
|
type: "bar",
|
|
data: {
|
|
labels: datalabels,
|
|
datasets: [{
|
|
label: "Total events for event type",
|
|
data: datacount,
|
|
backgroundColor: colors,
|
|
borderColor: colorsOutline,
|
|
borderWidth: 1,
|
|
borderRadius: 5,
|
|
borderSkipped: false
|
|
}]
|
|
},
|
|
options: {
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
title: {
|
|
display: true,
|
|
text: "Number of events",
|
|
font: {
|
|
size: 15
|
|
}
|
|
},
|
|
max: getYHeight(getMaxValue(datacount)), // sets the maximum and adds some ontop
|
|
stepSize: 5
|
|
},
|
|
maintainAspectRatio: false,
|
|
responsive: true
|
|
}
|
|
}
|
|
});
|
|
charts.barChart.init = false;
|
|
charts.barChart.chart = myChartBar;
|
|
}else{
|
|
const chart = charts.barChart.chart;
|
|
chart.data = {
|
|
labels: datalabels,
|
|
datasets: [{
|
|
label: "Total events for event type",
|
|
data: datacount,
|
|
backgroundColor: colors,
|
|
borderColor: colorsOutline,
|
|
borderWidth: 1,
|
|
borderRadius: 5,
|
|
borderSkipped: false
|
|
}]
|
|
}
|
|
chart.options.scales.y.max = getYHeight(getMaxValue(datacount));
|
|
chart.update();
|
|
}
|
|
}
|
|
async function singleCharts(timelabel, data, config){ // All single charts
|
|
let str = "";
|
|
let singleCharts = document.getElementById("singleCharts");
|
|
for(let i = 0; i < Object.keys(data).length; i+=2){
|
|
str +=
|
|
'<div class="row pb-2">' +
|
|
'<div class="col"></div>' +
|
|
'<div class="col-5 ms-2 me-1 ">' +
|
|
'<canvas id="myChart' + (i+1) + '"></canvas>' +
|
|
'</div>' +
|
|
'<div class="col-5 ms-1 me-2 ">' +
|
|
'<canvas id="myChart' + (i+2) + '"></canvas>' +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
singleCharts.innerHTML = str;
|
|
|
|
let counter = 0;
|
|
for (let i in data) {
|
|
const ctx = document.getElementById('myChart' + (counter+1)).getContext('2d');
|
|
const myChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: timelabel, // Timestamp
|
|
datasets: [{
|
|
label: data[i][1], // Event Type
|
|
data: data[i][0], // Event Count
|
|
backgroundColor: [
|
|
colors[counter]
|
|
],
|
|
borderColor: [
|
|
colorsOutline[counter]
|
|
],
|
|
borderWidth: 1,
|
|
fill: true
|
|
}]
|
|
},
|
|
options: config
|
|
});
|
|
charts.singleCharts.chart[counter] = myChart;
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
async function map(){
|
|
var map = L.map('map', {
|
|
crs: L.CRS.Simple,
|
|
minZoom: -5,
|
|
});
|
|
// horiz verti
|
|
// top-right : 52.95249, 10.40041 (1000, 1000)
|
|
// bottom-left : 52.82840, 10.21173 (0, 0)
|
|
// - 0.12409, 0.18868
|
|
var bounds = [[52.82840, 10.21173], [52.95249, 10.40041]];
|
|
var image = L.imageOverlay("static/map/unterluess1.jpeg", bounds).addTo(map);
|
|
map.fitBounds(bounds);
|
|
|
|
// 52.91522, 10.28567
|
|
var guez = L.latLng([52.91522, 10.28567]);
|
|
L.marker(guez).addTo(map);
|
|
|
|
/*for (let i = 1; i <= Object.keys(data).length; i++) {
|
|
var polygon = L.polygon([
|
|
data[i][2][0],
|
|
data[i][2][1],
|
|
data[i][2][2],
|
|
data[i][2][3]
|
|
]).addTo(map);
|
|
}*/
|
|
}
|
|
|
|
async function info(type, code, text){ // If user should get an information (Error, Info)
|
|
/*
|
|
(type) => "error", "info", "clear".
|
|
(code) => Distinct number.
|
|
(text) => Short text that should help with error or short information text.
|
|
|
|
TYPES:
|
|
- error => Red-Orange error message.
|
|
- info => Green info message.
|
|
- clear => "Deletes" the info popup.
|
|
*/
|
|
let container = document.getElementById("info-container");
|
|
if(type == "error"){ // Error
|
|
container.innerHTML =
|
|
'<div id="info" class="error p-2 container mx-auto text-center position-fixed">' +
|
|
'<p>Erorr-' + code + '</p>' +
|
|
'<p>' + text + '</p>' +
|
|
'</div>';
|
|
setTimeout(() => { // After 5000ms error slides to right
|
|
info("clear");
|
|
}, 5000);
|
|
|
|
}else if(type == "info"){ // Info
|
|
container.innerHTML =
|
|
'<div id="info" class="info p-2 container mx-auto text-center position-fixed">' +
|
|
'<p>Info-' + code + '</p>' +
|
|
'<p>' + text + '</p>' +
|
|
'</div>';
|
|
setTimeout(() => { // After 5000ms info slides to right
|
|
info("clear");
|
|
}, 5000);
|
|
|
|
}else if(type == "clear"){ // Clear
|
|
if(document.getElementById("info")){
|
|
document.getElementById("info").classList.add("slideToRight");
|
|
setTimeout(() => {
|
|
container.innerHTML = "";
|
|
}, 1000);
|
|
}
|
|
}
|
|
container.addEventListener("mousewheel", (e) => { // If user clicks on info popup (mousewheel click)
|
|
info("clear");
|
|
});
|
|
container.addEventListener("mousedown", (e) => { // If user clicks on info popup (mouse click)
|
|
info("clear");
|
|
});
|
|
}
|
|
async function resetZoomButton(){ // Reset zoom button
|
|
document.getElementById("reset_zoom").innerHTML = '<button id="reset_zoom_btn" class="btn btn-secondary ">Reset Zoom</button>';
|
|
document.getElementById("reset_zoom_btn").addEventListener("click", (e) => { // If button is clicked
|
|
charts.chartAll.chart.resetZoom();
|
|
});
|
|
}
|
|
|
|
|
|
</script>
|
|
<script src="static/libarys/bootstrap/bootstrap.bundle.min.js"></script>
|
|
</body>
|
|
|
|
</html> |