diff --git a/client/SEAWebClient.html b/client/SEAWebClient.html
index dd790e2..6089df5 100644
--- a/client/SEAWebClient.html
+++ b/client/SEAWebClient.html
@@ -21,7 +21,7 @@
-
+
@@ -30,7 +30,7 @@
-
+
diff --git a/client/cssFiles/SEAWebClientGraphics.css b/client/cssFiles/SEAWebClientGraphics.css
index cb1c926..1dd1a81 100644
--- a/client/cssFiles/SEAWebClientGraphics.css
+++ b/client/cssFiles/SEAWebClientGraphics.css
@@ -87,7 +87,9 @@
flex-direction: column;
flex-wrap: wrap;
position: absolute;
- background-color: rgb(255,255,255,0.8);
+ /* background-color: rgb(255,255,255,0.7); */
+ background-color: white;
+ opacity: 0.7;
color: #000000;
padding: 0.3em;
/*transition: left 0.5s, top 0.5s;*/
@@ -96,22 +98,39 @@
user-select: none;
-webkit-user-select: none;
cursor: grab;
- border: 1px solid black;
+ border: 1px solid #777777;
}
.legend:active{
cursor: grabbing;
}
+.legend:hover {
+ opacity: 1;
+}
+
+.legendanchor {
+ border: solid black;
+ border-width: 0 1px 1px 0;
+ display: none;
+ position: absolute;
+ padding: 7px;
+ transform: rotate(-45deg) translate(-7px, -3px);
+ -webkit-transform: rotate(-45deg) translateX(-7px, -3px);
+}
+
body.black .legend{
- background-color: rgb(0,0,0,0.8);
+ /*background-color: rgb(0,0,0,0.8);*/
+ background-color: black;
+ opacity: 0.7;
border: none;
color: white;
}
.legend .controls{
-
+ display: inline-block;
+ float: left;
}
.legend .control{
@@ -119,32 +138,53 @@ body.black .legend{
display: inline-block;
}
+/*
.legend .vcontrol{
clear: both;
float: left;
- border-bottom: 1px solid black;
padding-bottom: 0.1em;
padding-top: 0.1em;
}
+*/
-.legend .subcontrol{
+.legend .spacer {
+ width: 0.5em;
+ display: inline-block;
+}
+
+.hidebox{
+ display: inline-block;
cursor: pointer;
- display: inline-block;
- padding-bottom: 0.1em;
- padding-top: 0.1em;
- color: #777777;
+ float: right;
}
-.legend .subcontrol:hover{
- color: #000000;
+.dselect{
+ /*display: flex; */
+ cursor: pointer;
+ float: right;
+ background-color: #dddddd;
+ padding: 2px;
+ /*z-index: 10; */
+ position: absolute;
+ top: 3px;
+ right: 3px;
}
-.legend .subspacer{
- display: inline-block;
- width: 0.2em;
+.dselect:hover{
+ opacity: 0.5;
}
-.legend .vcontrol:hover{
+.cursorline{
+ display: none;
+ background-color: black;
+ position: absolute;
+ top: 0px;
+ left: 100px;
+ width: 2px;
+ height: 1000px;
+}
+
+.legend .control:hover{
color: #777777;
}
@@ -153,33 +193,32 @@ body.black .legend{
padding-bottom: 0.3em;
}
-.legendel{
+.legendrow{
cursor: pointer;
- display: flex;
- position: relative;
-
}
-.legendel:hover{
+.legendrow:hover{
color: #777777;
}
-.legendel .color{
+.legendrow .colorline{
width: 2em;
- height: 0.1em;
- margin-top: 0.45em;
+ height: 2px;
+ margin-bootom: 0.45em;
margin-right: 1em;
}
-.legendel .value{
+.value{
margin-left: 1em;
+ min-width: 3em
}
-.legendel.hidden .value{
+/*
+.legendrow.hidden .value{
display: none;
}
-/*.legendel.hidden:before{
+.legendel.hidden:before{
content: '';
width: 100%;
height: 0.05em;
@@ -187,12 +226,13 @@ body.black .legend{
margin-top: -0.025em;
background-color: #FFF;
position: absolute;
-}*/
+}
.legendel.hidden{
opacity: 0.5;
}
-.legendel.hidden .color{
+.legendel.hidden .colorline{
opacity: 0;
}
+*/
\ No newline at end of file
diff --git a/client/cssFiles/SEAWebClientSwiper.css b/client/cssFiles/SEAWebClientSwiper.css
index ee30807..7538cec 100644
--- a/client/cssFiles/SEAWebClientSwiper.css
+++ b/client/cssFiles/SEAWebClientSwiper.css
@@ -14,6 +14,10 @@
padding-bottom: 30px;
}
+.swiper-button-prev.swiper-button-disabled,
+.swiper-button-next.swiper-button-disabled {
+ opacity: 0;
+}
/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% */
/* PANEL */
.panel {
diff --git a/client/externalFiles/chartjs-zoom.js b/client/externalFiles/chartjs-zoom.js
index c713363..1c34373 100644
--- a/client/externalFiles/chartjs-zoom.js
+++ b/client/externalFiles/chartjs-zoom.js
@@ -1,20 +1,685 @@
-/*!
- * @license
- * chartjs-plugin-zoom
- * http://chartjs.org/
- * Version: 0.7.3
- *
- * Copyright 2019 Chart.js Contributors
- * Released under the MIT license
- * https://github.com/chartjs/chartjs-plugin-zoom/blob/master/LICENSE.md
+//'use strict';
+
+/*
+import {Chart} from 'chart.js';
+import Hammer from 'hammerjs';
+*/
+
+var helpers = Chart.helpers;
+
+// Take the zoom namespace of Chart
+var zoomNS = Chart.Zoom = Chart.Zoom || {};
+
+// Where we store functions to handle different scale types
+var zoomFunctions = zoomNS.zoomFunctions = zoomNS.zoomFunctions || {};
+var panFunctions = zoomNS.panFunctions = zoomNS.panFunctions || {};
+
+function resolveOptions(chart, options) {
+ var deprecatedOptions = {};
+ if (typeof chart.options.pan !== 'undefined') {
+ deprecatedOptions.pan = chart.options.pan;
+ }
+ if (typeof chart.options.zoom !== 'undefined') {
+ deprecatedOptions.zoom = chart.options.zoom;
+ }
+ var props = chart.$zoom;
+ options = props._options = helpers.merge({}, [options, deprecatedOptions]);
+
+ // Install listeners. Do this dynamically based on options so that we can turn zoom on and off
+ // We also want to make sure listeners aren't always on. E.g. if you're scrolling down a page
+ // and the mouse goes over a chart you don't want it intercepted unless the plugin is enabled
+ var node = props._node;
+ var zoomEnabled = options.zoom && options.zoom.enabled;
+ var dragEnabled = options.zoom.drag;
+ if (zoomEnabled && !dragEnabled) {
+ node.addEventListener('wheel', props._wheelHandler);
+ } else {
+ node.removeEventListener('wheel', props._wheelHandler);
+ }
+ if (zoomEnabled && dragEnabled) {
+ node.addEventListener('mousedown', props._mouseDownHandler);
+ node.ownerDocument.addEventListener('mouseup', props._mouseUpHandler);
+ } else {
+ node.removeEventListener('mousedown', props._mouseDownHandler);
+ node.removeEventListener('mousemove', props._mouseMoveHandler);
+ node.ownerDocument.removeEventListener('mouseup', props._mouseUpHandler);
+ }
+}
+
+function storeOriginalOptions(chart) {
+ var originalOptions = chart.$zoom._originalOptions;
+ helpers.each(chart.scales, function(scale) {
+ if (!originalOptions[scale.id]) {
+ originalOptions[scale.id] = helpers.clone(scale.options);
+ }
+ });
+ helpers.each(originalOptions, function(opt, key) {
+ if (!chart.scales[key]) {
+ delete originalOptions[key];
+ }
+ });
+}
+
+/**
+ * @param {string} mode can be 'x', 'y' or 'xy'
+ * @param {string} dir can be 'x' or 'y'
+ * @param {Chart} chart instance of the chart in question
*/
-!function(e,o){"object"==typeof exports&&"undefined"!=typeof module?module.exports=o(require("chart.js"),require("hammerjs")):"function"==typeof define&&define.amd?define(["chart.js","hammerjs"],o):(e=e||self).ChartZoom=o(e.Chart,e.Hammer)}(this,function(e,o){"use strict";e=e&&e.hasOwnProperty("default")?e.default:e,o=o&&o.hasOwnProperty("default")?o.default:o;var n=e.helpers,t=e.Zoom=e.Zoom||{},a=t.zoomFunctions=t.zoomFunctions||{},i=t.panFunctions=t.panFunctions||{};function m(e,o){var t={};void 0!==e.options.pan&&(t.pan=e.options.pan),void 0!==e.options.pan&&(t.zoom=e.options.zoom);var a=e.$zoom;o=a._options=n.merge({},[o,t]);var i=a._node,m=o.zoom&&o.zoom.enabled,r=o.zoom.drag;m&&!r?i.addEventListener("wheel",a._wheelHandler):i.removeEventListener("wheel",a._wheelHandler),m&&r?(i.addEventListener("mousedown",a._mouseDownHandler),i.ownerDocument.addEventListener("mouseup",a._mouseUpHandler)):(i.removeEventListener("mousedown",a._mouseDownHandler),i.removeEventListener("mousemove",a._mouseMoveHandler),i.ownerDocument.removeEventListener("mouseup",a._mouseUpHandler))}function r(e){var o=e.$zoom._originalOptions;n.each(e.scales,function(e){o[e.id]||(o[e.id]=n.clone(e.options))}),n.each(o,function(n,t){e.scales[t]||delete o[t]})}function l(e,o){return void 0===e||"string"==typeof e&&-1!==e.indexOf(o)}function s(e,o){if(e.scaleAxes&&e.rangeMax&&!n.isNullOrUndef(e.rangeMax[e.scaleAxes])){var t=e.rangeMax[e.scaleAxes];o>t&&(o=t)}return o}function c(e,o){if(e.scaleAxes&&e.rangeMin&&!n.isNullOrUndef(e.rangeMin[e.scaleAxes])){var t=e.rangeMin[e.scaleAxes];o=c&&s<=u?(i.min=l,i.max=s):lu&&(a=u-r,i.max=u,i.min=m+a)}function f(e,o,n){var t=i[e.type];t&&t(e,o,n)}e.Zoom.defaults=e.defaults.global.plugins.zoom={pan:{enabled:!1,mode:"xy",speed:20,threshold:10},zoom:{enabled:!1,mode:"xy",sensitivity:3,speed:.1}},t.zoomFunctions.category=function(e,o,n,a){var i=e.chart.data.labels,m=e.minIndex,r=i.length-1,l=e.maxIndex,u=a.sensitivity,d=e.isHorizontal()?e.left+e.width/2:e.top+e.height/2,p=e.isHorizontal()?n.x:n.y;t.zoomCumulativeDelta=o>1?t.zoomCumulativeDelta+1:t.zoomCumulativeDelta-1,Math.abs(t.zoomCumulativeDelta)>u&&(t.zoomCumulativeDelta<0?(p>=d?m<=0?l=Math.min(r,l+1):m=Math.max(0,m-1):p=r?m=Math.max(0,m-1):l=Math.min(r,l+1)),t.zoomCumulativeDelta=0):t.zoomCumulativeDelta>0&&(p>=d?m=mm?l=Math.max(m,l-1):l),t.zoomCumulativeDelta=0),e.options.ticks.min=c(a,i[m]),e.options.ticks.max=s(a,i[l]))},t.zoomFunctions.time=function(e,o,n,t){u(e,o,n,t),e.options.ticks.min=e.options.ticks.min,e.options.ticks.max=e.options.ticks.max},t.zoomFunctions.linear=u,t.zoomFunctions.logarithmic=u,t.panFunctions.category=function(e,o,n){var a,i=e.chart.data.labels,m=i.length-1,r=Math.max(e.ticks.length,1),l=n.speed,u=e.minIndex,d=Math.round(e.width/(r*l));t.panCumulativeDelta+=o,u=t.panCumulativeDelta>d?Math.max(0,u-1):t.panCumulativeDelta<-d?Math.min(m-r+1,u+1):u,t.panCumulativeDelta=u!==e.minIndex?0:t.panCumulativeDelta,a=Math.min(m,u+r-1),e.options.ticks.min=c(n,i[u]),e.options.ticks.max=s(n,i[a])},t.panFunctions.time=function(e,o,n){v(e,o,n);var t=e.options;t.ticks.min=t.ticks.min,t.ticks.max=t.ticks.max},t.panFunctions.linear=v,t.panFunctions.logarithmic=v,t.panCumulativeDelta=0,t.zoomCumulativeDelta=0;var h={id:"zoom",afterInit:function(e){e.resetZoom=function(){r(e);var o=e.$zoom._originalOptions;n.each(e.scales,function(e){var n=e.options.time,t=e.options.ticks;o[e.id]?(n&&(n.min=o[e.id].ticks.min,n.max=o[e.id].ticks.max),t&&(t.min=o[e.id].ticks.min,t.max=o[e.id].ticks.max)):(n&&(delete n.min,delete n.max),t&&(delete t.min,delete t.max))}),e.update()}},beforeUpdate:function(e,o){m(e,o)},beforeInit:function(e,a){e.$zoom={_originalOptions:{}};var i=e.$zoom._node=e.chart.ctx.canvas;m(e,a);var s=e.$zoom._options,c=s.pan&&s.pan.threshold;e.$zoom._mouseDownHandler=function(o){i.addEventListener("mousemove",e.$zoom._mouseMoveHandler),e.$zoom._dragZoomStart=o},e.$zoom._mouseMoveHandler=function(o){e.$zoom._dragZoomStart&&(e.$zoom._dragZoomEnd=o,e.update(0))},e.$zoom._mouseUpHandler=function(o){if(e.$zoom._dragZoomStart){i.removeEventListener("mousemove",e.$zoom._mouseMoveHandler);var n=e.$zoom._dragZoomStart,t=n.target.getBoundingClientRect().left,a=Math.min(n.clientX,o.clientX)-t,m=Math.max(n.clientX,o.clientX)-t,r=n.target.getBoundingClientRect().top,s=Math.min(n.clientY,o.clientY)-r,c=m-a,u=Math.max(n.clientY,o.clientY)-r-s;if(e.$zoom._dragZoomStart=null,e.$zoom._dragZoomEnd=null,!(c<=0&&u<=0)){var d=e.chartArea,v=e.$zoom._options.zoom,f=d.right-d.left,h=l(v.mode,"x")&&c?1+(f-c)/f:1,x=d.bottom-d.top,g=l(v.mode,"y");p(e,h,g&&u?1+(x-u)/x:1,{x:(a-d.left)/(1-c/f)+d.left,y:(s-d.top)/(1-u/x)+d.top}),"function"==typeof v.onZoomComplete&&v.onZoomComplete({chart:e})}}};var u=null;
-if(e.$zoom._wheelHandler=function(o){/*if(!o.ctrlKey){return;}*/
- //o.stopImmediatePropagation();
- var n=o.target.getBoundingClientRect(),
- t={x:o.clientX-n.left,y:o.clientY-n.top},
- a=e.$zoom._options.zoom,
- i=a.speed;
- o.deltaY>=0&&(i=-i),p(e,1+i,1+i,t),clearTimeout(u),u=setTimeout(function(){"function"==typeof a.onZoomComplete&&a.onZoomComplete({chart:e})},250),
- o.cancelable&&o.preventDefault()},o)
-{var d,v=new o.Manager(i);v.add(new o.Pinch),v.add(new o.Pan({threshold:c}));var h=function(o){var n=1/d*o.scale,t=o.target.getBoundingClientRect(),a={x:o.center.x-t.left,y:o.center.y-t.top},i=Math.abs(o.pointers[0].clientX-o.pointers[1].clientX),m=Math.abs(o.pointers[0].clientY-o.pointers[1].clientY),r=i/m;p(e,n,n,a,r>.3&&r<1.7?"xy":i>m?"x":"y"),d=o.scale};v.on("pinchstart",function(){d=1}),v.on("pinch",h),v.on("pinchend",function(e){h(e),d=null,t.zoomCumulativeDelta=0});var x=null,g=null,z=!1,_=function(o){if(null!==x&&null!==g){z=!0;var t=o.deltaX-x,a=o.deltaY-g;x=o.deltaX,g=o.deltaY,function(e,o,t){r(e);var a=e.$zoom._options.pan;if(a.enabled){var i=a.mode;n.each(e.scales,function(e){e.isHorizontal()&&l(i,"x")&&0!==o?(a.scaleAxes="x",f(e,o,a)):!e.isHorizontal()&&l(i,"y")&&0!==t&&(a.scaleAxes="y",f(e,t,a))}),e.update(0),"function"==typeof a.onPan&&a.onPan({chart:e})}}(e,t,a)}};v.on("panstart",function(e){x=0,g=0,_(e)}),v.on("panmove",_),v.on("panend",function(){x=null,g=null,t.panCumulativeDelta=0,setTimeout(function(){z=!1},500);var o=e.$zoom._options.pan;"function"==typeof o.onPanComplete&&o.onPanComplete({chart:e})}),e.$zoom._ghostClickHandler=function(e){z&&e.cancelable&&(e.stopImmediatePropagation(),e.preventDefault())},i.addEventListener("click",e.$zoom._ghostClickHandler),e._mc=v}},beforeDatasetsDraw:function(e){var o=e.chart.ctx;if(e.$zoom._dragZoomEnd){var n=function(e){for(var o=e.scales,n=Object.keys(o),t=0;t0&&(o.lineWidth=f.borderWidth,o.strokeStyle=f.borderColor||"rgba(225,225,225)",o.strokeRect(m,s,p,v)),o.restore()}},destroy:function(e){if(e.$zoom){var o=e.$zoom,n=o._node;n.removeEventListener("mousedown",o._mouseDownHandler),n.removeEventListener("mousemove",o._mouseMoveHandler),n.ownerDocument.removeEventListener("mouseup",o._mouseUpHandler),n.removeEventListener("wheel",o._wheelHandler),n.removeEventListener("click",o._ghostClickHandler),delete e.$zoom;var t=e._mc;t&&(t.remove("pinchstart"),t.remove("pinch"),t.remove("pinchend"),t.remove("panstart"),t.remove("pan"),t.remove("panend"))}}};return e.plugins.register(h),h});
+function directionEnabled(mode, dir, chart) {
+ if (mode === undefined) {
+ return true;
+ } else if (typeof mode === 'string') {
+ return mode.indexOf(dir) !== -1;
+ } else if (typeof mode === 'function') {
+ return mode({chart: chart}).indexOf(dir) !== -1;
+ }
+
+ return false;
+}
+
+function rangeMaxLimiter(zoomPanOptions, newMax) {
+ if (zoomPanOptions.scaleAxes && zoomPanOptions.rangeMax &&
+ !helpers.isNullOrUndef(zoomPanOptions.rangeMax[zoomPanOptions.scaleAxes])) {
+ var rangeMax = zoomPanOptions.rangeMax[zoomPanOptions.scaleAxes];
+ if (newMax > rangeMax) {
+ newMax = rangeMax;
+ }
+ }
+ return newMax;
+}
+
+function rangeMinLimiter(zoomPanOptions, newMin) {
+ if (zoomPanOptions.scaleAxes && zoomPanOptions.rangeMin &&
+ !helpers.isNullOrUndef(zoomPanOptions.rangeMin[zoomPanOptions.scaleAxes])) {
+ var rangeMin = zoomPanOptions.rangeMin[zoomPanOptions.scaleAxes];
+ if (newMin < rangeMin) {
+ newMin = rangeMin;
+ }
+ }
+ return newMin;
+}
+
+function zoomCategoryScale(scale, zoom, center, zoomOptions) {
+ var labels = scale.chart.data.labels;
+ var minIndex = scale.min;
+ var lastLabelIndex = labels.length - 1;
+ var maxIndex = scale.max;
+ var sensitivity = zoomOptions.sensitivity;
+ var chartCenter = scale.isHorizontal() ? scale.left + (scale.width / 2) : scale.top + (scale.height / 2);
+ var centerPointer = scale.isHorizontal() ? center.x : center.y;
+
+ zoomNS.zoomCumulativeDelta = zoom > 1 ? zoomNS.zoomCumulativeDelta + 1 : zoomNS.zoomCumulativeDelta - 1;
+
+ if (Math.abs(zoomNS.zoomCumulativeDelta) > sensitivity) {
+ if (zoomNS.zoomCumulativeDelta < 0) {
+ if (centerPointer >= chartCenter) {
+ if (minIndex <= 0) {
+ maxIndex = Math.min(lastLabelIndex, maxIndex + 1);
+ } else {
+ minIndex = Math.max(0, minIndex - 1);
+ }
+ } else if (centerPointer < chartCenter) {
+ if (maxIndex >= lastLabelIndex) {
+ minIndex = Math.max(0, minIndex - 1);
+ } else {
+ maxIndex = Math.min(lastLabelIndex, maxIndex + 1);
+ }
+ }
+ zoomNS.zoomCumulativeDelta = 0;
+ } else if (zoomNS.zoomCumulativeDelta > 0) {
+ if (centerPointer >= chartCenter) {
+ minIndex = minIndex < maxIndex ? minIndex = Math.min(maxIndex, minIndex + 1) : minIndex;
+ } else if (centerPointer < chartCenter) {
+ maxIndex = maxIndex > minIndex ? maxIndex = Math.max(minIndex, maxIndex - 1) : maxIndex;
+ }
+ zoomNS.zoomCumulativeDelta = 0;
+ }
+ scale.options.min = rangeMinLimiter(zoomOptions, labels[minIndex]);
+ scale.options.max = rangeMaxLimiter(zoomOptions, labels[maxIndex]);
+ }
+}
+
+function zoomNumericalScale(scale, zoom, center, zoomOptions) {
+ var range = scale.max - scale.min;
+ var newDiff = range * (zoom - 1);
+
+ var centerPoint = scale.isHorizontal() ? center.x : center.y;
+ var minPercent = (scale.getValueForPixel(centerPoint) - scale.min) / range;
+ var maxPercent = 1 - minPercent;
+
+ var minDelta = newDiff * minPercent;
+ var maxDelta = newDiff * maxPercent;
+
+ scale.options.ticks.min = rangeMinLimiter(zoomOptions, scale.min + minDelta);
+ scale.options.ticks.max = rangeMaxLimiter(zoomOptions, scale.max - maxDelta);
+ //console.log("ZOOM", zoom, scale)
+}
+
+function zoomScale(scale, zoom, center, zoomOptions) {
+ var fn = zoomFunctions[scale.type];
+ if (fn) {
+ fn(scale, zoom, center, zoomOptions);
+ }
+}
+
+/**
+ * @param chart The chart instance
+ * @param {number} percentZoomX The zoom percentage in the x direction
+ * @param {number} percentZoomY The zoom percentage in the y direction
+ * @param {{x: number, y: number}} focalPoint The x and y coordinates of zoom focal point. The point which doesn't change while zooming. E.g. the location of the mouse cursor when "drag: false"
+ * @param {string} whichAxes `xy`, 'x', or 'y'
+ * @param {number} animationDuration Duration of the animation of the redraw in milliseconds
+ */
+function doZoom(chart, percentZoomX, percentZoomY, focalPoint, whichAxes, animationDuration) {
+ var ca = chart.chartArea;
+ if (!focalPoint) {
+ focalPoint = {
+ x: (ca.left + ca.right) / 2,
+ y: (ca.top + ca.bottom) / 2,
+ };
+ }
+
+ var zoomOptions = chart.$zoom._options.zoom;
+
+ if (zoomOptions.enabled) {
+ storeOriginalOptions(chart);
+ // Do the zoom here
+ var zoomMode = typeof zoomOptions.mode === 'function' ? zoomOptions.mode({chart: chart}) : zoomOptions.mode;
+
+ // Which axe should be modified when figers were used.
+ var _whichAxes;
+ if (zoomMode === 'xy' && whichAxes !== undefined) {
+ // based on fingers positions
+ _whichAxes = whichAxes;
+ } else {
+ // no effect
+ _whichAxes = 'xy';
+ }
+
+ helpers.each(chart.scales, function(scale) {
+ if (scale.isHorizontal() && directionEnabled(zoomMode, 'x', chart) && directionEnabled(_whichAxes, 'x', chart)) {
+ zoomOptions.scaleAxes = 'x';
+ zoomScale(scale, percentZoomX, focalPoint, zoomOptions);
+ } else if (!scale.isHorizontal() && directionEnabled(zoomMode, 'y', chart) && directionEnabled(_whichAxes, 'y', chart)) {
+ // Do Y zoom
+ zoomOptions.scaleAxes = 'y';
+ zoomScale(scale, percentZoomY, focalPoint, zoomOptions);
+ }
+ });
+
+ if (animationDuration) {
+ // needs to create specific animation mode
+ if (!chart.options.animation.zoom) {
+ chart.options.animation.zoom = {
+ duration: animationDuration,
+ easing: 'easeOutQuad',
+ };
+ }
+ chart.update('zoom');
+ } else {
+ chart.update('none');
+ }
+
+ if (typeof zoomOptions.onZoom === 'function') {
+ zoomOptions.onZoom({chart: chart});
+ }
+ }
+}
+
+function panCategoryScale(scale, delta, panOptions) {
+ var labels = scale.chart.data.labels;
+ var lastLabelIndex = labels.length - 1;
+ var offsetAmt = Math.max(scale.ticks.length, 1);
+ var panSpeed = panOptions.speed;
+ var minIndex = scale.min;
+ var step = Math.round(scale.width / (offsetAmt * panSpeed));
+ var maxIndex;
+
+ zoomNS.panCumulativeDelta += delta;
+
+ minIndex = zoomNS.panCumulativeDelta > step ? Math.max(0, minIndex - 1) : zoomNS.panCumulativeDelta < -step ? Math.min(lastLabelIndex - offsetAmt + 1, minIndex + 1) : minIndex;
+ zoomNS.panCumulativeDelta = minIndex !== scale.min ? 0 : zoomNS.panCumulativeDelta;
+
+ maxIndex = Math.min(lastLabelIndex, minIndex + offsetAmt - 1);
+
+ scale.options.min = rangeMinLimiter(panOptions, labels[minIndex]);
+ scale.options.max = rangeMaxLimiter(panOptions, labels[maxIndex]);
+}
+
+function panNumericalScale(scale, delta, panOptions) {
+ var scaleOpts = scale.options;
+ var prevStart = scale.min;
+ var prevEnd = scale.max;
+ var newMin = scale.getValueForPixel(scale.getPixelForValue(prevStart) - delta);
+ var newMax = scale.getValueForPixel(scale.getPixelForValue(prevEnd) - delta);
+ var rangeMin = newMin;
+ var rangeMax = newMax;
+ var diff;
+
+ if (panOptions.scaleAxes && panOptions.rangeMin &&
+ !helpers.isNullOrUndef(panOptions.rangeMin[panOptions.scaleAxes])) {
+ rangeMin = panOptions.rangeMin[panOptions.scaleAxes];
+ }
+ if (panOptions.scaleAxes && panOptions.rangeMax &&
+ !helpers.isNullOrUndef(panOptions.rangeMax[panOptions.scaleAxes])) {
+ rangeMax = panOptions.rangeMax[panOptions.scaleAxes];
+ }
+
+ if (newMin >= rangeMin && newMax <= rangeMax) {
+ scaleOpts.ticks.min = newMin;
+ scaleOpts.ticks.max = newMax;
+ } else if (newMin < rangeMin) {
+ diff = prevStart - rangeMin;
+ scaleOpts.ticks.min = rangeMin;
+ scaleOpts.ticks.max = prevEnd - diff;
+ } else if (newMax > rangeMax) {
+ diff = rangeMax - prevEnd;
+ scaleOpts.ticks.max = rangeMax;
+ scaleOpts.ticks.min = prevStart + diff;
+ }
+}
+
+function panScale(scale, delta, panOptions) {
+ var fn = panFunctions[scale.type];
+ if (fn) {
+ fn(scale, delta, panOptions);
+ }
+}
+
+function doPan(chartInstance, deltaX, deltaY) {
+ storeOriginalOptions(chartInstance);
+ var panOptions = chartInstance.$zoom._options.pan;
+ if (panOptions.enabled) {
+ var panMode = typeof panOptions.mode === 'function' ? panOptions.mode({chart: chartInstance}) : panOptions.mode;
+
+ helpers.each(chartInstance.scales, function(scale) {
+ if (scale.isHorizontal() && directionEnabled(panMode, 'x', chartInstance) && deltaX !== 0) {
+ panOptions.scaleAxes = 'x';
+ panScale(scale, deltaX, panOptions);
+ } else if (!scale.isHorizontal() && directionEnabled(panMode, 'y', chartInstance) && deltaY !== 0) {
+ panOptions.scaleAxes = 'y';
+ panScale(scale, deltaY, panOptions);
+ }
+ });
+
+ chartInstance.update('none');
+
+ if (typeof panOptions.onPan === 'function') {
+ panOptions.onPan({chart: chartInstance});
+ }
+ }
+}
+
+function getXAxis(chartInstance) {
+ var scales = chartInstance.scales;
+ var scaleIds = Object.keys(scales);
+ for (var i = 0; i < scaleIds.length; i++) {
+ var scale = scales[scaleIds[i]];
+
+ if (scale.isHorizontal()) {
+ return scale;
+ }
+ }
+}
+
+function getYAxis(chartInstance) {
+ var scales = chartInstance.scales;
+ var scaleIds = Object.keys(scales);
+ for (var i = 0; i < scaleIds.length; i++) {
+ var scale = scales[scaleIds[i]];
+
+ if (!scale.isHorizontal()) {
+ return scale;
+ }
+ }
+}
+
+// Store these for later
+zoomNS.zoomFunctions.category = zoomCategoryScale;
+zoomNS.zoomFunctions.time = zoomNumericalScale;
+zoomNS.zoomFunctions.linear = zoomNumericalScale;
+zoomNS.zoomFunctions.logarithmic = zoomNumericalScale;
+zoomNS.panFunctions.category = panCategoryScale;
+zoomNS.panFunctions.time = panNumericalScale;
+zoomNS.panFunctions.linear = panNumericalScale;
+zoomNS.panFunctions.logarithmic = panNumericalScale;
+// Globals for category pan and zoom
+zoomNS.panCumulativeDelta = 0;
+zoomNS.zoomCumulativeDelta = 0;
+
+// Chartjs Zoom Plugin
+var zoomPlugin = {
+ id: 'zoom',
+
+ defaults: {
+ pan: {
+ enabled: false,
+ mode: 'xy',
+ speed: 20,
+ threshold: 10
+ },
+ zoom: {
+ enabled: false,
+ mode: 'xy',
+ sensitivity: 3,
+ speed: 0.1
+ }
+ },
+
+ afterInit: function(chartInstance) {
+
+ chartInstance.resetZoom = function() {
+ storeOriginalOptions(chartInstance);
+ var originalOptions = chartInstance.$zoom._originalOptions;
+ helpers.each(chartInstance.scales, function(scale) {
+
+ var options = scale.options;
+ if (originalOptions[scale.id]) {
+ options.min = originalOptions[scale.id].min;
+ options.max = originalOptions[scale.id].max;
+ } else {
+ delete options.min;
+ delete options.max;
+ }
+ });
+ chartInstance.update();
+ };
+
+ },
+
+ beforeUpdate: function(chart, args, options) {
+ resolveOptions(chart, options);
+ },
+
+ beforeInit: function(chartInstance, pluginOptions) {
+ chartInstance.$zoom = {
+ _originalOptions: {}
+ };
+ var node = chartInstance.$zoom._node = chartInstance.ctx.canvas;
+ resolveOptions(chartInstance, pluginOptions);
+
+ var options = chartInstance.$zoom._options;
+ var panThreshold = options.pan && options.pan.threshold;
+
+ chartInstance.$zoom._mouseDownHandler = function(event) {
+ node.addEventListener('mousemove', chartInstance.$zoom._mouseMoveHandler);
+ chartInstance.$zoom._dragZoomStart = event;
+ };
+
+ chartInstance.$zoom._mouseMoveHandler = function(event) {
+ if (chartInstance.$zoom._dragZoomStart) {
+ chartInstance.$zoom._dragZoomEnd = event;
+ chartInstance.update('none');
+ }
+ };
+
+ chartInstance.$zoom._mouseUpHandler = function(event) {
+ if (!chartInstance.$zoom._dragZoomStart) {
+ return;
+ }
+
+ node.removeEventListener('mousemove', chartInstance.$zoom._mouseMoveHandler);
+
+ var beginPoint = chartInstance.$zoom._dragZoomStart;
+
+ var offsetX = beginPoint.target.getBoundingClientRect().left;
+ var startX = Math.min(beginPoint.clientX, event.clientX) - offsetX;
+ var endX = Math.max(beginPoint.clientX, event.clientX) - offsetX;
+
+ var offsetY = beginPoint.target.getBoundingClientRect().top;
+ var startY = Math.min(beginPoint.clientY, event.clientY) - offsetY;
+ var endY = Math.max(beginPoint.clientY, event.clientY) - offsetY;
+
+ var dragDistanceX = endX - startX;
+ var dragDistanceY = endY - startY;
+
+ // Remove drag start and end before chart update to stop drawing selected area
+ chartInstance.$zoom._dragZoomStart = null;
+ chartInstance.$zoom._dragZoomEnd = null;
+
+ var zoomThreshold = (options.zoom && options.zoom.threshold) || 0;
+ if (dragDistanceX <= zoomThreshold && dragDistanceY <= zoomThreshold) {
+ return;
+ }
+
+ var chartArea = chartInstance.chartArea;
+
+ var zoomOptions = chartInstance.$zoom._options.zoom;
+ var chartDistanceX = chartArea.right - chartArea.left;
+ var xEnabled = directionEnabled(zoomOptions.mode, 'x', chartInstance);
+ var zoomX = xEnabled && dragDistanceX ? 1 + ((chartDistanceX - dragDistanceX) / chartDistanceX) : 1;
+
+ var chartDistanceY = chartArea.bottom - chartArea.top;
+ var yEnabled = directionEnabled(zoomOptions.mode, 'y', chartInstance);
+ var zoomY = yEnabled && dragDistanceY ? 1 + ((chartDistanceY - dragDistanceY) / chartDistanceY) : 1;
+
+ doZoom(chartInstance, zoomX, zoomY, {
+ x: (startX - chartArea.left) / (1 - dragDistanceX / chartDistanceX) + chartArea.left,
+ y: (startY - chartArea.top) / (1 - dragDistanceY / chartDistanceY) + chartArea.top
+ }, undefined, zoomOptions.drag.animationDuration);
+
+ if (typeof zoomOptions.onZoomComplete === 'function') {
+ zoomOptions.onZoomComplete({chart: chartInstance});
+ }
+ };
+
+ var _scrollTimeout = null;
+ var _lastWheelEvent = 0;
+ chartInstance.$zoom._wheelHandler = function(event) {
+ // Prevent the event from triggering the default behavior (eg. Content scrolling).
+ if (event.cancelable) {
+ event.preventDefault();
+ }
+
+ // Firefox always fires the wheel event twice:
+ // First without the delta and right after that once with the delta properties.
+ if (typeof event.deltaY === 'undefined' || event.deltaY == 0) { // M.Z. deltaY == 0
+ return;
+ }
+
+ var now = performance.now();
+ if (now < _lastWheelEvent + 50) { // M.Z. touchpad events are fired more frequently -> skip when too quick
+ return;
+ }
+ _lastWheelEvent = now;
+ var rect = event.target.getBoundingClientRect();
+ var offsetX = event.clientX - rect.left;
+ var offsetY = event.clientY - rect.top;
+
+ var center = {
+ x: offsetX,
+ y: offsetY
+ };
+
+ var zoomOptions = chartInstance.$zoom._options.zoom;
+ var speedPercent = zoomOptions.speed;
+
+ if (event.deltaY >= 0) {
+ speedPercent = -speedPercent;
+ }
+ doZoom(chartInstance, 1 + speedPercent, 1 + speedPercent, center);
+
+ clearTimeout(_scrollTimeout);
+ _scrollTimeout = setTimeout(function() {
+ if (typeof zoomOptions.onZoomComplete === 'function') {
+ zoomOptions.onZoomComplete({chart: chartInstance});
+ }
+ }, 250);
+ };
+
+ if (Hammer) {
+ var mc = new Hammer.Manager(node);
+ mc.add(new Hammer.Pinch());
+ mc.add(new Hammer.Pan({
+ threshold: panThreshold
+ }));
+
+ // Hammer reports the total scaling. We need the incremental amount
+ var currentPinchScaling;
+ var handlePinch = function(e) {
+ var diff = 1 / (currentPinchScaling) * e.scale;
+ var rect = e.target.getBoundingClientRect();
+ var offsetX = e.center.x - rect.left;
+ var offsetY = e.center.y - rect.top;
+ var center = {
+ x: offsetX,
+ y: offsetY
+ };
+
+ // fingers position difference
+ var x = Math.abs(e.pointers[0].clientX - e.pointers[1].clientX);
+ var y = Math.abs(e.pointers[0].clientY - e.pointers[1].clientY);
+
+ // diagonal fingers will change both (xy) axes
+ var p = x / y;
+ var xy;
+ if (p > 0.3 && p < 1.7) {
+ xy = 'xy';
+ } else if (x > y) {
+ xy = 'x'; // x axis
+ } else {
+ xy = 'y'; // y axis
+ }
+
+ doZoom(chartInstance, diff, diff, center, xy);
+
+ var zoomOptions = chartInstance.$zoom._options.zoom;
+ if (typeof zoomOptions.onZoomComplete === 'function') {
+ zoomOptions.onZoomComplete({chart: chartInstance});
+ }
+
+ // Keep track of overall scale
+ currentPinchScaling = e.scale;
+ };
+
+ mc.on('pinchstart', function() {
+ currentPinchScaling = 1; // reset tracker
+ });
+ mc.on('pinch', handlePinch);
+ mc.on('pinchend', function(e) {
+ handlePinch(e);
+ currentPinchScaling = null; // reset
+ zoomNS.zoomCumulativeDelta = 0;
+ });
+
+ var currentDeltaX = null;
+ var currentDeltaY = null;
+ var panning = false;
+ var handlePan = function(e) {
+ if (currentDeltaX !== null && currentDeltaY !== null) {
+ panning = true;
+ var deltaX = e.deltaX - currentDeltaX;
+ var deltaY = e.deltaY - currentDeltaY;
+ currentDeltaX = e.deltaX;
+ currentDeltaY = e.deltaY;
+ doPan(chartInstance, deltaX, deltaY);
+ }
+ };
+
+ mc.on('panstart', function(e) {
+ currentDeltaX = 0;
+ currentDeltaY = 0;
+ handlePan(e);
+ });
+ mc.on('panmove', handlePan);
+ mc.on('panend', function() {
+ currentDeltaX = null;
+ currentDeltaY = null;
+ zoomNS.panCumulativeDelta = 0;
+ setTimeout(function() {
+ panning = false;
+ }, 500);
+
+ var panOptions = chartInstance.$zoom._options.pan;
+ if (typeof panOptions.onPanComplete === 'function') {
+ panOptions.onPanComplete({chart: chartInstance});
+ }
+ });
+
+ chartInstance.$zoom._ghostClickHandler = function(e) {
+ if (panning && e.cancelable) {
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ }
+ };
+ node.addEventListener('click', chartInstance.$zoom._ghostClickHandler);
+
+ chartInstance._mc = mc;
+ }
+ },
+
+ beforeDatasetsDraw: function(chartInstance) {
+ var ctx = chartInstance.ctx;
+
+ if (chartInstance.$zoom._dragZoomEnd) {
+ var xAxis = getXAxis(chartInstance);
+ var yAxis = getYAxis(chartInstance);
+ var beginPoint = chartInstance.$zoom._dragZoomStart;
+ var endPoint = chartInstance.$zoom._dragZoomEnd;
+
+ var startX = xAxis.left;
+ var endX = xAxis.right;
+ var startY = yAxis.top;
+ var endY = yAxis.bottom;
+
+ if (directionEnabled(chartInstance.$zoom._options.zoom.mode, 'x', chartInstance)) {
+ var offsetX = beginPoint.target.getBoundingClientRect().left;
+ startX = Math.min(beginPoint.clientX, endPoint.clientX) - offsetX;
+ endX = Math.max(beginPoint.clientX, endPoint.clientX) - offsetX;
+ }
+
+ if (directionEnabled(chartInstance.$zoom._options.zoom.mode, 'y', chartInstance)) {
+ var offsetY = beginPoint.target.getBoundingClientRect().top;
+ startY = Math.min(beginPoint.clientY, endPoint.clientY) - offsetY;
+ endY = Math.max(beginPoint.clientY, endPoint.clientY) - offsetY;
+ }
+
+ var rectWidth = endX - startX;
+ var rectHeight = endY - startY;
+ var dragOptions = chartInstance.$zoom._options.zoom.drag;
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.fillStyle = dragOptions.backgroundColor || 'rgba(225,225,225,0.3)';
+ ctx.fillRect(startX, startY, rectWidth, rectHeight);
+
+ if (dragOptions.borderWidth > 0) {
+ ctx.lineWidth = dragOptions.borderWidth;
+ ctx.strokeStyle = dragOptions.borderColor || 'rgba(225,225,225)';
+ ctx.strokeRect(startX, startY, rectWidth, rectHeight);
+ }
+ ctx.restore();
+ }
+ },
+
+ destroy: function(chartInstance) {
+ if (!chartInstance.$zoom) {
+ return;
+ }
+ var props = chartInstance.$zoom;
+ var node = props._node;
+
+ node.removeEventListener('mousedown', props._mouseDownHandler);
+ node.removeEventListener('mousemove', props._mouseMoveHandler);
+ node.ownerDocument.removeEventListener('mouseup', props._mouseUpHandler);
+ node.removeEventListener('wheel', props._wheelHandler);
+ node.removeEventListener('click', props._ghostClickHandler);
+
+ delete chartInstance.$zoom;
+
+ var mc = chartInstance._mc;
+ if (mc) {
+ mc.remove('pinchstart');
+ mc.remove('pinch');
+ mc.remove('pinchend');
+ mc.remove('panstart');
+ mc.remove('pan');
+ mc.remove('panend');
+ mc.destroy();
+ }
+ }
+};
+
+
+Chart.plugins.register(zoomPlugin);
+//export default zoomPlugin;
\ No newline at end of file
diff --git a/client/jsFiles/SEAWebClientCommunication.js b/client/jsFiles/SEAWebClientCommunication.js
index ff7d8cb..ae233a2 100644
--- a/client/jsFiles/SEAWebClientCommunication.js
+++ b/client/jsFiles/SEAWebClientCommunication.js
@@ -94,7 +94,7 @@ function handleUpdateMessage(src, message) {
document.title = "SEA "+clientTitle + " " + message.title;
}
var header = document.getElementById("header");
- header.setAttribute("style", "width: auto;");
+ header.style.width = 'auto';
header.innerHTML = clientTitle;
console.log('ID', initCommands);
nextInitCommand();
@@ -137,6 +137,7 @@ function handleUpdateMessage(src, message) {
break;
// graph-message: Evokes redraw of graphics.
case "graph":
+ alert('obsolete code "graph" called')
console.log("graph");
createCharts2(message.graph);
break;
@@ -169,6 +170,7 @@ function handleUpdateMessage(src, message) {
}
function htmlEscape(str) {
+ str = "" + str;
if (!str) return "";
return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g,
''').replace(//g, '>');
@@ -196,8 +198,9 @@ function updateValues(message, src) {
var component = message.updates[i];
var value = component.value;
var matches = document.getElementsByName(component.name);
+
for (var j = 0; j < matches.length; j++) {
- var type = matches[j].getAttribute("__ctype__");
+ var type = matches[j].__ctype__;
if (type == "rdonly") {
var text = htmlEscape(value);
if (text) {
@@ -207,8 +210,7 @@ function updateValues(message, src) {
var row = matches[j].parentNode.parentNode.parentNode;
row.style.backgroundColor = "white";
var mval = matches[j].value;
- var oldValue = matches[j].getAttribute("oldValue");
- if (oldValue === null) oldValue = mval;
+ var oldValue = ('oldValue' in matches[j]) ? matches[j].oldValue : mval;
if (value != mval && parseFloat(value) != parseFloat(mval) && value != oldValue) {
if (matches[j] == document.activeElement
|| oldValue != mval) {
@@ -217,12 +219,13 @@ function updateValues(message, src) {
matches[j].value = value;
}
}
- matches[j].setAttribute("actualValue", value);
+ matches[j].actualValue = value;
resizeTextfield(matches[j]);
} else if (type == "checkbox") {
var row = matches[j].parentNode.parentNode;
row.style.backgroundColor = "white";
- matches[j].checked = value == 1 && value == "1";
+ console.log('CBX', matches[j].name, message, Boolean(value && value != 'false'));
+ matches[j].checked = Boolean(value && value != 'false');
} else if (type == "enum") {
matches[j].style.display = "block";
var row = matches[j].parentNode.parentNode;
@@ -277,14 +280,16 @@ function successHandler(s, message) {
}
if (message.path == "main") {
// Happens only initially or at device change.
- for (var sLocal = 0; sLocal < MAXBLOCK; sLocal++) {
+ for (var sLocal = 0; sLocal < 2; sLocal++) { // was up to MAXBLOCK
insertSlide(sLocal, message.title, "main", createContent(
sLocal, message));
}
+ insertSlide(2, "", "parameters", createContent(2, {components:[]}));
} else {
if (s < 0) { // redraw: check for slides in all swiper instances
for (var isw = 0; isw < MAXBLOCK; isw ++) {
- var slide = findSlide(isw, message.path);
+ var isl = findSlide(isw, message.path);
+ var slide = swiper[isl].slides[i];
if (slide) {
console.log("redraw", isw);
replaceSlideContent(slide, message.title,
@@ -292,8 +297,10 @@ function successHandler(s, message) {
}
}
} else {
- insertSlide(s, message.title, message.path, createContent(s,
- message));
+ // insertSlide(s, message.title, message.path, createContent(s, message));
+ let sLocal = paramSlider[s];
+ isl = insertSlide(sLocal, message.title, "parameters", createContent(sLocal, message));
+ swiper[sLocal].slideTo(isl); /* go to found slide */
}
}
nextInitCommand();
@@ -360,6 +367,7 @@ function successHandler(s, message) {
if (debugCommunication) {
console.log("graph-draw", message);
}
+ alert('obsolete code graph-draw called')
createCharts2(message.graph);
nextInitCommand();
// Request for updates.
diff --git a/client/jsFiles/SEAWebClientConsole.js b/client/jsFiles/SEAWebClientConsole.js
index 2811bbe..8681ff4 100644
--- a/client/jsFiles/SEAWebClientConsole.js
+++ b/client/jsFiles/SEAWebClientConsole.js
@@ -9,8 +9,8 @@ function createContentConsole(s) {
// Creates input-textfield and texarea showing console-history.
var commandline = document.createElement('input');
- commandline.setAttribute("type", "text");
- commandline.setAttribute("class", "row commandline");
+ commandline.type = "text";
+ commandline.classList.add("row", "commandline");
commandline.onkeydown = function (e) {
//console.log(histIndex);
@@ -38,10 +38,10 @@ function createContentConsole(s) {
}
};
- commandline.setAttribute("autocomplete", "on");
+ commandline.autocomplete = "on";
var wrapper = document.createElement('form');
- wrapper.setAttribute("class", "commandline-wrapper");
+ wrapper.classList.add("commandline-wrapper");
wrapper.onsubmit = function (e) {
e.preventDefault();
@@ -53,14 +53,14 @@ function createContentConsole(s) {
commandline.value = "";
};
- wrapper.setAttribute("method", 'GET');
+ wrapper.method = "GET";
wrapper.appendChild(commandline);
var history = document.createElement('div');
- history.setAttribute("class", "history");
+ history.classList.add("history");
var content = document.createElement('div');
- content.setAttribute("class", "content-console");
+ content.classList.add("content-console");
content.appendChild(wrapper);
content.appendChild(history);
return content;
diff --git a/client/jsFiles/SEAWebClientGraph.js b/client/jsFiles/SEAWebClientGraph.js
deleted file mode 100644
index 4a54cf4..0000000
--- a/client/jsFiles/SEAWebClientGraph.js
+++ /dev/null
@@ -1,1222 +0,0 @@
-// 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 += ""+modes[k] + " ";
- }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 on 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("Lin Log", function(e){
- //graphs.toggleAxesType();
- toggleAxesType();
- });
- if(scaleType !== "linear"){
- linlog.innerHTML = "Lin Log";
- }
-
- 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 on off";
- } else {
- autoScaleRow.innerHTML = "Autoscale on off";
- }
- }
-
- function toggleAxesType(){
- setAxesType((chart.options.scales.yAxes[0].type=== 'linear') ? 'logarithmic' : 'linear');
- }
-
- function setAxesType(type){
- scaleType = type;
- if(type == "linear"){
- linlog.innerHTML = "Lin Log";
- }else{
- linlog.innerHTML = "Lin Log";
- }
- 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)
-}
\ No newline at end of file
diff --git a/client/jsFiles/SEAWebClientGraphics.js b/client/jsFiles/SEAWebClientGraphics.js
index 5fe1076..fd6cadb 100644
--- a/client/jsFiles/SEAWebClientGraphics.js
+++ b/client/jsFiles/SEAWebClientGraphics.js
@@ -1,31 +1,1466 @@
-// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-// % GRAPHICS
+// Graph
+
/*
-var swiperGraphics = []; // This array contains graphics-swiper-Instances.
-var begin = -10; // Time to look back (in seconds).
-var timeServer; // Every time a graph-update-mesage is received the
- // server-timestamp is stored.
-var timeRange; // Range of time to regard (unix-time in seconds).
-var blocks; // array of graphic-blocks sorted by units. (as from var_list, but values added)
+interface:
-var MARGIN_TOP = 10;
-var MARGIN_RIGHT = 10;
-var MARGIN_BOTTOM = 20;
-var MARGIN_LEFT = 60;
+updateCharts2(graph)
+ graph is a dict iof array of (t,y)
-var width;
-var height;
-var margin;
+ called when data is updated
-var scaleX;
+graphs.receivedVars(blocks)
-zoomSelection = false;
+ block is a list of dict(tag, unit, curves)
+ curves is a dict(name, label, color, period)
-function createCharts(graph) {
- createCharts2(graph);
- return
+internals:
+
+graphs: containing main objects and methods
+ with the arrays:
+ graph_array: array fo graphs
+ graph_elm_array: the array of elements containing the graphs
+ vars_array: the array containing the variables meta data
+
+graph = Graph(..): one graph with meta data and methods for one chart (short: ch)
+in loops called gr
+
+chart = Chart(...): the chartjs chart
+*/
+
+function Timer(){
+ let start = window.performance.now();
+ return function(x = "timer"){
+ console.log(x, window.performance.now()-start);
+ }
}
-function updateCharts(graph){
- updateCharts2(graph)
+
+
+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) {
+ console.log(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(val, significant_digits=13) {
+ if (val == null) return '';
+ evalue = val.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 = {};
+ let blocks, liveMode=true, top_vars=[], bottom_vars=[];
+ let legendFlag = false, currentZoomMode = isTouchDevice ? 'xy' : 'x';
+ let prevTime = null, prevMin = null, prevMax = null, prevGraph = null; // zoom speed limitation
+ let cursorLinePos = null;
+
+ let type = 'linear';
+
+ let ngraphs = 0; // current number of graphs
+
+ let graph_array = [];
+ let graph_elm_array = [];
+ let vars_array = [];
+ let prev_blk = {};
+ let tag_dict = {};
+
+ let currentMinTime = 0, currentMaxTime = 0; // the currently displayed time range
+ let startTime; // time of query on server
+ let recvTime; // local relative time at receive of above
+ let minTime, maxTime; // the queried time range
+ let lastTime = 0; // time of most recent data point
+
+ let container = document.createElement('div');
+ container.classList.add("graphs-container");
+
+ function now(){
+ // the current time corrected for server time
+ return startTime + (performance.now()-recvTime);
+ }
+
+ function clear(gindex){
+ let graph_elm = graph_elm_array[gindex];
+ let graph = graph_array[gindex];
+
+ graph_elm.innerHTML = '';
+ graph_array[gindex] = undefined;
+ vars_array[gindex] = [];
+ if (graph) {
+ 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 graph_elm = graph_elm_array[gindex];
+ clear(gindex);
+
+ let selection = document.createElement('div');
+ selection.classList.add('selection');
+
+ delete prev_blk[gindex];
+ console.log('cresel', gindex, prev_blk, tag_dict);
+ let creidx = null;
+ let creblock = null;
+ for (let i in prev_blk) {
+ creblock = prev_blk[i];
+ if (tag_dict[creblock.tag] == gindex) {
+ creidx = i;
+ break;
+ }
+ }
+ for (let block of blocks) {
+ console.log('ck', block.tag, tag_dict[block.tag], creidx)
+ 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(){
+ if (block.tag in tag_dict) {
+ let idx = tag_dict[block.tag];
+ createSelection(idx);
+ prev_blk[idx] = block;
+ }
+ createGraph(gindex, block);
+ })
+ selection.appendChild(bel);
+
+ }
+ graph_elm.appendChild(selection);
+ if (creidx !== null) {
+ console.log('creblock', creidx, creblock);
+ createGraph(creidx, creblock);
+ }
+ }
+
+ let gotoNowElm = document.createElement('div');
+
+ function setLiveMode(mode=null) {
+ // set live mode and enable/disable 'go to now' button
+ if (mode !== null) liveMode = mode;
+ if (liveMode && cursorLinePos === null)
+ gotoNowElm.innerHTML = '';
+ else
+ gotoNowElm.innerHTML = 'go to now';
+ }
+
+ function createGraph(gindex, block){
+ clear(gindex);
+ tag_dict[block.tag] = gindex;
+ let dict = {}
+ for (let curve of block.curves) {
+ if (curve.show) {
+ vars_array[gindex].push(curve.name);
+ dict[curve.name] = curve;
+ }
+ }
+ //let varlist = top_vars.concat(bottom_vars);
+ varlist = vars_array[gindex];
+ let graph_elm = graph_elm_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 graph = new Graph(gindex, graph_elm, "Time", block.unit, block.tag, type);
+ graph_array[gindex] = graph;
+
+ 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, pdata, dict[key])
+ /*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;
+ }*/
+ }
+ }
+ graph.setMinMax(minTime,maxTime);
+ graph.autoScaleIf();
+ graph.update();
+ showLegends(legendFlag, false);
+ if (legendFlag) adjustLegends();
+
+ result = AJAX( "http://" + hostPort +
+ "/updategraph?variables=" + variables() +
+ "&id=" + clientID).getJSON().then(function(data) {
+ setLiveMode(data.live);
+ console.log('LIVE create', liveMode)
+ })
+ //console.log('UPDATE LIVE', result);
+ })
+ }
+
+ // add dataset to graph with graph_id
+ function addDataset(gindex, key, data, data_opts){
+ let graph = graph_array[gindex];
+ dataset_to_graph_map[key] = [gindex, graph.addDataset(key, data, data_opts)];
+ }
+
+ 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 (let i = 0; i < datasets.length; i++){
+ ds = datasets[i];
+ if (ds.borderWidth == 1) 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;
+ if (lasty !== null) {
+ extraMin = Math.min(extraMin, lasty);
+ extraMax = Math.max(extraMax, lasty);
+ }
+ }
+ }
+ if (min > max) return;
+ if (min == max) {
+ if (min == 0) {
+ ystep = 1;
+ } else {
+ ystep = Math.abs(min * 0.01);
+ }
+ min -= ystep;
+ max += ystep;
+ // chart.graph.setLabelDigits(min, max);
+ } else {
+ ystep = (max - min) * 0.1;
+ chart.graph.setLabelDigits(min, max);
+ min -= ystep * 0.02;
+ max += ystep * 0.02;
+ if (liveMode) {
+ extraMin -= ystep;
+ extraMax += ystep;
+ }
+ extraMin = Math.min(min - ystep * 0.5, extraMin);
+ extraMax = Math.max(max + ystep * 0.5, extraMax);
+ if (min >= axis.ticks.min && axis.ticks.min >= extraMin &&
+ max <= axis.ticks.max && axis.ticks.max <= extraMax) {
+ //console.log('NOCHANGE', max, axis.ticks.max, extraMax)
+ return; // do not yet change
+ }
+ //console.log('CHANMIN', min, axis.ticks.min, extraMin)
+ //console.log('CHANMAX', max, axis.ticks.max, extraMax)
+ min = extraMin;
+ max = extraMax;
+ }
+ axis.min = axis.ticks.min = min;
+ axis.max = axis.ticks.max = max;
+ }
+
+ function setMinMax(min, max){
+ currentMaxTime = max;
+ currentMinTime = min;
+ for (let gr of graph_array) {
+ if (gr) gr.setMinMax(min, max);
+ }
+ }
+
+ // responsible for new data being displayed on chart
+ function newDataHandler(key, data){
+ if(!(key in dataset_to_graph_map))
+ return
+ lastTime = Math.max(lastTime, data.x);
+ let i = dataset_to_graph_map[key];
+ graph_array[i[0]].pushData(i[1], data)
+ }
+
+ function clickHandler(evt) {
+ if(evt.target.tagName == "CANVAS"){
+ legendFlag = true;
+ let trect = evt.target.getBoundingClientRect();
+ let X = evt.clientX - trect.x, Y = evt.clientY - trect.y;
+ showLegends(true, false);
+ setLiveMode();
+ cursorLine(X);
+ update();
+ for (let gr of graph_array.slice(0, ngraphs)) {
+ if (gr && gr.chart.canvas == evt.target) {
+ bringToFront(gr.legend);
+ }
+ /* clickHandler(evt); */
+ }
+ }
+ }
+ container.addEventListener('click', clickHandler)
+
+ function setDataFromKey(key, data){
+ if(!(key in dataset_to_graph_map))
+ return
+ let i = dataset_to_graph_map[key];
+ graph_array[i[0]].setData(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){
+ setDataFromKey(key, pdata);
+ }
+ }
+ console.log("RELOAD")
+ // AJAX( "http://" + hostPort + "/updategraph?id=" + clientID).getJSON(); // activate updates
+ result = AJAX("http://" + hostPort +
+ "/updategraph?variables=" + variables() +
+ "&id=" + clientID).getJSON().then(function(data) {
+ setLiveMode(data.live);
+ console.log('LIVE reload', liveMode)
+ })
+ updateAuto(false);
+ //update();
+ });
+ }
+
+ function checkReload(graph){
+ let tk = graph.chart.options.scales.xAxes[0].ticks;
+ let xmin = tk.min, xmax = tk.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 || (!liveMode && 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
+ console.log('reloadData (range change)', xmin - minTime, maxTime - xmax, (xmax - xmin) / (maxTime - minTime))
+ reloadData(xmin, xmax);
+ minTime = xmin;
+ maxTime = xmax;
+ return; // autoScale amd update are done when receiving data
+ }
+ graph.autoScaleIf();
+ graph.update();
+ }
+
+ function update(){
+ for (let gr of graph_array.slice(0, ngraphs)) {
+ if (gr) gr.update();
+ }
+ }
+
+ function zoomCallback(graph){
+ let tk, min, max;
+ if (currentZoomMode == 'y') {
+ tk = graph.chart.options.scales.yAxes[0].ticks;
+ } else {
+ tk = graph.chart.options.scales.xAxes[0].ticks;
+ }
+ min = tk.min;
+ max = tk.max;
+ if (!isTouchDevice) {
+ /*
+ if (prevGraph != graph) {
+ prevTime = null;
+ }
+ if (prevTime !== null) {
+ // slow down (relevant when using touch pad)
+ fact = (max - min) / (prevMax - prevMin);
+ maxFact = 1 + (performance.now() - prevTime) * 0.001;
+ let w = 1;
+ if (fact > maxFact) {
+ w = (maxFact - 1) / (fact - 1);
+ } else if (fact < 1/maxFact) {
+ w = (maxFact - 1) / (1 / fact - 1);
+ }
+ min = prevMin + (min - prevMin) * w;
+ max = prevMax + (max - prevMax) * w;
+ }
+ prevMin = min; prevMax = max;
+ prevTime = performance.now(); prevGraph = graph;
+ */
+ }
+ if (currentZoomMode == 'y') {
+ tk.min = min;
+ tk.max = max;
+ graph.setAutoScale(false);
+ } else {
+ if (liveMode && max < lastTime) setLiveMode(false);
+ setMinMax(min, max);
+ }
+ console.log('zoomed')
+ update();
+ }
+
+ function panCallback(graph){
+ let tk = graph.chart.options.scales.xAxes[0].ticks;
+ let xmin = tk.min, xmax = tk.max;
+ if (liveMode && xmax < lastTime) setLiveMode(false);
+ setMinMax(xmin,xmax);
+ update();
+ }
+
+
+ function updateAuto(check=true){
+ if (liveMode) {
+ max = now();
+ if (currentMaxTime && max > currentMaxTime) {
+ max = currentMaxTime + Math.min(60000, 0.1 * (currentMaxTime - currentMinTime));
+ setMinMax(currentMinTime, max);
+ //reloadData(currentMinTime, max);
+ //check = false;
+ }
+ }
+ for (let gr of graph_array.slice(0, ngraphs)) {
+ if (gr) {
+ if (check) {
+ checkReload(gr);
+ } else {
+ gr.autoScaleIf();
+ gr.update();
+ }
+ }
+ }
+ }
+
+ function gotoNow() {
+ cursorLine(null);
+ if (!liveMode) {
+ setMinMax(graphs.now() - (currentMaxTime - currentMinTime), graphs.now());
+ }
+ setLiveMode(true);
+ updateAuto();
+ }
+
+ /*
+ 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;
+ }
+ */
+
+ function receivedVars(blocks_arg){
+ maxTime = timeRange[1]*1000;
+ minTime = timeRange[0]*1000;
+ if (currentMaxTime == 0) {
+ currentMaxTime = maxTime;
+ currentMinTime = minTime;
+ }
+ 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", container);
+ blocks = blocks_arg;
+
+ createGraphs();
+
+ container.parentNode.querySelector('.panel').classList.add('graphics');
+
+ gotoNowElm.addEventListener('click', gotoNow);
+ //gotoNowElm.innerHTML = "go to now";
+ container.parentNode.querySelector('.panel span').appendChild(gotoNowElm);
+
+ function removeCursor(evt=null) {
+ graphs.cursorLine(null);
+ graphs.update();
+ }
+ if (isTouchDevice) {
+ doubleTap(removeCursor);
+ } else {
+ window.addEventListener('dblclick', removeCursor);
+ showLegends(true, false);
+ adjustLegends();
+ let zoomMode = document.createElement('div');
+ function setInnerZoomMode(){
+ if (currentZoomMode == 'y') {
+ zoomMode.innerHTML = "☒ y-zoom";
+ } else {
+ zoomMode.innerHTML = "☐ y-zoom";
+ }
+ prevTime = null; // reset zoom speed time
+ }
+ setInnerZoomMode();
+
+ function toggleZoomMode(){
+ if (currentZoomMode == 'y')
+ currentZoomMode = 'x';
+ else
+ currentZoomMode = 'y';
+ setInnerZoomMode();
+ for (let gr of graph_array) {
+ if (gr) gr.setZoomMode(currentZoomMode);
+ }
+ }
+ zoomMode.addEventListener('click', toggleZoomMode);
+ container.parentNode.querySelector('.panel span').appendChild(zoomMode);
+ }
+
+ let gotoMainElm = document.createElement('div');
+ gotoMainElm.innerHTML = "×";
+ let currentSwiper = swiper[f];
+
+ function setSlidingMode(mode) {
+ currentSwiper.params.noSwipingClass = mode ? "allow-swipe" : "swiper-slide-main";
+ }
+
+ currentSwiper.enableSwiping(false);
+ currentSwiper.on('reachBeginning', function () {
+ currentSwiper.enableSwiping(false);
+ })
+
+ gotoMainElm.addEventListener('click', function () {
+ currentSwiper.enableSwiping(true);
+ console.log("MAIN")
+ currentSwiper.slideNext();
+ });
+ container.parentNode.querySelector('.panel span').appendChild(gotoMainElm);
+ }
+
+ function getBlocks(){
+ return blocks;
+ }
+
+ function showLegends(flag, doUpdate) {
+ for (let gr of graph_array) {
+ if (gr) {
+ gr.showLegend(flag);
+ if (doUpdate) gr.update();
+ }
+ }
+ legendFlag = flag;
+ //bringToFront(null);
+ }
+
+ function adjustLegends() {
+ let lastx = 0, lasty = 0, lasty0 = 0, lasty1 = 0, miny = 0;
+ let ys = 22;
+ for (let gr of graph_array.slice(0, ngraphs)) {
+ if (gr) {
+ prect = gr.legend.parentNode.getBoundingClientRect();
+ lrect = gr.legend.getBoundingClientRect();
+ if (miny == 0) miny = prect.top;
+ let x = prect.left, y = Math.max(miny, Math.min(prect.top, prect.bottom - lrect.height));
+ if (y < lasty) {
+ x = (lasty0 < lasty1) ? prect.left : lastx;
+ }
+ gr.positionLegend(x - prect.left, y - prect.top);
+ lastx = lrect.right;
+ lasty = lrect.bottom;
+ if (x < lrect.width / 2) {
+ lasty0 = lasty;
+ } else {
+ lasty1 = lasty;
+ }
+ }
+ }
+ }
+
+ function createGraphs() {
+ let n = Math.max(2, Math.floor(window.innerHeight / 200));
+
+ if (n != ngraphs) {
+ for (let i = ngraphs; i < n; i++) {
+ if (i >= graph_elm_array.length) {
+ let graph_elm = document.createElement('div');
+ graph_elm.classList.add('graph');
+ graph_elm.classList.add('graph-' + i);
+ container.appendChild(graph_elm);
+ graph_elm_array[i] = graph_elm;
+ } else {
+ graph_elm_array[i].style.display = 'flex';
+ }
+ let shown = false;
+ for (block of blocks) {
+ if (block.tag in tag_dict) {
+ idx = tag_dict[block.tag];
+ if (idx < ngraphs) continue;
+ }
+ // this block is either not shown or only in a hidden graph
+ for (let curve of block.curves) {
+ if (curve.show) shown = true;
+ }
+ if (shown) break;
+ }
+ if (shown) {
+ createGraph(i, blocks[i]);
+ } else {
+ createSelection(i);
+ }
+ }
+ for (let i = n; i < graph_elm_array.length; i++) {
+ clear(i);
+ graph_elm_array[i].style.display = 'none';
+ }
+ let hs = Math.floor(100 / (n + 1) + 1);
+ let h = (100 - (n-1) * hs); // first height
+ ngraphs = n;
+ for (el of graph_elm_array) {
+ el.style.height = h + '%';
+ h = hs;
+ }
+ }
+ }
+
+ let cursorElement = document.createElement('div')
+ cursorElement.classList.add('cursorline');
+ container.appendChild(cursorElement);
+
+ function cursorLine(setpos, query=false) {
+ if (query) {
+ if (!legendFlag) return null;
+ if (cursorLinePos === null) return setpos;
+ return cursorLinePos;
+ }
+ cursorLinePos = setpos;
+ if (setpos === null) {
+ cursorElement.style.display = 'none';
+ } else {
+ cursorElement.style.display = 'block';
+ cursorElement.style.left = (cursorLinePos - 1) + 'px';
+ cursorElement.style.height = window.innerHeight + 'px';
+ }
+ return cursorLinePos;
+ }
+
+ function resizeHandler() {
+ createGraphs();
+ adjustLegends();
+ if (cursorLinePos) {
+ // we do not want to update values -> remove cursor line
+ cursorLine(null);
+ update();
+ }
+ }
+ window.addEventListener('resize', resizeHandler);
+
+ let frontLegend = null;
+
+ function bringToFront(legend) {
+ if (legend != frontLegend) {
+ if (frontLegend) {
+ frontLegend.style.zIndex = 0;
+ //frontLegend.style.opacity = "0.7";
+ }
+ if (legend) {
+ legend.style.zIndex = 1;
+ //legend.style.opacity = "1";
+ }
+ frontLegend = legend;
+ }
+ }
+
+ return {
+ addDataset: addDataset,
+ //toggleAxesType: toggleAxesType,
+ newDataHandler: newDataHandler,
+ zoomCallback: zoomCallback,
+ panCallback: panCallback,
+ receivedVars: receivedVars,
+ createSelection: createSelection,
+ doUpdates: function(){return liveMode},
+ update: update,
+ updateAuto: updateAuto,
+ now: now,
+ checkReload: checkReload,
+ getBlocks: getBlocks,
+ createGraph: createGraph,
+ autoScale: autoScale,
+ showLegends: showLegends,
+ setLiveMode: setLiveMode,
+ cursorLine: cursorLine,
+ bringToFront: bringToFront,
+ }
+})();
+
+function Graph(gindex, container, x_label, y_label, tag, scaleType = "linear"){
+ let chart;
+
+ let maxspan = 10 * 864e5, minspan = 120000;
+ let autoScaleFlag = true;
+ let labelDigits = 6, labelMinWidth = 0, labelLongValue = '';
+
+ let parent = document.createElement("div");
+ parent.classList.add("chart-container");
+
+ let dselect = document.createElement('div');
+ dselect.classList.add('dselect');
+ dselect.innerHTML = "[" + tag + "]";
+ dselect.addEventListener('click', function(e){
+ graphs.createSelection(gindex);
+ })
+ parent.appendChild(dselect);
+
+ 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");
+ let self = this;
+ 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);
+ }
+ },
+ gridLines:{drawTicks:false},
+ scaleLabel: false, // {display: true, labelString: y_label},
+ type: scaleType,
+ position: 'right',
+ 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.panCallback(chart.graph);},
+ //onPanComplete: function({chart}){graphs.checkReload(chart.graph);redraw()},
+ onPanComplete: function({chart}){graphs.updateAuto();},
+ },
+ zoom: {
+ enabled: true,
+ drag: false,
+ mode: isTouchDevice ? 'xy': 'x',
+ speed: 0.1,
+ sensitivity: 1,
+ onZoom: function({chart}) { graphs.zoomCallback(chart.graph);},
+ //onZoomComplete: function({chart}){graphs.checkReload(chart.graph);redraw()},
+ onZoomComplete: function({chart}){graphs.updateAuto();},
+ }
+ }
+ });
+
+ //console.log('create legend')
+ let legend = document.createElement('div');
+ legend.classList.add('legend');
+ parent.appendChild(legend);
+
+ let legendAnchor = document.createElement('div');
+ legendAnchor.classList.add('legendanchor');
+ parent.appendChild(legendAnchor);
+
+ //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);
+ let sp = document.createElement('div');
+ sp.classList.add('spacer');
+ controls.appendChild(sp);
+ return c;
+ }
+
+ /*changecontrol = addControl("Change Dataset", function(){
+ graphs.createSelection(gindex);
+ });*/
+
+ hideBox = document.createElement('div');
+ hideBox.innerHTML = '×';
+ hideBox.classList.add('control');
+ hideBox.classList.add('hidebox');
+ hideBox.addEventListener('click', function () {graphs.showLegends(false, true);});
+
+ controls.appendChild(hideBox);
+
+ /*
+ addControl("Hide legend", function(){
+ legend.style.display = 'none';
+ redrawX = null;
+ });
+ */
+
+ /*let update_max = null;
+ addControl("Reset Zoom/Pan", function(){
+ if(update_max !== null){
+ chart.options.scales.xAxes[0].ticks.max = update_max;
+ update_max = null;
+ }
+ chart.resetZoom();
+ graphs.zoompan(chart.graph);
+ });*/
+
+ let autoScaleRow = addControl("☒ autoscale", function(){
+ setAutoScale(!autoScaleFlag);
+ });
+
+ let linlog = addControl("☐ log", function(e){
+ //graphs.toggleAxesType();
+ toggleAxesType();
+ });
+ if(scaleType !== "linear"){
+ linlog.innerHTML = "☒ log";
+ }
+
+ let tbl = document.createElement('table');
+ legend.appendChild(tbl);
+ let legendbody = document.createElement('tbody')
+ tbl.appendChild(legendbody);
+ let legendvalues = {};
+ let legendlabels = {};
+ let legendlines = {};
+
+ let startX=0,startY=0, startElX=0, startElY=0,legendmoving=false;
+
+ function positionLegend(x, y) {
+ let lrect = legend.getBoundingClientRect();
+ let prect = parent.getBoundingClientRect();
+ ys = 22;
+
+ x = Math.max(0, Math.min(x, prect.width - lrect.width));
+ legend.style.left = x + "px";
+
+ /*
+ let lim = prect.height - lrect.height;
+ Y = Math.max(Math.min(0, lim), Y);
+ Y = Math.min(Math.max(lim, 0), Y);
+ */
+ y = Math.max(ys-lrect.height, Math.min(prect.height-ys, y));
+ legend.style.top = y + "px";
+
+ let mid = y + lrect.height / 2;
+ if (mid >= 0 && mid <= prect.height) {
+ legendAnchor.style.display = 'none';
+ return;
+ }
+ if (y < 0) {
+ y = Math.min((prect.height - ys) / 2, y + lrect.height - ys);
+ } else {
+ y = Math.max((prect.height - ys) / 2, y);
+ }
+ legendAnchor.style.display = 'inline-block';
+ legendAnchor.style.left = x + lrect.width + 'px';
+ legendAnchor.style.top = y + 'px';
+ }
+
+ 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);
+ positionLegend(x, y);
+ }
+
+ 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;
+ }
+ //graphs.bringToFront(legend);
+ 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.addEventListener('mouseover', function(e){
+ graphs.bringToFront(legend);
+ });
+
+ legend.style.display = 'none';
+
+ let margin = 10;
+ /*
+ function clickHandler(e){
+ let trect = e.target.getBoundingClientRect();
+ let X = e.clientX - trect.x, Y = e.clientY - trect.y;
+ graphs.showLegends(true, false);
+ graphs.setLiveMode();
+ graphs.cursorLine(X);
+ graphs.update();
+ }
+ */
+ //canvas.addEventListener('click', clickHandler)
+
+ canvas.addEventListener('mouseover', function(e){
+ graphs.bringToFront(legend);
+ });
+
+ function showLegend(flag){
+ if (flag) {
+ legend.style.display = 'flex';
+ } else {
+ graphs.cursorLine(null);
+ legend.style.display = 'none';
+ }
+ }
+
+ function setZoomMode(to){
+ chart.options.zoom.mode = to;
+ }
+
+ function setPanOnOff(to){
+ chart.options.pan.enabled = to;
+ }
+
+ function addTime(data){
+ console.log('OBSOLETE addTime');
+ chart.data.labels = data
+ chart.update();
+ }
+
+ function addDataset(key, data, opts){
+ let dataset_index = chart.data.datasets.length;
+ chart.data.datasets.push({data: data, label: opts.label, key: key,
+ spanGaps: false, lineJoin: 'round', borderWidth: 2, steppedLine: opts.period == 0,
+ borderColor: opts.color,fill: false, pointRadius: 0, tension:0, showLine: true});
+
+ let dataset = chart.data.datasets[dataset_index];
+ let legendrow = document.createElement('tr');
+ legendrow.classList.add('legendrow')
+
+ let color = document.createElement('td');
+ color.classList.add('semitransparent');
+ let dlabel = document.createElement('td');
+ dlabel.classList.add('semitransparent');
+ let tdvalue = document.createElement('td');
+
+ legendrow.appendChild(color);
+ legendrow.appendChild(dlabel);
+ legendrow.appendChild(tdvalue);
+
+ let colorline = document.createElement('div');
+ color.appendChild(colorline);
+ colorline.classList.add('colorline');
+ colorline.style.backgroundColor = dataset.borderColor;
+
+ dlabel.innerHTML = dataset.label;
+ //dlabel.addEventListener('click', function(evt){
+ // /* dummy listener. really needed ? */
+ //});
+
+ let dvalue = document.createElement('div');
+ dvalue.classList.add('value');
+ tdvalue.appendChild(dvalue);
+
+ legendlines[key] = colorline;
+ legendvalues[key] = dvalue;
+ legendlabels[key] = dlabel;
+ legendrow.addEventListener('click', function(evt){
+ if (legendmoving) return;
+ for (let k in legendlabels) {
+ // set all labels to normal font
+ legendlabels[k].style.fontWeight = 400;
+ }
+ if (evt.target == dlabel) {
+ // disable all
+ for (let k in legendlines) {
+ legendlines[k].style.height = '1px';
+ }
+ for (ds of chart.data.datasets) {
+ ds.borderWidth = 1;
+ }
+ colorline.style.height = '2px';
+ dataset.borderWidth = 2;
+ dlabel.style.fontWeight = 700; // bold
+ } else {
+ if (dataset.borderWidth == 1) {
+ colorline.style.height = '2px';
+ dataset.borderWidth = 2;
+ } else {
+ colorline.style.height = '1px';
+ dataset.borderWidth = 1;
+ allDeselected = true;
+ for (ds of chart.data.datasets) {
+ if (ds.borderWidth != 1) allDeselected = false;
+ }
+ if (allDeselected) {
+ for (ds of chart.data.datasets) {
+ ds.borderWidth = 2;
+ }
+ for (let k in legendlines) {
+ legendlines[k].style.height = '2px';
+ }
+ }
+ }
+ }
+ console.log('AUTO')
+ graphs.autoScale(chart);
+ update();
+ });
+ legendbody.appendChild(legendrow);
+ return dataset_index;
+ }
+
+ function autoScaleIf(clear=false) {
+ if (clear) setAutoScale(false);
+ 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) {
+ // replace the last point, when x is lower. this is due to the artifical point added at the end by the live reader
+ removed = data.pop();
+ }
+ data.push(data_point);
+ }
+
+ function setData(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 setAutoScale(flag) {
+ autoScaleFlag = flag;
+ if (autoScaleFlag) {
+ graphs.autoScale(chart);
+ update();
+ autoScaleRow.innerHTML = "☒ autoscale";
+ } else {
+ autoScaleRow.innerHTML = "☐ autoscale";
+ }
+ }
+
+ function toggleAxesType(){
+ setAxesType((chart.options.scales.yAxes[0].type=== 'linear') ? 'logarithmic' : 'linear');
+ }
+
+ function setAxesType(type){
+ scaleType = type;
+ if(type == "linear"){
+ linlog.innerHTML = "☐ log";
+ }else{
+ linlog.innerHTML = "☒ log";
+ }
+ 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)
+ }
+
+
+ function redraw(){
+ x = graphs.cursorLine(canvas.clientWidth, true);
+ if (x === null) return;
+ for(let i in chart.data.datasets){
+ let y = null;
+ for(let j = 0; j < chart.getDatasetMeta(i).data.length; j++){
+ let dp = chart.getDatasetMeta(i).data[j];
+ if (dp._model.x >= x) break;
+ y = chart.data.datasets[i].data[dp._index].y;
+ }
+ valueElm = legendvalues[chart.data.datasets[i].key];
+ if (labelMinWidth == 0) {
+ valueElm.style.minWidth = '0px';
+ valueElm.innerHTML = labelLongValue;
+ labelMinWidth = valueElm.clientWidth;
+ valueElm.style.minWidth = labelMinWidth + 'px';
+ }
+ if (y !== null) {
+ valueElm.innerHTML = strFormat(y, labelDigits);
+ }
+ }
+
+ /*
+ //console.log('REDRAW', dselect.innerHTML, x);
+ 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.globalCompositeOperation = "source-over";
+ ctx.stroke();
+ ctx.globalCompositeOperation = "source-over";
+ */
+ }
+
+ function update(){
+ chart.update();
+ redraw();
+ }
+
+ function setLabelDigits(min, max) {
+ let dig = parseInt(Math.max(-min, max) / (max - min)).toFixed(0).length + 3;
+ let l = 0;
+ for (let val of [min, max]) {
+ evalue = val.toExponential(dig - 1).replace("e+", "e");
+ fvalue = val.toFixed(Math.max(0, dig - Math.abs(parseInt(val)).toFixed().length));
+ if (evalue.length < fvalue.length) fvalue = evalue;
+ if (fvalue.length > l) {
+ l = fvalue.length;
+ labelMinWidth = 0;
+ labelLongValue = fvalue;
+ }
+ }
+ labelDigits = dig;
+ }
+
+ self = {
+ addTime: addTime,
+ addDataset: addDataset,
+ pushData: pushData,
+ setMinMax: setMinMax,
+ setAxesType: setAxesType,
+ /* clickHandler: clickHandler, */
+ setZoomMode: setZoomMode,
+ showLegend: showLegend,
+ setPanOnOff:setPanOnOff,
+ redraw: redraw,
+ update: update,
+ setData: setData,
+ autoScaleIf: autoScaleIf,
+ setAutoScale: setAutoScale,
+ positionLegend: positionLegend,
+ setLabelDigits: setLabelDigits,
+ chart: chart,
+ legend: legend,
+ }
+
+ chart.graph = self;
+ return self;
+}
+
+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(false);
+ // graphs.update();
+}
+
+function createCharts2(arg) {
+ console.log('C2', arg)
+}
\ No newline at end of file
diff --git a/client/jsFiles/SEAWebClientGroup.js b/client/jsFiles/SEAWebClientGroup.js
index d0ddf9d..704ed43 100644
--- a/client/jsFiles/SEAWebClientGroup.js
+++ b/client/jsFiles/SEAWebClientGroup.js
@@ -13,7 +13,7 @@ function getGroup(s, name) {
return;
}
for (var i = 0; i < swiper[s].slides.length; i++) {
- var slideType = swiper[s].slides[i].getAttribute("slide-type");
+ var slideType = swiper[s].slides[i].slideType;
if (slideType == name) {
found = true;
swiper[s].slideTo(i);
@@ -33,56 +33,20 @@ function sendCommand(s, command) {
function createContent(s, message) {
// Depending on the message received from the server the content of the
- // group
- // is created dynamically. Handles draw-message.
+ // group is created dynamically. Handles draw-message.
var content = document.createElement('div');
- content.setAttribute("class", "content");
+ content.classList.add("content");
// Process components of the message
for (var i = 0; i < message.components.length; i++) {
var component = message.components[i];
- var name = component.name;
- var type = component.type;
- var title = name;
- if ("title" in component)
- title = component.title;
- var info = component.info;
- switch (type) {
- case "group":
- content.appendChild(createLink(s, name, title));
- break;
- case "rdonly":
- if ("link" in component) {
- content.appendChild(createReadOnlyInstrLink(s, name, title,
- component.link, info));
- } else {
- content.appendChild(createReadOnly(s, name, title, info));
- }
- break;
- case "rdlink":
- content.appendChild(createReadOnlyGroupLink(s, name, title, info));
- break;
- case "input":
- content.appendChild(createInput(s, name, title,
- info));
- break;
- case "checkbox":
- content.appendChild(createCheckbox(s, name, title,
- info));
- break;
- case "enum":
- content.appendChild(createSelection(s, name, title,
- component.enum_names, info));
- break;
- case "pushbutton":
- console.log(component);
- console.log(component.info);
- content.appendChild(createPushButton(s, name, title,
- info));
- break;
- default:
- break;
- }
+ if (!("title" in component))
+ component.title = name;
+ if (!("command" in component))
+ component.command = name;
+ createFunc = window['create_' + component.type + '_row']
+ if (createFunc)
+ content.appendChild(createFunc(s, component))
}
return content;
}
@@ -96,84 +60,50 @@ function gotoGroups(slideNames) {
}
}
-function createLink(s, name, title) {
+function create_group_row(s, component) {
// Creates row-element containing link.
+ var title = component.title;
var row = document.createElement('row');
- row.setAttribute("id", name);
- row.setAttribute("name", title);
- row.setAttribute("class", "interactive row link");
- row.setAttribute("tabindex", 0);
+ row.id = component.name;
+ row.name = title;
+ row.classList.add("interactive", "row", "link");
+ row.tabIndex = "0";
row.onclick = function () {
var slideNames = getSlideNames();
- slideNames[s] = name;
+ slideNames[s] = component.name;
document.title = "SEA "+ clientTitle + " " + slideNames.join(" ");
history.pushState({func: "gotoGroups", funarg: slideNames.join("%20")}, document.title, "#" + slideNames.join("%20"));
getGroup(s, name);
}
if (title === "console" || title === "device config") {
- row.setAttribute("class", "interactive row link link-static");
+ row.classList.add("interactive", "row", "link", "link-static");
row.innerHTML = "console";
}
row.innerHTML = title;
return row;
}
-function createReadOnly(s, name, title, info) {
- // Creates row-element containing read-only-item.
-
- var left = createTitle(title, info);
-
- var right = document.createElement('span');
- right.setAttribute("class", "col-right");
- right.setAttribute("name", name);
- right.setAttribute("__ctype__", "rdonly");
-
- return appendToContent(info, left, right);
-}
-
-function createReadOnlyGroupLink(s, name, title, info) {
- // Creates row-element containing link AND read-only-item.
- // for secop
-
- var left = createTitle(title, info);
-
- left.setAttribute("id", name);
- left.setAttribute("name", title);
- left.setAttribute("class", "interactive link");
-
- left.onclick = function () {
- getGroup(s, title);
- }
-
- var right = document.createElement('span');
- right.setAttribute("class", "col-right");
- right.setAttribute("name", name);
- right.setAttribute("__ctype__", "rdonly");
-
- return appendToContent(info, left, right);
-}
-
-function createReadOnlyInstrLink(s, name, title, link, info) {
+function create_rdonly_row(s, component) {
// Creates row-element containing link AND read-only-item.
- // var left = createTitle(title, info);
+ var link = component.link;
+ if (!link) // simple rdonly
+ return appendToContent(component, createTitle(component),
+ createParElement(component));
+
+ // with link
var left = document.createElement('a');
- left.setAttribute("class", "col-left");
- left.innerHTML = title;
+ left.classList.add("col-left");
+ left.innerHTML = component.title;
- left.setAttribute("id", name);
- left.setAttribute("name", title);
- left.setAttribute("class", "interactive link");
+ left.id = component.name;
+ left.name = component.title;
+ left.classList.add("interactive", "link");
- var right = document.createElement('span');
- right.setAttribute("class", "col-right");
- right.setAttribute("name", name);
- right.setAttribute("__ctype__", "rdonly");
-
- row = appendToContent(info, left, right);
+ row = appendToContent(component, left, createParElement(component));
row.onclick = function () {
this.style.backgroundColor = "orangered";
left.click();
@@ -184,31 +114,46 @@ function createReadOnlyInstrLink(s, name, title, link, info) {
left.href = link;
}
- row.setAttribute("class", "row clickable");
+ row.classList.add("row", "clickable");
return row;
}
-function createPushButton(s, name, title, info) {
+function create_rdlink_row(s, component) {
+ // Creates row-element containing link AND read-only-item.
+ var name = component.name;
+
+ var left = createTitle(component);
+ left.id = component.name;
+ left.name = component.title; // or setAttribute('name'.. ?
+ left.classList.add("interactive", "link");
+
+ left.onclick = function () {
+ getGroup(s, component.title);
+ }
+ return appendToContent(component, left, createParElement(component));
+}
+
+function create_pushbutton_row(s, component) {
// Creates row-element containing a push button
- var left = createTitle(title, info);
+ var name = component.name;
+ var command = component.command;
+ var left = createTitle(component);
console.log(info);
- left.setAttribute("id", name);
- left.setAttribute("name", title);
+ left.id = component.name;
+ left.name = component.title;
- var right = document.createElement('span');
- right.setAttribute("class", "col-right clickable push-button");
- right.setAttribute("name", name);
- right.setAttribute("__ctype__", "rdonly");
+ var right = createParElement(component);
+ right.classList.add("clickable", "push-button");
- row = appendToContent(info, left, right);
+ row = appendToContent(component, left, right);
right.onclick = function () {
if (writePermission) {
var row = left.parentNode;
right.style.backgroundColor = "orangered";
// Request for command
- sendCommand(s, name);
+ sendCommand(s, command);
} else {
prompt = true;
alertify.confirm("", "You are connected with " + clientTitle
@@ -223,7 +168,7 @@ function createPushButton(s, name, title, info) {
var row = left.parentNode;
row.style.backgroundColor = "orangered";
// Request for command
- sendCommand(s, name);
+ sendCommand(s, command);
prompt = false;
}, function () {
// User decided to cancel
@@ -232,23 +177,23 @@ function createPushButton(s, name, title, info) {
}
}
- row.setAttribute("class", "row");
+ row.classList.add("row");
return row;
}
-function createInput(s, name, title, info) {
+function create_input_row(s, component) {
// Creates row-element containing input-item.
- if (info) {
- var infoBox = createInfo(title, info);
- }
- var left = createTitle(title, info);
+ var name = component.name;
+ var command = component.command;
- var input = document.createElement('input');
- input.setAttribute("type", "text");
- input.setAttribute("class", "input-text");
- input.setAttribute("name", name);
- input.setAttribute("__ctype__", "input");
+ if (info) {
+ var infoBox = createInfo(component);
+ }
+ var left = createTitle(component);
+
+ var input = createParElement(component, 'input', 'input-text');
+ input.type = "text";
input.style.width = "100px";
input.addEventListener("focus", function(evt) {
let elm = evt.target;
@@ -258,7 +203,7 @@ function createInput(s, name, title, info) {
input.onkeydown = function (e) {
if (e.which === 27 || e.key == "Escape") {
// User decided to cancel
- input.value = input.getAttribute("oldValue");
+ input.value = intput.oldValue;
resizeTextfield(input);
var row = left.parentNode;
row.style.backgroundColor = "white";
@@ -266,9 +211,9 @@ function createInput(s, name, title, info) {
}
input.onfocus = function () {
- input.setAttribute("oldValue", input.value);
+ input.oldValue = input.value;
- if (isTouchDevice())
+ if (isTouchDevice)
setTimeout(function () {
posTextfield(s, left);
}, 1);
@@ -280,11 +225,9 @@ function createInput(s, name, title, info) {
}
var row = left.parentNode;
var value = input.value;
- var oldValue = input.getAttribute("oldValue") || value;
- if (!input.hasAttribute("actualValue")) {
- input.setAttribute("actualValue", oldValue);
- }
- var actualValue = input.getAttribute("actualValue");
+ let oldValue = 'oldValue' in input ? input.oldValue : value;
+ if (!('actualValue' in input)) input.actualValue = oldValue;
+ actualValue = input.actualValue;
if (value == actualValue || value == oldValue ||
parseFloat(value) == parseFloat(actualValue) || parseFloat(value) == parseFloat(oldValue)) {
input.value = actualValue;
@@ -306,12 +249,12 @@ function createInput(s, name, title, info) {
}, 3600000);
row.style.backgroundColor = "orangered";
// Request for command
- sendCommand(s, name + " " + value);
+ sendCommand(s, command + " " + value);
resizeTextfield(input);
prompt = false;
}, function () {
// User decided to cancel
- input.value = input.getAttribute("actualValue");
+ input.value = input.actualValue;
resizeTextfield(input);
row.style.backgroundColor = "white";
prompt = false;
@@ -321,16 +264,12 @@ function createInput(s, name, title, info) {
var form = document.createElement('form');
form.onsubmit = function (e) {
e.preventDefault();
- // remove following check: sometimes we want to send a command even with an unchanged value
- //if (input.value === input.getAttribute("oldValue")) {
- // // nothing to do.
- // return false;
- //}
if (writePermission) {
var row = left.parentNode;
row.style.backgroundColor = "orangered";
// Request for command
sendCommand(s, name + " " + input.value);
+ input.blur();
} else {
var value = input.value
prompt = true;
@@ -346,23 +285,21 @@ function createInput(s, name, title, info) {
var row = left.parentNode;
row.style.backgroundColor = "orangered";
// Request for command
- sendCommand(s, name + " " + value);
+ sendCommand(s, command + " " + value);
resizeTextfield(input);
prompt = false;
}, function () {
// User decided to cancel
- input.value = input.getAttribute("oldValue");
+ input.value = input.oldValue;
resizeTextfield(input);
prompt = false;
});
}
};
form.appendChild(input);
-
- var right = document.createElement('span');
- right.setAttribute("class", "col-right");
+ var right = createParElement(component);
right.appendChild(form);
- return appendToContent(info, left, right);
+ return appendToContent(component, left, right);
}
function posTextfield(s, left) {
@@ -383,16 +320,14 @@ function resizeTextfield(input) {
}
}
-function createCheckbox(s, name, title, info) {
+function create_checkbox_row(s, component) {
// Creates row-element containing checkbox-item
+ var command = component.command;
- var left = createTitle(title, info);
+ var left = createTitle(component);
- var input = document.createElement('input');
- input.setAttribute("type", "checkbox");
- input.setAttribute("name", name);
- input.setAttribute("__ctype__", "checkbox");
- input.setAttribute("class", "parameter-checkbox");
+ var input = createParElement(component, 'input', 'parameter-checkbox');
+ input.type = "checkbox";
input.onkeyup = function (e) {
if (e.keyCode === 32) {
@@ -401,8 +336,8 @@ function createCheckbox(s, name, title, info) {
}
var label = document.createElement('label');
- label.setAttribute("for", input);
- label.setAttribute("class", "parameter-label");
+ label.for = input;
+ label.classList.add("parameter-label");
label.onclick = function () {
handleCheckbox();
@@ -420,7 +355,7 @@ function createCheckbox(s, name, title, info) {
input.checked = true;
}
// Request for command
- sendCommand(s, name + " " + value);
+ sendCommand(s, command + " " + value);
} else {
alertify.confirm("", "You are connected with " + clientTitle
+ ".
"
@@ -441,7 +376,7 @@ function createCheckbox(s, name, title, info) {
input.checked = true;
}
// Request for command
- sendCommand(s, name + " " + value);
+ sendCommand(s, command + " " + value);
}, function () {
// User decided to cancel
});
@@ -449,32 +384,32 @@ function createCheckbox(s, name, title, info) {
};
var right = document.createElement('span');
- right.setAttribute("class", "col-right");
+ right.classList.add("col-right");
right.appendChild(input);
right.appendChild(label);
- return appendToContent(info, left, right);
+ return appendToContent(component, left, right);
}
-function createSelection(s, name, title, buttons, info) {
+function create_enum_row(s, component) {
// Creates row-element containing dropdown-selection.
+ var name = component.name;
+ var command = component.command;
+ var buttons = component.enum_names;
- var left = createTitle(title, info);
+ var left = createTitle(component);
- var select = document.createElement('select');
- select.setAttribute("name", name);
- select.setAttribute("__ctype__", "enum");
- select.setAttribute("class", "select-params");
+ var select = createParElement(component, 'select', 'select-params');
select.onfocus = function () {
- select.setAttribute("oldIndex", select.selectedIndex);
+ select.oldIndex = select.selectedIndex;
}
select.oninput = function () {
- if (writePermission && title != "device config") {
+ if (writePermission && component.title != "device config") {
var row = left.parentNode;
row.style.backgroundColor = "orangered";
// Request for command
- sendCommand(s, name + " " + this.value);
+ sendCommand(s, command + " " + this.value);
} else {
alertify.confirm("", "You are connected with " + clientTitle
+ ".
"
@@ -488,38 +423,37 @@ function createSelection(s, name, title, buttons, info) {
var row = left.parentNode;
row.style.backgroundColor = "orangered";
// Request for command
- sendCommand(s, name + " " + select.value);
+ sendCommand(s, command + " " + select.value);
}, function () {
// User decided to cancel
- select.value = select.options[select
- .getAttribute("oldIndex")].value;
+ select.value = select.options[select.oldIndex].value
});
}
};
for (var i = 0; i < buttons.length; i++) {
var option = document.createElement('option');
- option.setAttribute("type", "enum");
- option.setAttribute("class", "option-params");
- option.setAttribute("value", buttons[i].value);
+ option.type = "enum";
+ option.classList.add("option-params");
+ option.value = buttons[i].value;
option.appendChild(document.createTextNode(buttons[i].title));
select.add(option);
}
select.style.display = "none";
var right = document.createElement('span');
- right.setAttribute("class", "col-right");
+ right.classList.add("col-right");
right.appendChild(select);
- return appendToContent(info, left, right);
+ return appendToContent(component, left, right);
}
-function createTitle(title, info) {
+function createTitle(component) {
// Creates left side of row-tag containing title. Title may hold additional
// information, which is shown, when title-tag is clicked.
var left = document.createElement('span');
- if (info) {
- left.setAttribute("class", "col-left event-toggle-info");
+ if (component.info) {
+ left.classList.add("col-left", "event-toggle-info");
left.onclick = function () {
var infoBox = left.parentNode.childNodes[0];
@@ -530,41 +464,48 @@ function createTitle(title, info) {
}
}
- left.innerHTML = title + "(i)";
+ left.innerHTML = component.title + "(i)";
} else {
- left.setAttribute("class", "col-left");
- left.innerHTML = title;
+ left.classList.add("col-left");
+ left.innerHTML = component.title;
}
return left;
}
-function createInfo(info) {
+function createParElement(component, tag='span', cls='col-right') {
+ var right = document.createElement(tag);
+ if (cls)
+ right.classList.add(cls);
+ // right.name = is not sufficient, getElementsByName would not work
+ right.setAttribute('name', component.name);
+ right.__ctype__ = component.type;
+ return right;
+}
+
+function createInfo(component) {
// Creates info-box, which isn't visible by default but can be displayed.
var infoBox = document.createElement('div');
- infoBox.setAttribute("class", "info-box");
+ infoBox.classList.add("info-box");
infoBox.onclick = function () {
infoBox.style.display = "none";
}
- infoBox.innerHTML = info;
+ infoBox.innerHTML = component.info;
return infoBox;
}
-function appendToContent(info, left, right) {
- // Cretees row-tag containing infoBox (not visible by default), left side
- // (span)
- // and right side (span).
+function appendToContent(component, left, right) {
+ // Creates row-tag containing infoBox (not visible by default), left side
+ // (span) and right side (span).
var row = document.createElement('div');
- row.setAttribute("class", "row");
- if (info) {
- row.appendChild(createInfo(info));
+ row.classList.add("row");
+ if (component.info) {
+ row.appendChild(createInfo(component));
}
- for (var i = 1; i < arguments.length; i++)
- if (arguments[i]) {
- row.appendChild(arguments[i]);
- }
+ row.appendChild(left);
+ row.appendChild(right);
return row;
}
diff --git a/client/jsFiles/SEAWebClientMain.js b/client/jsFiles/SEAWebClientMain.js
index 807ee9a..1e8602b 100644
--- a/client/jsFiles/SEAWebClientMain.js
+++ b/client/jsFiles/SEAWebClientMain.js
@@ -2,31 +2,18 @@
// % INIT
var MAXBLOCK = 4; // max number of blocks
-
var elements = []; // grid elements
-
var swiper = []; // This array contains main-swiper-Instances.
-
var hostPort = ""; // Address and port of static html-file.
-
var clientID = ""; // ID given by server when SSE-connection is established.
-
var clientTitle = ""; // Contains name of instrument and device.
-
var getUpdates = true;
-
var getUpdatesGraphics = true;
-
var initCommands = [];
-
var loadingShown = true;
-
var writePermission = false;
-
var menuMode = false;
-
var panelOn = true;
-
var firstState = 0;
function Settings() {
@@ -188,11 +175,11 @@ function toggleHeader() {
panelOn = !panelOn;
if (panelOn) {
header.innerHTML = clientTitle
- /* header.setAttribute("style", "width: auto;"); */
- main_panel.setAttribute("style", "display: block;");
+ /* header.style.width = "auto;"; */
+ main_panel.style.display = "block";
} else {
- /* header.setAttribute("style", "width: 30px;"); */
- main_panel.setAttribute("style", "display: none;");
+ /* header.style.width = "30px"; */
+ main_panel.style.display = "none";
}
return true;
}
diff --git a/client/jsFiles/SEAWebClientResponsivity.js b/client/jsFiles/SEAWebClientResponsivity.js
index e002625..5d5708a 100644
--- a/client/jsFiles/SEAWebClientResponsivity.js
+++ b/client/jsFiles/SEAWebClientResponsivity.js
@@ -2,14 +2,12 @@
// % RESPONSIVITY
var nColumns = 1; // Viewport is subdivided in nColumns columns.
-
var nRows = 1; // Viewport is subdivided in nRows rows.
-
var gridCountGraphics = 2; // Number of displayed graphics-swipers.
-
var MINWIDTH = 400; // Minimal width of block.
-
var MINHEIGHT = 700; // Minimal height of block.
+let paramSlider = [0,1,2,3]; // the number of the parameter slider to open
+let prevActiveSlider = 0;
function createGrid() {
// Creates grid-elements. By default only the first one is shown
@@ -18,7 +16,7 @@ function createGrid() {
var elements = [];
for (var i = 0; i < 4; i++) {
var element = document.createElement('div');
- element.setAttribute("class", "grid-element");
+ element.classList.add("grid-element");
document.getElementById("center").appendChild(element);
elements.push(element);
}
@@ -66,6 +64,8 @@ function adjustGrid() {
|| document.body.clientWidth;
var height = window.innerHeight || document.documentElement.clientHeight
|| document.body.clientHeight;
+ paramSlider = [0,1,2,3];
+ prevActiveSlider = 0;
switch (nColumns) {
case 1:
@@ -134,6 +134,8 @@ function adjustGrid() {
function style(s, width, height) {
if (width) {
+ paramSlider[prevActiveSlider] = s;
+ prevActiveSlider = s;
elements[s].style.display = "inline-block";
elements[s].style.width = width;
} else {
@@ -145,7 +147,4 @@ function style(s, width, height) {
elements[s].style.float = "left";
}
-function isTouchDevice() {
- return !!('ontouchstart' in window)
- || !!('msmaxtouchpoints' in window.navigator);
-};
\ No newline at end of file
+let isTouchDevice = !!('ontouchstart' in window) || !!('msmaxtouchpoints' in window.navigator);
\ No newline at end of file
diff --git a/client/jsFiles/SEAWebClientSwiper.js b/client/jsFiles/SEAWebClientSwiper.js
index bf8ab24..6bc99e0 100644
--- a/client/jsFiles/SEAWebClientSwiper.js
+++ b/client/jsFiles/SEAWebClientSwiper.js
@@ -6,54 +6,63 @@ function insertSwiper(s) {
// 'grid-element' s.
var container = document.createElement('div');
- container.setAttribute("class", "swiper-container swiper-container-main");
+ container.classList.add("swiper-container", "swiper-container-main");
elements[s].appendChild(container);
var swiperwrapper = document.createElement('div');
- swiperwrapper.setAttribute("class", "swiper-wrapper swiper-wrapper-main");
- swiperwrapper.setAttribute("s", s);
+ swiperwrapper.classList.add("swiper-wrapper", "swiper-wrapper-main");
+ swiperwrapper.s = s;
container.appendChild(swiperwrapper);
var paginationWrapper = document.createElement('div');
- paginationWrapper.setAttribute("class", "swiper-pagination");
+ paginationWrapper.classList.add("swiper-pagination");
container.appendChild(paginationWrapper);
var buttonPrev = document.createElement("div");
- buttonPrev.setAttribute("class", "swiper-button-prev swiper-button-black");
+ buttonPrev.classList.add("swiper-button-prev", "swiper-button-black");
var buttonNext = document.createElement("div");
- buttonNext.setAttribute("class", "swiper-button-next swiper-button-black");
+ buttonNext.classList.add("swiper-button-next", "swiper-button-black");
var swiper = new Swiper(container, {
direction : 'horizontal',
pagination: {
el: paginationWrapper,
- clickable: true
+ clickable: true,
},
+ watchOverflow: true,
spaceBetween : 0,
navigation:{
prevEl: buttonPrev,
nextEl: buttonNext
- }
+ },
+ noSwiping: true, // this activates the noSwipingClass functionality
});
//console.log(swiper);
-
- if (!isTouchDevice()) {
- // Add swiper-button.
+ // the graphics slide will disable swiping (use hide box instead)
+ if (isTouchDevice) {
+ function enableSwiping(allow) {
+ swiper.params.noSwipingClass = allow ? null : "swiper-slide-main";
+ }
+ } else {
+ function enableSwiping(allow) {
+ buttonPrev.style.display = allow ? 'block' : 'none';
+ buttonNext.style.display = allow ? 'block' : 'none';
+ }
swiper.params.noSwipingClass = "swiper-slide-main";
container.appendChild(buttonPrev);
container.appendChild(buttonNext);
}
-
+ swiper.enableSwiping = enableSwiping;
return swiper;
}
function findSlide(s, type) {
var i;
for (i = 0; i < swiper[s].slides.length; i++) {
- if (swiper[s].slides[i].getAttribute("slide-type") === type) {
- return swiper[s].slides[i];
+ if (swiper[s].slides[i].slideType === type) {
+ return i;
}
}
return null;
@@ -66,66 +75,67 @@ function replaceSlideContent(slide, title, content) {
}
function insertSlide(s, title, type, content) {
- // Inserts new group to instance s of Swiper.
+ // Inserts new group to instance s of Swiper. return inserted position
- var slide = findSlide(s, type);
+ var isl = findSlide(s, type);
+ var slide = swiper[s].slides[isl];
if (slide) { // slide already exists
replaceSlideContent(slide, title, content);
- return
+ return isl;
}
var panel = document.createElement('div');
- panel.setAttribute("class", "panel");
+ panel.classList.add("panel");
titlewrapper = document.createElement('span');
titlewrapper.innerHTML = title;
panel.appendChild(titlewrapper);
+ /*
if (type == "_overview" || type == "main") {
//panel.appendChild(createHomeButton(s));
} else if (type != "graphics" && type != "_inst_select" && type != "console") {
panel.appendChild(createCloseButton(s));
}
+ */
/*if (type === "graphics") {
panel.appendChild(createUpdateButton(s));
}*/
slide = document.createElement('div');
- slide.setAttribute("class", "swiper-slide swiper-slide-main");
+ slide.classList.add("swiper-slide", "swiper-slide-main");
// Store type so it can be found easiely later.
- slide.setAttribute("slide-type", type);
+ slide.slideType = type;
slide.appendChild(panel);
slide.appendChild(content);
// Graphics-slide is put at mostleft position.
if (type == "graphics" || type == "_overview") {
- // Remove old graphics-slide.
- /* see above
- if(swiper[0].slides[0].getAttribute("slide-type") === "graphics"){
- swiper[0].removeSlide(0);
- }
- */
swiper[s].prependSlide(slide);
swiper[s].slideTo(0);
- } else if (type == "console") {
- swiper[s].appendSlide(slide);
+ return 0;
+ }
+ swiper[s].appendSlide(slide);
+ if (type == "console") {
if (s === 3) {
// Slide mostright swiper-instance to last position (console)
swiper[3].slideNext();
}
- } else {
- swiper[s].appendSlide(slide);
- if (swiper[s].slides.length > 1) {
- var consoleslide = swiper[s].slides[swiper[s].slides.length - 2];
- if (consoleslide.getAttribute["slide-type"] == "console") {
- // shift Console-slide to mostright position.
- swiper[s].removeSlide(swiper[s].slides.length - 2);
- swiper[s].appendSlide(consoleslide);
- // Slide to position of new slide
- swiper[s].slideTo(swiper[s].slides.length - 2);
- } else {
- swiper[s].slideTo(swiper[s].slides.length - 1);
- }
+ return swiper[s].slides.length - 1;
+ }
+ let pos = 0;
+ if (swiper[s].slides.length > 1) {
+ var consoleslide = swiper[s].slides[swiper[s].slides.length - 2];
+ if (consoleslide.slideType == "console") {
+ // shift Console-slide to mostright position.
+ swiper[s].removeSlide(swiper[s].slides.length - 2);
+ swiper[s].appendSlide(consoleslide);
+ // Slide to position of new slide
+ pos = swiper[s].slides.length - 2;
+ } else {
+ pos = swiper[s].slides.length - 1;
}
}
+ swiper[s].slideTo(pos);
+ return pos;
}
function createCloseButton(s) {
@@ -143,7 +153,7 @@ function createCloseButton(s) {
function createUpdateButton(s){
// Creates 'span'-element containing update-button (Should be removed later!)
var button = document.createElement('span');
- button.setAttribute("class","interactive toggle-updates-graphics")
+ button.classList.add("interactive", "toggle-updates-graphics")
button.onclick = function () {
getUpdatesGraphics = ! getUpdatesGraphics;
button.innerHTML = "updates = "+getUpdatesGraphics;
@@ -162,7 +172,7 @@ function getSlideNames() {
var sw = swiper[s];
var name = "";
if (sw.activeIndex != defaultSlidePos(s)) {
- name = sw.slides[sw.activeIndex].getAttribute("slide-type");
+ name = sw.slides[sw.activeIndex].slideType;
}
names.push();
}
diff --git a/histgraph.py b/histgraph.py
index 2facfde..80b95c7 100644
--- a/histgraph.py
+++ b/histgraph.py
@@ -117,31 +117,28 @@ def get_vars(main, time):
time, = get_abs_time(time)
# get last value only
- curves = main.get_curves(['$vars'], (time, time))
- for _, value in curves['$vars'].get():
- for var in value.split():
- vars = var.split("|")
- if len(vars) == 1:
- vars.append("")
- if len(vars) == 2:
- vars.append(vars[0])
- if len(vars) == 3:
- vars.append("")
- if len(vars) == 4:
- vars.append("") # exact flag
- name, unit, label, color, continuous = vars
- continuous = int(continuous) if continuous else 0
- if not unit in result:
- result[unit] = dict(tag=unit, unit=unit.split("_")[0], curves=Dict())
- result[unit]["curves"][name] = dict(name=name, label=label, color=color, continuous=continuous)
+ curves = main.get_curve_options((time, time))
+ for key, opts in curves.items():
+ if not opts.get('show', False):
+ continue
+ print(key, opts)
+ opts = dict(name=key, **opts)
+ unit = opts.pop('unit', '*')
+ tag = opts.pop('tag', unit)
+ if not tag in result:
+ result[tag] = dict(tag=tag, unit=unit, curves=Dict())
+ if 'label' not in opts:
+ opts['label'] = opts['name']
+ result[tag]["curves"][key] = opts
- for unit, curvegroup in result.items():
+ # determine colors
+ for _, curvegroup in result.items():
color_set = set()
auto_curves = []
curve_list = list(curvegroup["curves"].values())
curvegroup['curves'] = curve_list
for curve in curve_list:
- col = curve["color"].strip()
+ col = curve.get("color", '').strip()
c = ColorMap.to_code(col)
if c < 0:
valid = ColorMap.check_hex(col)
@@ -166,8 +163,9 @@ def get_vars(main, time):
def get_curves(main, keys, timerange, cut_begin=True):
curves = main.get_curves(keys, get_abs_time(*timerange), maxpoints=500, cut_begin=cut_begin)
+ opts = main.get_curve_options(timerange, keys)
#if 'tt:target' in curves:
# print('---')
# print(curves['tt:target'].fmtm())
# print('TT', curves['tt:target'].for_json()[-5:])
- return {k: c.for_json() for k, c in curves.items()}
+ return {k: c.for_json(opts.get(k, {}).get('period', 0)) for k, c in curves.items()}
diff --git a/seaweb_hist.py b/seaweb_hist.py
index 26d8959..058d507 100755
--- a/seaweb_hist.py
+++ b/seaweb_hist.py
@@ -25,8 +25,8 @@ import circularlog
import os
import signal
from histgraph import get_vars, get_curves, get_abs_time
-from histreader import MainCache
-from frappyreader import FrappyReader
+from histreader.histreader import MainCache
+from histreader.frappyreader import FrappyReader
try: import simplejson as json
except ImportError: import json
@@ -668,7 +668,7 @@ class DummyClient(SeaGraph):
self.syncreply = []
SeaGraph.__init__(self)
- def cmd_reply(self, command, replytype, tmo=5):
+ def cmd_reply(self, command, replytype=None, tmo=5):
self.linesocket.send_line(json.dumps(command))
t = 0
while True:
@@ -689,7 +689,7 @@ class DummyClient(SeaGraph):
raise Exception("timeout")
gevent.sleep(0.1)
t += 0.1
- if msg.type != replytype:
+ if replytype and msg.type != replytype:
logging.error('REPLY MISMATCH %s %s <> %s', command, replytype, msg.type)
return msg
@@ -788,8 +788,14 @@ def SecopEncode(cmd, par=None, value=None):
def convert_par(module, name, par):
result = dict(type='input', name=module+":"+name, title=name)
if par.get('readonly', True):
- result['type']='rdonly'
- #print result
+ result['type'] = 'rdonly'
+ else:
+ result['command'] = 'change %s:%s' % (module, name)
+ if par['datainfo']['type'] == 'enum':
+ result['enum_names'] = [dict(title=k, value=v) for k, v in par['datainfo']['members'].items()]
+ result['type'] = 'enum'
+ elif par['datainfo']['type'] == 'bool':
+ result['type'] = 'checkbox'
return result
@@ -799,7 +805,8 @@ def convert_event(messages):
updates = []
for msg in messages:
if msg.type == 'update':
- updates.append(dict(name=msg.par, value=str(msg.value[0])))
+ updates.append(dict(name=msg.par, value=msg.value[0]))
+ # updates.append(dict(name=msg.par, value=str(msg.value[0])))
return [dict(type='update', updates=updates)]
@@ -822,7 +829,7 @@ class SecopClient(SeaGraph):
self.idn = idn.value
self.description = self.cmd_reply("describe", "describing").value
- def cmd_reply(self, command, replytype, tmo=5):
+ def cmd_reply(self, command, replytype=None, tmo=5):
logging.info('COMMAND %r', command)
self.replytype = replytype
if self.out: self.out.write(">"+command+"\n")
@@ -855,7 +862,7 @@ class SecopClient(SeaGraph):
logging.info('REPLY %r', msg)
if msg.type.startswith('error_'):
return {}
- if not replytype.startswith(msg.type):
+ if replytype and not replytype.startswith(msg.type):
logging.error('REPLY MISMATCH %s <> %s', replytype, repr(msg))
self.replytype = ""
return msg
@@ -872,7 +879,7 @@ class SecopClient(SeaGraph):
return dict(type='draw', path='main', title='modules', components=components)
else:
module = self.description['modules'][path]
- parameters = module["accessibles"]
+ parameters = dict(module["accessibles"])
components = []
for name in SecopClient.skip_par:
if name in parameters:
@@ -900,8 +907,7 @@ class SecopClient(SeaGraph):
def w_sendcommand(self, command):
if not command:
return dict(type='accept-command')
- cmd = "change " + command
- self.cmd_reply(cmd, 'changed ' + command.split(' ')[0])
+ self.cmd_reply(command)
return dict(type='accept-command')
def poll(self):
@@ -1006,8 +1012,8 @@ if __name__ == '__main__':
instrument_config = instrument_list[inst_name]
- frd = FrappyReader('/Users/zolliker/frappyhist/%s' % inst_name, gevent=gevent)
- main_cache = MainCache('/Users/zolliker/frappyhist/%s_hist' % inst_name, frd, gevent=gevent)
+ frd = FrappyReader('/Users/zolliker/frappyhist/%s' % inst_name)
+ main_cache = MainCache('/Users/zolliker/frappyhist/%s_hist' % inst_name, frd, spawn_worker=False)
# logging.basicConfig(filename=inst_name+".log", filemode='w', level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')