tempStats.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?6" type="text/javascript"></script> 
	<script src="styles/highcharts-custom.js?14" 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?2" />
</head>
<body>
	<div data-role="page" id="tempStatTopPage" data-title="Temperature Chart - Wireless Tags">

		<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 class="loggedInOnly"><button data-icon="arrow-d" data-inline=1 data-theme="b" id="downloadTemperatureLogBtn">Download CSV</button></span>
			<input type="checkbox" name="since_calibration" id="since_calibration" class="custom" /><label for="since_calibration">Show since last calibration</label>
			<input type="checkbox" name="hourly_minmax" id="hourly_minmax" class="custom" /><label for="hourly_minmax">Show hourly highs &amp; lows</label>
			<span class="loggedInOnly">
				<select data-inine="1" data-icon="delete" data-theme="e" id="deleteTemperatureLogBtn" data-toptext="Delete..." onchange="deleteOptions($(this)); return false;" data-native-menu="false">
					<option value="1">Select range...</option><option value="2">Delete all data points </option>
				</select>
			</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('tempStatsDaily.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 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;
				}
			}
		}

		var params = ((old_style_query) ? window.location.search.substring(1) : localStorage["mytaglist.stats.slaveid"]).split('&');
		var slaveId = params[0];
		var tagName = params.length > 1 ? decodeURIComponent(params[1]) : localStorage["mytaglist.stats.name"];
		var isUUID = slaveId.length > 4;
		if (!tagName && params.length > 1 && isUUID) tagName = decodeURIComponent(params[1]);
		var temp_unit = params.length > 2 ? (params[2] == "F" ? 1 : 0) : -1;

		var since_calibration = localStorage["mytaglist.since_calibration"] == "true";
		var hourly_minmax = localStorage["mytaglist.hourly_minmax"] == "true";
		var hasALS = old_style_query ? params.length > 3 : localStorage["mytaglist.stats.hasALS"] == "true";
		//var isZmod = localStorage["mytaglist.stats.hasZmod"] == "true";
		var isFlat= localStorage["mytaglist.stats.isFlat"] == "true";
		var hasChipTemp = localStorage["mytaglist.stats.hasChipTemp"] == "true";
		var isWME = localStorage["mytaglist.stats.isWME"] == "true";
		var isMoisture = localStorage["mytaglist.stats.isMoisture"] == "true";
		var hideDewPoint = false;

		if (hasChipTemp) dewPointMode = true;
		if (isWME || isMoisture || isFlat) {
			dewPointMode = false;
			hideDewPoint = true;
		}

		var CapName = isFlat? "Water" : (isWME ? "WME" : (isMoisture ? "Moisture" : "Humidity"));

		/*var tempBL = JSON.parse(localStorage["mytaglist.stats.tempBL"]);
		if (hasALS)
			var luxBL = JSON.parse(localStorage["mytaglist.stats.luxBL"]);
		if (!dewPointMode)
			var capBL = JSON.parse(localStorage["mytaglist.stats.capBL"]);  */

		$("#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 {
				tempMinMaxChartSeries.remove(false);
				if (capMinMaxChartSeries) capMinMaxChartSeries.remove(true);
				if (luxMinMaxChartSeries) luxMinMaxChartSeries.remove(true);
			}
		});

		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 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 + "ethDownloadTempCSV.aspx?uuid=" + params[0] + "&name=" + tagName + "&fromDate=" + $("#custom_min").val() + "&toDate=" + $("#custom_max").val() + "&useDegF=" + temp_unit;
			});
		} else {
			$(".sharedOnly").hide();
			$("#downloadTemperatureLogBtn").click(function () {
				window.location = WSROOT + "ethDownloadTempCSV.aspx?id=" + params[0] + "&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": [slaveId], "type": "temperature"}),
					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>"+
						"<form><center><input type='checkbox' id='shareTemp'><label for='shareTemp'>Anyone with link can access temperature data for this tag</label> " +
						"<input type='checkbox' id='shareMotion'><label for='shareMotion'>Anyone with link can access motion log data for this tag</label>"+
						"<a rel='close' data-role='button' data-inline=1 data-theme='b' data-icon='check' href='#'>Apply Permissions</a></center></form></div>";

						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[0] != $("#shareMotion").is(":checked") || shareInfo.shareTemperature[0] != $("#shareTemp").is(":checked")) {
										$.ajax({
											url: WSROOT + "ethLogs.asmx/EditSharePermissions",
											data: JSON.stringify({ "ids": [slaveId], "shareTemperature": [$("#shareTemp").is(":checked")], "shareMotion":[$("#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, false); });
						$("#downloadURL").val(shareInfo.downloadUrl).click(function () { return selectedURL(this, false); });
						$("#iosURL").val(shareInfo.iosAppUrl).click(function () { return selectedURL(this, false); });
						$("#embedHTML").val(shareInfo.embedHTML).click(function () { return selectedURL(this, false); });
	},
	error: function (xhr, textStatus, exception) {
		popup_error(xhr, null);
	}
	});
	});
	}

	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");

	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);
		}
	}


	$loader.appendTo($body).css({ top: 100 });
	$body.addClass("ui-loading");

	var tempSeries = [], tempMinMaxSeries=[];
		var capSeries = [], capMinMaxSeries = [];
		var dpSeries = [], dpMinMaxSeries=[]; 
	var luxSeries=[], luxMinMaxSeries=[];
		var bandCapSeries = [], bandDpSeries = [];
	var bandTempSeries = [];
	var bandLuxSeries=[];
	var rawTempSeries = [];
		var rawCapSeries = [], rawDpSeries = [];
	var rawLuxSeries = [];

		var capChartSeries = null, tempMinMaxChartSeries = null, capMinMaxChartSeries = null;
		var dpChartSeries = null, dpMinMaxChartSeries = null;
	var luxChartSeries = null, luxMinMaxChartSeries = null;
		var tzoffset = (new Date()).getTimezoneOffset() * 60000;
		var luxAxisIndex;
	function createLuxAxis() {
		chart.addAxis({
			gridLineWidth: 0,
			title: { text: isFlat?"Analog": "Lux", style: { color: luxColor } },
			labels: { style: { color: luxColor } },
			opposite: true
			,type: "logarithmic"
		});
		luxAxisIndex = chart.yAxis.length - 1;
			luxChartSeries = chart.addSeries({
				name: isFlat ? "Analog Voltage" : "Ambient Light", yAxis: luxAxisIndex,
			tooltip: { valueSuffix: isFlat ? " V" : " lux" },
			color: luxColor, threshold: luxBL, negativeColor: luxBL? luxLowColor: luxColor
		});
		if (hourly_minmax) {
			luxMinMaxChartSeries = chart.addSeries({
				name: isFlat ? "Voltage Highs/Lows" : "Ambient Light Highs/Lows", yAxis: luxAxisIndex,
				tooltip: { valueSuffix: isFlat?" V":" lux" },
				color: luxRangeColor, visible: false, threshold: luxBL, negativeColor: luxBL? luxRangeLowColor: luxRangeColor
			});
		}
	}
	function createCapAxis() {
		//if (!dewPointMode)   // dewPointMode only decides if dp or cap series is displayed by fault
		{
			chart.addAxis({ // Secondary yAxis
				gridLineWidth: 0,
				title: {
					text: CapName, style: { color: capColor }
				},
				labels: {
					format: '{value} %', style: { color: capColor }
				},
				opposite: true
			});
		}
		capChartSeries = chart.addSeries({
			name: CapName,
			yAxis: 1, 
			tooltip: {
				valueSuffix: " %"
			},
			visible: !dewPointMode, 
			color: capColor, threshold: capBL, negativeColor: capBL ? capLowColor : capColor
		});
		if(!hideDewPoint)
			dpChartSeries = chart.addSeries({
				name: hasChipTemp?"Ambient Temperature":"Dew Point", 
				yAxis: 0,
				tooltip: {
					valueSuffix: "°" + (temp_unit == 1 ? "F" : "C")
				},
				color: dpColor, visible: dewPointMode
			});

		if (hourly_minmax) {
			capMinMaxChartSeries = chart.addSeries({
				name: CapName+' Highs/Lows', turboThreshold: 1e6,
				yAxis: 1,
				tooltip: {
					valueSuffix:  " %"
				},
				color: capRangeColor, visible: false, threshold: capBL, negativeColor: capBL? capRangeLowColor: capRangeColor
			});
			if (!hideDewPoint)
				dpMinMaxChartSeries = chart.addSeries({
					name: hasChipTemp?"Chip Highs/Lows":'Dew Point Highs/Lows', turboThreshold: 1e6,
					yAxis: 0,
					tooltip: {
						valueSuffix: "°" + (temp_unit == 1 ? "F" : "C")
					},
					color: dpRangeColor, visible: false 
				});
		}
	}
	function translateCapVal(val) {
		return val;
	}
	function translateDpVal(val, temp) {
		if (hasChipTemp) return translateTempVal(val);
		else{
			val = dewPoint(val, temp);
			if (temp_unit == 1) val = val * 9.0 / 5.0 + 32.0;
		}
		return val;
	}
	function translateTempVal(val) {
		return temp_unit==1? val* 9.0 / 5.0 + 32.0 : val;
	}
	
	// baseDate is Date.parse(date_string)
	function processOneDay(day, baseDate, prepend) {
		var avg = 0;
		var tods = day.tods_base64 != null ? new Uint32Array(new Uint8Array([...atob(day.tods_base64)].map(c => c.charCodeAt(0))).buffer) : day.tods;
		
		var count = tods ? tods.length : 24;
		var capMin = 100, capMax = 0, tempMin = temp_unit == 1 ? 220 : 115, tempMax = temp_unit == 1 ? -28.0 : -40.0;
		var dpMin = tempMin, dpMax = tempMax;
		var luxMin = 80000, luxMax = 0;
		
		var temps = day.temps_base64 != null ? new Float64Array(new Uint8Array([...atob(day.temps_base64)].map(c => c.charCodeAt(0))).buffer) : day.temps;
		var caps = day.caps_base64!=null ? new Float32Array(new Uint8Array([...atob(day.caps_base64)].map(c => c.charCodeAt(0))).buffer):  day.caps;
		var lux= day.lux_base64!=null ? new Float64Array(new Uint8Array([...atob(day.lux_base64)].map(c => c.charCodeAt(0))).buffer):  day.lux;

		for (var i = 0; i < count; i++) {
			var n = prepend ? count - i - 1 : i;

			var addDiscon = false;
			
			if (temps[n] != 0.0) {
				var val = translateTempVal( temps[n]);
				var dataPoint = [baseDate + (tods ? tods[n] * 1000 : HOUR * (n+0.5)), val];
				if (dataPoint[0] > dataRange.max) dataRange.max = dataPoint[0];

				avg+=val;
					
				if(tods){
					addRawDataPoint(dataPoint, prepend, rawTempSeries, chart.series[0]);
				} else {
					if (tempSeries.length > 0 && (crossDisconBoundary(tempSeries[tempSeries.length - 1][0], dataPoint[0]) || dataPoint[0] - tempSeries[tempSeries.length - 1][0] > 5 * HOUR  )) {
						tempSeries.push([dataPoint[0] - 1000, null]);
						addDiscon = true;
					}
					tempSeries.push(dataPoint);

					if (day.temps_min_base64 != null || day.temps_min != null) {
						if (addDiscon)
							tempMinMaxSeries.push([dataPoint[0] - 1000, null, null]);

						var temps_min = day.temps_min_base64 != null ? new Float64Array(new Uint8Array([...atob(day.temps_min_base64)].map(c => c.charCodeAt(0))).buffer) : day.temps_min;
						var temps_max = day.temps_max_base64 != null ? new Float64Array(new Uint8Array([...atob(day.temps_max_base64)].map(c => c.charCodeAt(0))).buffer) : day.temps_max;

						dataPoint = [dataPoint[0], translateTempVal(temps_min[n]), translateTempVal(temps_max[n])];
						tempMinMaxSeries.push(dataPoint);

						tempMax = Math.max(tempMax, dataPoint[2]); tempMin = Math.min(tempMin, dataPoint[1]);

					} else {
						tempMax = Math.max(tempMax, val); tempMin = Math.min(tempMin, val);
					}
				}
			}

			if (caps[n] != -99) {

				if (capChartSeries == null) createCapAxis();

				var dataPoint = [baseDate + (tods ? tods[n] * 1000 : HOUR * (n + 0.5)), translateCapVal(caps[n])];
				var dataPointDp = [dataPoint[0], translateDpVal(caps[n], temps[n])];
				if (tods) {
					addRawDataPoint(dataPoint, prepend, rawCapSeries, capChartSeries);
					if(!hideDewPoint) addRawDataPoint(dataPointDp, prepend, rawDpSeries, dpChartSeries);
				} else {
					if (addDiscon) {
						capSeries.push([dataPoint[0] - 1000, null]);
						if(!hideDewPoint) dpSeries.push([dataPoint[0] - 1000, null]);
					}
					capSeries.push(dataPoint);
					if(!hideDewPoint) dpSeries.push(dataPointDp);

					if (day.caps_min_base64 != null || day.caps_min != null) {
						if (addDiscon) {
							capMinMaxSeries.push([dataPoint[0] - 1000, null, null]);
							if(!hideDewPoint) dpMinMaxSeries.push([dataPoint[0] - 1000, null, null]);
						}
						var caps_min = day.caps_min_base64 != null ? new Float32Array(new Uint8Array([...atob(day.caps_min_base64)].map(c => c.charCodeAt(0))).buffer) : day.caps_min;
						var caps_max = day.caps_max_base64 != null ? new Float32Array(new Uint8Array([...atob(day.caps_max_base64)].map(c => c.charCodeAt(0))).buffer) : day.caps_max;

						dataPoint = [dataPoint[0], translateCapVal(caps_min[n]), translateCapVal(caps_max[n])];
						capMinMaxSeries.push(dataPoint);
						dataPointDp = [dataPoint[0], translateDpVal(caps_min[n], temps[n]), translateDpVal(caps_max[n], temps[n])];
						if(!hideDewPoint) dpMinMaxSeries.push(dataPointDp);

						capMax = Math.max(capMax, dataPoint[2]); capMin = Math.min(capMin, dataPoint[1]);
						dpMax = Math.max(dpMax, dataPointDp[2]); dpMin = Math.min(dpMin, dataPointDp[1]);

					} else {
						capMax = Math.max(capMax, dataPoint[1]); capMin = Math.min(capMin, dataPoint[1]);
						dpMax = Math.max(dpMax, dataPointDp[1]); dpMin = Math.min(dpMin, dataPointDp[1]);
					}
				}
			}

			if (lux != null && lux[n] > 0) {
				if (luxChartSeries == null) createLuxAxis();
				var dataPoint = [baseDate + (tods ? tods[n] * 1000 : HOUR * (n + 0.5)), lux[n]];
				if (tods) {
					addRawDataPoint(dataPoint, prepend, rawLuxSeries, luxChartSeries);
				} else {
					/*var addDiscon = false;
					if (luxSeries.length > 0 && (dataPoint[0] - luxSeries[luxSeries.length - 1][0] > 5 * HOUR || crossDisconBoundary(luxSeries[luxSeries.length - 1][0], dataPoint[0]))) {
						luxSeries.push([dataPoint[0] - 1000, null]);
						addDiscon = true;
					}*/
					if (addDiscon) luxSeries.push([dataPoint[0] - 1000, null]);
					luxSeries.push(dataPoint);

					if (day.lux_min_base64 != null || day.lux_min != null) {
						if (addDiscon)
							luxMinMaxSeries.push([dataPoint[0] - 1000, null, null]);

						var lux_min = day.lux_min_base64 != null ? new Float64Array(new Uint8Array([...atob(day.lux_min_base64)].map(c => c.charCodeAt(0))).buffer) : day.lux_min;
						var lux_max = day.lux_max_base64 != null ? new Float64Array(new Uint8Array([...atob(day.lux_max_base64)].map(c => c.charCodeAt(0))).buffer) : day.lux_max;

						dataPoint = [dataPoint[0], lux_min[n], lux_max[n]];
						luxMinMaxSeries.push(dataPoint);
						luxMax = Math.max(luxMax, dataPoint[2]); luxMin = Math.min(luxMin, dataPoint[1]);
					} else {
						luxMax = Math.max(luxMax, dataPoint[1]); luxMin = Math.min(luxMin, dataPoint[1]);
					}
				}
			}

		}
		avg/=count;
		
		if (!tods) {

			if (bandTempSeries.length > 0 && baseDate + HOUR*12 - bandTempSeries[bandTempSeries.length - 1][0] > 25 * HOUR) {
				addDiscon = true;
				bandTempSeries.push([baseDate, null, null]);
			} else
				addDiscon = false;

			//int bandi= findBandArrayIndexFor(baseDate,bandTempSeries);
			if (capSeries.length > 0) {
				if (addDiscon) {
					bandCapSeries.push([baseDate, null, null]);
					bandDpSeries.push([baseDate, null, null]);
				}
				bandCapSeries.push([baseDate + HOUR * 12, capMin, capMax]);
				bandDpSeries.push([baseDate + HOUR * 12, dpMin, dpMax]);
			}
			if (luxSeries.length > 0) {
				if (addDiscon)
					bandLuxSeries.push([baseDate, null,null]);

				bandLuxSeries.push([baseDate + HOUR * 12, luxMin, luxMax]);
			}
			bandTempSeries.push( [baseDate+HOUR*12, tempMin, tempMax]);
		}
		return avg;
	}
	var tempColor = "#CC2200", tempRangeColor="#FF8866", tempLowColor="#0066cc", tempRangeLowColor="#66b3ff";
	var capColor = "#007F00", capRangeColor = "#66E566", capLowColor="#F7A35C", capRangeLowColor="#fac294";
		var dpColor = "#00AACC", dpRangeColor = "#26D0F2";
		var luxColor = "#60A0DF", luxRangeColor = "#bfd9f2", luxLowColor = "#222", luxRangeLowColor = "#aaa";

	function dataLoader(fromDate, toDate, onData){
		$.ajax({
			url: WSROOT + (isUUID ? "ethLogShared.asmx/GetStats"+(hasALS?"Lux":"")+"RawByUUID" : "ethLogs.asmx/GetStats"+(hasALS?"Lux":"")+"Raw"),
			data: JSON.stringify(isUUID? {uuid: slaveId, fromDate: fromDate, toDate: toDate} : { id: slaveId, fromDate: fromDate, toDate: toDate }),
			success: function (retval, textStatus) {
				onData(retval.d);
			},
			error: function (xhr, textStatus, exception) {
				ajaxErrorHandler(xhr, exception);
			}
		});
	}
		function updateSeries(series,data,type) {
			if(series) {
				if(series.type!=type) series.update({type: type},false);
				if(data!=null)
					series.setData(data,false);
			}
		}
	function updateChartType(zoomLevel) {
		if (zoomLevel > ChartZoomLevelNormal) {
			updateSeries(chart.series[0], bandTempSeries, "arearange" );
			if (capChartSeries!=null)
				updateSeries(capChartSeries, bandCapSeries, "arearange");
			if (dpChartSeries!=null)
				updateSeries(dpChartSeries, bandDpSeries, "arearange");
			if (luxChartSeries != null)
				updateSeries(luxChartSeries, bandLuxSeries, "arearange");

			if (hourly_minmax) {
				tempMinMaxChartSeries.hide();
				if (capMinMaxChartSeries != null) capMinMaxChartSeries.hide();
				if (dpMinMaxChartSeries != null) dpMinMaxChartSeries.hide();
				if (luxMinMaxChartSeries != null) luxMinMaxChartSeries.hide();
			}
			chart.redraw();
		}
		else if (zoomLevel == ChartZoomLevelNormal) {
			if (hourly_minmax) {

				updateSeries(chart.series[0], tempSeries, "line");
				if (capChartSeries != null)
					updateSeries(capChartSeries, capSeries, "line");
				if (dpChartSeries != null)
					updateSeries(dpChartSeries, dpSeries, "line");
				if (luxChartSeries != null)
					updateSeries(luxChartSeries, luxSeries, "line");

				updateSeries(tempMinMaxChartSeries, tempMinMaxSeries, "arearange");				
				tempMinMaxChartSeries.show();

				if (capMinMaxChartSeries != null) {
					updateSeries(capMinMaxChartSeries, capMinMaxSeries, "arearange");
					if (capChartSeries.visible) capMinMaxChartSeries.show();
				}
				if (dpMinMaxChartSeries != null) {
					updateSeries(dpMinMaxChartSeries, dpMinMaxSeries, "arearange");
					if (dpChartSeries.visible) dpMinMaxChartSeries.show();
				}
				if (luxMinMaxChartSeries != null) {
					updateSeries(luxMinMaxChartSeries, luxMinMaxSeries, "arearange");
					luxMinMaxChartSeries.show();
				}
			} else {
				updateSeries(chart.series[0], tempSeries, "line");
				if (capChartSeries!=null)
					updateSeries(capChartSeries, capSeries, "line");
				if (dpChartSeries!=null)
					updateSeries(dpChartSeries, dpSeries, "line");
				if (luxChartSeries != null)
					updateSeries(luxChartSeries, luxSeries, "line");
			}
			chart.redraw();
		}
		else
		{
			updateSeries(chart.series[0], null, "line" );
			if (capChartSeries!=null)
				updateSeries(capChartSeries, null, "line");
			if (dpChartSeries!=null)
				updateSeries(dpChartSeries, null, "line");
			if (luxChartSeries != null)
				updateSeries(luxChartSeries, null, "line");

			if (hourly_minmax) {
				tempMinMaxChartSeries.hide();
				if (capMinMaxChartSeries != null) capMinMaxChartSeries.hide();
				if (dpMinMaxChartSeries != null) dpMinMaxChartSeries.hide();
				if (luxMinMaxChartSeries != null) luxMinMaxChartSeries.hide();
			}
			chart.redraw();
		}

	}

		function loadMarkers() {
				chart.yAxis.forEach(function (axis) {
					var series = null;
					for (var i = 0; i < axis.series.length; i++) {
						if (axis.series[i].visible) {
							series = axis.series[i];
							break;
						}
					}
					if (series == null) return;
					$.ajax({
						url: WSROOT + (isUUID ? "ethLogShared.asmx/LoadMarkers" : "ethLogs.asmx/LoadMarkers"),
						data: JSON.stringify({ "id": slaveId, "type": axisTitle2Type[axis.options.title.text] }),
						success: function (retval, textStatus) {
							retval.d.forEach(function (m) {
								restoreMarker(series, m);
							});
						},
						error: function (xhr, textStatus, exception) { }
					});
				});

		}
	
	function clearHourlyData() {
		tempSeries = []; tempMinMaxSeries = [];
		capSeries = []; capMinMaxSeries = [];
		dpSeries = []; dpMinMaxSeries = [];
		luxSeries = []; luxMinMaxSeries = [];
		bandCapSeries = [];
		bandDpSeries = [];
		bandTempSeries = [];
		bandLuxSeries = [];
	}
	function clearCachedRawData() {
		rawTempSeries.length = 0;
		rawCapSeries.length = 0;
		rawDpSeries.length = 0;
		rawLuxSeries.length = 0;
		chart.series[0].setData([]);
		if (capChartSeries!=null)
			capChartSeries.setData([]);
		if (dpChartSeries!=null)
			dpChartSeries.setData([]);
		if (luxChartSeries != null)
			luxChartSeries.setData([]);
	}
	function loadCachedRawData() {
		chart.series[0].setData(rawTempSeries);
		if (capChartSeries != null)
			capChartSeries.setData(rawCapSeries);
		if (dpChartSeries != null)
			dpChartSeries.setData(rawDpSeries);
		if (luxChartSeries != null)
			luxChartSeries.setData(rawLuxSeries);
	}
	var disconProcessed = false;
	function adjDiscon(d, deltaX) {
		if (d[2] + deltaX <= d[0]) return false;
		if (d[3] + deltaX >= d[1]) return false;
		$.ajax({
			url: WSROOT + "ethLogs.asmx/UpdateDiscon",
			data: JSON.stringify({ "id": slaveId, "begin": dateToFileTime2(d[0]), "end": dateToFileTime2(d[1]), "offsetSec":  Math.round((d[2]+deltaX-d[0])/1000)}),
			success: function (retval, textStatus) {
				d[2] += deltaX;
				d[3] += deltaX;
				[tempSeries, capSeries, dpSeries, luxSeries].forEach(function (series) {
					if (series != null) {
						series.forEach(function (dp) {
							if (dp[0] > d[0] && dp[0] < d[1]) dp[0] = dp[0] + deltaX;
						});
					}
				});
				if (hourly_minmax) {
					[tempMinMaxSeries, capMinMaxSeries,dpMinMaxSeries, luxMinMaxSeries].forEach(function (series) {
						if (series != null) {
							series.forEach(function (dp) {
								if (dp[0] > d[0] && dp[0] < d[1]) dp[0] = dp[0] + deltaX;
							});
						}
					});
				}
				//updateChartType(zoomLevel);
				chart.series.forEach(function (series) {
					series.data.forEach(function (dp) {
						if (dp.x > d[0] && dp.x < d[1]) {
							dp.update({ x: dp.x + deltaX }, false, false);
						}
					});
				});

				chart.xAxis[0].removePlotBand(d[0] + "L");
				chart.xAxis[0].removePlotBand(d[0] + "R");

				addDisconButtonPlotband(d);
				//chart.xAxis[0].update();
				//chart.xAxis[0].isDirty = true;
				chart.redraw();
			},
			error: function (xhr, textStatus, exception) {
				ajaxErrorHandler(xhr, exception);
			}
		});
		return true;
	}
	function addDisconButtonPlotband(d) {
		chart.xAxis[0].addPlotBand({
			id: d[0]+"L",
			color: 'rgba(255,170,170,1)', from: d[0], to: d[2],
			label: { text: '<', verticalAlign: 'middle' },
			events: {
				click: function (e) { adjDiscon(d, Math.max(10000, ((d[1] - d[0]) - (d[3] - d[2])) / 100) * -1); },
				mouseover: function (e) { if (d[2] > d[0]) this.svgElem.attr('fill', 'rgba(255,170,170,.5)'); },
				mouseout: function (e) { this.svgElem.attr('fill', this.options.color); }
			}
		});

		chart.xAxis[0].addPlotBand({
			id: d[0]+"R",
			color: 'rgba(255,170,170,1)', from: d[3], to: d[1],
			label: { text: '>', verticalAlign: 'middle' },
			events: {
				click: function (e) { adjDiscon(d, Math.max(10000, ((d[1] - d[0]) - (d[3] - d[2])) / 100)); },
				mouseover: function (e) { if (d[3] < d[1]) this.svgElem.attr('fill', 'rgba(255,170,170,.5)'); },
				mouseout: function (e) { this.svgElem.attr('fill', this.options.color); }
			}
		});
	}
	function processDiscon() {
		discons.forEach(function (d, i) {
			if (!disconProcessed && d[2] && d[3]) {
				chart.xAxis[0].addPlotBand({
					color: 'rgba(255,170,170,.1)', from: d[0], to: d[1]
				});
				disconProcessed = true;

				if (isUUID) return;
							// val moves from d[0] to d[2]-d[0]+(d[1]-d[3])+d[0] = d[2]+d[1]-d[3]

				addDisconButtonPlotband(d);				
			}
		});
	}

		var chartTitle=tagName.replace(/\+/g," ")+" Temperature"+((!hasChipTemp&&capSeries.length>0)? ("/"+CapName):"")+" Charts";
		$("#stat_title").text(chartTitle);

	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',
				style: { fontFamily: "ProximaNovaLight, Arial" },
				type: "line",
				events: {
					redraw: onChartRedraw, 
					selectedpoints: function (event) {
						var fromDateString = new Date(event.from).toLocaleString();
						var toDateString = new Date(event.to).toLocaleString();
						var num_visible_series = chart.series.length / (hourly_minmax ? 2 : 1);
						var btn = $("#deleteTemperatureLogBtn");
						btn.simpledialog({
							mode: 'bool', prompt: "Permanently delete all temperature/RH log data between " + fromDateString + " and " + toDateString + (zoomLevel == ChartZoomLevelRaw ? " ("+Math.ceil(event.points.length/num_visible_series )+" points)?":"?"),
							useModal: true, forceInput: true, cleanOnClose: true,
							'buttons': {
								'Yes': {
									click: function () {
										var oldhtml = show_finding(btn, "Deleting...")
										$.ajax({
											url: WSROOT + "ethLogs.asmx/DeleteTemperatureData2",
											data: JSON.stringify({
												id: slaveId, fromFT: dateToFileTime2(event.from ), toFT: dateToFileTime2(event.to )
											}),
											complete: function () {
												restore_finding(btn, oldhtml);
												select_to_delete_mode = false;
											},
											success: function (retval) {
												Highcharts.each(event.points, function (point) {
													point.update(null, false, false);
												});												
												chart.redraw();
												popup(retval.d + " records permanently removed.", null);
											}
										});
									}, icon: "forward"
								},
								'Cancel': {
									click: function () {
										Highcharts.each(event.points, function (point) {
											point.select(false);
										});
										select_to_delete_mode = false;
									}, icon: "back"
								}
							}
						});

					},
					click: function () {
						if (select_to_delete_mode) {
							var points = this.getSelectedPoints();
							if (points.length > 0) {
								Highcharts.each(points, function (point) {
									point.select(false);
								});
								select_to_delete_mode = false;
							}
						}
					},
					selection: function (e) {
						if (select_to_delete_mode) {
							Highcharts.each(this.series, function (series) {
								if (series.points != null) {
									Highcharts.each(series.points, function (point) {
										if (point == null) return;
										if (point.x >= e.xAxis[0].min && point.x <= e.xAxis[0].max) {
											point.select(true, true);
										}
									});
								}
							});
							Highcharts.fireEvent(this, 'selectedpoints', { points: this.getSelectedPoints(), from: e.xAxis[0].min, to: e.xAxis[0].max });
							return false;	// don't zoom
						} else {
							updateZoom(this, e.xAxis ? e.xAxis[0] : dataRange, function () { });
						}
					}
				}
			},
			xAxis: {
				type: 'datetime',
				events: {
					afterSetExtremes: function () {
						processDiscon();
					},
					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: 'Temperature' }, allowDecimals: false, 
				labels: { format: '{value}°' + (temp_unit == 1 ? "F" : "C") }
			}],
			tooltip: {
				borderColor: "gray",
				animation:false, 
				valueDecimals: 1,
				crosshairs: true,
				shared: true,
				footerFormat:old_style_query?"<br/><b>.</b>": "<br/><b>Press M to place a marker...</b>", 
				followPointer: false,
				style: { pointerEvents: 'all' },
				dateTimeLabelFormats: {
					second: "%A, %b %e, %I:%M:%S %p",
				}
			},
			legend: {
				layout: 'vertical', align: 'left', x: 80, verticalAlign: 'top', y: 40, floating: true,
				backgroundColor: (Highcharts.theme && Highcharts.theme.legendBackgroundColor) || '#FFFFFF'
			},
			plotOptions: {
				series: {
					marker: { enabled: false }
				}
			},

			series: [{
				name: 'Temperature',
				tooltip: {
					valueSuffix: "°" + (temp_unit == 1 ? "F" : "C")
				},
				events: {
					afterAnimate: onAnimationEnd
				},
				color: tempColor,
				threshold: tempBL ? translateTempVal(tempBL) : (temp_unit == 1 ? 32 : 0),
				negativeColor: tempLowColor
			}],
			lang: lang,
			exporting: {
				libURL:"https://code.highcharts.com/6.1.0/lib",
				chartOptions: {
					title: {text: chartTitle},
					subtitle: {
						text: null
					},
					chart: {
						width: $(window).width(),
						height: $(window).height() - 42
					}
				},
				filename: chartTitle,
				buttons: buttons
			}
		};
		if (hasALS){
			options.exporting.buttons["luxAxisButton"] = {
				text: "Log/Linear Scale",
				_titleKey: "logScale",
				onclick: function () {
					if (chart.yAxis[luxAxisIndex].options.type == "linear")
						chart.yAxis[luxAxisIndex].update({ type: "logarithmic" });
					else
						chart.yAxis[luxAxisIndex].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();

		if (hourly_minmax) {
			tempMinMaxChartSeries = chart.addSeries({
				name: 'Temperature Highs/Lows', turboThreshold: 1e6,
				tooltip: {
					valueSuffix: "°" + (temp_unit == 1 ? "F" : "C")
				}, visible: false, color: tempRangeColor, 
				threshold: tempBL ? translateTempVal(tempBL) : (temp_unit == 1 ? 32 : 0),
				negativeColor: tempRangeLowColor
			});
		}

		$("#moreButtons").show();

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

		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 updateTempUnit(d) {
		if (temp_unit == -1)
			temp_unit  = d.temp_unit;

		chart.yAxis[0].update({ labels: { format: '{value}°' + (temp_unit == 1 ? "F" : "C") } });
		chart.series[0].update({ tooltip: { valueSuffix: "°" + (temp_unit == 1 ? "F" : "C") } });
		if (dpChartSeries!=null)
			dpChartSeries.update({ tooltip: { valueSuffix: "°" + (temp_unit == 1 ? "F" : "C") } });
		if (tempMinMaxChartSeries!=null) 
			tempMinMaxChartSeries.update({ tooltip: { valueSuffix: "°" + (temp_unit == 1 ? "F" : "C") } });
		if (dpMinMaxChartSeries!=null)
			dpMinMaxChartSeries.update({ tooltip: { valueSuffix: "°" + (temp_unit == 1 ? "F" : "C") } });		
	}
	var hourlyDataCache = {};
	function hourlyDataLoader(onData) {
		if (hourlyDataCache[since_calibration] != null)
		{
			onData(hourlyDataCache[since_calibration]);
			return;
		}

		$.ajax({
			url: WSROOT + (isUUID ? "ethLogShared.asmx/GetTemperature" + (hasALS ? "Lux" : "") + "StatsByUUID3" : "ethLogs.asmx/GetTemperature" + (hasALS ? "Lux" : "") + "Stats3"),
			data: JSON.stringify({ "id": slaveId , "withMinMax": hourly_minmax, "sinceLastCalibration": since_calibration}),
			success: function (retval, textStatus) {
				hourlyDataCache[since_calibration] = retval.d.temps;
				onData(retval.d.temps);				
				updateTempUnit(retval.d);
			},
			error: function (xhr, textStatus, exception) {
				ajaxErrorHandler(xhr, exception);
			}
		});
	}
	var axisTitle2Type = { "Temperature": "temperature", "Humidity": "cap", "Lux": "light", "VOC": "tvoc" };
	function saveMarker(series, filetime, comment) {
		$.ajax({
			url: WSROOT + "ethClient.asmx/MergeMarker",
			data: JSON.stringify({ id: slaveId, filetime: filetime, type: axisTitle2Type[series.yAxis.options.title.text], comment: comment })
		});
	}
	function deleteMarker(series, filetime) {
		$.ajax({
			url: WSROOT + "ethClient.asmx/DeleteMarker",
			data: JSON.stringify({ id: slaveId, filetime: filetime, type: axisTitle2Type[series.yAxis.options.title.text] })
		});
	}

		var tempBL, capBL, luxBL;

	function dataSpanLoader(onData) {
		$.ajax({
			url: WSROOT + (isUUID ? "ethLogShared.asmx/GetMultiTagStatsSpanByUUIDs2" : "ethLogs.asmx/GetMultiTagStatsSpan2"),
			data: JSON.stringify({ "ids": [slaveId], "type": "temperature", "sinceLastCalibration": since_calibration }),
			success: function (retval, textStatus) {
				tempBL = retval.d.tempBL;
				capBL = retval.d.capBL;
				luxBL = retval.d.luxBL;
				temp_unit  = retval.d.temp_unit;
				tzo = retval.d.tzo;

				onData(fileTimeToDate(retval.d.from).getTime(), fileTimeToDate(retval.d.to).getTime());
				if (retval.d.discons != null && retval.d.discons[0] != null) {
					discons = retval.d.discons[0].map(function (d) { return [fileTimeToDate(d[0]).getTime(), fileTimeToDate(d[1]).getTime()]; });  // only support first tag discon.
				}
			},
			error: function (xhr, textStatus, exception) {
				ajaxErrorHandler(xhr, exception);
			}
		});
	}

	var select_to_delete_mode = false;
	function deleteOptions(btn) {
		if (btn.val() == "2") {
			$("#deleteTemperatureLogBtn").simpledialog({
				mode: 'bool', prompt: "This operation will permanently delete all temperature log data of this tag. Do you want to continue?",
				useModal: true, forceInput: true, cleanOnClose: true,
				'buttons': {
					'Yes': {
						click: function () {
							var oldhtml = show_finding(btn, "Deleting...")
							$.ajax({
								url: WSROOT + "ethLogs.asmx/DeleteTemperatureData",
								data: "{id: " + slaveId + "}",
								complete: function () { restore_finding(btn, oldhtml); },
								success: function (retval) {
									holder.empty();
									holder.html("<center>" + retval.d + " records permanently removed.</center>");
								}
							});
						}, icon: "forward"
					},
					'Cancel': {
						click: function () {
						}, icon: "back"
					}
				}
			});
		} else {
			popup("Please drag to select date range / points to be deleted. ", null);
			select_to_delete_mode = true;
		}
	}

		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) {
			var vp = [];
			chart.series.forEach(function (cs) {
				if (cs.visible && cs.points != null && cs.points[i-1]) {
					if(cs.points[i-1].x == s.points[i-1].x )
						vp.push(cs.points[i - 1]);
				}
			});
			chart.tooltip.refresh(vp);
			if (chart.hoverPoints != null && chart.hoverPoint == null)
				chart.hoverPoints.forEach(function (hp) { if (hp.series == s) chart.hoverPoint = hp; });

			createMarker(m.comment, m.filetime);
			chart.tooltip.hide();
			return true;
		}
	}
	return false;
}

	</script> 
	</div>

</body>
</html>