/* 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, range: { from: "", to: "" } } 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) })); this.setTimeRange(this.params.from, this.params.to, false, false) // 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 if (this.initDone) { document.getElementById("navbarPage" + page + "Link").classList.add("active") } 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) // 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 = "SensorMinimumMaximumAverage" 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 = undefined var max = undefined var avg = 0 for (s = 0; s < locationData["measvalues"].length; s++) { var thisValue = locationData["measvalues"][s] if (parseInt(thisValue['sensorid']) === parseInt(sensor.id)) { vals.push(thisValue) if (parseFloat(thisValue.measvalue) < min || min === undefined) { min = parseFloat(thisValue.measvalue) } if (parseFloat(thisValue.measvalue) > max || max === undefined) { max = parseFloat(thisValue.measvalue) } } } for (s = 0; s < vals.length; s++) { avg += vals[s].measvalue / vals.length } 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 = "" + sensor.sensorname + " (" + valueType.valuetype + ", " + valueType.valueunit + ")" + "" + min + "" + max + "" + (Math.round((avg) * 100) / 100) + "" 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 thisWidget.classList.add("text-center") 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 = '

' + latestVal.measvalue + valueType.valueunit + '

' //thisBody.innerHTML = 'Fit Meeeeeeeeeeee' break } firstRow.appendChild(thisWidget) } mainContent.innerHTML = ""; mainContent.appendChild(firstRow) mainContent.appendChild(tableContainer) } else if (page !== -1) { mainContent.innerHTML = "" dashboard.loadDiagram(mainContent, locationData, page) } loader.style = "position: absolute; left: 50%; opacity: 0;" mainContent.classList.add("opacity-animated"); mainContent.style = "opacity: 1;" flexFont(); }) } 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 = "", offset = true, autoReload = true) { // offset: if true the gien times will be substracted from the current time to get the target time this.blueweather.log("changing time range; form: " + from + "; to: " + to + "; (now is: " + new Date().getTime() + ")", 3) if (offset) { if (from !== "") { from = new Date().getTime() / 1000 - from } if (to !== "") { to = new Date().getTime() / 1000 - parseInt(to) } } else { if (from !== "") { from = parseInt(from) } if (to !== "") { to = parseInt(to) } } var tmpFrom = from var tmpTo = to if (tmpFrom === "") { tmpFrom = Math.round(new Date().getTime() / 1000 - 60 * 60 * 24) } if (tmpTo === "") { tmpTo = Math.round(new Date().getTime() / 1000) } var dateFromString = this.getDateTimeString(tmpFrom, tmpTo - tmpFrom) var dateToString = this.getDateTimeString(tmpTo, tmpTo - tmpFrom) dropdownMenuButton.innerHTML = " " + dateFromString + " - " + dateToString feather.replace() if (this.initDone && autoReload) { this.updateParams({ range: { to: to, from: from } }) } } loadSensors(locationData) { var sensorsList = document.getElementById("sensorsList") sensorsList.innerHTML = "" for (var i = 0; i < locationData.sensors.length; i++) { var sensor = locationData.sensors[i] console.log("parsing sensor ") var valueType; for (var v = 0; v < locationData['valuetypes'].length; v++) { var thisValType = locationData['valuetypes'][v] if (parseInt(thisValType.id) === parseInt(sensor.valuetypeid)) { valueType = thisValType; break; } } var currentHTML = sensorsList.innerHTML sensorsList.innerHTML = currentHTML + "
  • " + sensor["sensorname"] + "
  • " } feather.replace() } loadDiagram(parentId, locationData, sensorId) { if (typeof (parentId) === "string") { var mainContent = document.getElementById(parentId) } else { var mainContent = 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 = "" 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 }) } getDateTimeString(unixTime, range = 0) { if (Math.abs(unixTime - new Date().getTime() / 1000) <= 60) { return "now" } var d = new Date(parseInt(unixTime) * 1000); var datestr = "" 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) } else { // covers more than a month datestr = d.getFullYear() + "-" + ("0" + d.getDate()).slice(-2) + "-" + ("0" + (d.getMonth() + 1)).slice(-2) } return datestr } 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]; } } } } flexFont = function () { var divs = document.getElementsByClassName("flexFont"); for (var i = 0; i < divs.length; i++) { var relFontsize = divs[i].offsetWidth * 0.2; divs[i].style.fontSize = relFontsize + 'px'; } }; flexFont(); window.onload = function (event) { flexFont(); }; window.onresize = function (event) { flexFont(); }; feather.replace() dashboard = new BlueWeatherDashboard()