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')