tempStatsMulti.html HTML Source View


<!!DOCTYPE html>>
<html>
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
	<link rel="icon" type="image/png" href="favicon196.png" sizes="196x196">
	<link rel="stylesheet" href="styles/jquery.mobile-1.0b2.min.css?227" />
	<link rel="stylesheet" href="styles/eth.css" />
	<script src="styles/jquery-1.6.4.min.js" type="text/javascript"></script> 
	<script src="styles/jquery.mobile-1.0b2.min.js" type="text/javascript"></script> 
	<script src="styles/highcharts-custom.js?11" type="text/javascript"></script> 

	<script src="styles/modules/exporting.js" type="text/javascript"></script> 
	<script src="styles/modules/offline-exporting_x.js" type="text/javascript"></script> 

	<script type="text/javascript" src="styles/jquery.mobile.simpledialog.min.js"></script> 
	<link rel="stylesheet" type="text/css" href="styles/jquery.mobile.simpledialog.min.css" />
	<link rel="stylesheet" type="text/css" href="styles/charts.css" />
</head>
<body>
	<div data-role="page" id="tempStatTopPage" data-title="Temperature Statistics - Wireless Tag List">

		<div data-role="header" data-theme="b" data-position="inline">
			<a href="javascript:closeGraph();" data-icon="delete" data-iconpos="notext" data-ajax="false">Close</a>
			<h1 id="stat_title"></h1>
			<a class="ui-btn-right" id="top_right_btn" data-icon="share" data-ajax=0 data-theme="b">Share</a>
		</div>
		<div id="tempStatGraphs" style="width: 100%; touch-action: pan-y; ">
		</div>
		<span id="moreButtonTip" onclick="moreButtonTipClicked();"><span></span>Scroll for Options</span>
		<center id="moreButtons" style="display:none;">
			<span id="lightOnlyOptions">
				<input type="checkbox" name="log_axis" id="log_axis" class="custom" checked /><label for="log_axis">Log Scale</label>
			</span>
			<span class="loggedInOnly">
				<span id="editModeAvailable">
					<input type="checkbox" name="edit_mode" id="edit_mode" class="custom" /><label for="edit_mode">Click to remove point</label>
					<span id="editModeOnly" style="display:none">
						<button data-icon="arrow-l" data-inline=1 data-theme="a" disabled id="edit_mode_undo">Undo</button>
						<button data-icon="check" data-inline=1 data-theme="e" disabled id="edit_mode_save">Save</button>
					</span>
				</span>
				<button data-icon="arrow-d" data-inline=1 data-theme="b" id="downloadLogBtn">Download CSV</button>
			</span><span id="tempCapOnlyOptions">
	<!--<input type="checkbox" name="since_calibration" id="since_calibration" class="custom" /><label for="since_calibration">Show since last calibration</label>-->
	</span>
	<span id="tempCapLightOnlyOptions">
		<input type="checkbox" name="hourly_minmax" id="hourly_minmax" class="custom" /><label for="hourly_minmax">Show hourly highs &amp; lows</label>
	</span>
	<input type="date" name="custom_min" id="custom_min" class="inline-datepicker ui-body-null ui-corner-all ui-shadow-inset ui-body-c" />
	<input type="date" name="custom_max" id="custom_max" class="inline-datepicker ui-body-null ui-corner-all ui-shadow-inset ui-body-c" />

	<button data-icon="search" data-inline=1 data-theme="b" id="zoomBtn">Zoom To</button>

	<button data-icon="arrow-r" data-inline=1 data-theme="c" onclick="location.replace('tempStatsMultiDaily.html'+window.location.search)">View Daily Graph</button>
