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?226" />
	<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?1" 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>
			<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%; ">
		</div>
		<span id="moreButtonTip" onclick="moreButtonTipClicked();"><span></span>Scroll for Options</span>
		<center id="moreButtons">
			<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"><button data-icon="delete" data-inline=1 data-theme="e" id="deleteTemperatureLogBtn">Delete temperature log</button></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" type="text/javascript"></script> 
	<script src="styles/rawDataChart.js?838" 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 params = (window.location.search.length > 0 ? 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 = window.location.search.length > 0 ? params.length > 3 : localStorage["mytaglist.stats.hasALS"] == "true";
		var isZmod = localStorage["mytaglist.stats.hasZmod"] == "true";
		var hasChipTemp = localStorage["mytaglist.stats.hasChipTemp"] == "true";
		var isWME = localStorage["mytaglist.stats.isWME"] == "true";
		var isMoisture = localStorage["mytaglist.stats.isMoisture"] == "true";

		if (hasALS || hasChipTemp) dewPointMode = true;
		if (isWME) dewPointMode = false;
		var CapName = isWME ? "WME" : (isMoisture ? "Moisture" : "Humidity");

		$("#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");
		$(window).resize(function () {
			resizeChart();
			processDiscon();
		});
		
		var shareInfo;
		if (window.location.search.length > 0) {
			$(".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();
			});
		} else {
			$(".sharedOnly").hide();
			$("#downloadTemperatureLogBtn").click(function () {
				window.location = WSROOT + "ethDownloadTempCSV.aspx?id=" + params[0] + "&fromDate=" + $("#custom_min").val() + "&toDate=" + $("#custom_max").val();
			});
			$("#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 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'></div>"+
						"<div data-role='fieldcontain'><label for='downloadURL'>CSV download link:</label><input type='text' id='downloadURL'></div>"+
						"<div data-role='fieldcontain'><label for='iosURL'>Link to open graph in iOS app:</label><input type='text' id='iosURL'></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>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, $body);
		}
	}


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

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

	var capChartSeries = null, tempMinMaxChartSeries = null, capMinMaxChartSeries = null;
	var luxChartSeries = null, luxMinMaxChartSeries = null;
	var tzoffset = (new Date()).getTimezoneOffset() * 60000;
	function createLuxAxis() {
		chart.addAxis({
			gridLineWidth: 0,
			title: { text: isZmod?"VOC": "Lux", style: { color: luxColor } },
			labels: { style: { color: luxColor } },
			opposite: true
			,type: "logarithmic"
		});
		luxChartSeries = chart.addSeries({
			name: isZmod ? "Volatile Organic Compound" : "Ambient Light", yAxis: 1,
			tooltip: { valueSuffix: isZmod?" ppb":" lux" }, color: luxColor
		});
		if (hourly_minmax) {
			luxMinMaxChartSeries = chart.addSeries({
				name: isZmod?"VOC Highs/Lows":"Ambient Light Highs/Lows", yAxis: 1,
				tooltip: { valueSuffix: isZmod?" ppb":" lux" },
				color: luxRangeColor, visible: false
			});
		}
	}
	function createCapAxis() {
		if (!dewPointMode) {
			chart.addAxis({ // Secondary yAxis
				gridLineWidth: 0,
				title: {
					text: CapName, style: { color: capColor }
				},
				labels: {
					format: '{value} %', style: { color: capColor }
				},
				opposite: true
			});
		}
		capChartSeries = chart.addSeries({
			name: hasChipTemp?"Chip Temperature":( dewPointMode ? 'Dew Point' : CapName),
			yAxis: dewPointMode ? 0 : 1,
			tooltip: {
				valueSuffix: dewPointMode ? " °" + (temp_unit == 1 ? "F" : "C") : " %"
			},
			color: capColor
		});
		if (hourly_minmax) {
			capMinMaxChartSeries = chart.addSeries({
				name: hasChipTemp?"Chip Highs/Lows":(dewPointMode ? 'Dew Point Highs/Lows' : CapName+' Highs/Lows'), turboThreshold: 1e6,
				yAxis: dewPointMode ? 0 : 1,
				tooltip: {
					valueSuffix: dewPointMode ? " °" + (temp_unit == 1 ? "F" : "C") : " %"
				},
				color: capRangeColor, visible: false
			});
		}
	}
	function translateCapVal(val, temp) {
		if (hasChipTemp) return translateTempVal(val);
		if (dewPointMode) {
			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 luxMin = 80000, luxMax = 0;
		if (hasChipTemp) { capMin = tempMin; capMax = tempMax; }
		else if (dewPointMode) { capMin = tempMin; capMax = -100; }

		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], temps[n])];
				if (tods) {
					addRawDataPoint(dataPoint, prepend, rawCapSeries, capChartSeries);
				} else {
					/*var addDiscon = false;
					if (capSeries.length > 0 && (dataPoint[0] - capSeries[capSeries.length - 1][0] > 5 * HOUR || crossDisconBoundary(capSeries[capSeries.length - 1][0], dataPoint[0]))) {
						capSeries.push([dataPoint[0] - 1000, null]);
						addDiscon = true;
					}*/
					if (addDiscon) capSeries.push([dataPoint[0] - 1000, null]);
					capSeries.push(dataPoint);

					if (day.caps_min_base64 != null || day.caps_min != null) {
						if (addDiscon)
							capMinMaxSeries.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], temps[n]), translateCapVal(caps_max[n], temps[n])];
						capMinMaxSeries.push(dataPoint);

						capMax = Math.max(capMax, dataPoint[2]); capMin = Math.min(capMin, dataPoint[1]);
					} else {
						capMax = Math.max(capMax, dataPoint[1]); capMin = Math.min(capMin, dataPoint[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]);

				bandCapSeries.push([baseDate + HOUR * 12, capMin, capMax]);
			}
			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";
	var capColor = "#007F00", capRangeColor = "#66E566";
	var luxColor = "#60A0DF", luxRangeColor = "#bfd9f2";

	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.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 (luxChartSeries != null)
				updateSeries(luxChartSeries, bandLuxSeries, "arearange");

			if (hourly_minmax) {
				tempMinMaxChartSeries.hide();
				if (capMinMaxChartSeries != null) capMinMaxChartSeries.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 (luxChartSeries != null)
					updateSeries(luxChartSeries, luxSeries, "line");

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

				if (capMinMaxChartSeries != null) {
					updateSeries(capMinMaxChartSeries, capMinMaxSeries, "arearange");
					capMinMaxChartSeries.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 (luxChartSeries != null)
					updateSeries(luxChartSeries, luxSeries, "line");
			}
			chart.redraw();
		}
		else
		{
			updateSeries(chart.series[0], null, "line" );
			if (capChartSeries!=null)
				updateSeries(capChartSeries, null, "line");
			if (luxChartSeries != null)
				updateSeries(luxChartSeries, null, "line");

			if (hourly_minmax) {
				tempMinMaxChartSeries.hide();
				if (capMinMaxChartSeries != null) capMinMaxChartSeries.hide();
				if (luxMinMaxChartSeries != null) luxMinMaxChartSeries.hide();
			}
			chart.redraw();
		}
	}
	function clearHourlyData() {
		tempSeries = []; tempMinMaxSeries = [];
		capSeries = []; capMinMaxSeries = [];
		luxSeries = []; luxMinMaxSeries = [];
		bandCapSeries = [];
		bandTempSeries = [];
		bandLuxSeries = [];
	}
	function clearCachedRawData() {
		rawTempSeries.length = 0;
		rawCapSeries.length = 0;
		rawLuxSeries.length = 0;
		chart.series[0].setData([]);
		if (capChartSeries!=null)
			capChartSeries.setData([]);
		if (luxChartSeries != null)
			luxChartSeries.setData([]);
	}
	function loadCachedRawData() {
		chart.series[0].setData(rawTempSeries);
		if (capChartSeries != null)
			capChartSeries.setData(rawCapSeries);
		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, 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, 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 + " Temperature" + ((!hasChipTemp && capSeries.length > 0) ? (dewPointMode?"/Dew Point":"/"+CapName) : "") + " Charts";

	function createChart() {
		var latestDate = (new Date(dataRange.max)).toLocaleString();
		var options = {
			title: { text: chartTitle },
			subtitle: {
				text: "Drag to zoom, hold Shift key and drag to pan, press M to add markers"
			},
			animation: false,
			chart: {
				zoomType: 'x', panning: true, panKey: 'shift',
				style: { fontFamily: "ProximaNovaLight, Arial" },
				type: "line",
				events: {
					selection: function (event) {
						updateZoom(this, event.xAxis ? event.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).toISOString().substring(0, 10));
						} catch (e) { }

					}
				},
				dateTimeLabelFormats: {
					hour: '%I:%M %p'
				}
			},
			yAxis: [{
				title: { text: 'Temperature' }, allowDecimals: false
			}],
			tooltip: {
				borderColor: "gray",
				valueDecimals: 1,
				crosshairs: true,
				shared: true,
				followPointer: false,
				dateTimeLabelFormats: {
					second: "%A, %b %e, %I:%M:%S",
				}
			},
			legend: {
				layout: 'vertical', align: 'left', x: 80, verticalAlign: 'top', y: 40, floating: true,
				backgroundColor: (Highcharts.theme && Highcharts.theme.legendBackgroundColor) || '#FFFFFF'
			},
			series: [{
				name: 'Temperature',
				tooltip: {
					valueSuffix: " °" + (temp_unit == 1 ? "F" : "C")
				},
				color: tempColor
			}],
			lang:{
				last24: (new Date(dataRange.max - 24 * HOUR)).toLocaleString() + "~\n" + latestDate,
				lastWeek: (new Date(dataRange.max - 7 * 24 * HOUR)).toLocaleString() + "~\n" + latestDate,
				last30Day: (new Date(dataRange.max - 30 * 24 * HOUR)).toLocaleString() + "~\n" + latestDate,
				fullRange: (new Date(dataRange.min)).toLocaleString() + "~\n" + latestDate,
				logScale: "Switch between log/linear scale for lux/voc",
				contextButtonTitle: "Save chart as PDF or images"
			},
			exporting: {
				chartOptions: {
					subtitle: {
						text: null
					},
					chart: {
						width: $(window).width(),
						height: $(window).height() - 42
					}
				},
				filename: chartTitle,
				buttons: {
					dayButton: {
						text: 'Last 24 Hours',
						_titleKey: "last24",
						onclick: function () {
							var range = { min: dataRange.max - DAY, max: dataRange.max };
							updateZoom(holder.highcharts(), range, function () {
								holder.highcharts().xAxis[0].setExtremes(dataRange.max - DAY, dataRange.max);
							});
						}
					},
					weekButton: {
						text: 'Last Week',
						_titleKey: "lastWeek",
						onclick: function () {
							var range = { min: dataRange.max - DAY * 7, max: dataRange.max };
							updateZoom(holder.highcharts(), range, function () {
								holder.highcharts().xAxis[0].setExtremes(dataRange.max - DAY * 7, dataRange.max);
							});
						}
					},
					monthButton: {
						text: 'Last 30 Days',
						_titleKey: "last30Day",					
						onclick: function () {
							var range = { min: dataRange.max - DAY * 30, max: dataRange.max };
							updateZoom(holder.highcharts(), range, function () {
								holder.highcharts().xAxis[0].setExtremes(dataRange.max - DAY * 30, dataRange.max);
							});
						}
					},
					allButton: {
						text: 'All',
						_titleKey: "fullRange",					
						onclick: function () {
							updateZoom(holder.highcharts(), dataRange, function () {
								holder.highcharts().xAxis[0].setExtremes(dataRange.min, dataRange.max);
							});
						}
					}
				}
			}
		};
		if (hasALS){
			options.exporting.buttons["luxAxisButton"] = {
				text: "Log/Linear Scale",
				_titleKey: "logScale",
				onclick: function () {
					if (chart.yAxis[1].options.type == "linear")
						chart.yAxis[1].update({ type: "logarithmic" });
					else
						chart.yAxis[1].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
			});
		}
	}

	$("#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 (dewPointMode && capChartSeries!=null)
			capChartSeries.update({ tooltip: { valueSuffix: " °" + (temp_unit == 1 ? "F" : "C") } });
		if (hourly_minmax) 
			tempMinMaxChartSeries.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) {
				updateTempUnit(retval.d);
				hourlyDataCache[since_calibration] = retval.d.temps;
				onData(retval.d.temps);				
			},
			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": [slaveId], "type": "temperature", "sinceLastCalibration": since_calibration }),
			success: function (retval, textStatus) {
				onData(fileTimeToDate(retval.d.from).getTime(), fileTimeToDate(retval.d.to).getTime());
				updateTempUnit(retval.d);
				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);
			}
		});
	}

	if (params.length > 1) {
		$.ajax({
			url: WSROOT + "ethAccount.asmx/ReflectAspxAuthCookie",
			data: JSON.stringify({ "cookie": params[1] }),
			success: function (retval, textStatus) {
				loadFirstData();
			}
		});
	}
	else
	{
		loadFirstData();
	}

	$('#deleteTemperatureLogBtn').click(function (e) {
		var btn = $('#deleteTemperatureLogBtn');
		$(e.target).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"
				}
			}
		});
	});


	</script> 
	</div>

</body>
</html>