508 lines
No EOL
15 KiB
JavaScript
508 lines
No EOL
15 KiB
JavaScript
/* globals Chart:false, feather:false */
|
|
|
|
//var ctx = document.getElementById('myChart')
|
|
//var mainContent = document.getElementById('mainContent')
|
|
|
|
class BlueWeatherDashboard {
|
|
|
|
constructor() {
|
|
// cunstruct xmlhttp
|
|
this.initDone = false
|
|
this.blueweather = new BlueWeather()
|
|
|
|
this.params = {
|
|
loc: 1,
|
|
page: -1,
|
|
maxVals: 100
|
|
}
|
|
|
|
this.restoreParms(this.params)
|
|
|
|
this.loadDashboard(this.params)
|
|
this.initDone = true
|
|
}
|
|
|
|
loadDashboard() {
|
|
window.history.pushState("object or string", "Title", this.buildUrl({ "params": JSON.stringify(this.params) }));
|
|
|
|
// page: -1 for dashboard or sensor id
|
|
var mainContent = document.getElementById('mainContent')
|
|
var loader = document.getElementById('loader')
|
|
|
|
mainContent.style = "opacity: 0;"
|
|
loader.style = "position: absolute; left: 50%; opacity: 1;"
|
|
//mainContent.classList.remove("opacity-animated");
|
|
|
|
var page = this.params.page
|
|
|
|
this.blueweather.getLocationData(this.params.loc, { from: this.params.range.from, to: this.params.range.to }, this.params.maxVals, true, function (locationData) {
|
|
// refresh sensors list
|
|
dashboard.loadSensors(locationData.sensors)
|
|
|
|
// set acive page in navbar
|
|
document.getElementById("navbarPage" + page + "Link").classList.add("active")
|
|
|
|
if (page === -1) {
|
|
dashboard.setPageTitle("Dashboard")
|
|
|
|
// create widget row
|
|
var firstRow = document.createElement("div")
|
|
firstRow.classList.add("card-deck")
|
|
|
|
// create table
|
|
var tableContainer = document.createElement("div")
|
|
tableContainer.style = "overflow-x:auto;margin-top:20px;"
|
|
var table = document.createElement("table")
|
|
tableContainer.appendChild(table)
|
|
table.classList.add("table", "table-hover")
|
|
|
|
// create forst row of table
|
|
var tableHead = document.createElement("thead")
|
|
table.appendChild(tableHead)
|
|
tableHead.innerHTML = "<tr><th>Sensor</th><th>Minimum</th><th>Maximum</th><th>Average</th></tr>"
|
|
var tableBody = document.createElement("tbody")
|
|
table.appendChild(tableBody)
|
|
|
|
for (var i = 0; i < locationData.sensors.length; i++) {
|
|
var sensor = locationData.sensors[i]
|
|
|
|
// get the valuetype of the current sensor
|
|
var valueType;
|
|
|
|
for (var s = 0; s < locationData.valuetypes.length; s++) {
|
|
var thisValType = locationData.valuetypes[s]
|
|
if (parseInt(thisValType.id) === parseInt(sensor.valuetypeid)) {
|
|
valueType = thisValType;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// get all relevant meassurement values
|
|
var vals = []
|
|
var sum = 0
|
|
var min
|
|
var max
|
|
|
|
for (s = 0; s < locationData["measvalues"].length; s++) {
|
|
var thisValue = locationData["measvalues"][s]
|
|
if (parseInt(thisValue['sensorid']) === parseInt(sensor.id)) {
|
|
vals.push(thisValue)
|
|
sum += thisValue.measvalue
|
|
if(thisValue.measvalue < min || min === undefined) {
|
|
min = thisValue.measvalue
|
|
}
|
|
if(thisValue.measvalue > max || max === undefined) {
|
|
max = thisValue.measvalue
|
|
}
|
|
}
|
|
}
|
|
|
|
if(vals.length < 1) {
|
|
continue
|
|
}
|
|
|
|
vals.sort(function(a,b){
|
|
return b.timestamp - a.timestamp
|
|
})
|
|
|
|
// create table col
|
|
var tableRow = document.createElement("tr")
|
|
tableRow.innerHTML = "<td>" + sensor.sensorname + " (" + valueType.valuetype + ", " + valueType.valueunit + ")" + "</td><td>" + min + "</td><td>" + max + "</td><td>" + (Math.round((sum / vals.length)*100)/100) + "</td>"
|
|
tableBody.appendChild(tableRow)
|
|
|
|
// - create widget card -
|
|
var latestVal = vals[0]
|
|
|
|
// create card
|
|
var thisWidget = document.createElement("div")
|
|
thisWidget.classList.add("card")
|
|
|
|
// create header
|
|
var thisHeader = document.createElement("div")
|
|
thisHeader.classList.add("card-header")
|
|
thisHeader.innerHTML = sensor.sensorname + " (" + valueType.valuetype + ")"
|
|
thisWidget.appendChild(thisHeader)
|
|
|
|
// create body
|
|
var thisBody = document.createElement("div")
|
|
thisBody.classList.add("card-body")
|
|
thisWidget.appendChild(thisBody)
|
|
|
|
// create footer
|
|
var thisFooter = document.createElement("div")
|
|
thisFooter.classList.add("card-footer")
|
|
// fill footer
|
|
var thisUpdateTime = document.createElement("small")
|
|
thisUpdateTime.classList.add("text-muted")
|
|
var lastUpdated = new Date(latestVal.timestamp * 1000)
|
|
thisUpdateTime.innerHTML = "last updated " + (lastUpdated.getDate() === new Date().getDate() ? "today" : lastUpdated.getFullYear+"-"+lastUpdated.getMonth()+"-"+lastUpdated.getDate()) + " at " + lastUpdated.getHours() + ":" + lastUpdated.getMinutes()
|
|
thisFooter.appendChild(thisUpdateTime)
|
|
thisWidget.appendChild(thisFooter)
|
|
|
|
// - create widget body -
|
|
var displayProperties = JSON.parse(valueType.displayproperty).widget
|
|
|
|
switch (displayProperties.type) {
|
|
case "doughnut":
|
|
// the widget is gauge-like
|
|
var thisChart = document.createElement("canvas")
|
|
thisChart.id = "chartOf" + sensor.sensorname
|
|
|
|
var chartData = {
|
|
datasets: [
|
|
{
|
|
label: sensor.sensorname + " (" + valueType.valuetype + ")",
|
|
data: [
|
|
parseInt(latestVal.measvalue),
|
|
100 -parseInt( latestVal.measvalue)
|
|
]
|
|
}
|
|
],
|
|
labels: false
|
|
}
|
|
|
|
Object.assign(chartData.datasets[0], displayProperties.properties)
|
|
|
|
dashboard.createPercentCircle(thisChart, chartData)
|
|
|
|
thisBody.appendChild(thisChart)
|
|
|
|
break
|
|
case "text":
|
|
// the widget is pure text
|
|
thisWidget.classList.add("text-center")
|
|
thisBody.innerHTML = '<h1 class="align-self-center mx-auto" style="font-size:10vw;margin-top: auto;margin-bottom: auto;">' + latestVal.measvalue + valueType.valueunit + '</h1>'
|
|
|
|
|
|
//thisBody.innerHTML = '<svg viewBox="0 0 56 18"><text x="0" y="15">Fit Meeeeeeeeeeee</text></svg>'
|
|
break
|
|
}
|
|
|
|
firstRow.appendChild(thisWidget)
|
|
}
|
|
|
|
|
|
|
|
mainContent.innerHTML = "";
|
|
mainContent.appendChild(firstRow)
|
|
mainContent.appendChild(tableContainer)
|
|
}
|
|
else if (page !== -1) {
|
|
dashboard.loadDiagram("mainContent", locationData, page)
|
|
}
|
|
|
|
loader.style = "position: absolute; left: 50%; opacity: 0;"
|
|
mainContent.classList.add("opacity-animated");
|
|
mainContent.style = "opacity: 1;"
|
|
|
|
})
|
|
}
|
|
|
|
restoreParms(defaults = {}) {
|
|
this.params = Object.assign(defaults, JSON.parse(this.findGetParameter("params")))
|
|
}
|
|
|
|
updateParams(newParams) {
|
|
if (newParams.page !== this.params.page && this.initDone) {
|
|
document.getElementById("navbarPage" + this.params.page + "Link").classList.remove("active")
|
|
}
|
|
|
|
Object.assign(this.params, newParams)
|
|
|
|
window.history.pushState("object or string", "Title", this.buildUrl({ "params": JSON.stringify(this.params) }));
|
|
this.loadDashboard()
|
|
}
|
|
|
|
setTimeRange(from = "", to = "") {
|
|
this.blueweather.log("changing time range; form: " + from + "; to: " + to + "; (now is: " + new Date().getTime() + ")", 3)
|
|
|
|
if (from !== "") {
|
|
from = new Date().getTime() / 1000 - from
|
|
}
|
|
|
|
if (to !== "") {
|
|
to = new Date().getTime() / 1000 - parseInt(to)
|
|
}
|
|
|
|
this.updateParams({ range: { to: to, from: from } })
|
|
|
|
}
|
|
|
|
loadSensors(sensors) {
|
|
|
|
var sensorsList = document.getElementById("sensorsList")
|
|
sensorsList.innerHTML = ""
|
|
|
|
for (var i = 0; i < sensors.length; i++) {
|
|
var currentHTML = sensorsList.innerHTML
|
|
sensorsList.innerHTML = currentHTML +
|
|
"<li class=\"nav-item\"><a class=\"nav-link\" id=\"navbarPage" + sensors[i]["id"] + "Link\" href=\"#\" onclick=\"dashboard.updateParams({page: " + sensors[i]["id"] + "})\"><span data-feather=\"file\"></span>" + sensors[i]["sensorname"] + "</a></li>"
|
|
}
|
|
|
|
feather.replace()
|
|
}
|
|
|
|
loadDiagram(parentId, locationData, sensorId) {
|
|
mainContent = document.getElementById(parentId)
|
|
// get all relevant meassurement values
|
|
var vals = []
|
|
|
|
for (var i = 0; i < locationData["measvalues"].length; i++) {
|
|
var thisValue = locationData["measvalues"][i]
|
|
if (parseInt(thisValue['sensorid']) === sensorId) {
|
|
vals.push(thisValue)
|
|
}
|
|
}
|
|
|
|
// get the current sensor
|
|
var sensor;
|
|
|
|
for (i = 0; i < locationData['sensors'].length; i++) {
|
|
var thisSensor = locationData['sensors'][i]
|
|
if (parseInt(thisSensor.id) === sensorId) {
|
|
sensor = thisSensor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// get the value type
|
|
var valueType;
|
|
|
|
for (i = 0; i < locationData['valuetypes'].length; i++) {
|
|
var thisValType = locationData['valuetypes'][i]
|
|
if (parseInt(thisValType.id) === parseInt(sensor.valuetypeid)) {
|
|
valueType = thisValType;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// build chart data array
|
|
var chartData = {
|
|
data: [],
|
|
datasets: [
|
|
{
|
|
label: sensor.sensorname + " (" + valueType.valuetype + ")",
|
|
yAxisID: 'yAxis',
|
|
showLine: true,
|
|
data: vals
|
|
}
|
|
],
|
|
range: locationData.range
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < vals.length; i++) {
|
|
chartData.data.push({ x: vals[i]["timestamp"], y: vals[i]["measvalue"] })
|
|
}
|
|
|
|
Object.assign(chartData.datasets[0], JSON.parse(valueType.displayproperty)["chart"]["properties"])
|
|
Object.assign(chartData.datasets[0], { data: chartData.data, pointRadius: chartData.data.length > 500 ? 0 : 3 })
|
|
|
|
mainContent.innerHTML = "<canvas class=\"my-4 w-100\" id=\"myChart\"></canvas>"
|
|
this.setPageTitle(sensor.sensorname + " (" + valueType.valuetype + ")")
|
|
|
|
//blueweather.log("creating chart with data: " + JSON.stringify(chartData), 3)
|
|
|
|
this.createDiagram("myChart", chartData)
|
|
}
|
|
|
|
// --------------------
|
|
// - helper functions -
|
|
// --------------------
|
|
|
|
createPercentCircle(canvasElement, data) {
|
|
var randomScalingFactor = function () {
|
|
return Math.round(Math.random() * 100);
|
|
};
|
|
var ctx = canvasElement//document.getElementById(canvasId)
|
|
|
|
var chartData = data
|
|
var chartOptions = {
|
|
responsive: true,
|
|
circumference: Math.PI,
|
|
rotation: Math.PI,
|
|
legend: {
|
|
position: 'top',
|
|
},
|
|
animation: {
|
|
animateScale: true,
|
|
animateRotate: true
|
|
}
|
|
}
|
|
|
|
|
|
var myChart = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: chartData,
|
|
options: chartOptions
|
|
})
|
|
|
|
}
|
|
|
|
createDiagram(canvasId, data) {
|
|
// Graphs
|
|
var ctx = document.getElementById(canvasId)
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
var chartData = {
|
|
//labels: time,
|
|
datasets: data.datasets
|
|
};
|
|
|
|
var chartOptions = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
yAxes: [
|
|
{
|
|
id: 'yAxis',
|
|
type: 'linear',
|
|
position: 'left',
|
|
}
|
|
],
|
|
|
|
xAxes: [{
|
|
ticks: {
|
|
min: parseInt(data.range.from),
|
|
max: parseInt(data.range.to),
|
|
beginAtZero: false,
|
|
stepSize: (data.range.to - data.range.from) / 10,
|
|
userCallback: function (label, index, labels) {
|
|
var d = new Date(parseInt(label) * 1000);
|
|
var datestr = "";
|
|
|
|
var to = data.range.to
|
|
var from = data.range.from
|
|
|
|
if(to === ""){
|
|
to = new Date().getTime()
|
|
}
|
|
|
|
var range = data.range.to - data.range.from
|
|
|
|
if(range <= 60 * 60 * 24){
|
|
// covers one day or less
|
|
datestr = ("0" + d.getHours()).slice(-2) + ":" +
|
|
("0" + d.getMinutes()).slice(-2) + ":" +
|
|
("0" + d.getSeconds()).slice(-2)
|
|
}
|
|
else if(range <= 60 * 60 * 24 * 31){
|
|
// covers one month or less
|
|
|
|
datestr = ("0" + d.getDate()).slice(-2) + "-" +
|
|
("0"+(d.getMonth()+1)).slice(-2) + " " +
|
|
("0" + d.getHours()).slice(-2) + ":" +
|
|
("0" + d.getMinutes()).slice(-2) + ":" +
|
|
("0" + d.getSeconds()).slice(-2)
|
|
}
|
|
else {
|
|
// covers more than a month
|
|
|
|
datestr =
|
|
d.getFullYear() + "-" +
|
|
("0" + d.getDate()).slice(-2) + "-" +
|
|
("0"+(d.getMonth()+1)).slice(-2)
|
|
}
|
|
|
|
|
|
return datestr;
|
|
}
|
|
},
|
|
|
|
scaleLabel: {
|
|
display: true,
|
|
labelString: 'time'
|
|
}
|
|
}],
|
|
},
|
|
|
|
tooltips: {
|
|
callbacks: {
|
|
label: function (tooltipItem, data) {
|
|
|
|
var label = [data.datasets[tooltipItem.datasetIndex].label || ''];
|
|
|
|
if (label) {
|
|
label[0] += ': ';
|
|
}
|
|
|
|
label[0] += Math.round(tooltipItem.yLabel * 100) / 100;
|
|
|
|
var time = Math.round(tooltipItem.xLabel * 100) / 100;
|
|
|
|
var d = new Date(time * 1000);
|
|
var datestr =
|
|
d.getFullYear() + "-" +
|
|
("0" + d.getDate()).slice(-2) + "-" +
|
|
("0"+(d.getMonth()+1)).slice(-2) + " " +
|
|
("0" + d.getHours()).slice(-2) + ":" +
|
|
("0" + d.getMinutes()).slice(-2) + ":" +
|
|
("0" + d.getSeconds()).slice(-2)
|
|
;
|
|
|
|
label.push("Time: " + datestr);
|
|
|
|
return label;
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
var myChart = new Chart(ctx, {
|
|
type: 'scatter',
|
|
data: chartData,
|
|
options: chartOptions
|
|
})
|
|
}
|
|
|
|
setPageTitle(title) {
|
|
document.getElementById("titleH1").innerHTML = title
|
|
}
|
|
|
|
findGetParameter(parameterName) {
|
|
var result = null,
|
|
tmp = [];
|
|
location.search
|
|
.substr(1)
|
|
.split("&")
|
|
.forEach(function (item) {
|
|
tmp = item.split("=");
|
|
if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
buildUrl(params) {
|
|
var basepath = window.location.href.split("?")[0]
|
|
|
|
basepath += "?"
|
|
|
|
for (var param in params) {
|
|
basepath += param + "=" + params[param] + "&"
|
|
}
|
|
|
|
return basepath
|
|
}
|
|
|
|
whichTransitionEvent() {
|
|
var t;
|
|
var el = document.createElement('fakeelement');
|
|
var transitions = {
|
|
'transition': 'transitionend',
|
|
'OTransition': 'oTransitionEnd',
|
|
'MozTransition': 'transitionend',
|
|
'WebkitTransition': 'webkitTransitionEnd'
|
|
}
|
|
|
|
for (t in transitions) {
|
|
if (el.style[t] !== undefined) {
|
|
return transitions[t];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
feather.replace()
|
|
dashboard = new BlueWeatherDashboard() |