Live Chart woks

This commit is contained in:
rxf
2023-03-20 20:01:26 +01:00
parent 586b2641e2
commit be8d364116
10 changed files with 663 additions and 93 deletions
+265
View File
@@ -0,0 +1,265 @@
// Show the live data chart
import {logit, logerror} from './logit.js'
import * as utils from './chart_utilities.js'
// ******************************************************************
// PlotDayLive_Noise
// ******************************************************************
let noise_ymin = 30; // lowest value on y-axis for noise
let noise_ymax = 120; // highest value on y-axis for noise
let activeTab = 0; // active TAB number
let peaklim = 70; // threshold for peak count
export const showLive = (params) => {
PlotDayLive_Noise(37833)
}
export const PlotDayLive_Noise = async (sid) => {
let series1 = []
let series2 = []
let series3 = []
// ave the current (actual) values here
let aktVal = {}
// read the data from the server
const url = `http://localhost:3004/getsensordata?type=noise&sensorid=${sid}`
let ret = await fetch(url)
.catch(e => {
logerror(e)
});
let sensors = await ret.json()
let lastidx = sensors.count-1
// Put values into the arrays
let cnt = 0
sensors.values.forEach((x) => {
let dat = new Date(x.datetime).getTime()
series1.push([dat, x.noise_LAeq])
series2.push([dat, x.noise_LA_max])
series3.push([dat, x.noise_LA_min]) // put data and value into series array
})
if (sensors.values.length != 0) {
// Aktuelle Werte speichern
aktVal['LAeq'] = sensors.values[lastidx].noise_LAeq
aktVal['LAMax'] = sensors.values[lastidx].noise_LA_max
aktVal['LAMin'] = sensors.values[lastidx].noise_LA_min
// InfoTafel füllen
let infoTafel =
'<table class="infoTafel"><tr >' +
'<th colspan="3">Aktuelle Werte</th>' +
'</tr><tr>' +
'<td>LAeq</td><td>' + (aktVal.LAeq).toFixed(1) + '</td><td>dbA</td>'
infoTafel +=
'</tr><tr>' +
'<td>LAmin</td><td>' + (aktVal.LAMin).toFixed(0) + '</td><td>dbA</td>'
infoTafel +=
'</tr><tr>' +
'<td>LAmax</td><td>' + (aktVal.LAMax).toFixed(0) + '</td><td>dbA</td>'
infoTafel +=
'</tr></table>' +
'</div>'
}
let txtMeldung = false
// Plot-Options
let options = utils.createGlobObtions()
let series_LAeq = {
name: 'LAeq',
data: series1,
color: utils.colors.eq,
yAxis: 0,
zIndex: 1,
marker: {
enabled: false,
symbol: 'square',
},
visible: true,
fillColor: {
linearGradient: [0, 600, 0, 0],
stops: [
[0, 'rgb(238,223,236)'],
[1, 'rgb(238,223,236)']
]
},
}
let series_LAMax = {
name: 'LAmax',
data: series2,
color: utils.colors.max,
yAxis: 0,
zIndex: 0,
marker: {
enabled: false,
symbol: 'square',
},
visible: true,
fillColor: {
linearGradient: [0, 600, 0, 0],
stops: [
[0, 'rgb(255,233,236)'],
[1, 'rgb(255,233,236)']
]
},
// fillOpacity: 0.2,
}
let series_LAMin = {
name: 'LAmin',
data: series3,
color: utils.colors.min,
yAxis: 0,
zIndex: 6,
marker: {
enabled: false,
symbol: 'square',
},
visible: true,
fillColor: {
linearGradient: [0, 600, 0, 0],
stops: [
[0, 'rgb(219,216,217)'],
[1, 'rgb(219,216,217)']
]
},
}
let yAxis_LAeq = { // 1
title: {
text: 'dbA',
style: {
color: 'black'
}
},
max: noise_ymax,
min: noise_ymin,
// opposite: true,
tickAmount: 10,
useHTML: true,
}
options.series = []
options.yAxis = []
options.series[0] = series_LAeq
options.series[1] = series_LAMin
options.title.text = 'Noise level for one day'
options.subtitle.useHTML = true
options.subtitle.text = '2.5min log. average values <br /> &nbsp'
options.series[2] = series_LAMax
// options.yAxis[2] = yAxis_LAMin
options.yAxis[0] = yAxis_LAeq
// options.yAxis[1] = yAxis_LAMax
options.chart.zoomType = 'x'
options.chart.type = 'area'
options.chart.resetZoomButton= {
position: {
align: 'left',
verticalAlign: 'bottom',
x: 20,
y: -50,
},
relativeTo: 'chart'
}
let noDataTafel = '<div class="errTafel">' +
'Für heute liegen leider keine Daten vor!<br /> Bitte den Sensor überprüfen!\' <br />' +
'</div>'
let navy = 20
let navbreit = 55
let navtxt = ['-24h', '-12h', 'live', '+12h', '+24h']
let navtime = [-24, -12, 0, 12, 24]
let chr = Highcharts.chart('dlive', options, function (chart) {
utils.addSensorID2chart(chart, sensors.props, document.getElementById('dlive').offsetWidth)
// let breit = $(window).width()
// let navx = breit-350
// chart.renderer.label(
// infoTafel,
// breit - 250,
// 78, 'rect', 0, 0, true)
// .css({
// fontSize: '10pt',
// color: 'green'
// })
// .attr({
// zIndex: 5,
// }).add()
// for (let i = 0; i < navtxt.length; i++) {
// renderPfeil(i, chart, navx + (i * navbreit), navy, navtxt[i], navtime[i])
// }
if (txtMeldung == true) {
let labeText = ''
let errtext = chart.renderer.label(
noDataTafel,
250,
120, 'rect', 0, 0, true)
.css({
fontSize: '18pt',
color: 'red'
})
.attr({
zIndex: 1000,
stroke: 'black',
'stroke-width': 2,
fill: 'white',
padding: 10,
}).add()
}
})
}
let butOpts = [
{fill: 'lightblue', r: 2},
{fill: 'blue', r: 2, style: {color: 'white'}},
{fill: 'lightblue', r: 2},
{fill: 'lightblue', r: 2}
]
function renderPfeil(n, chart, x, y, txt, time) {
chart.renderer.button(txt, x, y, null, butOpts[0], butOpts[1], butOpts[2], butOpts[3])
.attr({
id: 'button' + n,
zIndex: 3,
width: 30,
})
.on('click', function () {
prevHour(time)
})
.add()
}
function prevHour(hours) {
console.log("Zurück um ", hours, "Stunden")
let start
if (startDay == "") {
start = moment()
start.subtract(24, 'h')
} else {
start = moment(startDay)
}
let mrk = moment()
mrk.subtract(24, 'h')
let startDay = ""
if (hours < 0) {
start.subtract(Math.abs(hours), 'h')
startDay = start.format("YYYY-MM-DDTHH:mm:ssZ")
} else if (hours > 0) {
start.add(hours, 'h')
if (!start.isAfter(mrk)) {
startDay = start.format("YYYY-MM-DDTHH:mm:ssZ")
}
}
doPlot('live', startDay)
}
+194
View File
@@ -0,0 +1,194 @@
// Utility routine for plotting the data
import {DateTime} from './luxon.min.js'
export const colors = {'eq': '#0000FF', 'max': '#FF0000', 'min': '#008000', 'peaks': '#DAA520'};
// Defaults für die Mittelwertbildungen
const refreshRate = 5; // Grafik so oft auffrischen (in Minuten)
let txtMeldung = false; // falls keine Daten da sind, Text melden
const avgTime = 30; // defaul average time für particulate matter
let doUpdate = true; // update every 5 min
let optSidsArray = []; // Arrray der letzten 5 Einträge
let nbrofdaysforavg = 8; // Stundenmittel Anzahl Tagte (default = 5)
let nbrofdaysfordaynight = 30; // use 30 days or day/night graphic
let stucols = 1; // Anzeige nur Balken (StundenMittel)
export function createGlobObtions() {
var globObject = {};
// Options, die für alle Plots identisch sind
globObject = {
chart: {
height: 600,
// width: 1000;
spacingRight: 20,
spacingLeft: 20,
spacingTop: 25,
backgroundColor: {
linearGradient: [0, 400, 0, 0],
stops: [
[0, '#eee'],//[0, '#ACD0AA'], //[0, '#A18D99'], // [0, '#886A8B'], // [0, '#F2D0B5'],
[1, '#fff']
]
},
type: 'line',
borderWidth: '2',
events: {
selection: function (event) {
if (event.xAxis) {
doUpdate = false;
} else {
doUpdate = true;
}
}
}
},
title: {
// text: 'Feinstaub über 1 Tag',
align: 'left',
style: {'fontSize': '25px'},
useHTML: true,
},
subtitle: {
text: 'Gemessene Werte und ' + avgTime + 'min-gleitende Mittelwerte',
align: 'left',
},
tooltip: {
valueDecimals: 1,
backgroundColor: 0,
borderWidth: 0,
borderRadius: 0,
useHTML: true,
formatter: function () {
return '<div style="border: 2px solid ' + this.point.color + '; padding: 3px;">' +
DateTime.fromMillis(this.x, {zone: 'utc'}).toFormat('dd.LL HH:mm:ss') + '<br />' +
'<span style="color: ' + this.point.color + '">&#9679;&nbsp;</span>' +
this.series.name + ':&nbsp; <b>' +
Highcharts.numberFormat(this.y, 1) +
'</b></div>';
}
},
xAxis: {
type: 'datetime',
title: {
text: 'date/time',
},
gridLineWidth: 2,
labels: {
formatter: function () {
let v = this.axis.defaultLabelFormatter.call(this);
if (v.indexOf(':') == -1) {
return '<span style="font-weight:bold;color:red">' + v + '<span>';
} else {
return v;
}
}
},
},
legend: {
enabled: true,
layout: 'horizontal',
// verticalAlign: 'top',
borderWidth: 1,
align: 'center',
},
plotOptions: {
series: {
animation: false,
turboThreshold: 0,
marker: {
enabled: false,
},
},
}
};
return globObject;
}
function calcWeekends(data, isyear) {
var weekend = [];
var oldDay = 8;
for (var i = 0; i < data.length; i++) {
var mom = moment(data[i].date);
if (isyear) {
mom = moment(data[i]._id)
}
var day = mom.day();
var st = mom.startOf('day');
if (day != oldDay) {
if (day == 6) {
weekend.push({
color: 'rgba(169,235,158,0.4)',
from: st.valueOf(),
to: st.add(1, 'days').valueOf(),
zIndex: 0
})
} else if (day == 0) {
weekend.push({
color: 'rgba(169,235,158,0.4)',
from: st.valueOf(),
to: st.add(1, 'days').valueOf(),
zIndex: 0
})
}
oldDay = day;
}
}
return weekend;
}
function calcDays(data, isyear) {
var days = [];
if (data.length == 0) {
return days
}
var oldday = moment(data[0].date).day();
if (isyear) {
oldday = moment(data[0]._id).day();
}
for (var i = 0; i < data.length; i++) {
var m = moment(data[i].date);
if (isyear) {
m = moment(data[i]._id);
}
var tag = m.day()
if (tag != oldday) {
m.startOf('day');
days.push({color: 'lightgray', value: m.valueOf(), width: 1, zIndex: 2});
oldday = tag;
}
}
return days;
};
function getIndoor(sensor) {
let p = new Promise(function (resolve, reject) {
let url = '/api/getdata?data=props&sensorid=' + sensor;
$.get(url, (data, err) => {
if (err != 'success') {
resolve(false)
} else {
resolve(data.values[0].indoor == 1)
}
})
})
return p
}
export async function addSensorID2chart(chart, props, width) {
console.log('widht: ',width);
let indoor = props.location[0].indoor
console.log('indoor: ',indoor)
let sens = chart.renderer.label(
`Sensor: ${props._id} ${indoor ? ' (indoor)' : ''}`,
width / 2 - 100, 65,
'text', 0, 0, true)
.css({
fontSize: '12pt',
'font-weight': 'bold',
})
.attr({
zIndex: 5,
}).add();
}
+35 -5
View File
@@ -3,8 +3,11 @@
import * as map from './map.js'
import * as dt from './datetime.js'
import {logit} from './logit.js'
import * as chart_live from './chart_live.js'
(async () => {
(async function () {
// global constants
const Stuttgart = [48.779, 9.16]
const MAXROWS_IN_TABLE = 1000
@@ -25,21 +28,49 @@ import * as dt from './datetime.js'
}
// END global variables
const tabtable = [
{id: 'kartentab', func: map.showMap},
{id: 'livetab', func: chart_live.showLive},
// {id: 'houravgtab', func: chart_houravg.showHouravg},
// {id: 'dayavgtab', func: chart_dayavg.showDayavg},
// {id: 'daynighttab', func: chart_daynight.showDaynight},
// {id: 'ldentab', func: chart_lden.showLden},
]
async function main() {
// initialise tabs
let triggerTabList = [].slice.call(document.querySelectorAll('#myTab a'))
let triggerTabList = [].slice.call(document.querySelectorAll('.nav-link'))
triggerTabList.forEach(function (triggerEl) {
let tabTrigger = new bootstrap.Tab(triggerEl)
triggerEl.addEventListener('click', function (event) {
event.preventDefault()
tabTrigger.show()
for(let x of tabtable) {
if (x.id === event.currentTarget.id) {
x.func(params)
}
}
logit(`tab chaned to ${event.currentTarget.id}`)
})
})
// show dat/time at start
// var triggerEl = document.querySelector('#tablist a[href="#profile"]')
// bootstrap.Tab.getInstance(triggerEl).show() // Select tab by name
//
// var triggerFirstTabEl = document.querySelector('#tablist li:first-child a')
// bootstrap.Tab.getInstance(triggerFirstTabEl).show() // Select first tab
// let nl = document.querySelectorAll('.nav-link')
// for (let x of nl) {
// x.addEventListener('click', (event) => {
// event.preventDefault()
// console.log(event)
// })
// }
dt.showDate(true)
// and show date/time every minute
setInterval(() => dt.showDate(false), 1000)
@@ -49,7 +80,6 @@ import * as dt from './datetime.js'
// show the map
map.showMap(params)
await map.buildMarkers()
}
main().catch(console.error)
+12
View File
@@ -0,0 +1,12 @@
import { DateTime } from './luxon.min.js'
export function logit(str) {
let s = `${DateTime.now().toISO()} => ${str}`;
console.log(s);
}
export function logerror(str) {
let s = `${DateTime.utc().toISO()} => *** ERROR *** ${str}`;
console.log(s);
}
+44 -32
View File
@@ -9,7 +9,7 @@ let popuptext = ''
const colorscale = ['#d53e4f', '#fc8d59', '#fee08b', '#e6f598', '#99d594', '#3288bd', '#808080'];
const dba = [100, 80, 60, 40, 20, 0, -999];
export const showMap = (params) => {
export const showMap = async (params) => {
map = L.map('map',{ scrollWheelZoom: false}).setView(params.center, parseInt(params.zoom))
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
@@ -37,7 +37,8 @@ export const showMap = (params) => {
return div
};
legend.addTo(map)
let lastdate = await buildMarkers()
showLastDate(lastdate);
}
@@ -58,7 +59,7 @@ function calcPolygon(bounds) {
});
;
}
/*
async function plotMap(cid, poly) {
// if sensor nbr is give, find coordinates, else use Stuttgart center
debug_log('plotMap()');
@@ -132,7 +133,7 @@ async function plotMap(cid, poly) {
}
}
*/
// With all Markers in cluster (markers) calculate the median
// of the values. With this median fetch the color and return it.
@@ -151,7 +152,7 @@ function getMedian(markers) {
}
return 0;
});
console.log(markers);
// console.log(markers);
let i = 0; // now find the 'offlines' (value == -1)
for (i = 0; i < markers.length; i++) {
if (markers[i].options.value > 0) {
@@ -165,7 +166,7 @@ function getMedian(markers) {
return getColor(markers[(Math.floor(lang / 2))].options.value); // median is in the middle
} else { // evaen ->
lang = lang / 2; // median is mean of both middle values
console.log(lang);
// console.log(lang);
let wert = (markers[lang - 1].options.value +
markers[lang].options.value) / 2;
return getColor(wert);
@@ -250,8 +251,7 @@ export async function buildMarkers() {
markers.addLayer(marker);
}
map.addLayer(markers);
// ToDo showLastDate(sensors.lastDate);
return sensors.lastdate
}
async function onMarkerClick(e, click) {
@@ -299,36 +299,48 @@ async function setCenter(adr) {
console.log(data);
}
/*
// Show the last date below tha map grafics
async function showLastDate(dt) {
const url = `http://localhost:3004/getproperties?type=noise&box=${bounds.toBBoxString()}`
// Show the last date below tha map grafics
async function showLastDate(ld) {
// const url = `http://localhost:3004/getproperties?type=noise&box=${bounds.toBBoxString()}`
//
// let ret = await fetch(url)
// .catch(e => {
// console.log(e)
// });
// let sensors = await ret.json()
//
// let url = '/api/getdata';
// let ld = moment(dt);
// let last = ld.subtract(1,'h')
// let erg;
// let allsens
// try {
// erg = await $.getJSON(url + `?data=props&last=1900-01-01`);
// allsens = erg.count
// erg = await $.getJSON(url + `?data=props&last=${last.format('YYYY-MM-DD HH:mm')}`);
// if ((erg != undefined) && (erg.values[0].error != undefined)) {
// showError(3,erg.values[0].error,aktsensorid);
// return;
// }
// }
// catch(e) {
// console.log(e)
// }
// fetch all sensors
const url = `http://localhost:3004/getmapdata?type=noise`
let ret = await fetch(url)
.catch(e => {
console.log(e)
});
let sensors = await ret.json()
let url = '/api/getdata';
let ld = moment(dt);
let last = ld.subtract(1,'h')
let erg;
let allsens
try {
erg = await $.getJSON(url + `?data=props&last=1900-01-01`);
allsens = erg.count
erg = await $.getJSON(url + `?data=props&last=${last.format('YYYY-MM-DD HH:mm')}`);
if ((erg != undefined) && (erg.values[0].error != undefined)) {
showError(3,erg.values[0].error,aktsensorid);
return;
let actsensors = 0
for (let x of sensors.values) {
if (x.value >= 0) {
actsensors++
}
}
catch(e) {
console.log(e)
}
$('#mapdate').html('Werte von ' + ld.format('YYYY-MM-DD HH:mm'));
$('#actsensors').html(`${erg.count} aktive Sensoren (angemeldet ${allsens})`)
document.querySelector('#mapdate').innerText = 'Values from ' + dt.formatISODate(ld).slice(0,-3)
document.querySelector('#actsensors').innerText = `Active sensors: ${actsensors} (registered: ${sensors.count})`
// $('#actsensors').html(`${erg.count} aktive Sensoren (angemeldet ${allsens})`)
}
*/