1222 lines
40 KiB
JavaScript
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)
|
|
} |