var pages = ["graphs", "records", "reports", "about", "pi"];
var pageName = "";
// If this page we're on now is listed as a subpage, use ".." to get to the relative root
function get_relative_url() {
var sPath = window.location.pathname.replace(/\/$/, "");
pageName = sPath.substring(sPath.lastIndexOf('/') + 1);
if (pages.includes(pageName)) {
var relative_url = "..";
} else {
var relative_url = ".";
}
belchertown_debug("URL: Relative URL is: " + relative_url);
return relative_url;
}
// Determine if debug is on via URL var or config setting
if (getURLvar("debug") && (getURLvar("debug") == "true" || getURLvar("debug") == "1")) {
var belchertown_debug_config = true;
belchertown_debug("Debug: URL debug variable enabled");
} else {
var belchertown_debug_config = 0;
belchertown_debug("Debug: skin.conf belchertown_debug enabled");
}
var moment_locale = "en-US";
moment.locale(moment_locale);
var graphgroups_raw = {"homepage": ["chart1", "chart2", "chart3", "chart4"], "day": ["chart1", "chart2", "chart3", "chart4"], "week": ["chart1", "chart2", "chart3", "chart4"], "month": ["chart1", "chart2", "chart3", "chart4"], "year": ["chart1", "chart2", "chart3", "chart4"]};
var graphgroups_titles = {"homepage": "Homepage", "day": "Today", "week": "This Week", "month": "This Month", "year": "This Year"};
var graphpage_content = {};
// declare icon_dict as global variable
var icon_dict = {};
function belchertown_debug(message) {
if (belchertown_debug_config > 0) {
console.log(message);
}
}
jQuery(document).ready(function() {
// Bootstrap hover tooltips
jQuery(function() {
jQuery('[data-toggle="tooltip"]').tooltip()
})
// If the visitor has overridden the theme, keep that theme going throughout the full site and their visit.
if (sessionStorage.getItem('theme') == "toggleOverride") {
belchertown_debug("Theme: sessionStorage override in place.");
changeTheme(sessionStorage.getItem('currentTheme'));
}
// Change theme if a URL variable is set
if (window.location.search.indexOf('theme')) {
if (window.location.search.indexOf('?theme=dark') === 0) {
belchertown_debug("Theme: Setting dark theme because of URL override");
changeTheme("dark", true);
} else if (window.location.search.indexOf('?theme=light') === 0) {
belchertown_debug("Theme: Setting light theme because of URL override");
changeTheme("light", true);
} else if (window.location.search.indexOf('?theme=auto') === 0) {
belchertown_debug("Theme: Setting auto theme because of URL override");
sessionStorage.setItem('theme', 'auto')
autoTheme(18, 22, 07, 18)
}
}
// Dark mode checkbox toggle switcher
try {
document.getElementById('themeSwitch').addEventListener('change', function(event) {
belchertown_debug("Theme: Toggle button changed");
(event.target.checked) ? changeTheme("dark", true) : changeTheme("light", true);
});
} catch (err) {
// Silently exit
}
// After charts are loaded, if an anchor tag is in the URL, let's scroll to it
jQuery(window).on('load', function() {
var anchor_tag = location.hash.replace('#', '');
if (anchor_tag != '') {
// Scroll the webpage to the chart. The timeout is to let jQuery finish appending the outer div so the height of the page is completed.
setTimeout(function() {
jQuery('html, body').animate({scrollTop: jQuery('#' + anchor_tag).offset().top}, 500);
}, 500);
}
});
});
// Run this on every page for dark mode if skin theme is auto
ajaxweewx().then(function(weewx_data) { // This call will make sure json/weewx_data.json is loaded before anything else
update_weewx_data(weewx_data); // Initial call to update (date, daily high, low, etc)
belchertown_debug(weewx_data); // Make weewx_data.json available in debugging console
}).catch(function(e) {
console.log(e);
});
// Disable AJAX caching
jQuery.ajaxSetup({
cache: false
});
// Get the URL variables. Source: https://stackoverflow.com/a/26744533/1177153
function getURLvar(k) {
var p = {};
location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(s, k, v) {p[k] = v});
return k ? p[k] : p;
}
// http://stackoverflow.com/a/14887961/1177153
var weatherdirection = { 0: "N", 90: "E", 180: "S", 270: "W", 360: "N" };
// Change the color of the outTemp_F variable
function get_outTemp_color(unit, outTemp, returnColor = false) {
outTemp = parseFloat(outTemp).toFixed(0); // Convert back to decimal literal
if (unit == "degree_F") {
if (outTemp <= 0) {
var outTemp_color = "#1278c8";
} else if (outTemp <= 25) {
var outTemp_color = "#30bfef";
} else if (outTemp <= 32) {
var outTemp_color = "#1fafdd";
} else if (outTemp <= 40) {
var outTemp_color = "rgba(0,172,223,1)";
} else if (outTemp <= 50) {
var outTemp_color = "#71bc3c";
} else if (outTemp <= 55) {
var outTemp_color = "rgba(90,179,41,0.8)";
} else if (outTemp <= 65) {
var outTemp_color = "rgba(131,173,45,1)";
} else if (outTemp <= 70) {
var outTemp_color = "rgba(206,184,98,1)";
} else if (outTemp <= 75) {
var outTemp_color = "rgba(255,174,0,0.9)";
} else if (outTemp <= 80) {
var outTemp_color = "rgba(255,153,0,0.9)";
} else if (outTemp <= 85) {
var outTemp_color = "rgba(255,127,0,1)";
} else if (outTemp <= 90) {
var outTemp_color = "rgba(255,79,0,0.9)";
} else if (outTemp <= 95) {
var outTemp_color = "rgba(255,69,69,1)";
} else if (outTemp <= 110) {
var outTemp_color = "rgba(255,104,104,1)";
} else if (outTemp >= 111) {
var outTemp_color = "rgba(218,113,113,1)";
}
} else if (unit == "degree_C") {
if (outTemp <= 0) {
var outTemp_color = "#1278c8";
} else if (outTemp <= -3.8) {
var outTemp_color = "#30bfef";
} else if (outTemp <= 0) {
var outTemp_color = "#1fafdd";
} else if (outTemp <= 4.4) {
var outTemp_color = "rgba(0,172,223,1)";
} else if (outTemp <= 10) {
var outTemp_color = "#71bc3c";
} else if (outTemp <= 12.7) {
var outTemp_color = "rgba(90,179,41,0.8)";
} else if (outTemp <= 18.3) {
var outTemp_color = "rgba(131,173,45,1)";
} else if (outTemp <= 21.1) {
var outTemp_color = "rgba(206,184,98,1)";
} else if (outTemp <= 23.8) {
var outTemp_color = "rgba(255,174,0,0.9)";
} else if (outTemp <= 26.6) {
var outTemp_color = "rgba(255,153,0,0.9)";
} else if (outTemp <= 29.4) {
var outTemp_color = "rgba(255,127,0,1)";
} else if (outTemp <= 32.2) {
var outTemp_color = "rgba(255,79,0,0.9)";
} else if (outTemp <= 35) {
var outTemp_color = "rgba(255,69,69,1)";
} else if (outTemp <= 43.3) {
var outTemp_color = "rgba(255,104,104,1)";
} else if (outTemp >= 43.4) {
var outTemp_color = "rgba(218,113,113,1)";
}
}
// Return the color value if requested, otherwise just set the div color
if (returnColor) {
return outTemp_color;
} else {
jQuery(".outtemp_outer").css("color", outTemp_color);
}
}
// Change the color of the aqi variable according to US-EPA standards
// (adjusted to match skin colors better)
function get_aqi_color(aqi, returnColor = false) {
if (aqi >= 301) {
var aqi_color = "#cc241d";
} else if (aqi >= 201) {
var aqi_color = "#b16286";
} else if (aqi >= 151) {
var aqi_color = "rgba(255,69,69,1)";
} else if (aqi >= 101) {
var aqi_color = "rgba(255,127,0,1)";
} else if (aqi >= 51) {
var aqi_color = "rgba(255,174,0,0.9)";
} else if (aqi < 51) {
var aqi_color = "#71bc3c";
}
// Return the color value if requested, otherwise just set the div color
if (returnColor) {
return aqi_color;
} else {
jQuery(".aqi_outer").css("color", aqi_color);
}
}
function get_gauge_color(value, options) {
if (options.color1) {
// Failsafe in case value drops below the lowest color position user has set.
// Otherwise color is undefined when the value is below color1_position
var color = options.color1
}
if (options.color2) {
if (value >= options.color2_position) {
var color = options.color2
}
}
if (options.color3) {
if (value >= options.color3_position) {
var color = options.color3
}
}
if (options.color4) {
if (value >= options.color4_position) {
var color = options.color4
}
}
if (options.color5) {
if (value >= options.color5_position) {
var color = options.color5
}
}
if (options.color6) {
if (value >= options.color6_position) {
var color = options.color6
}
}
if (options.color7) {
if (value >= options.color7_position) {
var color = options.color7
}
}
return color
}
function get_gauge_label(value, options) {
if (options.color1) {
if (options.color1_label) {
var label = options.color1_label
}
}
if (options.color2) {
if (value >= options.color2_position) {
var label = null
if (options.color2_label) {
var label = options.color2_label
}
}
}
if (options.color3) {
if (value >= options.color3_position) {
var label = null
if (options.color3_label) {
var label = options.color3_label
}
}
}
if (options.color4) {
if (value >= options.color4_position) {
var label = null
if (options.color4_label) {
var label = options.color4_label
}
}
}
if (options.color5) {
if (value >= options.color5_position) {
var label = null
if (options.color5_label) {
var label = options.color5_label
}
}
}
if (options.color6) {
if (value >= options.color6_position) {
var label = null
if (options.color6_label) {
var label = options.color6_label
}
}
}
if (options.color7) {
if (value >= options.color7_position) {
var label = null
if (options.color7_label) {
var label = options.color7_label
}
}
}
return label
}
function kts_to_beaufort(windspeed) {
// Given windspeed in knots, converts to Beaufort scale
if (windspeed <= 1) {
return 0
} else if (windspeed <= 3) {
return 1
} else if (windspeed <= 6) {
return 2
} else if (windspeed <= 10) {
return 3
} else if (windspeed <= 15) {
return 4
} else if (windspeed <= 21) {
return 5
} else if (windspeed <= 27) {
return 6
} else if (windspeed <= 33) {
return 7
} else if (windspeed <= 40) {
return 8
} else if (windspeed <= 47) {
return 9
} else if (windspeed <= 55) {
return 10
} else if (windspeed <= 63) {
return 11
} else if (windspeed > 63) {
return 12
}
}
function beaufort_cat(beaufort) {
// Given Beaufort number, returns category description
switch (beaufort) {
case 0:
return "calm"
case 1:
return "light air"
case 2:
return "light breeze"
case 3:
return "gentle breeze"
case 4:
return "moderate breeze"
case 5:
return "fresh breeze"
case 6:
return "strong breeze"
case 7:
return "near gale"
case 8:
return "gale"
case 9:
return "strong gale"
case 10:
return "storm"
case 11:
return "violent storm"
case 12:
return "hurricane force"
}
}
function highcharts_tooltip_factory(obsvalue, point_obsType, highchartsReturn = false, rounding, mirrored = false, numberFormat) {
// Mirrored values have the negative sign removed
if (mirrored) {
obsvalue = Math.abs(obsvalue);
}
if (point_obsType == "windDir") {
if (obsvalue >= 0 && obsvalue <= 11.25) {
ordinal = "N"; // N
} else if (obsvalue >= 11.26 && obsvalue <= 33.75) {
ordinal = "NNE"; // NNE
} else if (obsvalue >= 33.76 && obsvalue <= 56.25) {
ordinal = "NE"; // NE
} else if (obsvalue >= 56.26 && obsvalue <= 78.75) {
ordinal = "ENE"; // ENE
} else if (obsvalue >= 78.76 && obsvalue <= 101.25) {
ordinal = "E"; // E
} else if (obsvalue >= 101.26 && obsvalue <= 123.75) {
ordinal = "ESE"; // ESE
} else if (obsvalue >= 123.76 && obsvalue <= 146.25) {
ordinal = "SE"; // SE
} else if (obsvalue >= 146.26 && obsvalue <= 168.75) {
ordinal = "SSE"; // SSE
} else if (obsvalue >= 168.76 && obsvalue <= 191.25) {
ordinal = "S"; // S
} else if (obsvalue >= 191.26 && obsvalue <= 213.75) {
ordinal = "SSW"; // SSW
} else if (obsvalue >= 213.76 && obsvalue <= 236.25) {
ordinal = "SW"; // SW
} else if (obsvalue >= 236.26 && obsvalue <= 258.75) {
ordinal = "WSW"; // WSW
} else if (obsvalue >= 258.76 && obsvalue <= 281.25) {
ordinal = "W"; // W
} else if (obsvalue >= 281.26 && obsvalue <= 303.75) {
ordinal = "WNW"; // WNW
} else if (obsvalue >= 303.76 && obsvalue <= 326.25) {
ordinal = "NW"; // NW
} else if (obsvalue >= 326.26 && obsvalue <= 348.75) {
ordinal = "NNW"; // NNW
} else if (obsvalue >= 348.76 && obsvalue <= 360) {
ordinal = "N"; // N
}
// highchartsReturn returns the full wind direction string for highcharts tooltips. e.g "NNW (337)"
if (highchartsReturn) {
output = ordinal + " (" + Math.round(obsvalue) + "\xBA)";
} else {
output = ordinal;
}
} else {
try {
// Setup any graphs.conf overrides on formatting
var {decimals, decimalPoint, thousandsSep} = numberFormat;
// Try to apply the highcharts numberFormat for locale awareness. Use rounding from weewx.conf StringFormats.
// -1 is set from Python to notate no rounding data available and decimals from graphs.conf is undefined.
if (rounding == "-1" && typeof decimals === "undefined") {
output = Highcharts.numberFormat(obsvalue);
} else {
// If the amount of decimal is defined, use that instead since rounding is provided to the function.
if (typeof decimals !== "undefined") {
rounding = decimals;
}
// If decimalPoint is undefined, use the auto detect from the skin since this comes from the skin.
if (typeof decimalPoint === "undefined") {
decimalPoint = ".";
}
// If thousandsSep is undefined, use the auto detect from the skin since this comes from the skin.
if (typeof thousandsSep === "undefined") {
thousandsSep = ",";
}
output = Highcharts.numberFormat(obsvalue, rounding, decimalPoint, thousandsSep);
}
} catch (err) {
// Fall back to just returning the highcharts point number value, which is a best guess.
output = Highcharts.numberFormat(obsvalue);
}
}
return output;
}
// Handle wind arrow rotation with the ability to "rollover" past 0
// without spinning back around. e.g 350 to 3 would normally spin back around
// https://stackoverflow.com/a/19872672/1177153
function rotateThis(newRotation) {
if (newRotation == "N/A") {return;}
belchertown_debug("rotateThis: rotating to " + newRotation);
var currentRotation;
finalRotation = finalRotation || 0; // if finalRotation undefined or 0, make 0, else finalRotation
currentRotation = finalRotation % 360;
if (currentRotation < 0) {currentRotation += 360;}
if (currentRotation < 180 && (newRotation > (currentRotation + 180))) {finalRotation -= 360;}
if (currentRotation >= 180 && (newRotation <= (currentRotation - 180))) {finalRotation += 360;}
finalRotation += (newRotation - currentRotation);
jQuery(".wind-arrow").css("transform", "rotate(" + finalRotation + "deg)");
jQuery(".arrow").css("transform", "rotate(" + finalRotation + "deg)");
}
// Title case strings. https://stackoverflow.com/a/45253072/1177153
function titleCase(str) {
return str.toLowerCase().split(' ').map(function(word) {
return word.replace(word[0], word[0].toUpperCase());
}).join(' ');
}
function autoTheme(sunset_hour, sunset_min, sunrise_hour, sunrise_min) {
// First check if ?theme= is in URL. If so, bail out and do not change anything.
if (getURLvar("theme") && getURLvar("theme") != "auto") {
belchertown_debug("Auto theme: theme override detected in URL. Skipping auto theme");
return true;
}
belchertown_debug("Auto theme: checking to see if theme needs to be switched");
var d = new Date();
var nowHour = d.getHours();
var nowMinutes = d.getMinutes();
nowHour = nowHour;
sunrise_hour = sunrise_hour;
sunset_hour = sunset_hour;
// Determine if it's day time. https://stackoverflow.com/a/14718577/1177153
if (sunrise_hour <= nowHour && nowHour < sunset_hour) {
dayTime = true;
} else {
dayTime = false;
}
belchertown_debug("Auto theme: sunrise: " + sunrise_hour);
belchertown_debug("Auto theme: now: " + nowHour);
belchertown_debug("Auto theme: sunset: " + sunset_hour);
belchertown_debug("Auto theme: are we in daylight hours: " + dayTime);
belchertown_debug("Auto theme: sessionStorage.getItem('theme') = " + sessionStorage.getItem('theme'));
if (dayTime == true) {
// Day time, set light if needed
// Only change theme if user has not overridden the auto option with the toggle
if (sessionStorage.getItem('theme') == "auto") {
belchertown_debug("Auto theme: setting light theme since dayTime variable is true (day)");
changeTheme("light");
} else {
belchertown_debug("Auto theme: cannot set light theme since visitor used toggle to override theme. Refresh to reset the override.");
}
} else {
// Night time, set dark if needed
// Only change theme if user has not overridden the auto option with the toggle
if (sessionStorage.getItem('theme') == "auto") {
belchertown_debug("Auto theme: setting dark theme since dayTime variable is false (night)");
changeTheme("dark");
} else {
belchertown_debug("Auto theme: cannot set dark theme since visitor used toggle to override theme. Refresh to reset the override.");
}
}
}
function changeTheme(themeName, toggleOverride = false) {
belchertown_debug("Theme: Changing to " + themeName);
// If the configured theme is auto, but the user toggles light/dark, remove the auto option.
if (toggleOverride) {
belchertown_debug("Theme: toggle override clicked.");
belchertown_debug("Theme: sessionStorage.getItem('theme') was previously: " + sessionStorage.getItem('theme'));
// This was applied only to auto theme config, but now it's applied to all themes so visitor has full control on light/dark mode
//if ( sessionStorage.getItem('theme') == "auto" ) { }
sessionStorage.setItem('theme', 'toggleOverride');
belchertown_debug("Theme: sessionStorage.getItem('theme') is now: " + sessionStorage.getItem('theme'));
}
if (themeName == "dark") {
// Apply dark theme
jQuery('body').addClass("dark");
jQuery('body').removeClass("light");
jQuery("#themeSwitch").prop("checked", true);
sessionStorage.setItem('currentTheme', 'dark');
} else if (themeName == "light") {
// Apply light theme
jQuery('body').addClass("light");
jQuery('body').removeClass("dark");
jQuery("#themeSwitch").prop("checked", false);
sessionStorage.setItem('currentTheme', 'light');
}
}
async function ajaxweewx() {
resp = await fetch(get_relative_url() + "/json/weewx_data.json");
if (!resp.ok) {
throw new Error("HTTP error! Unable to load weewx_data.json");
} else {
return await resp.json();
}
}
// Update weewx data elements
//var station_obs_array = "";
var unit_rounding_array = "";
var unit_label_array = "";
var weewx_data = "";
function update_weewx_data(data) {
belchertown_debug("Updating weewx data");
weewx_data = data;
// Auto theme if enabled
autoTheme(data["almanac"]["sunset_hour"], data["almanac"]["sunset_minute"], data["almanac"]["sunrise_hour"], data["almanac"]["sunrise_minute"]);
//station_obs_array = data["station_observations"];
unit_rounding_array = data["unit_rounding"];
unit_label_array = data["unit_label"];
// Daily High Low
high = data["day"]["outTemp"]["max"];
low = data["day"]["outTemp"]["min"];
jQuery(".high").html(high);
jQuery(".low").html(low);
try {
// Barometer trending by finding a negative number
count = (data["current"]["barometer_trend"].match(/-/g) || []).length
} catch (err) {
// Returned "current" data does not have this value
}
if (count >= 1) {
jQuery(".pressure-trend").html('');
} else {
jQuery(".pressure-trend").html('');
}
// Daily max gust span
jQuery(".dailymaxgust").html(parseFloat(data["day"]["wind"]["max"]).toFixed(1));
// Daily Snapshot Stats Section
try {
jQuery(".snapshot-records-today-header").html(tzAdjustedMoment(data["current"]["epoch"]).format('dddd, LL'));
jQuery(".snapshot-records-month-header").html(tzAdjustedMoment(data["current"]["epoch"]).format('MMMM YYYY'));
} catch (err) {
// Returned "current" data does not have this value
}
jQuery(".dailystatshigh").html(data["day"]["outTemp"]["max"]);
jQuery(".dailystatslow").html(data["day"]["outTemp"]["min"]);
jQuery(".dailystatswindavg").html(data["day"]["wind"]["average"]);
jQuery(".dailystatswindmax").html(data["day"]["wind"]["max"]);
jQuery(".dailystatsrain").html(data["day"]["rain"]["sum"]);
jQuery(".dailystatsrainrate").html(data["day"]["rain"]["max"]);
jQuery(".dailywindrun").html(data["day"]["wind"]["windrun"]);
// Month Snapshot Stats Section
jQuery(".monthstatshigh").html(data["month"]["outTemp"]["max"]);
jQuery(".monthstatslow").html(data["month"]["outTemp"]["min"]);
jQuery(".monthstatswindavg").html(data["month"]["wind"]["average"]);
jQuery(".monthstatswindmax").html(data["month"]["wind"]["max"]);
jQuery(".monthstatsrain").html(data["month"]["rain"]["sum"]);
jQuery(".monthstatsrainrate").html(data["month"]["rain"]["max"]);
// Sunrise and Sunset
jQuery(".sunrise-value").html(tzAdjustedMoment(parseFloat(data["almanac"]["sunrise_epoch"]).toFixed(0)).format("LT"));
jQuery(".sunset-value").html(tzAdjustedMoment(parseFloat(data["almanac"]["sunset_epoch"]).toFixed(0)).format("LT"));
jQuery(".moonrise-value").html(tzAdjustedMoment(parseFloat(data["almanac"]["moon"]["moon_rise_epoch"]).toFixed(0)).format("LT"));
jQuery(".moonset-value").html(tzAdjustedMoment(parseFloat(data["almanac"]["moon"]["moon_set_epoch"]).toFixed(0)).format("LT"));
// Moon icon, phase and illumination percent
jQuery(".moon-icon").html(moon_icon(data["almanac"]["moon"]["moon_index"]));
jQuery(".moon-phase").html(titleCase(data["almanac"]["moon"]["moon_phase"])); // Javascript function above
jQuery(".moon-visible").html("" + data["almanac"]["moon"]["moon_fullness"] + "% visible");
// Close current modal if open
jQuery('#almanac').modal('hide');
jQuery(".almanac-extras-modal-body").html(data["almanac"]["almanac_extras_modal_html"]);
try {
almanac_updated = "Last Updated " + tzAdjustedMoment(data["current"]["datetime_raw"]).format("LL, LTS");
jQuery(".almanac_last_updated").html(almanac_updated);
} catch (err) {
// Returned "current" data does not have this value
}
}
// function returns html for moon-icon according to moonphase value and currentTheme setting
function moon_icon(moonphase){
var moon_icon_dict = {
"0": "
",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": "",
"7": "",
}
var output = moon_icon_dict[moonphase];
if (sessionStorage.getItem('currentTheme') === 'dark') {
return output;
} else {
return output.replace('-moon-','-moon-alt-');
}
}
// function to display selected forecast according to value of interval (1, 3 or 24); 0 hides all forecasts
function forecast_select(interval) {
if (interval == 0) {
jQuery(".forecastrow").hide();
} else {
oldinterval = sessionStorage.getItem("forecastInterval");
if (interval != oldinterval) {
// hide the old forecast
var forecast = document.getElementById((oldinterval + "hour-selected-forecast"));
var button = document.getElementById(("button" + oldinterval));
if (forecast != null) forecast.style.display = "none";
if (button != null) button.style.borderStyle = "hidden";
// display the new forecast and store its interval value
forecast = document.getElementById((interval + "hour-selected-forecast"));
button = document.getElementById(("button" + interval));
if (forecast != null) forecast.style.display = "block";
if (button != null) button.style.borderStyle = "solid";
sessionStorage.setItem("forecastInterval", interval);
}
}
}
function forecast_default(interval) {
sessionStorage.setItem("defaultInterval", interval);
}
// function adjusts daytime format for forecast_1hr & _3hr; assumes "ddd LT" format for daytime
function forecast_time(i, interval, daytime) {
if ((daytime.indexOf(" ") == -1) || (interval == "forecast_24hr")) return daytime;
var output = daytime
var strday = daytime.substr(0, daytime.indexOf(" "));
var strtime = daytime.substr(daytime.indexOf(" ") + 1);
if (interval == "forecast_1hr") {
if ((i == 0) || (strtime == "00:00") || (strtime == "12:00 AM")) {
output = strtime + "
" + strday;
} else {
output = strtime + "
";
}
} else if (interval == "forecast_3hr") {
if ((i != 0) && ((strtime > "02:59") || (strtime > "02:59 AM"))) {
output = strtime;
}
}
return output;
}
Highcharts.setOptions({
global: {
//useUTC: false
timezoneOffset: 240.0
},
lang: {
months: moment.months(),
shortMonths: moment.monthsShort(),
weekdays: moment.weekdays(),
shortWeekdays: moment.weekdaysShort(),
decimalPoint: ".",
thousandsSep: ","
}
});
function showChart(json_file, prepend_renderTo = false) {
// Relative URL by finding what page we're on currently.
jQuery.getJSON(get_relative_url() + '/json/' + json_file + '.json', function(data) {
// Loop through each chart name (e.g. chart1, chart2, chart3)
jQuery.each(data, function(plotname, obsname) {
var observation_type = undefined;
// Ignore the Belchertown Version since this "plot" has no other options
if (plotname == "belchertown_version") {
return true;
}
// Ignore the generated timestamp since this "plot" has no other options
if (plotname == "generated_timestamp") {
return true;
}
// Ignore the chartgroup_title since this "plot" has no other options
if (plotname == "chartgroup_title") {
return true;
}
// Set the chart's tooltip date time format, then return since this "plot" has no other options
if (plotname == "tooltip_date_format") {
tooltip_date_format = obsname;
return true;
}
// Set the chart colors, then return right away since this "plot" has no other options
if (plotname == "colors") {
colors = obsname.split(",");
return true;
}
// Set the chart credits, then return right away since this "plot" has no other options
if (plotname == "credits") {
credits = obsname.split(",")[0];
return true;
}
// Set the chart credits url, then return right away since this "plot" has no other options
if (plotname == "credits_url") {
credits_url = obsname.split(",")[0];
return true;
}
// Set the chart credits position, then return right away since this "plot" has no other options
if (plotname == "credits_position") {
credits_position = obsname;
return true;
}
// Loop through each chart options
jQuery.each(data[plotname]["options"], function(optionName, optionVal) {
switch (optionName) {
case "type":
type = optionVal;
break;
case "renderTo":
renderTo = optionVal;
break;
case "title":
title = optionVal;
break;
case "subtitle":
subtitle = optionVal;
break;
case "yAxis_label":
yAxis_label = optionVal;
break;
case "chart_group":
chart_group = optionVal;
break;
case "gapsize":
gapsize = optionVal;
break;
case "connectNulls":
connectNulls = optionVal;
break;
case "rounding":
rounding = optionVal;
break;
case "xAxis_categories":
xAxis_categories = optionVal;
break;
case "plot_tooltip_date_format":
plot_tooltip_date_format = optionVal;
break;
case "css_class":
css_class = optionVal;
break;
case "css_height":
css_height = optionVal;
break;
case "css_width":
css_width = optionVal;
break;
case "legend":
legend_enabled = optionVal;
break;
case "exporting":
exporting_enabled = optionVal;
break;
}
});
// Handle any per-chart date time format override
if (typeof plot_tooltip_date_format !== "undefined") {
var tooltip_date_format = plot_tooltip_date_format;
}
var options = {
chart: {
renderTo: '',
spacing: [5, 10, 10, 0],
type: '',
zoomType: 'x'
},
exporting: {
chartOptions: {
chart: {
events: {
load: function() {
this.title.update({style: {color: '#e5554e'}});
if (sessionStorage.getItem('currentTheme') === 'dark') {
var darktheme_textcolor = '#fff';
for (var i = this.yAxis.length - 1; i >= 0; i--) {
this.yAxis[i].update({
title: {style: {color: darktheme_textcolor}},
labels: {style: {color: darktheme_textcolor}},
gridLineColor: '#707073',
tickColor: '#707073'
});
}
for (var i = this.xAxis.length - 1; i >= 0; i--) {
this.xAxis[i].update({
title: {style: {color: darktheme_textcolor}},
labels: {style: {color: darktheme_textcolor}},
gridLineColor: '#707073',
tickColor: '#707073'
});
}
this.legend.update({itemStyle: {color: darktheme_textcolor}});
//this.credits.update({style:{color: darktheme_textcolor}});
this.subtitle.update({style: {color: darktheme_textcolor}});
this.chartBackground.attr({fill: jQuery(".highcharts-background").css("fill")});
} else {
var lighttheme_textcolor = '#666666';
for (var i = this.yAxis.length - 1; i >= 0; i--) {
this.yAxis[i].update({
title: {style: {color: lighttheme_textcolor}},
labels: {style: {color: lighttheme_textcolor}},
});
}
for (var i = this.xAxis.length - 1; i >= 0; i--) {
this.xAxis[i].update({
title: {style: {color: lighttheme_textcolor}},
labels: {style: {color: lighttheme_textcolor}},
});
}
}
}
}
}
},
// scale: 1,
// width: 1000,
// sourceWidth: 1000,
enabled: JSON.parse(String(exporting_enabled)) // Convert string to bool
},
title: {
useHTML: true,
text: ''
},
subtitle: {
text: ''
},
legend: {
enabled: JSON.parse(String(legend_enabled)) // Convert string to bool
},
xAxis: {
dateTimeLabelFormats: {
day: '%e %b',
week: '%e %b',
month: '%b %y',
},
lineColor: '#555',
minRange: 900000,
minTickInterval: 900000,
title: {
style: {
font: 'bold 12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'
}
},
ordinal: false,
type: 'datetime'
},
yAxis: [{
endOnTick: true,
lineColor: '#555',
minorGridLineWidth: 0,
startOnTick: true,
showLastLabel: true,
title: {
},
opposite: false
}],
plotOptions: {
area: {
lineWidth: 2,
gapSize: '',
gapUnit: 'value',
marker: {
enabled: false,
radius: 2
},
threshold: null,
softThreshold: true
},
line: {
lineWidth: 2,
gapSize: '',
gapUnit: 'value',
marker: {
enabled: false,
radius: 2
},
},
spline: {
lineWidth: 2,
gapSize: '',
gapUnit: 'value',
marker: {
enabled: false,
radius: 2
},
},
areaspline: {
lineWidth: 2,
gapSize: '',
gapUnit: 'value',
marker: {
enabled: false,
radius: 2
},
threshold: null,
softThreshold: true
},
scatter: {
gapSize: '',
gapUnit: 'value',
marker: {
radius: 2
},
},
},
// Highstock is needed for gapsize. Disable these 3 to make it look like standard Highcharts
scrollbar: {
enabled: false
},
navigator: {
enabled: false
},
rangeSelector: {
enabled: false
},
tooltip: {
enabled: true,
crosshairs: true,
dateTimeLabelFormats: {
hour: '%e %b %H:%M'
},
// For locale control with moment.js
formatter: function(tooltip) {
try {
// The first returned item is the header, subsequent items are the points.
// Mostly applies to line style charts (line, spline, area)
return [tzAdjustedMoment(this.x / 1000).format(tooltip_date_format)].concat(
this.points.map(function(point) {
// If observation_type is in the series array, use that otherwise use the obsType
var point_obsType = point.series.userOptions.observation_type ? point.series.userOptions.observation_type : point.series.userOptions.obsType;
var rounding = point.series.userOptions.rounding;
var mirrored = point.series.userOptions.mirrored_value;
var numberFormat = point.series.userOptions.numberFormat ? point.series.userOptions.numberFormat : "";
return "\u25CF " + point.series.name + ': ' + highcharts_tooltip_factory(point.y, point_obsType, true, rounding, mirrored, numberFormat);
})
);
} catch (e) {
// There's an error so check if it's windDir to apply wind direction label, or if it's a scatter. If none of those revert back to default tooltip.
if (this.series.userOptions.obsType == "windDir" || this.series.userOptions.observation_type == "windDir") {
// If observation_type is in the series array, use that otherwise use the obsType
var point_obsType = this.series.userOptions.observation_type ? this.series.userOptions.observation_type : this.series.userOptions.obsType;
var rounding = this.series.userOptions.rounding;
var mirrored = this.series.userOptions.mirrored_value;
return tzAdjustedMoment(this.x / 1000).format(tooltip_date_format) + '
' + highcharts_tooltip_factory(this.point.y, point_obsType, true, rounding, mirrored);
} else if (this.series.userOptions.type == "scatter") {
// Catch anything else that might be a scatter plot. Scatter plots will just show x,y coordinates without this.
return "\u25CF " + this.series.name + ': ' + Highcharts.numberFormat(this.y);
} else {
return tooltip.defaultFormatter.call(this, tooltip);
}
}
},
split: true,
},
credits: {},
series: [{}]
};
// Default options completed, build overrides from JSON and graphs.conf
// Set the chart render div and title
if (prepend_renderTo) {
options.chart.renderTo = json_file + "_" + renderTo;
} else {
options.chart.renderTo = renderTo;
}
belchertown_debug(options.chart.renderTo + ": building a " + type + " chart");
if (css_class) {
jQuery("#" + options.chart.renderTo).addClass(css_class);
belchertown_debug(options.chart.renderTo + ": div id is " + options.chart.renderTo + " and adding CSS class: " + css_class);
}
options.chart.type = type;
options.title.text = "" + title + ""; // Anchor link to chart for direct linking
options.subtitle.text = subtitle;
options.plotOptions.area.gapSize = gapsize;
options.plotOptions.line.gapSize = gapsize;
options.plotOptions.spline.gapSize = gapsize;
options.plotOptions.scatter.gapSize = gapsize;
if (connectNulls == "true") {
options.plotOptions.series = {connectNulls: connectNulls};
}
options.colors = colors;
// If we have xAxis categories, reset xAxis and populate it from these options. Also need to reset tooltip since there's no datetime for moment.js to use.
if (xAxis_categories.length >= 1) {
belchertown_debug(options.chart.renderTo + ": has " + xAxis_categories.length + " xAxis categories. Resetting xAxis and tooltips for grouping");
options.xAxis = {}
options.xAxis.categories = xAxis_categories;
options.tooltip = {}
options.tooltip = {
enabled: true,
crosshairs: true,
split: true,
formatter: function() {
// The first returned item is the header, subsequent items are the points
return [this.x].concat(
this.points.map(function(point) {
// If observation_type is in the series array, use that otherwise use the obsType
var point_obsType = point.series.userOptions.observation_type ? point.series.userOptions.observation_type : point.series.userOptions.obsType;
var rounding = point.series.userOptions.rounding;
var mirrored = point.series.userOptions.mirrored_value;
var numberFormat = point.series.userOptions.numberFormat ? point.series.userOptions.numberFormat : "";
return "\u25CF " + point.series.name + ': ' + highcharts_tooltip_factory(point.y, point_obsType, true, rounding, mirrored, numberFormat);
})
);
},
}
}
// Reset the series everytime we loop.
options.series = [];
// Build the series
var i = 0;
jQuery.each(data[plotname]["series"], function(seriesName, seriesVal) {
observation_type = data[plotname]["series"][seriesName]["obsType"];
options.series[i] = data[plotname]["series"][seriesName];
i++;
});
/* yAxis customization handler and label handling
Take the following example.
yAxis is in observation 0 (rainTotal), so that label is caught and set by yAxis1_active.
If you move yAxis to observation 1 (rainRate), then the label is caught and set by yAxis_index.
There may be a more efficient way to do this. If so, please submit a pull request :)
[[[chart3]]]
title = Rain
[[[[rainTotal]]]]
name = Rain Total
yAxis = 1
[[[[rainRate]]]]
*/
var yAxis1_active = undefined;
// Find if any series have yAxis = 1. If so, save the array number so we can set labels correctly.
// We really care if yAxis is in array 1+, so we can go back and set yAxis 0 to the right label.
var yAxis_index = options.series.findIndex(function(item) {return item.yAxis == 1})
// Handle series specific data, overrides and non-Highcharts options that we passed through
options.series.forEach(s => {
if (s.yAxis == "1") {
// If yAxis = 1 is set for the observation, add a new yAxis and associate that observation to the right side of the chart
yAxis1_active = true;
options.yAxis.push({ // Secondary yAxis
opposite: true,
title: {
text: s.yAxis_label,
},
}),
// Associate this series to the new yAxis 1
s.yAxis = 1
// We may have already passed through array 0 in the series without setting the "multi axis label", go back and explicitly define it.
if (yAxis_index >= 1) {
options.yAxis[0].title.text = options.series[0].yAxis_label;
}
} else {
if (yAxis1_active) {
// This yAxis is first in the data series, so we can set labels without needing to double back
options.yAxis[0].title.text = s.yAxis_label;
} else {
// Apply the normal yAxis 0's label without observation name
options.yAxis[0].title.text = s.yAxis_label;
}
// Associate this series to yAxis 1
s.yAxis = 0;
}
// Run yAxis customizations
this_yAxis = s.yAxis;
belchertown_debug(options.chart.renderTo + ": " + s.obsType + " is on yAxis " + this_yAxis);
// Some charts may require a defined min/max on the yAxis
options.yAxis[this_yAxis].min = s.yAxis_min !== "undefined" ? s.yAxis_min : null;
options.yAxis[this_yAxis].max = s.yAxis_max !== "undefined" ? s.yAxis_max : null;
// Some charts may require a defined soft min/max on the yAxis
options.yAxis[this_yAxis].softMin = s.yAxis_softMin !== "undefined" ? parseInt(s.yAxis_softMin) : null;
options.yAxis[this_yAxis].softMax = s.yAxis_softMax !== "undefined" ? parseInt(s.yAxis_softMax) : null;
// Set the yAxis tick interval. Mostly used for barometer.
if (s.yAxis_tickInterval) {
options.yAxis[this_yAxis].tickInterval = s.yAxis_tickInterval;
}
// Set yAxis minorTicks. This is a graph-wide setting so setting it for any of the yAxis will set it for the graph itself
if (s.yAxis_minorTicks) {
options.yAxis[this_yAxis].minorTicks = true;
}
// Barometer chart plots get a higher precision yAxis tick
if (s.obsType == "barometer") {
// Define yAxis label float format if rounding is defined. Default to 2 decimals if nothing defined
if (typeof s.rounding !== "undefined") {
options.yAxis[this_yAxis].labels = {format: '{value:.' + s.rounding + 'f}'}
} else {
options.yAxis[this_yAxis].labels = {format: '{value:.2f}'}
}
}
// Rain, RainRate and rainTotal (special Belchertown skin observation) get yAxis precision
if (s.obsType == "rain" || s.obsType == "rainRate" || s.obsType == "rainTotal") {
options.yAxis[this_yAxis].min = 0;
options.yAxis[this_yAxis].minRange = 0.01;
options.yAxis[this_yAxis].minorGridLineWidth = 1;
}
if (s.obsType == "windDir") {
options.yAxis[this_yAxis].tickInterval = 90;
options.yAxis[this_yAxis].labels = {
useHTML: true,
formatter: function() {var value = weatherdirection[this.value]; return value !== 'undefined' ? value : this.value;}
}
}
// Check if this series has a gapsize override
if (s.gapsize) {
options.plotOptions.area.gapSize = s.gapsize;
options.plotOptions.line.gapSize = s.gapsize;
options.plotOptions.spline.gapSize = s.gapsize;
options.plotOptions.scatter.gapSize = s.gapsize;
}
// If this chart is a mirrored chart, make the yAxis labels non-negative
if (s.mirrored_value) {
belchertown_debug(options.chart.renderTo + ": mirrored chart due to mirrored_value = true");
options.yAxis[s.yAxis].labels = {formatter: function() {return Math.abs(this.value);}}
}
// Lastly, apply any numberFormat label overrides
if (typeof s.numberFormat !== "undefined" && Object.keys(s.numberFormat).length >= 1) {
var {decimals, decimalPoint, thousandsSep} = s.numberFormat
options.yAxis[this_yAxis].labels = {formatter: function() {return Highcharts.numberFormat(this.value, decimals, decimalPoint, thousandsSep);}}
}
});
// If windRose is present, configure a special chart to show that data
if (observation_type == "windRose") {
var categories = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N/A'];
options.chart.className = "highcharts-windRose"; // Used for dark mode
options.chart.type = "column";
options.chart.polar = true;
options.chart.alignTicks = false;
options.pane = {size: '80%'}
// Reset xAxis and rebuild
options.xAxis = {}
options.xAxis.min = 0;
options.xAxis.max = 16;
options.xAxis.crosshair = true;
options.xAxis.categories = categories;
options.xAxis.tickmarkPlacement = 'on';
options.xAxis.labels = {useHTML: true}
//options.legend.align = "right";
options.legend.verticalAlign = "top";
options.legend.x = 210;
options.legend.y = 119;
options.legend.layout = "vertical";
options.legend.floating = true;
options.yAxis[0].min = 0;
options.yAxis[0].endOnTick = false;
options.yAxis[0].reversedStacks = false;
options.yAxis[0].title.text = "Frequency (%)";
options.yAxis[0].gridLineWidth = 0;
options.yAxis[0].labels = {enabled: false}
options.yAxis[0].zIndex = 800;
options.plotOptions = {
column: {
stacking: 'normal',
shadow: false,
groupPadding: 0,
pointPlacement: 'on',
}
}
// Reset the tooltip
options.tooltip = {}
options.tooltip.shared = true;
options.tooltip.valueSuffix = '%';
options.tooltip.followPointer = true;
options.tooltip.useHTML = true;
// Since wind rose is a special observation, I did not re-do the JSON arrays to accomodate it as a separate array.
// So we need to grab the data array within the series and save it to a temporary array, delete the entire chart series,
// and reapply the windrose data back to the series.
var newSeries = options.series[0].data;
options.series = [];
newSeries.forEach(ns => {
options.series.push(ns);
});
}
// Configure gauge chart formatting
if (options.chart.type == "gauge") {
// Highcharts does not allow the guage background to have rounded ends. To get around
// this, define a "dummy" series that fills the gauge with the appropriate color.
// This way, the ends are rounded if the user specifies.
//
// Gauge chart works best with only one data point, so the most recent (last) data point
// is used
options.series[0].data = [{
y: 9999999,
color: '#e6e6e6',
className: 'highcharts-pane',
zIndex: 0,
dataLabels: {enabled: false}
}, {
y: options.series[0].data.pop()[1],
color: options.series[0].color,
}]
options.chart.type = "solidgauge"
options.pane = {
startAngle: -140,
endAngle: 140,
background: [{
outerRadius: 0,
innerRadius: 0,
}]
}
// If user has set colors_enabled, change the color according to the value
if (options.series[0].colors_enabled) {
options.series[0].data[1].color = get_gauge_color(options.series[0].data[1]["y"], options.series[0])
}
options.plotOptions = {
solidgauge: {
dataLabels: {
useHTML: true,
enabled: true,
borderWidth: 0,
style: {
fontWeight: 'bold',
lineHeight: '0.5em',
textAlign: 'center',
fontSize: '50px',
// Match color if set by user
color: options.series[0].data[1].color,
textOutline: 'none'
}
},
}
}
if (get_gauge_label(options.series[0].data[1]["y"], options.series[0])) {
options.plotOptions.solidgauge.dataLabels.format = "{y:.#f}
" + get_gauge_label(options.series[0].data[1]["y"], options.series[0]) + ''
options.plotOptions.solidgauge.dataLabels.y = -25
} else if (unit_label_array[observation_type] == null) {
options.plotOptions.solidgauge.dataLabels.format = "{y:.#f}"
} else {
options.plotOptions.solidgauge.dataLabels.format = "{y:.#f}
" + unit_label_array[observation_type] + ''
options.plotOptions.solidgauge.dataLabels.y = -25
}
options.yAxis = {
min: 0,
max: 100,
lineColor: null,
tickPositions: []
}
// Override default max and min if user has specified
if (options.series[0].yAxis_max) {
options.yAxis.max = options.series[0].yAxis_max;
}
if (options.series[0].yAxis_min) {
options.yAxis.min = options.series[0].yAxis_min;
}
options.tooltip.enabled = false
options.xAxis.crosshair = false
}
// If AQI chart is present, configure a special chart
if (observation_type == "aqiChart") {
// Highcharts does not allow the guage background to have rounded ends. To get around
// this, define a "dummy" series that fills the gauge with the appropriate color.
// This way, the ends are rounded if the user specifies.
options.series[0].data = [{
y: 500,
color: '#e6e6e6',
className: 'highcharts-pane',
zIndex: 0,
dataLabels: {enabled: false}
}, {
y: options.series[0].data[0]['y'],
color: get_aqi_color(options.series[0].data[0]['y'], true),
category: options.series[0].data[0]['category']
}]
options.chart.type = "solidgauge"
options.pane = {
startAngle: -140,
endAngle: 140,
background: [{
outerRadius: 0,
innerRadius: 0,
}]
}
options.plotOptions = {
solidgauge: {
dataLabels: {
useHTML: true,
enabled: true,
y: -30,
borderWidth: 0,
format: '{y}
' + options.series[0].data[1]['category'] + '',
style: {
fontWeight: 'bold',
lineHeight: '0.5em',
textAlign: 'center',
fontSize: '50px',
color: options.series[0].data[1].color,
textOutline: 'none'
}
},
linecap: 'round',
rounded: true
}
}
options.yAxis = {
min: 0,
max: 500,
lineColor: null,
tickPositions: []
}
options.tooltip.enabled = false
options.xAxis.crosshair = false
}
// If Hays chart is present, configure a special chart to show that data
if (observation_type == "haysChart") {
options.chart.type = "arearange"
options.chart.polar = true;
options.plotOptions = {
turboThreshold: 0,
series: {
marker: {
enabled: false
}
}
};
// Find min and max of the series data for the yAxis min and max
var maximum_flattened = [];
options.series[0].data.forEach(seriesData => {
maximum_flattened.push(seriesData[2]);
});
var range_max = Math.max(...maximum_flattened);
if (options.series[0].yAxis_softMax) {
var range_max = options.series[0].yAxis_softMax;
}
options.legend = {"enabled": false}
options.yAxis = {
showFirstLabel: false,
tickInterval: 2,
tickmarkPlacement: 'on',
min: -1,
softMax: range_max,
title: {
text: options.series[0].yAxis_label,
},
labels: {
align: 'center',
x: 0,
y: 0
},
}
options.tooltip = {
split: false,
shared: true,
followPointer: true,
useHTML: true,
formatter: function(tooltip) {
return this.points.map(function(point) {
var rounding = point.series.userOptions.rounding;
var mirrored = point.series.userOptions.mirrored_value;
var numberFormat = point.series.userOptions.numberFormat ? point.series.userOptions.numberFormat : "";
return "" + tzAdjustedMoment(point.x / 1000).format(tooltip_date_format) + "
\u25CF High: " + highcharts_tooltip_factory(point.point.high, observation_type, true, rounding, mirrored, numberFormat) + "
\u25CF Low: " + highcharts_tooltip_factory(point.point.low, observation_type, true, rounding, mirrored, numberFormat);
});
}
}
var currentSeries = options.series;
var currentSeriesData = options.series[0].data;
var range_unit = options.series[0].range_unit;
var rounding = options.series[0].rounding;
var newSeriesData = [];
var currentSeriesColor = options.series[0].color;
currentSeriesData.forEach(seriesData => {
newSeriesData.push({
x: seriesData[0],
low: seriesData[1],
high: seriesData[2],
});
});
options.series = [];
options.series.push({
data: newSeriesData,
obsType: "haysChart",
obsUnit: range_unit,
rounding: rounding,
color: currentSeriesColor,
fillColor: currentSeriesColor,
connectEnds: false,
});
}
// If weather range is present, configure a special chart to show that data
// https://www.highcharts.com/blog/tutorials/209-the-art-of-the-chart-weather-radials/
if (observation_type == "weatherRange") {
if (options.series[0].area_display) {
options.chart.type = "arearange";
} else {
options.chart.type = "columnrange";
}
// If polar is defined, use it and add a special dark mode CSS class
if (JSON.parse(String(options.series[0].polar.toLowerCase()))) {
options.chart.polar = true; // Make sure the option is a string, then convert to bool
options.chart.className = "highcharts-weatherRange belchertown-polar"; // Used for dark mode
} else {
options.chart.className = "highcharts-weatherRange"; // Used for dark mode
}
options.legend = {"enabled": false}
// Find min and max of the series data for the yAxis min and max
var minimum_flattened = [];
var maximum_flattened = [];
options.series[0].data.forEach(seriesData => {
minimum_flattened.push(seriesData[1]);
maximum_flattened.push(seriesData[2]);
});
var range_min = Math.min(...minimum_flattened);
var range_max = Math.max(...maximum_flattened);
var yAxis_tickInterval = Math.ceil(Math.round(range_max / 5) / 5) * 5; // Divide max outTemp by 5 and round it, then round that value up to the nearest 5th multiple. This gives clean yAxis tick lines.
options.yAxis = {
showFirstLabel: true,
tickInterval: yAxis_tickInterval,
min: range_min,
max: range_max,
title: {
text: options.series[0].yAxis_label,
},
}
options.xAxis = {
dateTimeLabelFormats: {
day: '%e %b',
week: '%e %b',
month: '%b %y',
},
showLastLabel: true,
crosshair: true,
type: "datetime"
}
options.plotOptions = {}
options.plotOptions = {
series: {
turboThreshold: 0,
showInLegend: false,
borderWidth: 0,
marker: {
enabled: false,
},
}
}
if (options.series[0].area_display) {
if (options.series[0].range_unit == "degree_F") {
options.plotOptions.series.zones = [
{value: 0, color: "#1278c8"},
{value: 25, color: "#30bfef"},
{value: 32, color: "#1fafdd"},
{value: 40, color: "rgba(0,172,223,1)"},
{value: 50, color: "#71bc3c"},
{value: 55, color: "rgba(90,179,41,0.8)"},
{value: 65, color: "rgba(131,173,45,1)"},
{value: 70, color: "rgba(206,184,98,1)"},
{value: 75, color: "rgba(255,174,0,0.9)"},
{value: 80, color: "rgba(255,153,0,0.9)"},
{value: 85, color: "rgba(255,127,0,1)"},
{value: 90, color: "rgba(255,79,0,0.9)"},
{value: 95, color: "rgba(255,69,69,1)"},
{value: 110, color: "rgba(255,104,104,1)"},
{color: "rgba(218,113,113,1)"},
]
} else {
options.plotOptions.series.zones = [
{value: -5, color: "#1278c8"},
{value: -3.8, color: "#30bfef"},
{value: 0, color: "#1fafdd"},
{value: 4.4, color: "rgba(0,172,223,1)"},
{value: 10, color: "#71bc3c"},
{value: 12.7, color: "rgba(90,179,41,0.8)"},
{value: 18.3, color: "rgba(131,173,45,1)"},
{value: 21.1, color: "rgba(206,184,98,1)"},
{value: 23.8, color: "rgba(255,174,0,0.9)"},
{value: 26.6, color: "rgba(255,153,0,0.9)"},
{value: 29.4, color: "rgba(255,127,0,1)"},
{value: 32.2, color: "rgba(255,79,0,0.9)"},
{value: 35, color: "rgba(255,69,69,1)"},
{value: 43.3, color: "rgba(255,104,104,1)"},
{color: "rgba(218,113,113,1)"},
]
}
} else {
options.plotOptions.series.stacking = "normal"
}
options.tooltip = {
split: false,
shared: true,
followPointer: true,
useHTML: true,
formatter: function(tooltip) {
return this.points.map(function(point) {
var rounding = point.series.userOptions.rounding;
var mirrored = point.series.userOptions.mirrored_value;
var numberFormat = point.series.userOptions.numberFormat ? point.series.userOptions.numberFormat : "";
return "" + tzAdjustedMoment(point.x / 1000).format(tooltip_date_format) + "
\u25CF High: " + highcharts_tooltip_factory(point.point.high, observation_type, true, rounding, mirrored, numberFormat) + "
\u25CF Low: " + highcharts_tooltip_factory(point.point.low, observation_type, true, rounding, mirrored, numberFormat) + "
\u25CF Average: " + highcharts_tooltip_factory(point.point.average, observation_type, true, rounding, mirrored, numberFormat);
});
}
}
// Update data
var currentSeries = options.series;
var currentSeriesData = options.series[0].data;
var range_unit = options.series[0].range_unit;
var rounding = options.series[0].rounding;
var newSeriesData = [];
currentSeriesData.forEach(seriesData => {
if (options.series[0].color) {
var color = options.series[0].color;
} else {
// Set color of the column based on the average temperature, or return default if not temperature
var color = get_outTemp_color(range_unit, seriesData[3], true);
}
newSeriesData.push({
x: seriesData[0],
low: seriesData[1],
high: seriesData[2],
average: seriesData[3],
color: color
});
});
options.series = [];
options.series.push({
data: newSeriesData,
obsType: "weatherRange",
obsUnit: range_unit,
rounding: rounding
});
}
// Apply any width, height CSS overrides to the parent div of the chart
if (css_height != "") {
jQuery("#" + options.chart.renderTo).parent().css({
'height': css_height,
'padding': '0px 15px',
'margin-bottom': '20px'
});
}
if (css_width != "") {
jQuery("#" + options.chart.renderTo).parent().css('width', css_width);
}
if (credits != "highcharts_default") {
options.credits.text = credits;
}
if (credits_url != "highcharts_default") {
options.credits.href = credits_url;
}
if (credits_position != "highcharts_default") {
options.credits.position = JSON.parse(credits_position);
}
// Finally all options are done, now show the chart
var chart = new Highcharts.chart(options);
// If using debug, show a copy paste debug for use with jsfiddle
belchertown_debug(options);
belchertown_debug("Highcharts.chart('container', " + JSON.stringify(options) + ");");
});
});
};
function tzAdjustedMoment(input) {
let tz = "";
if (!tz) {
return moment.unix(input).utcOffset(-240.0);
} else {
return moment.unix(input).tz(tz);
}
}