</center>
		<center class="sharedOnly">
			Captured by <a href="http://wirelesstag.net">Wireless Sensor Tags</a>
		</center>

		<script src="styles/client.js?22" type="text/javascript"></script> 
		<script src="styles/rawDataChart.js?99c" type="text/javascript"></script> 
		<script type="text/javascript">

			if (localStorage["moreButtonTipSeen"] == "1")
				$("#moreButtonTip").hide();

			function moreButtonTipClicked() {
				$('html, body').animate({ scrollTop: $("#moreButtons").offset().top }, 500, 'linear');
				$("#moreButtonTip").hide();
				localStorage["moreButtonTipSeen"] = "1";
			}

			var tzoffset = (new Date()).getTimezoneOffset() * 60000;
			var minmaxChartLines = [];
			var editMode = false;
			var editModeStack = [];
			function updateRawSeries(tagId) {
				var i = $.inArray(tagId, arrayOfIds);
				if(i>=0)
					chart.series[i * (hourly_minmax ? 2 : 1)].setData(id2RawSeries[tagId], true);
			}
			function removeFromRawSeries(point) {
				editModeStack.push(point);
				var i = 0;
				for (; i < id2RawSeries[point.id].length;i++){
					if (id2RawSeries[point.id][i][0] == point.x) {
						id2RawSeries[point.id].splice(i, 1);
						updateRawSeries(point.id);
						return;
					}
				}
			}
			function undoRemoveFromRawSeries() {
				var i = 0, point = editModeStack.pop();
				for (; i < id2RawSeries[point.id].length; i++) {
					if (id2RawSeries[point.id][i][0] > point.x) {
						id2RawSeries[point.id].splice(i, 0, [point.x, point.y]);
						updateRawSeries(point.id);
						return;
					}
				}
			}			
			$("#edit_mode_undo").click(function () {
				undoRemoveFromRawSeries();
				if (editModeStack.length == 0) $("#edit_mode_save, #edit_mode_undo").button("disable");
			});
			$("#edit_mode_save").click(function () {
				$("#edit_mode_save, #edit_mode_undo").button("disable");
				if (statType == "temperature" && temp_unit == 1) {
					for (var i = 0; i < editModeStack.length; i++) {
						editModeStack[i].y = (editModeStack[i].y - 32.0) * 5.0 / 9.0;
					}
				}
				$.ajax({
					url: WSROOT + "ethLogs.asmx/DeleteRawDataPoints",
					data: JSON.stringify({ "ids": slaveIds, "type": statType, "withMinMax": hourly_minmax, "sinceLastCalibration": since_calibration, "stack": editModeStack }),
					success: function (retval, textStatus) {
						if (retval.d.deleted_count) {
							popup("Attempted to delete " + editModeStack.length + " data points, " + retval.d.deleted_count + " deleted. ");
						}
						editModeStack = [];
						hourlyDataCache[since_calibration] = retval.d.temps;
						updateMetadata(retval.d);
						statData = null; clearHourlyData();
					},
					error: function (xhr, textStatus, exception) {
						ajaxErrorHandler(xhr, exception);
						$("#edit_mode_save, #edit_mode_undo").button("enable");
					}
				});
			});
			$("#edit_mode").change(function () {
				if (this.checked) $("#editModeOnly").show();
				else $("#editModeOnly").hide();
				editMode = this.checked;
			});

			$("#log_axis").change(function () {
				chart.yAxis[0].update({
					type: this.checked ? "logarithmic" : "linear"
				});
			});
			function resizeChart() {
				holder.highcharts().setSize(
				   $(window).width(),
				   $(window).height() - 42,
				   false
				);
				holder.highcharts().options.exporting.chartOptions.chart = { width: $(window).width(), height: $(window).height() - 42, marginTop: 50 };
			}
			var holder = $("#tempStatGraphs");

			var slaveIds, temp_unit;

			var old_style_query=(window.location.search.length>0);
			delete localStorage["mytaglist.stats.fromDate"];
			if (window.location.search.length > 0) {
				var queryString = window.location.search.substring(1).split("&");
				for (var i = 0; i < queryString.length; i++) {
					var pair = queryString[i].split('=');
					if (pair.length == 2) {
						localStorage["mytaglist.stats."+decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
						old_style_query= false;
					}
				}
			}

			if (old_style_query) {
				var params = window.location.search.substring(1).split('&');
				slaveIds = params[0].split(':');
				statType = params.length > 1 ? params[1] : "temperature";
				temp_unit = params.length > 2 ? (params[2] == "F" ? 1 : 0) : -1;
			}
			else{
				slaveIds = localStorage["mytaglist.stats.slaveids"].split(':');
				statType = localStorage["mytaglist.stats.type"] || "temperature";
				temp_unit = -1;
			}

			var since_calibration = false, hourly_minmax= false;
			if (statType == "temperature" || statType == "cap" || statType == "dp") {
				//since_calibration = localStorage["mytaglist.since_calibration"] == "true";
				hourly_minmax = localStorage["mytaglist.hourly_minmax"] == "true";
				//$("#since_calibration").attr("checked", since_calibration);
				$("#hourly_minmax").attr("checked", hourly_minmax);
				/*$("#since_calibration").change(function () {
					since_calibration = localStorage["mytaglist.since_calibration"] = this.checked ? "true" : "false";
					statData = null;
					clearHourlyData();
					loadFirstData();
				});*/
				$("#hourly_minmax").change(function () {
					hourly_minmax = localStorage["mytaglist.hourly_minmax"] = this.checked ? "true" : "false";
					if (hourly_minmax)
						location.reload();
					else {
						minmaxChartLines.forEach(function (s) { s.remove(false); });
					}
				});
			} else {
				$("#tempCapOnlyOptions").hide();
				if (statType != "light" && statType!="tvoc") $("#tempCapLightOnlyOptions").hide();
			}

			if (statType != "light"  && statType!="tvoc") $("#lightOnlyOptions").hide();

			if (statType == "motion")
				$("#editModeAvailable").hide();

			function previewEmbedHTML2() {
				$.ajax({
					url: WSROOT + "ethLogs.asmx/EditSharePermissions",
					data: JSON.stringify({
						"ids": slaveIds,
						"shareTemperature": shareInfo.shareTemperature.map(function () { return $("#shareTemp").is(":checked"); }),
						"shareMotion": shareInfo.shareMotion.map(function () { return $("#shareMotion").is(":checked"); })
					}),
					success: function () {
						$("#embdedHTMLPreview").html($("#embedHTML").val());
					},
					error: function (xhr, textStatus, exception) {
						popup_error(xhr, null);
					}
				});
			}

			var isUUID = slaveIds[0].length > 4;
			var shareInfo;
			if (old_style_query) {
				$(".loggedInOnly").hide();
				$(".sharedOnly").show();

				var btn = $("#top_right_btn");
				btn.data("icon", "arrow-d");
				btn[0].innerHTML = "Download";
				btn.click(function () {
					window.location = WSROOT + "ethDownloadMultiStatsCSV.aspx?uuids=" + slaveIds.join(":") + "&type=" + statType + "&fromDate=" + $("#custom_min").val() + "&toDate=" + $("#custom_max").val() + "&useDegF=" + temp_unit;
				});
			} else {
				$(".sharedOnly").hide();
				$("#downloadLogBtn").click(function () {
					window.location = WSROOT + "ethDownloadMultiStatsCSV.aspx?ids=" + slaveIds.join(":") + "&type=" + statType + "&fromDate=" + $("#custom_min").val() + "&toDate=" + $("#custom_max").val()+ "&useDegF=" + temp_unit;
				});
				$("#top_right_btn").click(function () {
					var btn2 = $("#top_right_btn").find(".ui-btn-inner");
					var oldhtml2 = show_finding(btn2, "Loading...");
					$.ajax({
						url: WSROOT + "ethLogs.asmx/GetSharePermissions",
						data: JSON.stringify({ "ids": slaveIds, "type": statType=="motion"?"motion_new":statType }),
						complete: function () { restore_finding(btn2, oldhtml2); },
						success: function (retval, textStatus) {
							shareInfo = retval.d;

							var copy_icon = '<svg onclick="copyURL(this);" style="width:24px;height:24px;padding-left:4px;vertical-align:text-bottom" viewBox="0 0 24 24"><path fill="#888" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" /></svg >'

							var html = "<div style='padding: 15px; width: " + (window.innerWidth - 200) + "px'><b>Share this data</b>" +
							"<div data-role='fieldcontain'><label for='graphURL'>Link to open graph in Web:</label><input type='text' id='graphURL'>" + copy_icon + "</div>"+
							"<div data-role='fieldcontain'><label for='downloadURL'>CSV download link:</label><input type='text' id='downloadURL'>" + copy_icon + "</div>"+
							"<div data-role='fieldcontain'><label for='iosURL'>Link to open graph in iOS app:</label><input type='text' id='iosURL'>" + copy_icon + "</div>"+
							"<div data-role='fieldcontain'><label for='embedHTML'>HTML to embed latest readings:</label><input type='text' id='embedHTML'>" + copy_icon + "<button onclick='previewEmbedHTML2()' data-theme='d'>Preview HTML</button></div><div id='embdedHTMLPreview'></div>" +
							"<form><center><input type='checkbox' id='shareTemp'><label for='shareTemp'>Anyone with link can access temperature data for these tags</label> " +
							"<input type='checkbox' id='shareMotion'><label for='shareMotion'>Anyone with link can access motion log data for these tags</label>"+
							"<a rel='close' data-role='button' data-theme='b' data-inline=1 href='#'>Apply Permissions</a></center></form></div>";

							var holder = $("#tempStatGraphs");
							if (holder.data('simpledialog')) {
								holder.data('simpledialog').options.fullHTML = html;
								holder.simpledialog('refresh').simpledialog('open');
							} else {
								holder.simpledialog({
									'mode': 'blank',
									'left': 80, 'top': '0px',
									'prompt': false,
									'forceInput': false,
									'useModal': true,
									pickPageTheme: 'c',
									'fullHTML': html,
									onClosed: function () {
										if (shareInfo.shareMotion.every(function (e) { return e; }) != $("#shareMotion").is(":checked") ||
											shareInfo.shareTemperature.every(function (e) { return e; }) != $("#shareTemp").is(":checked")) {
										
											$.ajax({
												url: WSROOT + "ethLogs.asmx/EditSharePermissions",
												data: JSON.stringify({
													"ids": slaveIds,
													"shareTemperature": shareInfo.shareTemperature.map(function () { return $("#shareTemp").is(":checked"); }),
													"shareMotion": shareInfo.shareMotion.map(function () { return $("#shareMotion").is(":checked"); })
												}),
												error: function (xhr, textStatus, exception) {
													popup_error(xhr, null);
												}
											});
										}
									}
								});
							}

							$("#shareMotion").attr("checked", shareInfo.shareMotion[0]).checkboxradio("refresh");
							$("#shareTemp").attr("checked", shareInfo.shareTemperature[0]).checkboxradio("refresh");

							$("#graphURL").val(shareInfo.graphUrl).click(function () { return selectedURL(this, statType == "motion"); });
							$("#downloadURL").val(shareInfo.downloadUrl).click(function () { return selectedURL(this, statType == "motion"); });
							$("#iosURL").val(shareInfo.iosAppUrl).click(function () { return selectedURL(this, statType == "motion"); });
							$("#embedHTML").val(shareInfo.embedHTML).click(function () { return selectedURL(this, statType == "motion"); });

						},
						error: function (xhr, textStatus, exception) {
							popup_error(xhr, null);
						}
					});
				});
			}



			var statTypeTranslationLUT = {
				"temperature":
				{
					name: "Temperature",
					unitGen: function () {return temp_unit ? '°F' : '°C'; }, 
					preProcess: function (degC, i) { if (degC == 0.0) return NaN; return temp_unit == 1 ? degC * 9.0 / 5.0 + 32.0 : degC; },
					ymaxInit: function () { return temp_unit == 1 ? -28 : -40;},
					ymaxPost: function (ymax) { return Math.ceil(Math.min(temp_unit ? 220 : 125, ymax) / 5 + 0.2) * 5; },
					yminInit: function () { return temp_unit == 1 ? 220 : 115;},
					yminPost: function (ymin) { return Math.floor(ymin / 5 - 0.2) * 5; },
					decimals: 1
				},
				"dp":
				{
					name: "Dew Point",
					unitGen: function () { return temp_unit ? '°F' : '°C'; },
					preProcess: function (degC, i) { if (degC == 0.0) return NaN; return temp_unit == 1 ? degC * 9.0 / 5.0 + 32.0 : degC; },
					ymaxInit: function () { return temp_unit == 1 ? -28 : -40; },
					ymaxPost: function (ymax) { return Math.ceil(Math.min(temp_unit ? 220 : 125, ymax) / 5 + 0.2) * 5; },
					yminInit: function () { return temp_unit == 1 ? 220 : 115; },
					yminPost: function (ymin) { return Math.floor(ymin / 5 - 0.2) * 5; },
					decimals: 1
				},
				"cap":
					{
						name: "Moisture/RH",
						unitGen: function () { return "%"; },
						preProcess: function (val, i) { if (val < 0 || val==-99) return NaN; return val; },
						ymaxInit: function () { return 0;},
						ymaxPost: function (ymax) { return ymax;},
						yminInit: function () { return 100;},
						yminPost: function (ymin) { return ymin; },
						decimals: 1
					},
				"batteryVolt": 
					{
						name: "Battery Voltage",
						unitGen: function () { return "V"; },
						preProcess: function (val, i) { if (val == 0) return NaN; return val; },
						ymaxInit: function () { return 2.0; },
						ymaxPost: function (ymax) { return ymax; },
						yminInit: function () { return 3.5; },
						yminPost: function (ymin) { return ymin; },
						decimals: 2
					},
				"light":
					{
						name: "Ambient Light",
						unitGen: function () { return " lux"; },
					preProcess: function (val, i) { if (val == 0) return 0.005; if (val < 0) return NaN; return val; },
						ymaxInit: function () { return 0.005; },
						ymaxPost: function (ymax) { return ymax; },
						yminInit: function () { return 30000; },
						yminPost: function (ymin) { return ymin; },
						decimals: 2
					},
				"tvoc":
					{
						name: "Volatile Organic Compound",
						unitGen: function () { return " ppm"; },
						preProcess: function (val, i) { if (val <= 0) return NaN; return val; },
						ymaxInit: function () { return 0; },
						ymaxPost: function (ymax) { return ymax; },
						yminInit: function () { return 30000; },
						yminPost: function (ymin) { return ymin; },
						decimals: 2
					},
				"signal":
					{
						name: "Received Signal Level + TX Back-Off",
						unitGen: function () { return "dBm"; },
						preProcess: function (val, i) { if (val == 0) return NaN; return val; },
						ymaxInit: function () { return -120; },
						ymaxPost: function (ymax) { return ymax; },
						yminInit: function () { return 0; },
						yminPost: function (ymin) { return ymin; },
						decimals: 1
					},
				"motion":
					{
						name: "Motion/Door",
						unitGen: function () { return " times"; },
						preProcess: function (val, i) { return val; },
						ymaxInit: function () { return 0; },
						ymaxPost: function (ymax) { return ymax; },
						yminInit: function () { return 100; },
						yminPost: function (ymin) { return ymin; },
						decimals: 0,
						total: 1
					}

			};
			var statTypeTranslation = statTypeTranslationLUT[statType];
			var id2Series = {}, id2RawSeries = {}, id2BandSeries = {}, id2nameMapping = {};
			var id2minmaxSeries = {};
			var arrayOfIds=[];

			function processOneDay(day, baseDate, prepend) {
				
				var tagCount = day.ids.length;
				var tods = day.tods_base64 != null ? day.tods_base64.map(s => new Uint32Array(new Uint8Array([...atob(s)].map(c => c.charCodeAt(0))).buffer) ) : day.tods;

				for (var tagi = 0; tagi < tagCount; tagi++) {

					var tagv = day.values_base64!=null ? new Float64Array(new Uint8Array([...atob(day.values_base64[tagi])].map(c => c.charCodeAt(0))).buffer):  day.values[tagi];
					var tagId = day.ids[tagi];

					if (-1 == $.inArray(tagId, arrayOfIds)) {
						arrayOfIds.push(tagId);
						var option = {
							name: id2nameMapping[tagId],
							tagId:tagId,
							tooltip: {
								valueSuffix: statTypeTranslation.unitGen()
							},
							point:{
								events: {
									click: function () {
										//alert("x=" + this.x + " y=" + this.y + " tagId=" + this.series.options.tagId);
										if ($("#edit_mode_undo").is(":visible")) {
											$("#edit_mode_save, #edit_mode_undo").button("enable");
											removeFromRawSeries({ x: this.x, y: this.y, id: this.series.options.tagId });
										}
									}
								}
							}
						};
						
						if (statType == "motion") {
							option.step = "center";
						}
						var s = chart.addSeries(option);
						if (hourly_minmax)
							minmaxChartLines.push(chart.addSeries({
								name: id2nameMapping[tagId] + " Highs/Lows",
								type: "arearange",
								color: s.color, turboThreshold:1e6, 
								tooltip: {
									valueSuffix: statTypeTranslation.unitGen()
								}
							}));
					}
					var series = null, rawSeries = null, tod = null, minmaxSeries=null;
					if (tods) {
						tod = tods[tagi];
						rawSeries = id2RawSeries[tagId];
						if (rawSeries == null) {
							id2RawSeries[tagId] = rawSeries = [];
						}
					} else {
						series = id2Series[tagId];
						if (series == null) {
							id2Series[tagId] = series = [];
						}
						if (hourly_minmax) {
							minmaxSeries = id2minmaxSeries[tagId];
							if (minmaxSeries == null) id2minmaxSeries[tagId] = minmaxSeries = [];
						}
					}
					var ymin = statTypeTranslation.yminInit(), ymax = statTypeTranslation.ymaxInit();
					var avg = 0;
					for (var j = 0; j < tagv.length; j++) {
						var n = prepend ? tagv.length - j - 1 : j;
						var val = statTypeTranslation.preProcess(tagv[n]);
						if (!isNaN(val)) {							
							var dataPoint = [baseDate + (tod ? tod[n] * 1000 : HOUR * (n+0.5)), val];
							if (dataPoint[0] > dataRange.max) dataRange.max = dataPoint[0];  
							
							if (tods) {
								addRawDataPoint(dataPoint, prepend, rawSeries, chart.series[$.inArray(tagId, arrayOfIds) * (hourly_minmax ? 2 : 1)]);								
							} else {
								if (prepend) {
									series.unshift(dataPoint);
									if (minmaxSeries) minmaxSeries.unshift( [dataPoint[0], statTypeTranslation.preProcess(day.min[tagi][n]), statTypeTranslation.preProcess(day.max[tagi][n])] );
								} else {
									var addDiscon = false;
									if (series.length > 0 && dataPoint[0] - series[series.length - 1][0] > 5 * HOUR) {
										series.push([dataPoint[0] - 1000, null]);
										addDiscon = true;
									}

									series.push(dataPoint);
									if (minmaxSeries) {
										if (addDiscon)
											minmaxSeries.push([dataPoint[0] - 1000, null, null]);
										minmaxSeries.push([dataPoint[0], statTypeTranslation.preProcess(day.min[tagi][n]), statTypeTranslation.preProcess(day.max[tagi][n])]);
									}
								}
							}
							ymax = Math.max(ymax, val); ymin = Math.min(ymin, val);
							avg += val;
						}
					}
					if (!tods) {
						var bandSeries = id2BandSeries[tagId];
						if (!bandSeries) {
							id2BandSeries[tagId] = bandSeries = [];
						}
						var dataPoint;
						if (statType == "motion") {
							dataPoint = [baseDate + HOUR * 12, avg];
						} else {
							dataPoint = [baseDate + HOUR * 12, ymin, ymax];
						}
						if (bandSeries.length > 0 && dataPoint[0] - bandSeries[bandSeries.length - 1][0] > 25 * HOUR)
						{
							if (statType == "motion") 
								bandSeries.push([dataPoint[0] - HOUR, null]);
							else
								bandSeries.push([ dataPoint[0] - HOUR, null, null]);
						}
						bandSeries.push(dataPoint);
					}
				}
			}
			
			var $loader = $("<div class='ui-loader ui-body-a ui-corner-all'><span class='ui-icon ui-icon-loading spin'></span><h1 id='loader_title'>Loading Data...</h1></div>");
			$body = $("body");
			$loader.appendTo($body).css({ top: 100 });
			$body.addClass("ui-loading");

			function ajaxErrorHandler(xhr, exception) {
				if (xhr.responseText.toLowerCase().indexOf("unauthorized") != -1 || exception.toLowerCase().indexOf("unauthorized") != -1 || xhr.responseText.toLowerCase().indexOf("authentication failed") != -1)
					location.replace("signin.html?ReturnUrl=" + encodeURIComponent(window.location.pathname + window.location.search));
				else {
					$loader.remove();
					popup_error(xhr, null);
				}
			}
			function dataLoader(fromDate, toDate, onData) {
				$.ajax({
					url: WSROOT + (isUUID ? "ethLogShared.asmx/GetMultiTagStatsRawByUUIDs" : "ethLogs.asmx/GetMultiTagStatsRaw"),
					data: JSON.stringify({ ids: slaveIds, type: statType, fromDate: fromDate, toDate: toDate }),
					success: function (retval, textStatus) {
						updateMetadata(retval.d);
						onData(retval.d.stats);

						//chart.yAxis[0].update({ labels: { format: '{value}' + statTypeTranslation.unitGen() } });
					},
					error: function (xhr, textStatus, exception) {
						ajaxErrorHandler(xhr, exception);
					}
				});
			}
			var motionEventTypes = ["Disarmed", "Armed", "Moved", "Opened", "Closed", "Detected", "Timed Out", "Stabilizing..."];
			function motionEventFormatter(){
				return motionEventTypes[this.value];
			}
			function setMotionRawYAxis() {
				if (statType == "motion" ) {
					chart.yAxis[0].update({
						labels: {
							formatter: motionEventFormatter
						}
					});
					for (var i = 0; i < chart.series.length; i++)
						chart.series[i].update({ type: "scatter" },i==chart.series.length-1);
				}
			}
			function revertMotionRawYAxis() {
				if (statType == "motion" ) {
					chart.yAxis[0].update({
						labels: {
							formatter: null
						}
					});

					/*for (var i = 0; i < chart.series.length; i++)
						chart.series[i].update({
							type: "line"
						});*/
				}

			}
			var markerLoaded = false;
			function updateChartType(zoomLevel) {

				var step = (hourly_minmax ? 2 : 1);
				if (zoomLevel > ChartZoomLevelNormal) {
					$("#editModeAvailable").hide(); 
					var type = statType == "motion" ? "line" : "arearange";
					
					for (var i = 0; i < chart.series.length; i += step) {
						if(chart.series[i].type!=type)chart.series[i].update({ type: type }, false);
						chart.series[i].setData(id2BandSeries[arrayOfIds[i/step]], false);
						if (hourly_minmax) {
							chart.series[i + 1].setData(id2minmaxSeries[arrayOfIds[i / step]], false);
							chart.series[i + 1].hide();
						}
					}
					chart.redraw();
				}
				else if (zoomLevel == ChartZoomLevelNormal) {
					$("#editModeAvailable").hide();
					for (var i = 0; i < chart.series.length; i += step) {
						if (chart.series[i].type != "line") chart.series[i].update({ type: "line" }, false);
						chart.series[i].setData(id2Series[arrayOfIds[i / step]], false);

						if (hourly_minmax) {
							chart.series[i + 1].setData(id2minmaxSeries[arrayOfIds[i / step]], false);
							chart.series[i + 1].show();
						}
					}

					
					chart.redraw();

				}
				else {
					if (statType != "motion")
						$("#editModeAvailable").show();

					for (var i = 0; i < chart.series.length; i += step) {
						if (chart.series[i].type != "line") chart.series[i].update({ type: "line" }, false);
						if (hourly_minmax) {
							chart.series[i + 1].setData(id2minmaxSeries[arrayOfIds[i / step]], false);
							chart.series[i + 1].hide();
						}
						//chart.series[i].setData(id2RawSeries[arrayOfIds[i]], i == chart.series.length - 1);   // already done in processOneDay addPoint
					}
					chart.redraw();
				}
				if (zoomLevel != ChartZoomLevelRaw) revertMotionRawYAxis();

			}
			function loadMarkers() {
				arrayOfIds.forEach(function (tagId) {
					var series = chart.series[$.inArray(tagId, arrayOfIds) * (hourly_minmax ? 2 : 1)];
					$.ajax({
						url: WSROOT + (isUUID ? "ethLogShared.asmx/LoadMarkers" : "ethLogs.asmx/LoadMarkers"),
						data: JSON.stringify({ "id": isUUID ? slaveIds[tagId] : tagId, "type": statType }),
						success: function (retval, textStatus) {
							retval.d.forEach(function (m) {
								restoreMarker(series, m);
							});
						},
						error: function (xhr, textStatus, exception) { }
					});
				});
			}
			function clearHourlyData() {
				id2minmaxSeries = {};
				id2Series = {};
				id2BandSeries = {};
			}
			function clearCachedRawData() {
				id2RawSeries = {};
				for (var i = 0; i < chart.series.length; i++)
					chart.series[i].setData([], i==chart.series.length-1);
				setMotionRawYAxis();
			}
			function loadCachedRawData() {
				var step = (hourly_minmax ? 2 : 1);
				for (var i = 0; i < chart.series.length; i += step) {
					chart.series[i].setData(id2RawSeries[arrayOfIds[i/step]], i == chart.series.length - step);
				}
				setMotionRawYAxis();
			}

			function saveMarker(series, filetime, comment) {
				if (-1 == chart.series.indexOf(series)) return;
				var step = (hourly_minmax ? 2 : 1);

				$.ajax({
					url: WSROOT + "ethClient.asmx/MergeMarker",
					data: JSON.stringify({ id: isUUID? slaveIds[arrayOfIds[chart.series.indexOf(series)/step]] : arrayOfIds[chart.series.indexOf(series)/step], filetime: filetime, type: statType, comment: comment })
				});
			}
			function deleteMarker(series, filetime) {
				if (-1 == chart.series.indexOf(series)) return;
				var step = (hourly_minmax ? 2 : 1);
				$.ajax({
					url: WSROOT + "ethClient.asmx/DeleteMarker",
					data: JSON.stringify({ id: isUUID? slaveIds[arrayOfIds[chart.series.indexOf(series)/step]] : arrayOfIds[chart.series.indexOf(series)/step], filetime: filetime, type: statType })
				});
			}
	

			//var availableColors = ['red', 'yellow', 'green', 'blue', '#FF00FF', '#13919F', '#660F8B', '#848B0F', '#0F368B', '#D53366', '#151B8D', '#F778A1', '#F87431',
				//'#20E42D', '#F6A40C', '#4079CA', '#40CA93', '#DDE469', '#979F0F', '#C4522C', '#29BA7C', '#4B29BA', '#4A3A72', '#00FFFF', '#808000'];
			$("#stat_title").text(statTypeTranslation.name + " Charts");
	function createChart(buttons, lang) {
		var options = {
			title: { text: null },
			subtitle: {
				text: isTouchScreendevice()? null:"Drag to zoom, hold Shift key and drag to pan"
			},
			animation: false,
			chart: {
				zoomType: 'x', panning: true, panKey: 'shift',
				type: "line",
				style: { fontFamily: "ProximaNovaLight, Arial" },
				events: {
					selection: function (event) {
						updateZoom(this,event.xAxis? event.xAxis[0]:dataRange,function() {});
					},
					redraw: onChartRedraw
				}
			},
			xAxis: {
				type: 'datetime',
				events: {
					setExtremes: function (event) {
						//if(event.trigger=="pan")
							updatePan(this.chart,event);
						try {
							$("#custom_min").val(new Date(event.min - tzoffset).toISOString().substring(0, 10));
							$("#custom_max").val(new Date(event.max - tzoffset - 1000).toISOString().substring(0, 10));
						} catch (e) { }
					}
				}
			},
			yAxis: [{
				title: { text: statTypeTranslation.name }, allowDecimals: statType == "batteryVolt", type: (statType == "light" || statType == "tvoc") ? "logarithmic" : "linear"
			}
			],
			tooltip: {
				animation: false, 
				borderColor: "gray",
				valueDecimals: statTypeTranslation.decimals,
				crosshairs: true,
				followPointer: false,
				footerFormat:old_style_query?"<br/><b>.</b>":"<br/><b>Press M to place a marker...</b>", 
				style: {pointerEvents: 'all' }, 
				dateTimeLabelFormats: {
					second: "%A, %b %e, %I:%M:%S %p",
				}
			},
			plotOptions: {
				series: {
					events: {
						afterAnimate: onAnimationEnd
					},
					marker: { enabled: false }
				},
				scatter: {
					tooltip: {
						crosshairs: true,
						xDateFormat: "%A, %b %e, %H:%M:%S",
						headerFormat: '<b>{point.key}</b>',
						pointFormat: ""
					}
				}
			},
			legend: {
				layout: 'horizontal', align: 'left', x: 75, verticalAlign: 'top', y: 0, floating: true,
				backgroundColor: (Highcharts.theme && Highcharts.theme.legendBackgroundColor) || '#FFFFFF'
			},
			series: [],

			lang: lang,
			exporting: {
				libURL:"https://code.highcharts.com/6.1.0/lib",
				chartOptions: {
					subtitle: {
						text: null
					},
					legend: {
						itemDistance: 50
					},
					chart: {
						width: $(window).width(),
						height: $(window).height() - 42
					}
				},
				filename: statTypeTranslation.name + " Charts",
				buttons: buttons
			}
		};
		if (statType == "light" || statType == "tvoc") {
			options.exporting.buttons["luxAxisButton"] = {
				text: "Log/Linear Scale",				_titleKey: "logScale",
				onclick: function () {
					if (chart.yAxis[0].options.type == "linear")
						chart.yAxis[0].update({ type: "logarithmic" });
					else
						chart.yAxis[0].update({ type: "linear" });
				}
			};
		}

		holder.highcharts(options);

		var menuItems = Highcharts.getOptions().exporting.buttons.contextButton.menuItems;
		menuItems.unshift({ separator: true });
		for (button in options.exporting.buttons)
			menuItems.unshift(options.exporting.buttons[button]);

		resizeChart();
		chart = holder.highcharts();
		$("#moreButtons").show();

		$(window).resize(function () {
			resizeChart();
		});

		if (localStorage["moreButtonTipSeen"] != "1") {
			$(window).scroll(function () {
				function elementScrolled(elem) {
					var docViewTop = $(window).scrollTop();
					var docViewBottom = docViewTop + $(window).height();
					var elemTop = $(elem).offset().top;
					return ((elemTop <= docViewBottom) && (elemTop >= docViewTop));
				}
				if (elementScrolled('#moreButtons')) {
						$("#moreButtonTip").hide();
						localStorage["moreButtonTipSeen"] = "1";
				}
			});
		}


	}

			$("#zoomBtn").click(function () {
				var range = { min: new Date($("#custom_min").val()).getTime() + tzoffset, max: new Date($("#custom_max").val()).getTime() + 3600 * 1000 * 23.9 + tzoffset };
				updateZoom(holder.highcharts(), range, function () {
					holder.highcharts().xAxis[0].setExtremes(range.min, range.max);
				});
			});


			function updateMetadata(d) {
				for (var i = 0; i < d.ids.length; i++) {
					id2nameMapping[d.ids[i]] = d.names[i];
				}
				tzo = d.tzo;
				if (temp_unit == -1) 
					temp_unit = d.temp_unit;

			}
			var hourlyDataCache = {};
			function hourlyDataLoader(onData) {
				if (hourlyDataCache[since_calibration] != null) {
					onData(hourlyDataCache[since_calibration]);
					return;
				}

				$.ajax({
					url: WSROOT + (isUUID? "ethLogShared.asmx/GetHourlyStatsByUUIDs2":"ethLogs.asmx/GetHourlyStats2"),
					data: JSON.stringify({ "ids": slaveIds, "type": statType, "withMinMax": hourly_minmax, "sinceLastCalibration": since_calibration }),
					success: function (retval, textStatus) {
						hourlyDataCache[since_calibration] = retval.d.temps;

						updateMetadata(retval.d);
						onData(retval.d.stats);

						//chart.yAxis[0].update({ labels: { format: '{value}' + statTypeTranslation.unitGen() } });
					},
					error: function (xhr, textStatus, exception) {
						ajaxErrorHandler(xhr, exception);
					}
				});
			}
			function dataSpanLoader(onData){
				$.ajax({
					url: WSROOT + (isUUID ? "ethLogShared.asmx/GetMultiTagStatsSpanByUUIDs2" : "ethLogs.asmx/GetMultiTagStatsSpan2"),
					data: JSON.stringify({ "ids": slaveIds, "type": statType, "sinceLastCalibration": since_calibration }),
					success: function (retval, textStatus) {
						updateMetadata(retval.d);
						onData(fileTimeToDate(retval.d.from).getTime(), fileTimeToDate(retval.d.to).getTime());

						chart.yAxis[0].update({ labels: { format: '{value}' + statTypeTranslation.unitGen() } });
					},
					error: function (xhr, textStatus, exception) {
						ajaxErrorHandler(xhr, exception);
					}
				});
			}

				loadFirstData();

			function attemptRestoreMarker(s, m) {
if (!s.points) return;
	for (var i = 1; i < s.points.length; i++) {
		if (s.points[i - 1].x <= m.date && m.date < s.points[i].x) {
			s.points[i - 1].onMouseOver();
			createMarker(m.comment, m.filetime);
			chart.tooltip.hide();
			return true;
		}
	}
	return false;
}

		</script> 
	</div>

</body>
</html>