Files
seweb/client/jsFiles/SEAWebClientGraph.js
2020-12-10 16:58:59 +01:00

1222 lines
40 KiB
JavaScript

// Graph
function Timer(){
let start = window.performance.now();
return function(x = "timer"){
console.log(x, window.performance.now()-start);
}
}
window.addEventListener('load', function(){
var urlParams = new URLSearchParams(window.location.search);
if(urlParams.has('white')){
document.body.classList.add('white');
}
else if(urlParams.has('black')){
document.body.classList.add('black');
}
})
function addClass(obj, cls){
if(!obj.classList.contains(cls))
obj.classList.add(cls)
}
function delClass(obj, cls){
if(obj.classList.contains(cls))
obj.classList.remove(cls)
}
function AJAX(addr){
var xhr = new XMLHttpRequest();
if (debugCommunication) console.log('AJAX', addr);
this.sendJSON = function(array, returnIsJSON = true){
xhr.open("POST", addr, true);
xhr.send(JSON.stringify(array));
return new Promise(function(resolve, reject){
xhr.addEventListener('load', function(){
if(this.status == 200){
if(returnIsJSON){
this.responseJSON = JSON.parse(this.responseText);
}
resolve(this);
}
});
});
}
this.getJSON = function(){
xhr.open("GET", addr, true);
xhr.send();
return new Promise(function(resolve, reject){
xhr.addEventListener('load', function(){
if(this.status == 200){
if (debugCommunication) console.log('A RES', JSON.parse(this.responseText));
resolve(JSON.parse(this.responseText));
}
});
});
}
this.get = function(responseType = "text"){
xhr.open("GET", addr, true);
xhr.responseType = responseType;
xhr.send();
return new Promise(function(resolve, reject){
xhr.addEventListener('load', function(){
if(this.status == 200){
resolve(this);
}
});
});
}
return this;
}
function doubleTap(callback){
var timeout;
var lastTap = 0, lastX=NaN, lastY=NaN;
function handler(event) {
var currentTime = new Date().getTime();
var tapLength = currentTime - lastTap;
let touch = event.changedTouches ? event.changedTouches[0] : event,
x = touch.clientX,
y = touch.clientY;
clearTimeout(timeout);
if (tapLength < 500 && tapLength > 0 && Math.abs(lastX-x) < 40 && Math.abs(lastY-y) < 40) {
event.preventDefault();
callback()
} else {
timeout = setTimeout(function() {
clearTimeout(timeout);
}, 500);
}
lastTap = currentTime;
lastX = x;
lastY = y;
}
window.addEventListener('touchend', handler);
return {stop: function(){ window.removeEventListener('touchend', handler) }}
}
function maxAr(array, tmin, tmax){
return Math.max.apply(Math, array.map(function(o) {
if (o.y == null || o.x < tmin || o.x > tmax) return -1e99;
return o.y;
}));
}
function minAr(array, tmin, tmax){
return Math.min.apply(Math, array.map(function(o) {
if (o.y == null || o.x < tmin || o.x > tmax) return 1e99;
return o.y;
}));
}
function strFormat(str, significant_digits) {
if (str == null) return '';
evalue = str.toExponential(significant_digits-1).replace(/0*e/, 'e').replace(/\.e/, 'e').replace("e+", "e");
fvalue = Number.parseFloat(evalue).toString();
if(fvalue.length <= evalue.length)
return fvalue;
else
return evalue;
}
let graphs = (function (){
let dataset_to_graph_map = {},
blocks, liveMode=true, top_vars=[], bottom_vars=[], zoomed =false;
let type = 'linear';
let ngraphs = 4;
let chart_array = new Array(ngraphs);
let graph_array = new Array(ngraphs);
let vars_array = new Array(ngraphs);
let container = document.createElement('div');
container.classList.add("graphs-container");
let currentMinTime = 0;
let currentMaxTime = 0;
for (let i = 0; i < ngraphs; i++) {
let gr = document.createElement('div');
gr.classList.add('graph');
gr.classList.add('graph-' + i);
container.appendChild(gr);
graph_array[i] = gr;
}
function clear(gindex){
let gr = graph_array[gindex];
let ch = chart_array[gindex];
gr.innerHTML = '';
chart_array[gindex] = undefined;
vars_array[gindex] = [];
if (ch) {
for (let key in dataset_to_graph_map) {
if (dataset_to_graph_map[key][0] == gindex) {
delete dataset_to_graph_map[key];
}
}
}
}
function createSelection(gindex){
let el = graph_array[gindex];
clear(gindex);
let selection = document.createElement('div');
selection.classList.add('selection');
for(let block of blocks){
let bel = document.createElement('div');
bel.classList.add('select');
let title = document.createElement('div');
title.classList.add('title');
title.innerHTML = block.tag;
bel.appendChild(title);
let params = document.createElement('div');
params.classList.add('params');
for(let param of block.curves){
let pel = document.createElement('div');
pel.classList.add('param');
pel.innerHTML = param.label;
params.appendChild(pel);
}
bel.appendChild(params);
bel.addEventListener('click', function(){
createGraph(gindex, block)
})
selection.appendChild(bel);
}
el.appendChild(selection);
}
function createGraph(gindex, block){
clear(gindex);
let dict = {}
for (let curve of block.curves) {
vars_array[gindex].push(curve.name);
dict[curve.name] = curve;
}
//let varlist = top_vars.concat(bottom_vars);
varlist = vars_array[gindex];
let el = graph_array[gindex];
AJAX("http://" + hostPort + "/graph?time=" + minTime/1000 + "," + maxTime/1000 + "&variables=" + varlist + "&id=" + clientID).getJSON().then(function(data){
//console.log('Graph', block, data)
let chart = new Graph(gindex, el, "Time", block.unit, block.tag, type);
chart_array[gindex] = chart;
for(let key in data.graph){
if(!vars_array[gindex].includes(key)){
continue;
}
let pdata = [];
for(let e of data.graph[key]){
pdata.push({x: e[0]*1000, y: e[1]});
}
if(pdata.length > 0){
addDataset(gindex, key, [dict[key].label, dict[key].color, pdata, dict[key].continuous])
/*console.log(timeRange);
if(data[data.length-1].x-data[0].x > d && data[data.length-1].x-data[0].x < (30*60+10)*1000){ // Adjust to requested time
d = data[data.length-1].x-data[0].x
max = data[data.length-1].x;
min = data[0].x;
}*/
}
}
chart.setMinMax(minTime,maxTime);
chart.autoScaleIf();
chart.update();
result = AJAX( "http://" + hostPort +
"/updategraph?variables=" + variables() +
"&id=" + clientID).getJSON().then(function(data) {
liveMode = data.live;
console.log('LIVE create', liveMode)
})
//console.log('UPDATE LIVE', result);
})
}
// add dataset to graph with graph_id
function addDataset(gindex, key, dataset){
let g = chart_array[gindex];
dataset_to_graph_map[key] = [gindex, g.addDataset(key, dataset)];
}
function autoScale(chart) {
axis = chart.options.scales.yAxes[0];
tax = chart.options.scales.xAxes[0].ticks;
datasets = chart.data.datasets;
let max = -1e99;
let min = 1e99;
// if there are datasets with values and think lines,
// consider them only. if not, consider all (second pass in the following loop)
let extraMin = min;
let extraMax = max;
for (testwidth = 1; testwidth >= 0; testwidth--) {
for (let i = 0; i < datasets.length; i++){
ds = datasets[i];
if (ds.borderWidth <= testwidth) continue;
let lmax = maxAr(ds.data, tax.min, tax.max);
let lmin = minAr(ds.data, tax.min, tax.max);
if(lmax > max)
max = lmax;
if(lmin < min)
min = lmin;
if (ds.data.length && liveMode) {
lasty = ds.data.slice(-1)[0].y;
console.log('LASTY', lasty);
extraMin = Math.min(extraMin, lasty);
extraMax = Math.max(extraMax, lasty);
}
}
if (min > max) continue; // do a second pass over all curves
break;
}
if (min > max) return;
if (min == max) {
if (min == 0) {
ystep = 1;
} else {
ystep = Math.abs(min * 0.01);
}
min -= ystep;
max += ystep;
} else {
ystep = (max - min) * 0.1;
if (liveMode) {
extraMin = Math.min(min, extraMin - ystep);
extraMax = Math.max(max, extraMax + ystep);
} else {
extraMin = min - ystep * 0.5;
extraMax = max + ystep * 0.5;
}
if (min >= axis.ticks.min && axis.ticks.min >= extraMin &&
max <= axis.ticks.max && axis.ticks.max <= extraMax) {
console.log('NOCHANGE')
return; // do not yet change
}
console.log(min, axis.ticks.min, extraMin)
console.log(max, axis.ticks.max, extraMax)
min = extraMin;
max = extraMax;
}
//console.log('autoScale', min, max, tax.min, tax.max);
axis.min = axis.ticks.min = min;
axis.max = axis.ticks.max = max;
}
function setMinMax(min, max){
currentMaxTime = max;
currentMinTime = min;
for (let ch of chart_array) {
if (ch) ch.setMinMax(min, max);
}
}
/*
function toggleAxesType(){
type = (type === 'linear') ? 'logarithmic' : 'linear';
if(top_chart)
top_chart.setAxesType(type);
if(bottom_chart)
bottom_chart.setAxesType(type);
return type;
}
*/
// responsible for new data being displayed on chart
function newDataHandler(key, data){
if(!(key in dataset_to_graph_map))
return
maxTime = data.x;
let i = dataset_to_graph_map[key];
chart_array[i[0]].pushData(i[1], data)
}
function clickHandler(evt) {
if(evt.target.tagName == "CANVAS"){
for (let ch of chart_array) {
if (ch) ch.clickHandler(evt);
}
}
}
container.addEventListener('click', clickHandler)
function reloadDataFlag(key, data){
if(!(key in dataset_to_graph_map))
return
let i = dataset_to_graph_map[key];
chart_array[i[0]].reloadData(i[1], data);
}
function variables() {
let vardict = {};
for (let vars of vars_array) {
for (let v of vars) {
vardict[v] = 1;
}
}
return Object.keys(vardict);
}
function reloadData(min, max){
min = min/1000;
if(max > now()){
max = 0;
}else{
max = max/1000;
}
AJAX("http://" + hostPort + "/graph?time=" + min + ","+max+"&variables=" + variables() + "&id=" + clientID).getJSON().then(function(data){
for(let key in data.graph){
let pdata = [];
for(let e of data.graph[key]){
//if(e[0] == null || e[1] == null){
// continue;
//}
pdata.push({x: e[0]*1000, y: e[1]});
}
if(pdata.length > 0){
reloadDataFlag(key, pdata);
}
}
// AJAX( "http://" + hostPort + "/updategraph?id=" + clientID).getJSON(); // activate updates
result = AJAX("http://" + hostPort +
"/updategraph?variables=" + variables() +
"&id=" + clientID).getJSON().then(function(data) {
liveMode = data.live;
console.log('LIVE reload', liveMode)
})
update();
});
}
function checkReload(chart){
let xmin = chart.options.scales.xAxes[0].ticks.min,
xmax = chart.options.scales.xAxes[0].ticks.max;
if (xmax < now()-100000) { // was 100000 = 100sec
if (liveMode) console.log('UPDATES OFF?')
//doUpdates = false;
}else{
if (!liveMode) console.log('UPDATES ON?')
//doUpdates = true;
}
if (xmin < minTime || xmax > maxTime || xmax - xmin < 0.5 * (maxTime - minTime)) {
//TODO: the criterium for getting finer resolution data should depend, if better res. is available
// this information has to come from the server
reloadData(xmin, xmax);
minTime = xmin;
maxTime = xmax;
} else {
if (chart.autoScaleFlag) autoScale(chart);
chart.update();
}
}
function zoompan(chart, redrawX = null){
let xmin = chart.options.scales.xAxes[0].ticks.min,
xmax = chart.options.scales.xAxes[0].ticks.max;
setMinMax(xmin,xmax);
//console.log('zoompan', autoScaleFlag);
for (let ch of chart_array) {
ch.autoScaleIf();
if (ch) ch.redraw(redrawX);
}
update();
}
function update(){
for (let ch of chart_array) {
if (ch) ch.update();
}
}
function updateAuto(){
if (liveMode) {
max = now();
if (currentMaxTime && max > currentMaxTime) {
max = currentMaxTime + Math.min(60000, 0.1 * (currentMaxTime - currentMinTime));
setMinMax(currentMinTime, max);
reloadData(currentMinTime, max);
return;
}
}
for (let ch of chart_array) {
if (ch) {
ch.autoScaleIf();
ch.update();
}
}
}
/*
let g_varlist = [];
function getVarlist(blocks){
var varlist = [];
for (var i = 0; i < blocks.length; i++) {
for (var j = 0; j < blocks[i].curves.length; j++) {
varlist.push(blocks[i].curves[j].name);
}
}
return varlist;
}
*/
let startTime, recvTime, minTime, maxTime;
function receivedVars(nblocks){
maxTime = timeRange[1]*1000;
minTime = timeRange[0]*1000;
AJAX("http://" + hostPort + "/gettime?time=-1800,0&id="+ clientID).getJSON().then(function(data){
startTime = data.time[1]*1000;
maxTime = startTime + 60000;
console.log('MAXTIME', maxTime - Date.now());
minTime = data.time[0]*1000;
recvTime = performance.now();
});
// g_varlist = getVarlist(nblocks)
let f = 0;
insertSlide(f, "graphics", "graphics", container);
blocks = nblocks;
//createSelection(true);
//createSelection(false);
for (let i=0; i < ngraphs; i++) {
if (blocks[i])
createGraph(i, blocks[i]);
else
createSelection(i);
}
container.parentNode.querySelector('.panel').classList.add('graphics');
if(isTouchDevice()){
let currentZoomSwipe = true;
let zoomSwipe = document.createElement('div');
function innerZoomSwipe(){
if(currentZoomSwipe){
zoomSwipe.innerHTML = "panning";
swiper[f].params.noSwipingClass = "swiper-slide-main";
for (let ch of chart_array) {
if (ch) ch.setPanOnOff(true);
}
}else{
zoomSwipe.innerHTML = "swiping";
swiper[f].params.noSwipingClass = "abc";
for (let ch of chart_array) {
if (ch) ch.setPanOnOff(false);
}
}
}
innerZoomSwipe();
doubleTap(function(){
currentZoomSwipe = !currentZoomSwipe;
innerZoomSwipe();
});
container.parentNode.querySelector('.panel span').appendChild(zoomSwipe);
}else{
let currentMode = 0,
modes = ['xy','x','y'],
zoomMode = document.createElement('div');
function setInnerZoomMode(){
zoomMode.innerHTML = "Zoom mode: ";
for(let k = 0; k < modes.length; k++){
if(k==currentMode){
zoomMode.innerHTML += "<strong>"+modes[k] + "</strong> ";
}else
zoomMode.innerHTML += modes[k] + " ";
}
}
setInnerZoomMode();
function toggleZoomMode(){
currentMode++;
if(currentMode >= modes.length){
currentMode = 0;
}
setInnerZoomMode();
for (let ch of chart_array) {
if (ch) ch.setZoomMode(modes[currentMode]);
}
}
zoomMode.addEventListener('click', toggleZoomMode);
container.parentNode.querySelector('.panel span').appendChild(zoomMode);
}
}
function now(){
return startTime + (performance.now()-recvTime);
}
function getBlocks(){
return blocks;
}
return {
addDataset: addDataset,
//toggleAxesType: toggleAxesType,
newDataHandler: newDataHandler,
zoompan: zoompan,
receivedVars: receivedVars,
createSelection: createSelection,
doUpdates: function(){return liveMode},
update: update,
updateAuto: updateAuto,
now: now,
zoomed: zoomed,
checkReload: checkReload,
getBlocks: getBlocks,
createGraph: createGraph,
autoScale: autoScale,
}
})();
function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){
let chart;
let maxspan = 10 * 864e5;
let minspan = 120000;
let parent = document.createElement("div");
parent.classList.add("chart-container");
container.appendChild(parent);
let canvas = document.createElement("canvas");
canvas.setAttribute("width", "500");
canvas.setAttribute("height", "500");
canvas.style.width = "500px";
canvas.style.height = "500px";
parent.appendChild(canvas);
let ctx = canvas.getContext("2d");
chart = new Chart(ctx, {
type: 'scatter',
options: {
responsive: true,
maintainAspectRatio: false,
animation:{duration:0},
scales: {
yAxes: [{ticks: {
beginAtZero: false,
mirror: true,
padding: -10,
//workaround for proper number format
callback: function(label, index, labels) {
if(index == 0 || index == labels.length-1)
return "";
return strFormat(label, 9);
}
},
gridLines:{drawTicks:false},
scaleLabel: false, // {display: true, labelString: y_label},
type:scaleType,
afterBuildTicks: function(axis, ticks) {
if (scaleType == "logarithmic" && ticks.length <= 4) {
y1 = ticks[0];
y0 = ticks.slice(-1)[0];
span = y1 - y0;
step = Math.abs(span * 0.3).toExponential(0);
if (step[0] > '5') {
step = '5' + step.substr(1);
} else if (step[0] > '2') {
step = '2' + step.substr(1);
}
step = Number.parseFloat(step);
ticks = [y1];
for (let yt = Math.ceil(y1 / step) * step; yt > y0; yt -= step) {
ticks.push(yt);
}
ticks.push(y0);
}
return ticks
},
}],
xAxes: [{
scaleLabel: false,//{display: true, labelString: x_label},
type: 'time',
time: {
displayFormats: {'millisecond': 'HH:mm:ss.SSS', 'second': 'HH:mm:ss', 'minute': 'HH:mm','hour': 'dd HH:mm', 'day': 'dd MMM DD', 'week': 'MMM DD', 'month': 'MMM DD'},
},
ticks: { padding: -20,
callback: function(label, index, labels) {
let l = labels.length - 1;
if (index == 0 || index == l) return "";
if (index == 1 || index == l - 1) {
// skip first and / or last label, if too close to the end
let minstep = 0.05 * (labels[l].value - labels[0].value);
if (index == 1) {
if (labels[1].value - labels[0].value < minstep) return "";
} else {
if (labels[l].value - labels[l-1].value < minstep) return "";
}
}
hourofday = /\S+ (\d+:00)/.exec(label);
if (hourofday && hourofday[1] != '00:00') {
return hourofday[1];
}
return label;
}
},
afterBuildTicks: function(axis, ticks) {
if (!ticks || ticks.length <= 2) return ticks;
first = ticks[0].value;
step = ticks[1].value - first;
offset = (first - axis._adapter.startOf(first, 'day')) % step;
let start = 0;
if (ticks[0].value - offset < axis.min) start = 1;
let v = axis.min;
result = [{value: v, major: false}];
for (tick of ticks.slice(start)) {
v = tick.value - offset;
result.push({value: v, major: false});
}
v += step;
if (v < axis.max) result.push({value:v, major: false});
result.push({value: axis.max, major: false});
return result;
},
gridLines:{drawTicks:false},
}],
},
tooltips: false,
legend: false,
pan: {
enabled: true,
mode: "xy",
speed: 10,
threshold: 10,
onPan: function({chart}) { graphs.zoomed = true; graphs.zoompan(chart);},
onPanComplete: function({chart}){graphs.checkReload(chart);redraw()}
},
zoom: {
enabled: true,
drag: false,
mode: "xy",
speed: 0.1,
sensitivity: 1,
onZoom: function({chart}) { graphs.zoomed = true; graphs.zoompan(chart);},
onZoomComplete: function({chart}){graphs.checkReload(chart);redraw()},
}
}
});
let autoScaleFlag = true;
//console.log('create legend')
let legend = document.createElement('div');
legend.classList.add('legend');
let legendels = {};
//graphSwiper.appendSlide(parent);
let controls = document.createElement('div');
controls.classList.add('controls');
legend.appendChild(controls);
function addControl(inner, callback){
let c = document.createElement('div');
c.classList.add('control');
c.classList.add('vcontrol');
c.innerHTML = inner;
c.addEventListener('click', function(e){
if(!legendmoving)
callback(e);
})
controls.appendChild(c);
return c;
}
/*changecontrol = addControl("Change Dataset", function(){
graphs.createSelection(gindex);
});*/
let changecontrol = document.createElement('div');
controls.append(changecontrol);
let blocks = graphs.getBlocks();
for (let i = 0; i < blocks.length; i++) {
let block = blocks[i];
let c = document.createElement('div');
c.classList.add('control');
c.classList.add('subcontrol');
c.innerHTML = block.tag;
if (tag == block.tag) {
c.style.color = "#000000";
}
c.addEventListener('click', function(e){
if(!legendmoving) {}
graphs.createGraph(gindex, block);
//console.log('BLOCK', block)
})
changecontrol.appendChild(c);
spacer = document.createElement('div');
spacer.classList.add('subspacer');
changecontrol.appendChild(spacer);
}
addControl("Hide legend", function(){
legend.style.display = 'none';
redrawX = null;
/*for(let el of legendels){
el.innerHTML = "";
}*/
});
/*let update_max = null;
addControl("Reset Zoom/Pan", function(){
graphs.zoomed = false;
if(update_max !== null){
chart.options.scales.xAxes[0].ticks.max = update_max;
update_max = null;
}
chart.resetZoom();
graphs.zoompan(chart);
});*/
let autoScaleRow = addControl("Autoscale Y <strong>on</strong> off", function(){
toggleAutoScale();
});
addControl("Go to now", function(){
let length = chart.options.scales.xAxes[0].ticks.max - chart.options.scales.xAxes[0].ticks.min;
chart.options.scales.xAxes[0].ticks.max = graphs.now();
chart.options.scales.xAxes[0].ticks.min = graphs.now()-length;
graphs.zoomed= false;
graphs.zoompan(chart, canvas.clientWidth);
graphs.checkReload(chart);
legend.style.left = "0px";
});
let linlog = addControl("<strong>Lin</strong> Log", function(e){
//graphs.toggleAxesType();
toggleAxesType();
});
if(scaleType !== "linear"){
linlog.innerHTML = "Lin <strong>Log</strong>";
}
let startX=0,startY=0, startElX=0, startElY=0,legendmoving=false;
function legendmousemove(e){
if (Math.abs(e.pageX-startX) + Math.abs(e.pageY-startY) < 4) return;
legendmoving=true;
let X = startElX + (e.pageX-startX), Y = startElY + (e.pageY-startY);
if(X > 0 && X+legend.getBoundingClientRect().width < parent.getBoundingClientRect().width){
legend.style.left = X + "px";
}else if(X > 0){
legend.style.left = (parent.getBoundingClientRect().width-legend.getBoundingClientRect().width)+"px";
}else{
legend.style.left = "0px";
}
if(Y>0 && Y+legend.getBoundingClientRect().height< parent.getBoundingClientRect().height){
legend.style.top = Y + "px";
}else if(Y>0){
legend.style.top = (parent.getBoundingClientRect().height-legend.getBoundingClientRect().height) + "px";
}else{
legend.style.top = "0px";
}
}
function legendmouseup(e){
setTimeout(function(){
legendmoving=false;
}, 200);
window.removeEventListener('mousemove', legendmousemove);
window.removeEventListener('mouseup', legendmouseup);
window.removeEventListener('blur', legendmouseup);
}
legend.addEventListener('mousedown', function(e){
if(e.which !== 1){
return;
}
startX = e.pageX;
startY = e.pageY;
startElX = legend.offsetLeft;
startElY = legend.offsetTop;
window.addEventListener('mousemove', legendmousemove);
window.addEventListener('mouseup', legendmouseup);
window.addEventListener('blur', legendmouseup);
})
legend.style.display = 'none';
parent.appendChild(legend);
let margin = 10;
function clickHandler(e){
let trect = e.target.getBoundingClientRect();
let X = e.clientX - trect.x, Y = e.clientY - trect.y;
prect = parent.getBoundingClientRect();
if(legend.style.display == 'none'){
legend.style.display = 'flex';
let lrect = legend.getBoundingClientRect();
if(X+lrect.width+margin < prect.width){
legend.style.left = X + "px";
}else if(X - lrect.width+margin < 0){
legend.style.left = "0px";
}else{
legend.style.left = (X-lrect.width)+"px";
}
if(Y+lrect.height+margin < prect.height){
legend.style.top = Y + "px";
}else if(Y - lrect.height+margin <0 ){
legend.style.top = "0px";
}else{
legend.style.top = (Y-lrect.height) + "px";
}
}
redrawX = X;
//drawLineX(e.layerX);
graphs.update();
}
//canvas.addEventListener('click', clickHandler)
function hideLegend(){
//legend.style.display = 'none';
}
function setZoomMode(to){
chart.options.zoom.mode = to;
}
function setPanOnOff(to){
chart.options.pan.enabled = to;
}
function addTime(data){
chart.data.labels = data
chart.update();
}
function addDataset(key, data){
let dataset_index = chart.data.datasets.length;
chart.data.datasets.push({data: data[2], label: data[0], key: key,
spanGaps: false, lineJoin: 'round', borderWidth: 2, steppedLine: data[3] == 0,
borderColor: data[1],fill: false, pointRadius: 0, tension:0, showLine: true});
let dataset = chart.data.datasets[dataset_index];
let legendel = document.createElement('div');
let legendelvalue = document.createElement('div');
legendelvalue.classList.add('value');
legendels[key] = legendelvalue;
legendel.classList.add('legendel')
let color = document.createElement('div');
color.classList.add('color')
color.style.backgroundColor = dataset.borderColor;
legendel.appendChild(color);
let dlabel = document.createElement('div');
dlabel.innerHTML = dataset.label;
dlabel.addEventListener('click', function(evt){
/*
console.log('LABEL', evt.target)
if(legendmoving)
return
legendel.firstChild.style.height = '2px';
dataset.borderWidth = 2;
labelClicked = true;
//console.log('LABEL', evt.target)
*/
});
legendel.appendChild(dlabel);
legendel.appendChild(legendelvalue);
legendel.addEventListener('click', function(evt){
if (legendmoving) return;
for (let k in legendels) {
// set all labels to normal font
legendels[k].parentNode.children[1].style.fontWeight = 400;
}
if (evt.target == dlabel) {
// disable all
for (let k in legendels) {
legendels[k].parentNode.firstChild.style.height = '1px';
}
for (ds of chart.data.datasets) {
ds.borderWidth = 1;
}
color.style.height = '2px';
dataset.borderWidth = 2;
dlabel.style.fontWeight = 700; // bold
} else {
if (dataset.borderWidth == 1) {
legendel.firstChild.style.height = '2px';
dataset.borderWidth = 2;
} else {
legendel.firstChild.style.height = '1px';
dataset.borderWidth = 1;
}
}
graphs.autoScale(chart);
chart.update();
});
legend.appendChild(legendel);
return dataset_index;
}
function autoScaleIf() {
if (autoScaleFlag) graphs.autoScale(chart);
}
function pushData(dataset_index, data_point){
data = chart.data.datasets[dataset_index].data;
//if (chart.data.datasets[dataset_index].key == 'tt:target')
// console.log('BEFORE', data.slice(-3))
if (data.slice(-1)[0] && data.slice(-1)[0].x >= data_point.x) {
removed = data.pop();
}
data.push(data_point);
//if (chart.data.datasets[dataset_index].key == 'tt:target')
// console.log('PUSHED', data.slice(-3))
/*
if (graphs.zoomed) {
update_max = data_point.x;
} else {
chart.options.scales.xAxes[0].ticks.max = data_point.x;
}
*/
}
function reloadData(index, data){
chart.data.datasets[index].data = data;
}
function setMinMax(min, max){
let ax = chart.options.scales.xAxes[0];
let ay = chart.options.scales.yAxes[0];
// clamp X-span
let span = max - min;
let half = 0;
if (chart.lastXmin) {
if (span > maxspan) {
half = maxspan * 0.5;
} else if (span < minspan) {
half = minspan * 0.5;
}
}
if (half) { // clamped
mid = (chart.lastXmin + chart.lastXmax) * 0.5;
min = mid - half;
max = mid + half;
ay.ticks.min = chart.lastYmin;
ay.ticks.max = chart.lastYmax;
} else {
chart.lastXmin = min;
chart.lastXmax = max;
chart.lastYmin = ay.ticks.min;
chart.lastYmax = ay.ticks.max;
}
// custom algorithm for tick step
mainstep = 1000;
step = 1000;
for (info of [['second', 60, [1, 2, 5, 10, 15, 30]],
['minute', 60, [1, 2, 5, 10, 15, 30]],
['hour', 24, [1, 2, 4, 6, 12]],
['day', 365, [1,2,4,7,14,31]]]) {
if (span < 12 * mainstep * info[2].slice(-1)[0]) {
for (fact of info[2]) {
step = mainstep * fact;
if (span < 12 * step) {
break;
}
}
break;
}
mainstep *= info[1];
}
ax.time.unit = info[0];
ax.time.stepSize = Math.round(step / mainstep);
//ax.ticks.unit = ax.time.unit;
//ax.ticks.stepSize =ax.time.stepSize;
//console.log('INFO', step, mainstep, info, ax, ax.time);
ax.ticks.max = max;
ax.ticks.min = min;
}
function toggleAutoScale () {
autoScaleFlag = !autoScaleFlag;
if (autoScaleFlag) {
graphs.autoScale(chart);
update();
autoScaleRow.innerHTML = "Autoscale <strong>on</strong> off";
} else {
autoScaleRow.innerHTML = "Autoscale on <strong>off</strong>";
}
}
function toggleAxesType(){
setAxesType((chart.options.scales.yAxes[0].type=== 'linear') ? 'logarithmic' : 'linear');
}
function setAxesType(type){
scaleType = type;
if(type == "linear"){
linlog.innerHTML = "<strong>Lin</strong> Log";
}else{
linlog.innerHTML = "Lin <strong>Log</strong>";
}
chart.options.scales.yAxes[0].type = type;
chart.options.animation.duration = 800;
if (autoScaleFlag) graphs.autoScale(chart);
update();
setTimeout(function(){chart.options.animation.duration = 0;},850)
}
let redrawX = null;
function redraw(a=null){
if(a!==null)
redrawX = a;
if(redrawX !== null){
drawLineX(redrawX);
}
}
function drawLineX(x){
test = []
for(let i in chart.data.datasets){
let d = 0;
for(let j = 0;j < chart.getDatasetMeta(i).data.length; j++){
let dp = chart.getDatasetMeta(i).data[j];
let d2 = Math.abs(dp._model.x - x)
if(d == 0 || d2 < d){
d = d2;
legendels[chart.data.datasets[i].key].innerHTML =
strFormat(chart.data.datasets[i].data[dp._index].y, 6);
test[i]=dp
}
}
}
//chart.render()
//console.log(test)
/*for(let a of test){
if(a === undefined)
continue;
ctx.beginPath();
ctx.arc(a._model.x, a._model.y, 3, 0, 2 * Math.PI, false);
ctx.fillStyle = "rgba(0,0,0,0.8)";
ctx.fill();
}*/
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.lineWidth = 2;
ctx.strokeStyle = "rgba(0,0,0,0.8)";
ctx.globalCompositeOperation = "destination-over";
ctx.stroke();
ctx.globalCompositeOperation = "source-over";
}
redrawX = canvas.clientWidth || null;
redraw();
function update(){
chart.update();
redraw();
}
return {
addTime: addTime,
addDataset: addDataset,
pushData: pushData,
setMinMax: setMinMax,
setAxesType: setAxesType,
clickHandler: clickHandler,
setZoomMode: setZoomMode,
hideLegend: hideLegend,
setPanOnOff:setPanOnOff,
redraw: redraw,
update: update,
reloadData: reloadData,
autoScaleIf: autoScaleIf,
chart: chart,
}
}
let map = {};/*
function createCharts2(graph){
//console.log(graph, blocks, graphs);
/*document.querySelector(".content-graphics").innerHTML = "";
document.querySelector(".content-graphics").style.overflow = "auto";
document.querySelector(".content-graphics").style.paddingTop = "2em";
document.querySelector(".content-graphics").style.paddingRight = "5em";
for(let block of blocks){
block.graph = graphs.addGraph("Time",block.unit)
}
graphs.addToDOM();
let max=0, min=0, d=0;
for(let key in graph){
map[key] = {};
for(let block of blocks){
for(let curve of block.curves){
if(curve.name == key){
let data = [];
for( let e of graph[key]){
if(e[1] == null || e[1] == null){
continue;
}
data.push({x: e[0]*1000, y: e[1]});
}
if(data.length>0){
if(data[data.length-1].x-data[0].x > d && data[data.length-1].x-data[0].x < (30*60+10)*1000){ // Adjust to requested time
d = data[data.length-1].x-data[0].x
max = data[data.length-1].x;
min = data[0].x;
}
graphs.addDataset(block.graph, key, {data: data, label:curve.label, origLabel: curve.label,borderColor: curve.color,fill: false, pointRadius: 0, tension:0, showLine: true})
}
}
}
}
}
graphs.setMinMax(min,max);
}*/
let lastupdate = performance.now();
function updateCharts2(graph){
if(!graphs.doUpdates()) {
console.log('graphs.doUpdates skipped');
return;
}
for(let key in graph){
if (graph[key][0] != null) {
// there is at least ONE valid datapoint
for (pt of graph[key]) {
graphs.newDataHandler(key, {x: pt[0]*1000, y: pt[1]});
}
}
}
graphs.updateAuto();
// graphs.update();
}
function createCharts2(arg) {
console.log('C2', arg)
}