1/********************************************************************\
4 Created by: Stefan Ritt
6 Contents: JavaScript image history routines
8 Note: please load midas.js and mhttpd.js before mihistory.js
10 \********************************************************************/
16function mihistory_init() {
17 // go through all data-name="mhistory" tags
18 let mhist = Array.from(document.getElementsByTagName("div")).filter(d => {
19 return d.className === "mjsihistory";
22 let baseURL = window.location.href;
23 if (baseURL.indexOf("?cmd") > 0)
24 baseURL = baseURL.substr(0, baseURL.indexOf("?cmd"));
25 baseURL += "?cmd=history";
27 for (let i = 0; i < mhist.length; i++) {
28 mhist[i].dataset.baseURL = baseURL;
29 mhist[i].mhg = new MihistoryGraph(mhist[i]);
30 mhist[i].mhg.initializeIPanel(i);
31 mhist[i].mhg.resize();
32 mhist[i].resize = function () {
38function mihistory_create(parentElement, baseURL, panel, index) {
39 let d = document.createElement("div");
40 parentElement.appendChild(d);
41 d.dataset.baseURL = baseURL;
42 d.dataset.panel = panel;
43 d.mhg = new MihistoryGraph(d);
44 d.mhg.initializeIPanel(index);
48function getUrlVars() {
50 window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
56function MihistoryGraph(divElement) { // Constructor
58 // create canvas inside the div
59 this.parentDiv = divElement;
60 this.baseURL = divElement.dataset.baseURL;
61 this.panel = divElement.dataset.panel;
63 this.imageElem = document.createElement("img");
64 this.imageElem.style.border = "2px solid #808080";
65 this.imageElem.style.margin = "auto";
66 this.imageElem.style.display = "block";
67 this.imageElem.id = "hiImage_" + this.panel;
69 this.buttonCanvas = document.createElement("canvas");
70 this.buttonCanvas.width = 30;
71 this.buttonCanvas.height = 30;
72 this.buttonCanvas.style.position = "absolute";
73 this.buttonCanvas.style.top = "0px";
74 this.buttonCanvas.style.right = "0px";
75 this.buttonCanvas.style.zIndex = "11";
77 this.axisCanvas = document.createElement("canvas");
79 this.fileLabel = document.createElement("div");
80 this.fileLabel.id = "fileLabel_" + this.panel;
81 this.fileLabel.style.backgroundColor = "white";
82 this.fileLabel.style.opacity = "0.7";
83 this.fileLabel.style.fontSize = "10px";
84 this.fileLabel.style.padding = "2px";
85 this.fileLabel.innerHTML = "";
87 divElement.style.position = "relative";
88 this.fileLabel.style.position = "absolute";
89 this.fileLabel.style.top = "4px";
90 this.fileLabel.style.right = "34px";
91 this.fileLabel.style.zIndex = "10";
93 divElement.appendChild(this.imageElem);
94 divElement.appendChild(this.fileLabel);
95 divElement.appendChild(this.buttonCanvas);
96 divElement.appendChild(this.axisCanvas);
100 background: "#FFFFFF",
107 this.tScale = 8*3600;
108 this.tMax = Math.floor(new Date() / 1000);
109 this.tMin = this.tMax - this.tScale;
111 this.showZoomButtons = true;
112 this.currentTime = 0;
113 this.currentIndex = 0;
115 this.updatePaused = false;
117 // overwrite scale from URL if present
118 this.requestedTime = Math.floor(decodeURI(getUrlVars()["T"]));
119 if (!Number.isNaN(this.requestedTime)) {
120 this.tMax = this.requestedTime;
121 this.tMin = this.requestedTime - this.tScale;
124 // callbacks when certain actions are performed.
125 // All callback functions should accept a single parameter, which is the
126 // MhistoryGraph object that triggered the callback.
128 resetAxes: undefined,
130 jumpToCurrent: undefined
136 src: "maximize-2.svg",
137 title: "Show only this plot",
138 click: function (t) {
139 window.location.href = t.baseURL + "&group=" + "Images" + "&panel=" + t.panel;
143 src: "chevrons-left.svg",
144 title: "Play backwards",
145 click: function (t) {
156 src: "chevron-left.svg",
157 title: "Step one image back",
158 click: function (t) {
161 if (t.currentIndex > 0)
169 title: "Stop playing",
170 click: function (t) {
177 src: "chevron-right.svg",
178 title: "Step one image forward",
179 click: function (t) {
182 if (t.currentIndex < t.imageArray.length-1)
193 src: "chevrons-right.svg",
194 title: "Play forward",
195 click: function (t) {
207 title: "Zoom in time axis",
208 click: function (t) {
211 t.drag.Vt = 0 // stop inertia
213 if (t.callbacks.timeZoom !== undefined)
214 t.callbacks.timeZoom(t);
219 title: "Zoom out time axis",
220 click: function (t) {
223 t.drag.Vt = 0 // stop inertia
227 if (t.callbacks.timeZoom !== undefined)
228 t.callbacks.timeZoom(t);
233 title: "Jump to last image",
234 click: function (t) {
237 t.drag.Vt = 0; // stop inertia
239 t.currentIndex = t.imageArray.length-1;
242 if (t.callbacks.jumpToCurrent !== undefined)
243 t.callbacks.jumpToCurrent(t);
248 title: "Select time...",
249 click: function (t) {
251 let currentYear = new Date().getFullYear();
252 let dCur = new Date(t.currentTime * 1000);
254 if (document.getElementById('y1').length === 0) {
255 for (let i = currentYear; i > currentYear - 5; i--) {
256 let o = document.createElement('option');
257 o.value = i.toString();
258 o.appendChild(document.createTextNode(i.toString()));
259 document.getElementById('y1').appendChild(o);
263 document.getElementById('m1').selectedIndex = dCur.getMonth();
264 document.getElementById('d1').selectedIndex = dCur.getDate() - 1;
265 document.getElementById('h1').selectedIndex = dCur.getHours();
266 document.getElementById('y1').selectedIndex = currentYear - dCur.getFullYear
268 document.getElementById('dlgQueryQuery').onclick = function () {
272 dlgShow("dlgQueryT");
277 title: "Download current image...",
278 click: function (t) {
283 src: "help-circle.svg",
286 dlgShow("dlgIHelp", false);
292 dlgLoad('dlgIHistory.html');
295 this.button.forEach(b => {
297 b.img.src = "icons/" + b.src;
300 // mouse event handlers
301 divElement.addEventListener("mousedown", this.mouseEvent.bind(this), true);
302 divElement.addEventListener("dblclick", this.mouseEvent.bind(this), true);
303 divElement.addEventListener("mousemove", this.mouseEvent.bind(this), true);
304 divElement.addEventListener("touchstart", this.mouseEvent.bind(this), true);
305 divElement.addEventListener("touchmove", this.mouseEvent.bind(this), true);
306 divElement.addEventListener("touchend", this.mouseEvent.bind(this), true);
307 divElement.addEventListener("touchcancel", this.mouseEvent.bind(this), true);
308 divElement.addEventListener("mouseup", this.mouseEvent.bind(this), true);
309 divElement.addEventListener("wheel", this.mouseWheelEvent.bind(this), true);
311 // Keyboard event handler (has to be on the window!)
312 window.addEventListener("keydown", this.keyDown.bind(this));
315function timeToSec(str) {
316 let s = parseFloat(str);
317 switch (str[str.length - 1]) {
335function doQueryT(t) {
337 dlgHide('dlgQueryT');
340 document.getElementById('y1').value,
341 document.getElementById('m1').selectedIndex,
342 document.getElementById('d1').selectedIndex + 1,
343 document.getElementById('h1').selectedIndex);
348 let tm = d.getTime() / 1000;
350 t.tMin = tm - t.tScale;
352 if (tm < t.imageArray[0].time) {
353 t.requestedTime = tm;
356 t.setCurrentIndex(tm);
359 if (t.callbacks.timeZoom !== undefined)
360 t.callbacks.timeZoom(t);
364MihistoryGraph.prototype.keyDown = function (e) {
365 if (e.key === "u") { // 'u' key
372MihistoryGraph.prototype.initializeIPanel = function (index) {
375 this.panel = this.parentDiv.dataset.panel;
377 if (this.panel === undefined) {
378 dlgMessage("Error", "Definition of \'dataset-panel\' missing for image history panel \'" + this.parentDiv.id + "\'. " +
379 "Please use syntax:<br /><br /><b><div class=\"mjsihistory\" " +
380 "data-group=\"<Group>\" data-panel=\"<Panel>\"></div></b>", true);
384 if (this.panel === "")
388 this.marker = {active: false};
389 this.drag = {active: false};
390 this.data = undefined;
391 this.pendingUpdates = 0;
394 this.imageArray = [];
396 // pause main refresh for a moment
397 mhttpd_refresh_pause(true);
398 this.updatePaused = true;
400 // retrieve panel definition from ODB
401 mjsonrpc_db_copy(["/History/Images/" + this.panel]).then(function (rpc) {
402 if (rpc.result.status[0] !== 1) {
403 dlgMessage("Error", "Image \'" + this.panel + "\' not found in ODB", true)
405 this.odb = rpc.result.data[0];
406 this.loadInitialData();
408 }.bind(this)).catch(function (error) {
409 if (error.xhr !== undefined)
410 mjsonrpc_error_alert(error);
416MihistoryGraph.prototype.setCurrentIndex = function(t) {
417 let tmin = Math.abs(t - this.imageArray[0].time);
419 for (let i = 0; i < this.imageArray.length; i++) {
420 if (Math.abs(t - this.imageArray[i].time) < tmin) {
421 tmin = Math.abs(t - this.imageArray[i].time);
425 this.currentIndex = imin;
429MihistoryGraph.prototype.loadInitialData = function () {
431 // get time scale from ODB
432 this.tScale = timeToSec(this.odb["Timescale"]);
434 // overwrite via <div ... data-scale=<value> >
435 if (this.parentDiv.dataset.scale !== undefined)
436 this.tScale = timeToSec(this.parentDiv.dataset.scale);
438 if (!Number.isNaN(this.requestedTime)) {
439 this.tMax = this.requestedTime;
440 this.tMin = this.requestedTime - this.tScale;
442 this.tMinRequested = this.tMax;
444 this.tMax = Math.floor(new Date() / 1000);
445 this.tMin = this.tMax - this.tScale;
446 this.tMinRequested = this.tMax;
449 let table = document.createElement("table");
455 mjsonrpc_call("hs_image_retrieve",
458 "start_time": Math.floor(this.tMax),
459 "end_time": Math.floor(this.tMax),
461 .then(function (rpc) {
463 this.receiveData(rpc);
466 this.updateTimer = window.setTimeout(this.update.bind(this), 1000);
467 this.scrollTimer = window.setTimeout(this.scrollRedraw.bind(this), 1000);
469 }.bind(this)).catch(function (error) {
470 mjsonrpc_error_alert(error);
475MihistoryGraph.prototype.loadOldData = function () {
477 if (this.tMin - this.tScale / 2 < this.tMinRequested) {
479 let oldTMinRequested = this.tMinRequested;
480 this.tMinRequested = this.tMin - this.tScale;
482 this.pendingUpdates++;
483 this.parentDiv.style.cursor = "progress";
484 mjsonrpc_call("hs_image_retrieve",
487 "start_time": Math.floor(this.tMinRequested),
488 "end_time": Math.floor(oldTMinRequested),
490 .then(function (rpc) {
492 this.pendingUpdates--;
493 if (this.pendingUpdates === 0)
494 this.parentDiv.style.cursor = "default";
496 this.receiveData(rpc);
500 .catch(function (error) {
501 mjsonrpc_error_alert(error);
508MihistoryGraph.prototype.loadNextImage = function () {
509 // look from current image backwards for first image not loaded
511 let nParallel = 10; // number of parallel loads
512 for (let i = this.currentIndex; i >= 0; i--) {
513 if (this.imageArray[i].image.src === undefined || this.imageArray[i].image.src === "") {
514 // load up to one window beyond current window
515 if (this.imageArray[i].time > this.tMin - this.tScale) {
516 this.imageArray[i].image.onload = function () {
519 this.imageArray[i].image.mhg = this;
520 this.imageArray[i].image.src = this.panel + "/" + this.imageArray[i].image_name;
522 if (n === nParallel) {
523 this.imageArray[i].image.onload = function () {
525 this.mhg.loadNextImage();
533 // now check images AFTER currentIndex, like if we start with URL T=...
534 for (let i = this.imageArray.length-1 ; i >= 0; i--)
535 if (this.imageArray[i].image.src === undefined || this.imageArray[i].image.src === "") {
536 this.imageArray[i].image.mhg = this;
537 this.imageArray[i].image.src = this.panel + "/" + this.imageArray[i].image_name;
539 if (n === nParallel) {
540 this.imageArray[i].image.onload = function () {
541 this.mhg.loadNextImage();
548MihistoryGraph.prototype.receiveData = function (rpc) {
550 if (rpc.result.data.time.length > 0) {
551 let first = (this.imageArray.length === 0);
556 // append new values to end of array
557 for (let i = 0; i < rpc.result.data.time.length; i++) {
559 time: rpc.result.data.time[i],
560 image_name: rpc.result.data.filename[i],
563 if (this.imageArray.length === 0 ||
564 img.time > this.imageArray[this.imageArray.length - 1].time) {
565 this.imageArray.push(img);
567 lastImageIndex = this.imageArray.length - 1;
568 } else if (img.time < this.imageArray[0].time) {
574 // add new entries to the left side of the array
575 this.imageArray = i1.concat(this.imageArray);
576 this.currentIndex += i1.length;
580 this.currentIndex = this.imageArray.length - 1;
582 if (!Number.isNaN(this.requestedTime)) {
583 this.setCurrentIndex(this.requestedTime);
584 this.requestedTime = NaN;
588 // after loading of fist image, resize panel
589 let img = this.imageArray[this.currentIndex];
590 img.image.onload = function () {
591 this.mhg.imageElem.src = this.src;
592 this.mhg.imageElem.initialWidth = this.width;
593 this.mhg.imageElem.initialHeight = this.height;
595 if (this.mhg.tMinRequested === 0)
596 this.mhg.tMinRequested = this.mhg.tMax;
598 // all done, so resume updates
599 mhttpd_refresh_pause(false);
600 this.mhg.updatePaused = false;
602 img.image.mhg = this;
603 // trigger loading of image
604 img.image.src = this.panel + "/" + img.image_name;
607 this.loadNextImage();
612MihistoryGraph.prototype.update = function () {
614 // don't update if we are paused
615 if (this.updatePaused) {
616 this.updateTimer = window.setTimeout(this.update.bind(this), 1000);
620 // don't update window if content is hidden (other tab, minimized, etc.)
621 if (document.hidden) {
622 this.updateTimer = window.setTimeout(this.update.bind(this), 500);
626 let t = Math.floor(new Date() / 1000);
627 let tStart = this.imageArray[this.imageArray.length-1].time+1;
629 mjsonrpc_call("hs_image_retrieve",
632 "start_time": Math.floor(tStart),
633 "end_time": Math.floor(t),
635 .then(function (rpc) {
637 this.receiveData(rpc);
640 this.updateTimer = window.setTimeout(this.update.bind(this), 1000);
642 }.bind(this)).catch(function (error) {
643 mjsonrpc_error_alert(error);
648MihistoryGraph.prototype.scrollRedraw = function () {
650 this.tMax = Math.floor(new Date() / 1000);
651 this.tMin = this.tMax - this.tScale;
655 this.scrollTimer = window.setTimeout(this.scrollRedraw.bind(this), 1000);
658MihistoryGraph.prototype.play = function () {
659 window.clearTimeout(this.playTimer);
661 let oldIndex = this.currentIndex;
663 if (this.playMode > 0) {
664 this.currentIndex += this.playMode;
665 if (this.currentIndex >= this.imageArray.length-1) {
666 this.currentIndex = this.imageArray.length - 1;
674 if (this.playMode < 0) {
675 this.currentIndex += this.playMode;
676 if (this.currentIndex < 0) {
677 this.currentIndex = 0;
683 if (!this.imageArray[this.currentIndex].image.complete)
684 this.currentIndex = oldIndex;
686 // shift time axis according to current image
687 this.tMax = this.imageArray[this.currentIndex].time;
688 this.tMin = this.tMax - this.tScale;
693 if (this.callbacks.timeZoom !== undefined)
694 this.callbacks.timeZoom(this);
696 if (this.playMode === 0)
699 this.playTimer = window.setTimeout(this.play.bind(this), 100);
702MihistoryGraph.prototype.mouseEvent = function (e) {
704 if (e.touches !== undefined) {
705 if (e.touches.length > 1)
709 // fix buttons for IE
710 if (!e.which && e.button) {
711 if ((e.button & 1) > 0) e.which = 1; // Left
712 else if ((e.button & 4) > 0) e.which = 2; // Middle
713 else if ((e.button & 2) > 0) e.which = 3; // Right
716 // discard pinch and zoom
717 if (e.type === "touchstart" || e.type === "touchmove" ||
718 e.type === "touchend" || e.type === "touchcancel") {
719 if (e.touches.length > 1)
723 // calculate mouse coordinates relative to history panel
724 let rect = document.getElementById("hiImage_" + this.panel).parentElement.getBoundingClientRect();
727 if (e.type === "touchstart" || e.type === "touchmove" ||
728 e.type === "touchend" || e.type === "touchcancel") {
729 mouseX = Math.floor(e.changedTouches[e.changedTouches.length - 1].clientX - rect.left);
730 mouseY = Math.floor(e.changedTouches[e.changedTouches.length - 1].clientY - rect.top);
732 mouseX = e.clientX - rect.left;
736 let cursor = this.pendingUpdates > 0 ? "progress" : "default";
739 if (e.type === "mousedown" || e.type === "touchstart") {
744 if (e.target === this.buttonCanvas) {
745 this.button.forEach(b => {
746 if (mouseY > b.y1 && mouseY < b.y1 + b.width &&
756 cursor = "ew-resize";
758 this.drag.active = true;
761 this.drag.xStart = mouseX;
762 this.drag.tStart = this.xToTime(mouseX);
763 this.drag.tMinStart = this.tMin;
764 this.drag.tMaxStart = this.tMax;
767 } else if (e.type === "mouseup" || e.type === "touchend" || e.type === "touchcancel") {
769 if (this.drag.active) {
770 this.drag.active = false;
771 let now = new Date().getTime();
772 if (this.drag.lastDt !== undefined && now - this.drag.lastT !== 0)
773 this.drag.Vt = this.drag.lastDt / (now - this.drag.lastT);
776 this.drag.lastMoveT = now;
777 window.setTimeout(this.inertia.bind(this), 50);
780 } else if (e.type === "mousemove" || e.type === "touchmove") {
782 // change cursor to arrow over image and axis
783 cursor = "ew-resize";
785 if (this.drag.active) {
788 cursor = "ew-resize";
789 let dt = (mouseX - this.drag.xStart) / (this.x2 - this.x1) * (this.tMax - this.tMin);
790 this.tMin = this.drag.tMinStart - dt;
791 this.tMax = this.drag.tMaxStart - dt;
792 this.drag.lastDt = (mouseX - this.drag.lastOffsetX) / (this.x2 - this.x1) * (this.tMax - this.tMin);
793 this.drag.lastT = new Date().getTime();
794 this.drag.lastOffsetX = mouseX;
796 // don't go into the future
797 if (this.tMax > this.drag.lastT / 1000) {
798 this.tMax = this.drag.lastT / 1000;
799 this.tMin = this.tMax - (this.drag.tMaxStart - this.drag.tMinStart);
802 // seach image closest to current time
803 if (this.imageArray.length > 0)
804 this.setCurrentIndex(this.tMax);
808 if (this.callbacks.timeZoom !== undefined)
809 this.callbacks.timeZoom(this);
812 // change cursor to pointer over buttons
813 if (e.target === this.buttonCanvas) {
814 this.button.forEach(b => {
815 if (e.offsetX > b.x1 && e.offsetY > b.y1 &&
816 e.offsetX < b.x1 + b.width && e.offsetY < b.y1 + b.height) {
823 if (this.showZoomButtons) {
824 // check for zoom buttons
825 if (e.offsetX > this.width - 30 - 48 && e.offsetX < this.width - 30 - 24 &&
826 e.offsetY > this.y1 - 24 && e.offsetY < this.y1) {
830 if (e.offsetX > this.width - 30 - 24 && e.offsetX < this.width - 30 &&
831 e.offsetY > this.y1 - 24 && e.offsetY < this.y1) {
837 } else if (e.type === "dblclick") {
841 this.parentDiv.title = title;
842 this.parentDiv.style.cursor = cursor;
844 if (e.touches !== undefined) {
845 if (e.type === "touchmove" && e.touches.length === 1)
848 // for all mouse events
852MihistoryGraph.prototype.mouseWheelEvent = function (e) {
854 // only use horizontal events
863 this.tMax += e.deltaX * 5;
864 this.tMin = this.tMax - this.tScale;
866 // don't go into the future
867 let t = new Date().getTime();
868 if (this.tMax > t / 1000) {
869 this.tMax = t / 1000;
870 this.tMin = this.tMax - this.tScale;
873 // search image closest to current time
874 if (this.imageArray.length > 0)
875 this.setCurrentIndex(this.tMax);
880MihistoryGraph.prototype.inertia = function () {
881 if (this.drag.Vt !== 0) {
882 let now = new Date().getTime();
883 let dt = now - this.drag.lastMoveT;
884 this.drag.lastMoveT = now;
886 this.tMin -= this.drag.Vt * dt;
887 this.tMax -= this.drag.Vt * dt;
889 this.drag.Vt = this.drag.Vt * 0.85;
890 if (Math.abs(this.drag.Vt) < 0.005) {
896 if (this.callbacks.timeZoom !== undefined)
897 this.callbacks.timeZoom(this);
899 if (this.drag.Vt !== 0)
900 window.setTimeout(this.inertia.bind(this), 50);
904MihistoryGraph.prototype.setTimespan = function (tMin, tMax, scroll) {
907 this.scroll = scroll;
908 this.setCurrentIndex(tMax);
912MihistoryGraph.prototype.resize = function () {
913 this.width = this.parentDiv.clientWidth;
914 this.height = this.parentDiv.clientHeight;
916 this.axisCanvas.width = this.width;
917 this.axisCanvas.height = 30;
921 if (this.imageElem.initialWidth > 0) {
922 iAR = this.imageElem.initialWidth / this.imageElem.initialHeight;
923 vAR = this.width / (this.height - 30);
927 this.imageElem.width = this.width;
928 this.imageElem.height = this.height - 30;
931 this.imageElem.height = this.height - 30 - 4;
932 this.imageElem.width = (this.height - 30) * iAR;
934 this.imageElem.width = this.width-4;
935 this.imageElem.height = this.width / iAR;
938 if (this.imageElem.height + 30 < this.parentDiv.clientHeight) {
939 let diff = this.parentDiv.clientHeight - (this.imageElem.height + 30);
940 this.parentDiv.style.height = (parseInt(this.parentDiv.style.height) - diff) + "px";
941 if (this.parentDiv.parentNode.tagName === "TD") {
942 this.parentDiv.parentNode.style.height = this.parentDiv.style.height;
946 this.buttonCanvas.height = this.imageElem.height;
951MihistoryGraph.prototype.redraw = function () {
952 let f = this.draw.bind(this);
953 window.requestAnimationFrame(f);
956MihistoryGraph.prototype.timeToX = function (t) {
957 return (t - this.tMin) / (this.tMax - this.tMin) * (this.x2 - this.x1) + this.x1;
960MihistoryGraph.prototype.xToTime = function (x) {
961 return (x - this.x1) / (this.x2 - this.x1) * (this.tMax - this.tMin) + this.tMin;
964MihistoryGraph.prototype.yToValue = function (y) {
965 return (this.y1 - y) / (this.y1 - this.y2) * (this.yMax - this.yMin) + this.yMin;
968function convertLastWritten(last) {
970 return "no data available";
972 let d = new Date(last * 1000).toLocaleDateString(
974 day: '2-digit', month: 'short', year: '2-digit',
975 hour12: false, hour: '2-digit', minute: '2-digit'
979 return "last data: " + d;
982MihistoryGraph.prototype.updateURL = function() {
983 if (this.currentTime > 0) {
984 let url = window.location.href;
985 if (url.search("&T=") !== -1)
986 url = url.slice(0, url.search("&T="));
987 url += "&T=" + Math.round(this.currentTime);
989 if (url !== window.location.href)
990 window.history.replaceState({}, "Image History", url);
994MihistoryGraph.prototype.draw = function () {
996 // set image from this.currentIndex
997 if (this.imageArray.length > 0) {
998 if (this.imageElem.src !== this.imageArray[this.currentIndex].image.src)
999 this.imageElem.src = this.imageArray[this.currentIndex].image.src;
1000 if (!this.imageArray[this.currentIndex].image.complete)
1001 this.fileLabel.innerHTML = "Loading " + this.imageArray[this.currentIndex].image_name;
1003 this.fileLabel.innerHTML = this.imageArray[this.currentIndex].image_name;
1004 this.currentTime = this.imageArray[this.currentIndex].time;
1007 // check for valid axis
1008 if (this.tMax === undefined || Number.isNaN(this.tMax))
1010 if (this.tMin === undefined || Number.isNaN(this.tMin))
1013 // don't go into the future
1014 let t = new Date().getTime();
1015 if (this.tMax > t / 1000) {
1016 this.tMax = t / 1000;
1017 this.tMin = this.tMax - this.tScale;
1023 this.x2 = this.width-30;
1026 let ctx = this.axisCanvas.getContext("2d");
1027 ctx.fillStyle = this.color.background;
1028 ctx.fillRect(0, 0, this.width, this.height);
1030 ctx.strokeStyle = this.color.axis;
1031 ctx.drawLine(this.x1, 8, this.x2, 8);
1032 ctx.strokeStyle = "#C00000";
1033 ctx.drawLine(this.x2, 0, this.x2, 30);
1035 ctx.strokeStyle = this.color.axis;
1036 this.drawTAxis(ctx, this.x1, 8, this.x2 - this.x1, this.width,
1037 4, 7, 10, 10, this.tMin, this.tMax);
1039 // marks on time axis
1040 for (let i=0 ; i<this.imageArray.length ; i++) {
1041 let x = this.timeToX(this.imageArray[i].time);
1042 if (this.imageArray[i].image.src !== "" && this.imageArray[i].image.complete)
1043 ctx.strokeStyle = this.color.mark;
1045 ctx.strokeStyle = this.color.axis;
1047 ctx.drawLine(x, 0, x, 8);
1051 ctx = this.buttonCanvas.getContext("2d");
1052 if (this.button.length*28+2 < this.buttonCanvas.height)
1053 this.buttonCanvas.height = this.button.length*28+2;
1056 this.button.forEach(b => {
1063 if (b.src === "maximize-2.svg") {
1064 let s = window.location.href;
1065 if (s.indexOf("&T") > -1)
1066 s = s.substr(0, s.indexOf("&T"));
1067 if (s === encodeURI(this.baseURL + "&group=" + "Images" + "&panel=" + this.panel)) {
1073 if (b.src === "play.svg" && !this.scroll)
1074 ctx.fillStyle = "#FFC0C0";
1075 else if (b.src === "chevrons-right.svg" && this.playMode > 0)
1076 ctx.fillStyle = "#C0FFC0";
1077 else if (b.src === "chevrons-left.svg" && this.playMode < 0)
1078 ctx.fillStyle = "#C0FFC0";
1080 ctx.fillStyle = "#F0F0F0";
1082 ctx.strokeStyle = "#808080";
1083 ctx.fillRect(b.x1, b.y1, b.width, b.height);
1084 ctx.strokeRect(b.x1, b.y1, b.width, b.height);
1085 ctx.drawImage(b.img, b.x1 + 2, b.y1 + 2);
1087 if (b.src === "chevrons-left.svg" && this.playMode < 0) {
1088 ctx.fillStyle = "#404040";
1089 ctx.font = "8px sans-serif";
1090 ctx.fillText("x" + (-this.playMode), b.x1+2, b.y1+b.height-2);
1093 if (b.src === "chevrons-right.svg" && this.playMode > 0) {
1094 ctx.fillStyle = "#404040";
1095 ctx.font = "8px sans-serif";
1096 let s = "x" + this.playMode;
1097 ctx.fillText(s, b.x1+b.width-ctx.measureText(s).width-2, b.y1+b.height-2);
1103 this.lastDrawTime = new Date().getTime();
1106 if (this.updateURLTimer !== undefined)
1107 window.clearTimeout(this.updateURLTimer);
1109 if (this.index === 0)
1110 this.updateURLTimer = window.setTimeout(this.updateURL.bind(this), 500);
1115 day: '2-digit', month: 'short', year: '2-digit',
1116 hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'
1121 day: '2-digit', month: 'short', year: '2-digit',
1122 hour12: false, hour: '2-digit', minute: '2-digit'
1127 day: '2-digit', month: 'short', year: '2-digit',
1128 hour12: false, hour: '2-digit', minute: '2-digit'
1132 timeZone: 'UTC', day: '2-digit',
1133 month: 'short', year: '2-digit'
1137 timeZone: 'UTC', hour12: false,
1138 hour: '2-digit', minute: '2-digit', second: '2-digit'
1142 timeZone: 'UTC', hour12: false, hour: '2-digit', minute: '2-digit'
1146 timeZone: 'UTC', hour12: false, hour: '2-digit', minute: '2-digit'
1151 day: '2-digit', month: 'short', year: '2-digit',
1152 hour12: false, hour: '2-digit', minute: '2-digit'
1156 timeZone: 'UTC', day: '2-digit', month: 'short', year: '2-digit'
1159function itimeToLabel(sec, base, forceDate) {
1160 let d = mhttpd_get_display_time(sec).date;
1164 return d.toLocaleTimeString('en-GB', ioptions1);
1165 } else if (base < 600) {
1166 return d.toLocaleTimeString('en-GB', ioptions2);
1167 } else if (base < 3600 * 24) {
1168 return d.toLocaleTimeString('en-GB', ioptions3);
1170 return d.toLocaleTimeString('en-GB', ioptions4);
1175 return d.toLocaleTimeString('en-GB', ioptions5);
1176 } else if (base < 600) {
1177 return d.toLocaleTimeString('en-GB', ioptions6);
1178 } else if (base < 3600 * 3) {
1179 return d.toLocaleTimeString('en-GB', ioptions7);
1180 } else if (base < 3600 * 24) {
1181 return d.toLocaleTimeString('en-GB', ioptions8);
1183 return d.toLocaleTimeString('en-GB', ioptions9);
1189MihistoryGraph.prototype.drawTAxis = function (ctx, x1, y1, width, xr, minor, major,
1190 text, label, xmin, xmax) {
1191 const base = [1, 5, 10, 60, 15 * 60, 30 * 60, 60 * 60, 3 * 60 * 60, 6 * 60 * 60,
1192 12 * 60 * 60, 24 * 60 * 60, 0];
1194 ctx.textAlign = "left";
1195 ctx.textBaseline = "top";
1197 if (xmax <= xmin || width <= 0)
1200 /* force date display if xmax not today */
1201 let d1 = new Date(xmax * 1000);
1202 let d2 = new Date();
1203 let forceDate = d1.getDate() !== d2.getDate() || (d2 - d1 > 1000 * 3600 * 24);
1205 /* use 5 pixel as min tick distance */
1206 let dx = Math.round((xmax - xmin) / (width / 5));
1209 for (tick_base = 0; base[tick_base]; tick_base++) {
1210 if (base[tick_base] > dx)
1213 if (!base[tick_base])
1215 dx = base[tick_base];
1217 let major_base = tick_base;
1220 let label_base = major_base;
1224 let str = itimeToLabel(xmin, label_dx, forceDate);
1225 let maxwidth = ctx.measureText(str).width;
1227 /* increasing label_dx, if labels would overlap */
1228 if (maxwidth > 0.8 * label_dx / (xmax - xmin) * width) {
1229 if (base[label_base + 1])
1230 label_dx = base[++label_base];
1232 label_dx += 3600 * 24;
1234 if (label_base > major_base + 1) {
1235 if (base[major_base + 1])
1236 major_dx = base[++major_base];
1238 major_dx += 3600 * 24;
1241 if (major_base > tick_base + 1) {
1242 if (base[tick_base + 1])
1243 dx = base[++tick_base];
1252 let d = new Date(xmin * 1000);
1253 let tz = d.getTimezoneOffset() * 60;
1255 let x_act = Math.floor((xmin - tz) / dx) * dx + tz;
1257 ctx.strokeStyle = this.color.axis;
1258 ctx.drawLine(x1, y1, x1 + width, y1);
1261 let x_screen = Math.round((x_act - xmin) / (xmax - xmin) * width + x1);
1262 let xs = Math.round(x_screen);
1264 if (x_screen > x1 + width + 0.001)
1267 if (x_screen >= x1) {
1268 if ((x_act - tz) % major_dx === 0) {
1269 if ((x_act - tz) % label_dx === 0) {
1271 ctx.strokeStyle = this.color.axis;
1272 ctx.drawLine(xs, y1, xs, y1 + text);
1276 let str = itimeToLabel(x_act, label_dx, forceDate);
1278 // if labels at edge, don't show them
1279 let xl = xs - ctx.measureText(str).width / 2;
1280 if (xl > 0 && xl + ctx.measureText(str).width < xr) {
1281 ctx.strokeStyle = this.color.label;
1282 ctx.fillStyle = this.color.label;
1283 ctx.fillText(str, xl, y1 + label);
1288 ctx.strokeStyle = this.color.axis;
1289 ctx.drawLine(xs, y1, xs, y1 + major);
1294 ctx.strokeStyle = this.color.axis;
1295 ctx.drawLine(xs, y1, xs, y1 + minor);
1304MihistoryGraph.prototype.download = function () {
1306 let filename = this.imageArray[this.currentIndex].image_name;
1308 // use trick from FileSaver.js
1309 let a = document.getElementById('downloadHook');
1311 a = document.createElement("a");
1312 a.style.display = "none";
1313 a.id = "downloadHook";
1314 document.body.appendChild(a);
1317 a.href = this.panel + "/" + filename;
1318 a.download = filename;
1320 dlgAlert("Image downloaded to '" + filename + "'");