diff --git a/api/thermostat_group.php b/api/thermostat_group.php
index fe91530..bbd8cbd 100644
--- a/api/thermostat_group.php
+++ b/api/thermostat_group.php
@@ -29,7 +29,7 @@ class thermostat_group extends cora\crud {
'generate_profile' => 604800, // 7 Days
'generate_profiles' => 604800, // 7 Days
'get_scores' => 604800, // 7 Days
- 'get_metrics' => 604800 // 7 Days
+ // 'get_metrics' => 604800 // 7 Days
];
/**
@@ -565,7 +565,8 @@ class thermostat_group extends cora\crud {
$metric_codes = [
'setpoint_heat',
- 'setpoint_cool'
+ 'setpoint_cool',
+ 'runtime_per_heating_degree_day'
];
$metrics = [];
@@ -636,6 +637,25 @@ class thermostat_group extends cora\crud {
$metrics['setpoint_cool']['histogram'][$setpoint_cool]++;
$metrics['setpoint_cool']['values'][] = $setpoint_cool;
}
+
+ // runtime_per_heating_degree_day
+ if(
+ isset($other_thermostat_group['profile']) === true &&
+ isset($other_thermostat_group['profile']['runtime']) == true &&
+ $other_thermostat_group['profile']['runtime']['heat_1'] !== null &&
+ isset($other_thermostat_group['profile']['degree_days']) === true &&
+ $other_thermostat_group['profile']['degree_days']['heat'] !== null
+ ) {
+ $runtime_per_heating_degree_day = round(
+ $other_thermostat_group['profile']['runtime']['heat_1'] / $other_thermostat_group['profile']['degree_days']['heat'],
+ 1
+ );
+ if(isset($metrics['runtime_per_heating_degree_day']['histogram'][(string)$runtime_per_heating_degree_day]) === false) {
+ $metrics['runtime_per_heating_degree_day']['histogram'][(string)$runtime_per_heating_degree_day] = 0;
+ }
+ $metrics['runtime_per_heating_degree_day']['histogram'][(string)$runtime_per_heating_degree_day]++;
+ $metrics['runtime_per_heating_degree_day']['values'][] = $runtime_per_heating_degree_day;
+ }
}
}
@@ -656,6 +676,13 @@ class thermostat_group extends cora\crud {
$metrics['setpoint_cool']['median'] = array_median($metrics['setpoint_cool']['values']);
unset($metrics['setpoint_cool']['values']);
+ // runtime_per_heating_degree_day
+ $metrics['runtime_per_heating_degree_day']['standard_deviation'] = round($this->standard_deviation(
+ $metrics['runtime_per_heating_degree_day']['values']
+ ), 2);
+ $metrics['runtime_per_heating_degree_day']['median'] = array_median($metrics['runtime_per_heating_degree_day']['values']);
+ unset($metrics['runtime_per_heating_degree_day']['values']);
+
return $metrics;
}
diff --git a/js/beestat/home_comparisons.js b/js/beestat/comparisons.js
similarity index 88%
rename from js/beestat/home_comparisons.js
rename to js/beestat/comparisons.js
index 2dbc9ab..5ecbd57 100644
--- a/js/beestat/home_comparisons.js
+++ b/js/beestat/comparisons.js
@@ -1,4 +1,4 @@
-beestat.home_comparisons = {};
+beestat.comparisons = {};
/**
* Fire off an API call to get the comparison scores using the currently
@@ -14,7 +14,7 @@ beestat.home_comparisons = {};
* @param {Function} callback Optional callback to fire when the API call
* completes.
*/
-beestat.home_comparisons.get_comparison_scores = function(callback) {
+beestat.comparisons.get_comparison_scores = function(callback) {
var types = [
'heat',
'cool',
@@ -29,15 +29,26 @@ beestat.home_comparisons.get_comparison_scores = function(callback) {
'get_scores',
{
'type': type,
- 'attributes': beestat.home_comparisons.get_comparison_attributes(type)
+ 'attributes': beestat.comparisons.get_comparison_attributes(type)
},
- type
+ 'score_' + type
);
});
+ api.add_call(
+ 'thermostat_group',
+ 'get_metrics',
+ {
+ 'attributes': beestat.comparisons.get_comparison_attributes('resist') // todo
+ },
+ 'metrics'
+ );
+
api.set_callback(function(data) {
+ beestat.cache.set('data.metrics', data.metrics);
+
types.forEach(function(type) {
- beestat.cache.set('data.comparison_scores_' + type, data[type]);
+ beestat.cache.set('data.comparison_scores_' + type, data['score_' + type]);
});
if (callback !== undefined) {
@@ -56,7 +67,7 @@ beestat.home_comparisons.get_comparison_scores = function(callback) {
*
* @return {Object} The comparison attributes.
*/
-beestat.home_comparisons.get_comparison_attributes = function(type) {
+beestat.comparisons.get_comparison_attributes = function(type) {
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
var thermostat_group =
beestat.cache.thermostat_group[thermostat.thermostat_group_id];
diff --git a/js/beestat/style.js b/js/beestat/style.js
index 9e00f5c..c04b752 100644
--- a/js/beestat/style.js
+++ b/js/beestat/style.js
@@ -298,3 +298,34 @@ beestat.series.indoor_resist_delta = {
'color': beestat.style.color.gray.dark
};
beestat.series.indoor_resist_delta_raw = beestat.series.indoor_resist_delta;
+
+// Temperature Profiles New
+beestat.series.indoor_heat_1_delta = {
+ 'name': 'Indoor Heat 1 Δ',
+ 'color': beestat.series.compressor_heat_1.color
+};
+beestat.series.indoor_heat_1_delta_raw = beestat.series.indoor_heat_1_delta;
+
+beestat.series.indoor_heat_2_delta = {
+ 'name': 'Indoor Heat 2 Δ',
+ 'color': beestat.series.compressor_heat_2.color
+};
+beestat.series.indoor_heat_2_delta_raw = beestat.series.indoor_heat_2_delta;
+
+beestat.series.indoor_cool_1_delta = {
+ 'name': 'Indoor Cool 1 Δ',
+ 'color': beestat.series.compressor_cool_1.color
+};
+beestat.series.indoor_cool_1_delta_raw = beestat.series.indoor_cool_1_delta;
+
+beestat.series.indoor_cool_2_delta = {
+ 'name': 'Indoor Cool 2 Δ',
+ 'color': beestat.series.compressor_cool_2.color
+};
+beestat.series.indoor_cool_2_delta_raw = beestat.series.indoor_cool_2_delta;
+
+beestat.series.indoor_resist_delta = {
+ 'name': 'Indoor Δ',
+ 'color': beestat.style.color.gray.dark
+};
+beestat.series.indoor_resist_delta_raw = beestat.series.indoor_resist_delta;
diff --git a/js/beestat/user.js b/js/beestat/user.js
index b47c3d7..877ea73 100644
--- a/js/beestat/user.js
+++ b/js/beestat/user.js
@@ -32,8 +32,7 @@ beestat.user.has_early_access = function() {
return user.user_id === 1 ||
(
user.patreon_status !== null &&
- user.patreon_status.patron_status === 'active_patron' &&
- user.patreon_status.currently_entitled_amount_cents >= 500
+ user.patreon_status.patron_status === 'active_patron'
);
};
diff --git a/js/component/card/comparison_issue.js b/js/component/card/comparison_issue.js
deleted file mode 100644
index cf3ebff..0000000
--- a/js/component/card/comparison_issue.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Possible issue with your comparison.
- */
-beestat.component.card.comparison_issue = function() {
- beestat.component.card.apply(this, arguments);
-};
-beestat.extend(beestat.component.card.comparison_issue, beestat.component.card);
-
-/**
- * Decorate
- *
- * @param {rocket.Elements} parent
- */
-beestat.component.card.comparison_issue.prototype.decorate_contents_ = function(parent) {
- parent.style('background', beestat.style.color.red.dark);
- parent.appendChild($.createElement('p').innerText('Notice how one or more of the lines below slopes down or is very flat? The expectation is that these slope upwards. This may affect the accuracy of your scores.'));
- parent.appendChild($.createElement('p').innerText('I\'ll be investigating these situations and improving the algorithm as much as possible to provide as accurate results as I can. Thank you!'));
-};
-
-/**
- * Get the title of the card.
- *
- * @return {string} The title of the card.
- */
-beestat.component.card.comparison_issue.prototype.get_title_ = function() {
- return 'Possible issue with your temperature profiles!';
-};
-
diff --git a/js/component/card/comparison_settings.js b/js/component/card/comparison_settings.js
index 10abe27..5ac122e 100644
--- a/js/component/card/comparison_settings.js
+++ b/js/component/card/comparison_settings.js
@@ -51,24 +51,24 @@ beestat.component.card.comparison_settings.prototype.decorate_contents_ = functi
* If the data is available, then get the data if we don't already have it
* loaded. If the data is not available, poll until it becomes available.
*/
- if (thermostat_group.temperature_profile === null) {
+ if (thermostat_group.profile === null) {
// This will show the loading screen.
self.data_available_();
var poll_interval = 10000;
beestat.add_poll_interval(poll_interval);
- beestat.dispatcher.addEventListener('poll.home_comparisons_load', function() {
+ beestat.dispatcher.addEventListener('poll.comparisons_load', function() {
if (self.data_available_() === true) {
beestat.remove_poll_interval(poll_interval);
- beestat.dispatcher.removeEventListener('poll.home_comparisons_load');
+ beestat.dispatcher.removeEventListener('poll.comparisons_load');
new beestat.api()
.add_call(
'thermostat_group',
- 'generate_temperature_profiles',
+ 'generate_profiles',
{},
- 'generate_temperature_profiles'
+ 'generate_profiles'
)
.add_call(
'thermostat_group',
@@ -78,7 +78,7 @@ beestat.component.card.comparison_settings.prototype.decorate_contents_ = functi
)
.set_callback(function(response) {
beestat.cache.set('thermostat_group', response.thermostat_group);
- (new beestat.layer.home_comparisons()).render();
+ (new beestat.layer.comparisons()).render();
})
.send();
}
@@ -127,7 +127,7 @@ beestat.component.card.comparison_settings.prototype.decorate_region_ = function
// Open up the loading window.
self.show_loading_('Calculating Score for ' + region + ' region');
- beestat.home_comparisons.get_comparison_scores(function() {
+ beestat.comparisons.get_comparison_scores(function() {
// Rerender to get rid of the loader.
self.rerender();
});
@@ -200,7 +200,7 @@ beestat.component.card.comparison_settings.prototype.decorate_property_ = functi
// Open up the loading window.
self.show_loading_('Calculating Score for ' + property_type.text);
- beestat.home_comparisons.get_comparison_scores(function() {
+ beestat.comparisons.get_comparison_scores(function() {
// Rerender to get rid of the loader.
self.rerender();
});
diff --git a/js/component/card/early_access.js b/js/component/card/early_access.js
new file mode 100644
index 0000000..80c2e85
--- /dev/null
+++ b/js/component/card/early_access.js
@@ -0,0 +1,27 @@
+/**
+ * Early access
+ */
+beestat.component.card.early_access = function() {
+ beestat.component.card.apply(this, arguments);
+};
+beestat.extend(beestat.component.card.early_access, beestat.component.card);
+
+/**
+ * Decorate
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.early_access.prototype.decorate_contents_ = function(parent) {
+ parent.style('background', beestat.style.color.green.base);
+ parent.appendChild($.createElement('p').innerText('Experimental early access features below! ⤵'));
+};
+
+/**
+ * Get the title of the card.
+ *
+ * @return {string} The title of the card.
+ */
+// beestat.component.card.early_access.prototype.get_title_ = function() {
+// return 'Possible issue with your temperature profiles!';
+// };
+
diff --git a/js/component/card/metrics.js b/js/component/card/metrics.js
new file mode 100644
index 0000000..0db362f
--- /dev/null
+++ b/js/component/card/metrics.js
@@ -0,0 +1,84 @@
+/**
+ * Metrics card.
+ */
+beestat.component.card.metrics = function(thermostat_group_id) {
+ this.thermostat_group_id_ = thermostat_group_id;
+
+ var self = this;
+
+ /*
+ * Debounce so that multiple setting changes don't re-trigger the same
+ * event. This fires on the trailing edge so that all changes are accounted
+ * for when rerendering.
+ */
+ var data_change_function = beestat.debounce(function() {
+ self.rerender();
+ }, 10);
+
+ beestat.dispatcher.addEventListener(
+ 'cache.data.metrics',
+ data_change_function
+ );
+
+ beestat.component.card.apply(this, arguments);
+
+ // this.layer_.register_loader(beestat.comparisons.get_comparison_metricss);
+};
+beestat.extend(beestat.component.card.metrics, beestat.component.card);
+
+/**
+ * Decorate
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.metrics.prototype.decorate_contents_ = function(parent) {
+ var self = this;
+
+ var metrics = [
+ 'setpoint_heat',
+ 'setpoint_cool',
+ // 'runtime_per_heating_degree_day'
+ ];
+
+ // Decorate the metrics
+ var metric_container = $.createElement('div')
+ .style({
+ 'display': 'grid',
+ // 'grid-template-columns': 'repeat(auto-fit, minmax(160px, 1fr))',
+ 'grid-template-columns': '1fr 1fr 1fr',
+ 'margin': '0 0 ' + beestat.style.size.gutter + 'px -' + beestat.style.size.gutter + 'px'
+ });
+ parent.appendChild(metric_container);
+
+ metrics.forEach(function(metric) {
+ var div = $.createElement('div')
+ .style({
+ 'padding': beestat.style.size.gutter + 'px 0 0 ' + beestat.style.size.gutter + 'px'
+ });
+ metric_container.appendChild(div);
+
+ (new beestat.component.metric[metric](self.thermostat_group_id_)).render(div);
+ });
+
+
+
+
+};
+
+/**
+ * Get the title of the card.
+ *
+ * @return {string} The title of the card.
+ */
+beestat.component.card.metrics.prototype.get_title_ = function() {
+ return 'Metrics';
+};
+
+/**
+ * Decorate the menu.
+ *
+ * @param {rocket.Elements} parent
+ */
+/*beestat.component.card.my_home.prototype.decorate_top_right_ = function(parent) {
+
+};*/
diff --git a/js/component/card/score.js b/js/component/card/score.js
index c225b5c..9ed45ca 100644
--- a/js/component/card/score.js
+++ b/js/component/card/score.js
@@ -20,7 +20,7 @@ beestat.component.card.score = function() {
beestat.component.card.apply(this, arguments);
- this.layer_.register_loader(beestat.home_comparisons.get_comparison_scores);
+ this.layer_.register_loader(beestat.comparisons.get_comparison_scores);
};
beestat.extend(beestat.component.card.score, beestat.component.card);
diff --git a/js/component/card/temperature_profiles_new.js b/js/component/card/temperature_profiles_new.js
new file mode 100644
index 0000000..66c44df
--- /dev/null
+++ b/js/component/card/temperature_profiles_new.js
@@ -0,0 +1,237 @@
+/**
+ * Temperature profiles.
+ *
+ * @param {number} thermostat_group_id The thermostat_group_id this card is
+ * displaying data for.
+ */
+beestat.component.card.temperature_profiles_new = function(thermostat_group_id) {
+ this.thermostat_group_id_ = thermostat_group_id;
+
+ beestat.component.card.apply(this, arguments);
+};
+beestat.extend(beestat.component.card.temperature_profiles_new, beestat.component.card);
+
+/**
+ * Decorate card.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.temperature_profiles_new.prototype.decorate_contents_ = function(parent) {
+ var data = this.get_data_();
+ this.chart_ = new beestat.component.chart.temperature_profiles_new(data);
+ this.chart_.render(parent);
+};
+
+/**
+ * Get all of the series data.
+ *
+ * @return {object} The series data.
+ */
+beestat.component.card.temperature_profiles_new.prototype.get_data_ = function() {
+ var thermostat_group = beestat.cache.thermostat_group[
+ this.thermostat_group_id_
+ ];
+
+ var data = {
+ 'x': [],
+ 'series': {},
+ 'metadata': {
+ 'series': {},
+ 'chart': {
+ 'title': this.get_title_(),
+ 'subtitle': this.get_subtitle_(),
+ 'outdoor_temperature': beestat.temperature({
+ 'temperature': (thermostat_group.weather.temperature / 10),
+ 'round': 0
+ })
+ }
+ }
+ };
+
+ if (
+ thermostat_group.profile === null
+ ) {
+ this.chart_.render(parent);
+ this.show_loading_('Calculating');
+ } else {
+ // Global x range.
+ var x_min = Infinity;
+ var x_max = -Infinity;
+
+ var y_min = Infinity;
+ var y_max = -Infinity;
+ for (var type in thermostat_group.profile.temperature) {
+ // Cloned because I mutate this data for temperature conversions.
+ var profile = beestat.clone(
+ thermostat_group.profile.temperature[type]
+ );
+
+ if (profile !== null) {
+ // Convert the data to Celsius if necessary
+ var deltas_converted = {};
+ for (var key in profile.deltas) {
+ deltas_converted[beestat.temperature({'temperature': key})] =
+ beestat.temperature({
+ 'temperature': (profile.deltas[key]),
+ 'delta': true,
+ 'round': 3
+ });
+ }
+
+ profile.deltas = deltas_converted;
+ var linear_trendline = this.get_linear_trendline_(profile.deltas);
+
+ var min_max_keys = Object.keys(profile.deltas);
+
+ // This specific trendline x range.
+ var this_x_min = Math.min.apply(null, min_max_keys);
+ var this_x_max = Math.max.apply(null, min_max_keys);
+
+ // Global x range.
+ x_min = Math.min(x_min, this_x_min);
+ x_max = Math.max(x_max, this_x_max);
+
+ data.series['trendline_' + type] = [];
+ data.series['raw_' + type] = [];
+
+ /**
+ * Data is stored internally as °F with 1 value per degree. That data
+ * gets converted to °C which then requires additional precision
+ * (increment).
+ *
+ * The additional precision introduces floating point error, so
+ * convert the x value to a fixed string.
+ *
+ * The string then needs converted to a number for highcharts, so
+ * later on use parseFloat to get back to that.
+ *
+ * Stupid Celsius.
+ */
+ var increment;
+ var fixed;
+ if (beestat.setting('temperature_unit') === '°F') {
+ increment = 1;
+ fixed = 0;
+ } else {
+ increment = 0.1;
+ fixed = 1;
+ }
+ for (var x = this_x_min; x <= this_x_max; x += increment) {
+ var x_fixed = x.toFixed(fixed);
+ var y = (linear_trendline.slope * x_fixed) +
+ linear_trendline.intercept;
+
+ data.series['trendline_' + type].push([
+ parseFloat(x_fixed),
+ y
+ ]);
+ if (profile.deltas[x_fixed] !== undefined) {
+ data.series['raw_' + type].push([
+ parseFloat(x_fixed),
+ profile.deltas[x_fixed]
+ ]);
+ y_min = Math.min(y_min, profile.deltas[x_fixed]);
+ y_max = Math.max(y_max, profile.deltas[x_fixed]);
+ }
+
+ data.metadata.chart.y_min = y_min;
+ data.metadata.chart.y_max = y_max;
+ }
+ }
+ }
+ }
+
+ return data;
+};
+
+/**
+ * Get a linear trendline from a set of data.
+ *
+ * @param {Object} data The data; at least two points required.
+ *
+ * @return {Object} The slope and intercept of the trendline.
+ */
+beestat.component.card.temperature_profiles_new.prototype.get_linear_trendline_ = function(data) {
+ // Requires at least two points.
+ if (Object.keys(data).length < 2) {
+ return null;
+ }
+
+ var sum_x = 0;
+ var sum_y = 0;
+ var sum_xy = 0;
+ var sum_x_squared = 0;
+ var n = 0;
+
+ for (var x in data) {
+ x = parseFloat(x);
+ var y = parseFloat(data[x]);
+
+ sum_x += x;
+ sum_y += y;
+ sum_xy += (x * y);
+ sum_x_squared += Math.pow(x, 2);
+ n++;
+ }
+
+ var slope = ((n * sum_xy) - (sum_x * sum_y)) /
+ ((n * sum_x_squared) - (Math.pow(sum_x, 2)));
+ var intercept = ((sum_y) - (slope * sum_x)) / (n);
+
+ return {
+ 'slope': slope,
+ 'intercept': intercept
+ };
+};
+
+/**
+ * Get the title of the card.
+ *
+ * @return {string} The title.
+ */
+beestat.component.card.temperature_profiles_new.prototype.get_title_ = function() {
+ return 'Temperature Profiles';
+};
+
+/**
+ * Get the subtitle of the card.
+ *
+ * @return {string} The subtitle.
+ */
+beestat.component.card.temperature_profiles_new.prototype.get_subtitle_ = function() {
+ var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
+ var thermostat_group = beestat.cache.thermostat_group[
+ thermostat.thermostat_group_id
+ ];
+
+ var generated_at_m = moment(
+ thermostat_group.profile.metadata.generated_at
+ );
+
+ return 'Generated ' + generated_at_m.format('MMM Do @ h a') + ' (updated weekly)';
+};
+
+/**
+ * Decorate the menu.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.temperature_profiles_new.prototype.decorate_top_right_ = function(parent) {
+ var self = this;
+
+ var menu = (new beestat.component.menu()).render(parent);
+
+ menu.add_menu_item(new beestat.component.menu_item()
+ .set_text('Download Chart')
+ .set_icon('download')
+ .set_callback(function() {
+ self.chart_.export();
+ }));
+
+ menu.add_menu_item(new beestat.component.menu_item()
+ .set_text('Help')
+ .set_icon('help_circle')
+ .set_callback(function() {
+ window.open('https://doc.beestat.io/9c0fba6793dd4bc68f798c1516f0ea25');
+ }));
+};
diff --git a/js/component/chart/temperature_profiles_new.js b/js/component/chart/temperature_profiles_new.js
new file mode 100644
index 0000000..5adb56e
--- /dev/null
+++ b/js/component/chart/temperature_profiles_new.js
@@ -0,0 +1,346 @@
+/**
+ * Temperature profiles chart.
+ *
+ * @param {object} data The chart data.
+ */
+beestat.component.chart.temperature_profiles_new = function(data) {
+ this.data_ = data;
+
+ beestat.component.chart.apply(this, arguments);
+};
+beestat.extend(beestat.component.chart.temperature_profiles_new, beestat.component.chart);
+
+/**
+ * Override for get_options_xAxis_labels_formatter_.
+ *
+ * @return {Function} xAxis labels formatter.
+ */
+beestat.component.chart.temperature_profiles_new.prototype.get_options_xAxis_labels_formatter_ = function() {
+ return function() {
+ return this.value + beestat.setting('temperature_unit');
+ };
+};
+
+/**
+ * Override for get_options_series_.
+ *
+ * @return {Array} All of the series to display on the chart.
+ */
+beestat.component.chart.temperature_profiles_new.prototype.get_options_series_ = function() {
+ var series = [];
+
+ // Trendline data
+ series.push({
+ 'data': this.data_.series.trendline_heat_1,
+ 'name': 'indoor_heat_1_delta',
+ 'color': beestat.series.indoor_heat_1_delta.color,
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'line',
+ 'lineWidth': 2,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ // Trendline data
+ series.push({
+ 'data': this.data_.series.trendline_heat_2,
+ 'name': 'indoor_heat_2_delta',
+ 'color': beestat.series.indoor_heat_2_delta.color,
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'line',
+ 'lineWidth': 2,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ // Trendline data
+ series.push({
+ 'data': this.data_.series.trendline_cool_1,
+ 'name': 'indoor_cool_1_delta',
+ 'color': beestat.series.indoor_cool_1_delta.color,
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'line',
+ 'lineWidth': 2,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ // Trendline data
+ series.push({
+ 'data': this.data_.series.trendline_cool_2,
+ 'name': 'indoor_cool_2_delta',
+ 'color': beestat.series.indoor_cool_2_delta.color,
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'line',
+ 'lineWidth': 2,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ // Trendline data
+ series.push({
+ 'data': this.data_.series.trendline_resist,
+ 'name': 'indoor_resist_delta',
+ 'color': beestat.series.indoor_resist_delta.color,
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'line',
+ 'lineWidth': 2,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ // Raw data
+ series.push({
+ 'data': this.data_.series.raw_heat_1,
+ 'name': 'indoor_heat_1_delta_raw',
+ 'color': beestat.series.indoor_heat_1_delta_raw.color,
+ 'dashStyle': 'ShortDot',
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'spline',
+ 'lineWidth': 1,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ // Raw data
+ series.push({
+ 'data': this.data_.series.raw_heat_2,
+ 'name': 'indoor_heat_2_delta_raw',
+ 'color': beestat.series.indoor_heat_2_delta_raw.color,
+ 'dashStyle': 'ShortDot',
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'spline',
+ 'lineWidth': 1,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ // Raw data
+ series.push({
+ 'data': this.data_.series.raw_cool_1,
+ 'name': 'indoor_cool_1_delta_raw',
+ 'color': beestat.series.indoor_cool_1_delta_raw.color,
+ 'dashStyle': 'ShortDot',
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'spline',
+ 'lineWidth': 1,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ // Raw data
+ series.push({
+ 'data': this.data_.series.raw_cool_2,
+ 'name': 'indoor_cool_2_delta_raw',
+ 'color': beestat.series.indoor_cool_2_delta_raw.color,
+ 'dashStyle': 'ShortDot',
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'spline',
+ 'lineWidth': 1,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ // Raw data
+ series.push({
+ 'data': this.data_.series.raw_resist,
+ 'name': 'indoor_resist_delta_raw',
+ 'color': beestat.series.indoor_resist_delta_raw.color,
+ 'dashStyle': 'ShortDot',
+ 'marker': {
+ 'enabled': false,
+ 'states': {'hover': {'enabled': false}}
+ },
+ 'type': 'spline',
+ 'lineWidth': 1,
+ 'states': {'hover': {'lineWidthPlus': 0}}
+ });
+
+ return series;
+};
+
+/**
+ * Override for get_options_yAxis_.
+ *
+ * @return {Array} The y-axis options.
+ */
+beestat.component.chart.temperature_profiles_new.prototype.get_options_yAxis_ = function() {
+ var absolute_y_max = Math.max(
+ Math.abs(this.data_.metadata.chart.y_min),
+ Math.abs(this.data_.metadata.chart.y_max)
+ );
+
+ var y_min = absolute_y_max * -1;
+ var y_max = absolute_y_max;
+
+ return [
+ {
+ 'alignTicks': false,
+ 'gridLineColor': beestat.style.color.bluegray.light,
+ 'gridLineDashStyle': 'longdash',
+ 'title': {'text': null},
+ 'labels': {
+ 'style': {'color': beestat.style.color.gray.base},
+ 'formatter': function() {
+ return this.value + beestat.setting('temperature_unit');
+ }
+ },
+ 'min': y_min,
+ 'max': y_max,
+ 'plotLines': [
+ {
+ 'color': beestat.style.color.bluegray.light,
+ 'dashStyle': 'solid',
+ 'width': 3,
+ 'value': 0,
+ 'zIndex': 1
+ }
+ ]
+ }
+ ];
+};
+
+/**
+ * Override for get_options_tooltip_formatter_.
+ *
+ * @return {Function} The tooltip formatter.
+ */
+beestat.component.chart.temperature_profiles_new.prototype.get_options_tooltip_formatter_ = function() {
+ var self = this;
+
+ return function() {
+ var sections = [];
+ var section = [];
+ this.points.forEach(function(point) {
+ var series = point.series;
+
+ var value = beestat.temperature({
+ 'temperature': point.y,
+ 'units': true,
+ 'convert': false,
+ 'delta': true,
+ 'type': 'string'
+ }) + ' / h';
+
+ if (series.name.indexOf('raw') === -1) {
+ section.push({
+ 'label': beestat.series[series.name].name,
+ 'value': value,
+ 'color': series.color
+ });
+ }
+ });
+ sections.push(section);
+
+ return self.tooltip_formatter_helper_(
+ 'Outdoor Temp: ' +
+ beestat.temperature({
+ 'temperature': this.x,
+ 'round': 0,
+ 'units': true,
+ 'convert': false
+ }),
+ sections
+ );
+ };
+};
+
+/**
+ * Override for get_options_chart_zoomType_.
+ *
+ * @return {string} The zoom type.
+ */
+beestat.component.chart.temperature_profiles_new.prototype.get_options_chart_zoomType_ = function() {
+ return null;
+};
+
+/**
+ * Override for get_options_legend_.
+ *
+ * @return {object} The legend options.
+ */
+beestat.component.chart.temperature_profiles_new.prototype.get_options_legend_ = function() {
+ return {
+ 'enabled': false
+ };
+};
+
+/**
+ * Override for get_options_xAxis_.
+ *
+ * @return {object} The xAxis options.
+ */
+beestat.component.chart.temperature_profiles_new.prototype.get_options_xAxis_ = function() {
+ return {
+ 'lineWidth': 0,
+ 'tickLength': 0,
+ 'tickInterval': 5,
+ 'gridLineWidth': 1,
+ 'gridLineColor': beestat.style.color.bluegray.light,
+ 'gridLineDashStyle': 'longdash',
+ 'labels': {
+ 'style': {
+ 'color': beestat.style.color.gray.base
+ },
+ 'formatter': this.get_options_xAxis_labels_formatter_()
+ },
+ 'crosshair': this.get_options_xAxis_crosshair_(),
+ 'plotLines': [
+ {
+ 'color': beestat.series.outdoor_temperature.color,
+ 'dashStyle': 'ShortDash',
+ 'width': 1,
+ 'label': {
+ 'style': {
+ 'color': beestat.series.outdoor_temperature.color
+ },
+ 'useHTML': true,
+ 'text': 'Now: ' + beestat.temperature({
+ 'temperature': this.data_.metadata.chart.outdoor_temperature,
+ 'convert': false,
+ 'units': true,
+ 'round': 0
+ })
+ },
+ 'value': this.data_.metadata.chart.outdoor_temperature,
+ 'zIndex': 2
+ }
+ ]
+ };
+};
+
+/**
+ * Override for get_options_chart_height_.
+ *
+ * @return {number} The height of the chart.
+ */
+beestat.component.chart.temperature_profiles_new.prototype.get_options_chart_height_ = function() {
+ return 300;
+};
+
+/**
+ * Override for get_options_plotOptions_series_connectNulls_.
+ *
+ * @return {boolean} Whether or not to connect nulls.
+ */
+beestat.component.chart.temperature_profiles_new.prototype.get_options_plotOptions_series_connectNulls_ = function() {
+ return true;
+};
diff --git a/js/component/header.js b/js/component/header.js
index 46b1c20..dd54ec8 100644
--- a/js/component/header.js
+++ b/js/component/header.js
@@ -38,7 +38,7 @@ beestat.component.header.prototype.decorate_ = function(parent) {
'icon': 'signal_variant'
},
{
- 'layer': 'home_comparisons',
+ 'layer': 'comparisons',
'text': 'Comparisons',
'icon': 'home_group'
}
diff --git a/js/component/metric.js b/js/component/metric.js
new file mode 100644
index 0000000..ca24ce2
--- /dev/null
+++ b/js/component/metric.js
@@ -0,0 +1,137 @@
+/**
+ * Generic customizable metric.
+ */
+beestat.component.metric = function() {
+ beestat.component.apply(this, arguments);
+};
+beestat.extend(beestat.component.metric, beestat.component);
+
+beestat.component.metric.prototype.rerender_on_breakpoint_ = false;
+
+/**
+ * Decorate
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.metric.prototype.decorate_ = function(parent) {
+ if (beestat.cache.data.metrics === undefined) { // todo
+ parent.appendChild($.createElement('div').innerText('Loading...'));
+ return;
+ }
+
+ var outer_container = $.createElement('div').style({
+ 'background': beestat.style.color.bluegray.dark,
+ 'padding': (beestat.style.size.gutter / 2)
+ });
+
+ outer_container.appendChild(
+ $.createElement('div').innerText(this.get_title_())
+ );
+
+ var inner_container = $.createElement('div').style({
+ 'position': 'relative',
+ 'margin-top': '50px',
+ 'margin-bottom': '20px',
+ 'margin-left': '25px'
+ });
+
+ var icon = $.createElement('div').style({
+ 'position': 'absolute',
+ 'top': '-12px',
+ 'left': '-28px'
+ });
+
+ (new beestat.component.icon(this.get_icon_()))
+ .set_color(this.get_color_())
+ .render(icon);
+
+ var line = $.createElement('div').style({
+ 'background': this.get_color_(),
+ 'height': '5px',
+ 'border-radius': '5px'
+ });
+
+ var min = $.createElement('div')
+ .innerText(this.get_min_(true))
+ .style({
+ 'position': 'absolute',
+ 'top': '10px',
+ 'left': '0px'
+ });
+
+ var max = $.createElement('div')
+ .innerText(this.get_max_(true))
+ .style({
+ 'position': 'absolute',
+ 'top': '10px',
+ 'right': '0px'
+ });
+
+ var label = $.createElement('div')
+ .innerText(this.get_value_())
+ .style({
+ 'position': 'absolute',
+ 'top': '-25px',
+ 'left': this.get_marker_position_() + '%',
+ 'width': '100px',
+ 'text-align': 'center',
+ 'margin-left': '-50px',
+ 'font-weight': beestat.style.font_weight.bold
+ });
+
+ var circle = $.createElement('div').style({
+ 'background': this.get_color_(),
+ 'position': 'absolute',
+ 'top': '-4px',
+ 'left': this.get_marker_position_() + '%',
+ 'margin-left': '-7px',
+ 'width': '14px',
+ 'height': '14px',
+ 'border-radius': '50%'
+ });
+
+ var chart = $.createElement('div').style({
+ 'position': 'absolute',
+ 'top': '-40px',
+ 'left': '0px',
+ 'width': '100%',
+ 'height': '40px'
+ });
+
+ var histogram = this.get_histogram_();
+ var histogram_max = this.get_histogram_max_();
+ var column_width = (100 / histogram.length) + '%';
+ histogram.forEach(function(data) {
+ var column = $.createElement('div').style({
+ 'display': 'inline-block',
+ 'background': 'rgba(255, 255, 255, 0.1)',
+ 'width': column_width,
+ 'height': (data.count / histogram_max * 100) + '%'
+ });
+ chart.appendChild(column);
+ });
+
+ inner_container.appendChild(icon);
+ inner_container.appendChild(line);
+ inner_container.appendChild(min);
+ inner_container.appendChild(max);
+ inner_container.appendChild(label);
+ inner_container.appendChild(circle);
+ inner_container.appendChild(chart);
+
+ outer_container.appendChild(inner_container);
+
+ parent.appendChild(outer_container);
+};
+
+beestat.component.metric.prototype.get_marker_position_ = function() {
+ return 100 * (this.get_value_() - this.get_min_()) / (this.get_max_() - this.get_min_());
+};
+
+beestat.component.metric.prototype.get_histogram_max_ = function() {
+ var max = -Infinity;
+ this.get_histogram_().forEach(function(data) {
+ max = Math.max(max, data.count);
+ });
+ return max;
+};
diff --git a/js/component/metric/runtime_per_heating_degree_day.js b/js/component/metric/runtime_per_heating_degree_day.js
new file mode 100644
index 0000000..09635e1
--- /dev/null
+++ b/js/component/metric/runtime_per_heating_degree_day.js
@@ -0,0 +1,98 @@
+/**
+ * Runtime per heating degree day metric.
+ *
+ * @param {number} thermostat_group_id The thermostat group.
+ */
+beestat.component.metric.runtime_per_heating_degree_day = function(thermostat_group_id) {
+ this.thermostat_group_id_ = thermostat_group_id;
+
+ beestat.component.metric.apply(this, arguments);
+};
+beestat.extend(beestat.component.metric.runtime_per_heating_degree_day, beestat.component.metric);
+
+beestat.component.metric.runtime_per_heating_degree_day.prototype.rerender_on_breakpoint_ = false;
+
+/**
+ * Get the title of this metric.
+ *
+ * @return {string} The title of this metric.
+ */
+beestat.component.metric.runtime_per_heating_degree_day.prototype.get_title_ = function() {
+ return 'Runtime / HDD';
+};
+
+/**
+ * Get the icon of this metric.
+ *
+ * @return {string} The icon of this metric.
+ */
+beestat.component.metric.runtime_per_heating_degree_day.prototype.get_icon_ = function() {
+ return 'fire';
+};
+
+/**
+ * Get the color of this metric.
+ *
+ * @return {string} The color of this metric.
+ */
+beestat.component.metric.runtime_per_heating_degree_day.prototype.get_color_ = function() {
+ return beestat.series.compressor_heat_1.color;
+};
+
+/**
+ * Get the minimum value of this metric (within two standard deviations).
+ *
+ * @return {mixed} The minimum value of this metric.
+ */
+beestat.component.metric.runtime_per_heating_degree_day.prototype.get_min_ = function() {
+ var standard_deviation =
+ beestat.cache.data.metrics.runtime_per_heating_degree_day.standard_deviation;
+ return (beestat.cache.data.metrics.runtime_per_heating_degree_day.median - (standard_deviation * 2)).toFixed(1);
+};
+
+/**
+ * Get the maximum value of this metric (within two standard deviations).
+ *
+ * @return {mixed} The maximum value of this metric.
+ */
+beestat.component.metric.runtime_per_heating_degree_day.prototype.get_max_ = function() {
+ var standard_deviation =
+ beestat.cache.data.metrics.runtime_per_heating_degree_day.standard_deviation;
+ return (beestat.cache.data.metrics.runtime_per_heating_degree_day.median + (standard_deviation * 2)).toFixed(1);
+};
+
+/**
+ * Get the value of this metric.
+ *
+ * @return {mixed} The value of this metric.
+ */
+beestat.component.metric.runtime_per_heating_degree_day.prototype.get_value_ = function() {
+ var thermostat_group = beestat.cache.thermostat_group[
+ this.thermostat_group_id_
+ ];
+ // todo: store this explicitly on the profile so it doesn't have to be calculated in JS?
+ return (thermostat_group.profile.runtime.heat_1 /
+ thermostat_group.profile.degree_days.heat).toFixed(1);
+};
+
+/**
+ * Get a histogram between the min and max values of this metric.
+ *
+ * @return {array} The histogram.
+ */
+beestat.component.metric.runtime_per_heating_degree_day.prototype.get_histogram_ = function() {
+ var histogram = [];
+ for (var value in beestat.cache.data.metrics.runtime_per_heating_degree_day.histogram) {
+ if (
+ value >= this.get_min_() &&
+ value <= this.get_max_()
+ ) {
+ var count = beestat.cache.data.metrics.runtime_per_heating_degree_day.histogram[value];
+ histogram.push({
+ 'value': value,
+ 'count': count
+ });
+ }
+ }
+ return histogram;
+};
diff --git a/js/component/metric/setpoint_cool.js b/js/component/metric/setpoint_cool.js
new file mode 100644
index 0000000..7495739
--- /dev/null
+++ b/js/component/metric/setpoint_cool.js
@@ -0,0 +1,119 @@
+/**
+ * Cool setpoint metric.
+ *
+ * @param {number} thermostat_group_id The thermostat group.
+ */
+beestat.component.metric.setpoint_cool = function(thermostat_group_id) {
+ this.thermostat_group_id_ = thermostat_group_id;
+
+ beestat.component.metric.apply(this, arguments);
+};
+beestat.extend(beestat.component.metric.setpoint_cool, beestat.component.metric);
+
+beestat.component.metric.setpoint_cool.prototype.rerender_on_breakpoint_ = false;
+
+/**
+ * Get the title of this metric.
+ *
+ * @return {string} The title of this metric.
+ */
+beestat.component.metric.setpoint_cool.prototype.get_title_ = function() {
+ return 'Cool Setpoint';
+};
+
+/**
+ * Get the icon of this metric.
+ *
+ * @return {string} The icon of this metric.
+ */
+beestat.component.metric.setpoint_cool.prototype.get_icon_ = function() {
+ return 'snowflake';
+};
+
+/**
+ * Get the color of this metric.
+ *
+ * @return {string} The color of this metric.
+ */
+beestat.component.metric.setpoint_cool.prototype.get_color_ = function() {
+ return beestat.series.compressor_cool_1.color;
+};
+
+/**
+ * Get the minimum value of this metric (within two standard deviations).
+ *
+ * @param {boolean} units Whether or not to return a numerical value or a
+ * string with units.
+ *
+ * @return {mixed} The minimum value of this metric.
+ */
+beestat.component.metric.setpoint_cool.prototype.get_min_ = function(units) {
+ var standard_deviation =
+ beestat.cache.data.metrics.setpoint_cool.standard_deviation;
+ return beestat.temperature({
+ 'temperature': beestat.cache.data.metrics.setpoint_cool.median - (standard_deviation * 2),
+ 'round': 0,
+ 'units': units
+ });
+};
+
+/**
+ * Get the maximum value of this metric (within two standard deviations).
+ *
+ * @param {boolean} units Whether or not to return a numerical value or a
+ * string with units.
+ *
+ * @return {mixed} The maximum value of this metric.
+ */
+beestat.component.metric.setpoint_cool.prototype.get_max_ = function(units) {
+ var standard_deviation =
+ beestat.cache.data.metrics.setpoint_cool.standard_deviation;
+ return beestat.temperature({
+ 'temperature': beestat.cache.data.metrics.setpoint_cool.median + (standard_deviation * 2),
+ 'round': 0,
+ 'units': units
+ });
+};
+
+/**
+ * Get the value of this metric.
+ *
+ * @param {boolean} units Whether or not to return a numerical value or a
+ * string with units.
+ *
+ * @return {mixed} The value of this metric.
+ */
+beestat.component.metric.setpoint_cool.prototype.get_value_ = function(units) {
+ var thermostat_group = beestat.cache.thermostat_group[
+ this.thermostat_group_id_
+ ];
+ return beestat.temperature({
+ 'temperature': thermostat_group.profile.setpoint.cool,
+ 'units': units
+ });
+};
+
+/**
+ * Get a histogram between the min and max values of this metric.
+ *
+ * @param {boolean} units Whether or not to return a numerical value or a
+ * string with units.
+ *
+ * @return {array} The histogram.
+ */
+beestat.component.metric.setpoint_cool.prototype.get_histogram_ = function(units) {
+ var histogram = [];
+ for (var temperature in beestat.cache.data.metrics.setpoint_cool.histogram) {
+ if (
+ temperature >= this.get_min_(units) &&
+ temperature <= this.get_max_(units)
+ ) {
+ var count = beestat.cache.data.metrics.setpoint_cool.histogram[temperature];
+ histogram.push({
+ 'value': beestat.temperature(temperature),
+ 'count': count
+ });
+ }
+ }
+ return histogram;
+};
diff --git a/js/component/metric/setpoint_heat.js b/js/component/metric/setpoint_heat.js
new file mode 100644
index 0000000..442f199
--- /dev/null
+++ b/js/component/metric/setpoint_heat.js
@@ -0,0 +1,119 @@
+/**
+ * Heat setpoint metric.
+ *
+ * @param {number} thermostat_group_id The thermostat group.
+ */
+beestat.component.metric.setpoint_heat = function(thermostat_group_id) {
+ this.thermostat_group_id_ = thermostat_group_id;
+
+ beestat.component.metric.apply(this, arguments);
+};
+beestat.extend(beestat.component.metric.setpoint_heat, beestat.component.metric);
+
+beestat.component.metric.setpoint_heat.prototype.rerender_on_breakpoint_ = false;
+
+/**
+ * Get the title of this metric.
+ *
+ * @return {string} The title of this metric.
+ */
+beestat.component.metric.setpoint_heat.prototype.get_title_ = function() {
+ return 'Heat Setpoint';
+};
+
+/**
+ * Get the icon of this metric.
+ *
+ * @return {string} The icon of this metric.
+ */
+beestat.component.metric.setpoint_heat.prototype.get_icon_ = function() {
+ return 'fire';
+};
+
+/**
+ * Get the color of this metric.
+ *
+ * @return {string} The color of this metric.
+ */
+beestat.component.metric.setpoint_heat.prototype.get_color_ = function() {
+ return beestat.series.compressor_heat_1.color;
+};
+
+/**
+ * Get the minimum value of this metric (within two standard deviations).
+ *
+ * @param {boolean} units Whether or not to return a numerical value or a
+ * string with units.
+ *
+ * @return {mixed} The minimum value of this metric.
+ */
+beestat.component.metric.setpoint_heat.prototype.get_min_ = function(units) {
+ var standard_deviation =
+ beestat.cache.data.metrics.setpoint_heat.standard_deviation;
+ return beestat.temperature({
+ 'temperature': beestat.cache.data.metrics.setpoint_heat.median - (standard_deviation * 2),
+ 'round': 0,
+ 'units': units
+ });
+};
+
+/**
+ * Get the maximum value of this metric (within two standard deviations).
+ *
+ * @param {boolean} units Whether or not to return a numerical value or a
+ * string with units.
+ *
+ * @return {mixed} The maximum value of this metric.
+ */
+beestat.component.metric.setpoint_heat.prototype.get_max_ = function(units) {
+ var standard_deviation =
+ beestat.cache.data.metrics.setpoint_heat.standard_deviation;
+ return beestat.temperature({
+ 'temperature': beestat.cache.data.metrics.setpoint_heat.median + (standard_deviation * 2),
+ 'round': 0,
+ 'units': units
+ });
+};
+
+/**
+ * Get the value of this metric.
+ *
+ * @param {boolean} units Whether or not to return a numerical value or a
+ * string with units.
+ *
+ * @return {mixed} The value of this metric.
+ */
+beestat.component.metric.setpoint_heat.prototype.get_value_ = function(units) {
+ var thermostat_group = beestat.cache.thermostat_group[
+ this.thermostat_group_id_
+ ];
+ return beestat.temperature({
+ 'temperature': thermostat_group.profile.setpoint.heat,
+ 'units': units
+ });
+};
+
+/**
+ * Get a histogram between the min and max values of this metric.
+ *
+ * @param {boolean} units Whether or not to return a numerical value or a
+ * string with units.
+ *
+ * @return {array} The histogram.
+ */
+beestat.component.metric.setpoint_heat.prototype.get_histogram_ = function(units) {
+ var histogram = [];
+ for (var temperature in beestat.cache.data.metrics.setpoint_heat.histogram) {
+ if (
+ temperature >= this.get_min_(units) &&
+ temperature <= this.get_max_(units)
+ ) {
+ var count = beestat.cache.data.metrics.setpoint_heat.histogram[temperature];
+ histogram.push({
+ 'value': beestat.temperature(temperature),
+ 'count': count
+ });
+ }
+ }
+ return histogram;
+};
diff --git a/js/component/modal/change_system_type.js b/js/component/modal/change_system_type.js
index 974e21a..3a6567d 100644
--- a/js/component/modal/change_system_type.js
+++ b/js/component/modal/change_system_type.js
@@ -148,7 +148,7 @@ beestat.component.modal.change_system_type.prototype.get_buttons_ = function() {
// Re-run comparison scores as they are invalid for the new system
// type.
- beestat.home_comparisons.get_comparison_scores();
+ beestat.comparisons.get_comparison_scores();
// Close the modal.
self.dispose();
diff --git a/js/js.php b/js/js.php
index d4dbcc5..fcf3781 100755
--- a/js/js.php
+++ b/js/js.php
@@ -27,7 +27,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
- echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
@@ -40,7 +40,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
- echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
// Component
@@ -51,7 +51,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
- echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
@@ -65,9 +65,12 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
@@ -98,6 +101,10 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
}
else {
echo '' . PHP_EOL;
diff --git a/js/layer/home_comparisons.js b/js/layer/comparisons.js
similarity index 67%
rename from js/layer/home_comparisons.js
rename to js/layer/comparisons.js
index d2a6846..f630007 100644
--- a/js/layer/home_comparisons.js
+++ b/js/layer/comparisons.js
@@ -1,100 +1,103 @@
-/**
- * Home comparisons layer.
- */
-beestat.layer.home_comparisons = function() {
- beestat.layer.apply(this, arguments);
-};
-beestat.extend(beestat.layer.home_comparisons, beestat.layer);
-
-beestat.layer.home_comparisons.prototype.decorate_ = function(parent) {
- /*
- * Set the overflow on the body so the scrollbar is always present so
- * highcharts graphs render properly.
- */
- $('body').style({
- 'overflow-y': 'scroll',
- 'background': beestat.style.color.bluegray.light,
- 'padding': '0 ' + beestat.style.size.gutter + 'px'
- });
-
- (new beestat.component.header('home_comparisons')).render(parent);
-
- var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
- var thermostat_group = beestat.cache.thermostat_group[thermostat.thermostat_group_id];
-
- // All the cards
- var cards = [];
-
- if (window.is_demo === true) {
- cards.push([
- {
- 'card': new beestat.component.card.demo(),
- 'size': 12
- }
- ]);
- }
-
- cards.push([
- {
- 'card': new beestat.component.card.comparison_settings(),
- 'size': 6
- },
- {
- 'card': new beestat.component.card.my_home(),
- 'size': 6
- }
- ]);
-
- // Scores and graph
- if (thermostat_group.temperature_profile !== null) {
- cards.push([
- {
- 'card': new beestat.component.card.score.heat(),
- 'size': 4
- },
- {
- 'card': new beestat.component.card.score.cool(),
- 'size': 4
- },
- {
- 'card': new beestat.component.card.score.resist(),
- 'size': 4
- }
- ]);
-
- if (
- (
- thermostat_group.temperature_profile.heat !== undefined &&
- thermostat_group.temperature_profile.heat.linear_trendline.slope < 0
- ) ||
- (
- thermostat_group.temperature_profile.cool !== undefined &&
- thermostat_group.temperature_profile.cool.linear_trendline.slope < 0
- )
- ) {
- cards.push([
- {
- 'card': new beestat.component.card.comparison_issue(),
- 'size': 12
- }
- ]);
- }
-
- cards.push([
- {
- 'card': new beestat.component.card.temperature_profiles(thermostat_group.thermostat_group_id),
- 'size': 12
- }
- ]);
- }
-
- // Footer
- cards.push([
- {
- 'card': new beestat.component.card.footer(),
- 'size': 12
- }
- ]);
-
- (new beestat.component.layout(cards)).render(parent);
-};
+/**
+ * Home comparisons layer.
+ */
+beestat.layer.comparisons = function() {
+ beestat.layer.apply(this, arguments);
+};
+beestat.extend(beestat.layer.comparisons, beestat.layer);
+
+beestat.layer.comparisons.prototype.decorate_ = function(parent) {
+ /*
+ * Set the overflow on the body so the scrollbar is always present so
+ * highcharts graphs render properly.
+ */
+ $('body').style({
+ 'overflow-y': 'scroll',
+ 'background': beestat.style.color.bluegray.light,
+ 'padding': '0 ' + beestat.style.size.gutter + 'px'
+ });
+
+ (new beestat.component.header('comparisons')).render(parent);
+
+ var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
+ var thermostat_group = beestat.cache.thermostat_group[thermostat.thermostat_group_id];
+
+ // All the cards
+ var cards = [];
+
+ if (window.is_demo === true) {
+ cards.push([
+ {
+ 'card': new beestat.component.card.demo(),
+ 'size': 12
+ }
+ ]);
+ }
+
+ cards.push([
+ {
+ 'card': new beestat.component.card.comparison_settings(),
+ 'size': 6
+ },
+ {
+ 'card': new beestat.component.card.my_home(),
+ 'size': 6
+ }
+ ]);
+
+ // Scores and graph
+ if (thermostat_group.profile !== null) {
+ cards.push([
+ {
+ 'card': new beestat.component.card.score.heat(),
+ 'size': 4
+ },
+ {
+ 'card': new beestat.component.card.score.cool(),
+ 'size': 4
+ },
+ {
+ 'card': new beestat.component.card.score.resist(),
+ 'size': 4
+ }
+ ]);
+
+ cards.push([
+ {
+ 'card': new beestat.component.card.temperature_profiles(thermostat_group.thermostat_group_id),
+ 'size': 12
+ }
+ ]);
+
+ if (beestat.user.has_early_access() === true) {
+ cards.push([
+ {
+ 'card': new beestat.component.card.early_access(),
+ 'size': 12
+ }
+ ]);
+ cards.push([
+ {
+ 'card': new beestat.component.card.metrics(thermostat_group.thermostat_group_id),
+ 'size': 12
+ }
+ ]);
+ cards.push([
+ {
+ 'card': new beestat.component.card.temperature_profiles_new(thermostat_group.thermostat_group_id),
+ 'size': 12
+ }
+ ]);
+ }
+ }
+
+ // Footer
+ cards.push([
+ {
+ 'card': new beestat.component.card.footer(),
+ 'size': 12
+ }
+ ]);
+
+ (new beestat.component.layout(cards)).render(parent);
+};