mirror of
https://github.com/beestat/app.git
synced 2026-03-29 21:02:17 -04:00
Added historical temperature profiles and profile download
This commit is contained in:
parent
a654a1ba19
commit
7529dc5b78
@ -19,7 +19,7 @@ beestat.component.card.temperature_profiles = function(thermostat_id) {
|
||||
}, 10);
|
||||
|
||||
beestat.dispatcher.addEventListener(
|
||||
['cache.thermostat'],
|
||||
['cache.thermostat', 'cache.profile'],
|
||||
change_function
|
||||
);
|
||||
|
||||
@ -27,6 +27,21 @@ beestat.component.card.temperature_profiles = function(thermostat_id) {
|
||||
};
|
||||
beestat.extend(beestat.component.card.temperature_profiles, beestat.component.card);
|
||||
|
||||
/**
|
||||
* Override subtitle to store a reference for live updates.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.card.temperature_profiles.prototype.decorate_subtitle_ = function(parent) {
|
||||
this.subtitle_element_ = $.createElement('div')
|
||||
.style({
|
||||
'font-weight': beestat.style.font_weight.light,
|
||||
'margin-bottom': (beestat.style.size.gutter / 4)
|
||||
});
|
||||
this.subtitle_element_.innerHTML(this.get_subtitle_() || '');
|
||||
parent.appendChild(this.subtitle_element_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate card.
|
||||
*
|
||||
@ -63,6 +78,79 @@ beestat.component.card.temperature_profiles.prototype.decorate_contents_ = funct
|
||||
|
||||
this.chart_ = new beestat.component.chart.temperature_profiles(data);
|
||||
this.chart_.render(chart_container);
|
||||
|
||||
// Historical profile slider
|
||||
var profiles = this.get_profiles_();
|
||||
if (profiles.length >= 2) {
|
||||
var self = this;
|
||||
|
||||
if (this.profile_index_ === undefined || this.profile_index_ >= profiles.length) {
|
||||
this.profile_index_ = profiles.length - 1;
|
||||
}
|
||||
|
||||
var slider_container = $.createElement('div').style({
|
||||
'display': 'flex',
|
||||
'align-items': 'center',
|
||||
'margin-top': '8px'
|
||||
});
|
||||
parent.appendChild(slider_container);
|
||||
|
||||
var range_container = $.createElement('div').style({
|
||||
'flex-grow': '1'
|
||||
});
|
||||
slider_container.appendChild(range_container);
|
||||
|
||||
var date_label = $.createElement('div').style({
|
||||
'white-space': 'nowrap',
|
||||
'margin-left': '8px',
|
||||
'min-width': '100px',
|
||||
'text-align': 'right'
|
||||
});
|
||||
date_label.innerText(
|
||||
moment(profiles[this.profile_index_].profile.metadata.generated_at).format('MMM D, YYYY')
|
||||
);
|
||||
slider_container.appendChild(date_label);
|
||||
|
||||
var range = new beestat.component.input.range();
|
||||
range
|
||||
.set_min(0)
|
||||
.set_max(profiles.length - 1)
|
||||
.set_value(this.profile_index_)
|
||||
.render(range_container);
|
||||
|
||||
range.addEventListener('input', function() {
|
||||
self.profile_index_ = parseInt(range.get_value(), 10);
|
||||
|
||||
date_label.innerText(
|
||||
moment(profiles[self.profile_index_].profile.metadata.generated_at).format('MMM D, YYYY')
|
||||
);
|
||||
|
||||
var new_data = self.get_data_();
|
||||
|
||||
// Update the chart data in-place to avoid DOM recreation
|
||||
self.chart_.data_ = new_data;
|
||||
var new_series = self.chart_.get_options_series_();
|
||||
new_series.forEach(function(s) {
|
||||
if (s.data === undefined) {
|
||||
s.data = [];
|
||||
}
|
||||
});
|
||||
self.chart_.chart_.update({
|
||||
'series': new_series,
|
||||
'exporting': {
|
||||
'filename': self.chart_.get_options_exporting_filename_(),
|
||||
'chartOptions': {
|
||||
'subtitle': {
|
||||
'text': new_data.metadata.chart.subtitle
|
||||
}
|
||||
}
|
||||
}
|
||||
}, true, false, false);
|
||||
|
||||
// Update the card subtitle
|
||||
self.subtitle_element_.innerHTML(self.get_subtitle_() || '');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -90,9 +178,16 @@ beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
|
||||
}
|
||||
};
|
||||
|
||||
var selected_profile = this.get_selected_profile_();
|
||||
var profiles = this.get_profiles_();
|
||||
var is_current = (this.profile_index_ === undefined || this.profile_index_ === profiles.length - 1);
|
||||
|
||||
if (
|
||||
thermostat.profile === null ||
|
||||
moment().diff(moment(thermostat.profile.metadata.generated_at), 'days') >= 7
|
||||
is_current &&
|
||||
(
|
||||
selected_profile === null ||
|
||||
moment().diff(moment(selected_profile.metadata.generated_at), 'days') >= 7
|
||||
)
|
||||
) {
|
||||
this.show_loading_('Fetching');
|
||||
new beestat.api()
|
||||
@ -117,15 +212,15 @@ beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
|
||||
beestat.cache.set('thermostat', response.thermostat);
|
||||
})
|
||||
.send();
|
||||
} else {
|
||||
} else if (selected_profile !== null) {
|
||||
const profile_extremes = this.get_profile_extremes_(5);
|
||||
|
||||
var y_min = Infinity;
|
||||
var y_max = -Infinity;
|
||||
for (var type in thermostat.profile.temperature) {
|
||||
for (var type in selected_profile.temperature) {
|
||||
// Cloned because I mutate this data for temperature conversions.
|
||||
var profile = beestat.clone(
|
||||
thermostat.profile.temperature[type]
|
||||
selected_profile.temperature[type]
|
||||
);
|
||||
|
||||
if (profile !== null) {
|
||||
@ -213,6 +308,16 @@ beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lock the y-axis scale to the most recent profile so historical
|
||||
// profiles don't cause the chart to jump around.
|
||||
if (is_current) {
|
||||
this.current_y_min_ = y_min;
|
||||
this.current_y_max_ = y_max;
|
||||
} else if (this.current_y_min_ !== undefined) {
|
||||
data.metadata.chart.y_min = this.current_y_min_;
|
||||
data.metadata.chart.y_max = this.current_y_max_;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -233,21 +338,21 @@ beestat.component.card.temperature_profiles.prototype.get_title_ = function() {
|
||||
* @return {string} The subtitle.
|
||||
*/
|
||||
beestat.component.card.temperature_profiles.prototype.get_subtitle_ = function() {
|
||||
const thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||
var selected_profile = this.get_selected_profile_();
|
||||
|
||||
// If the profile has not yet been generated.
|
||||
if (thermostat.profile === null) {
|
||||
if (selected_profile === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const generated_at_m = moment(
|
||||
thermostat.profile.metadata.generated_at
|
||||
selected_profile.metadata.generated_at
|
||||
);
|
||||
|
||||
let duration_text = '';
|
||||
|
||||
// How much data was used to generate this.
|
||||
const duration_weeks = Math.round(thermostat.profile.metadata.duration / 7);
|
||||
const duration_weeks = Math.round(selected_profile.metadata.duration / 7);
|
||||
duration_text += ' from the past';
|
||||
if (duration_weeks === 0) {
|
||||
duration_text += ' few days';
|
||||
@ -260,7 +365,7 @@ beestat.component.card.temperature_profiles.prototype.get_subtitle_ = function()
|
||||
}
|
||||
duration_text += ' of data';
|
||||
|
||||
return 'Generated ' + generated_at_m.format('MMM Do @ h a') + duration_text + ' (updated weekly).';
|
||||
return 'Generated ' + generated_at_m.format('MMM Do, YYYY @ h a') + duration_text + ' (updated weekly).';
|
||||
};
|
||||
|
||||
/**
|
||||
@ -281,11 +386,29 @@ beestat.component.card.temperature_profiles.prototype.decorate_top_right_ = func
|
||||
}));
|
||||
|
||||
if (this.has_data_() === true) {
|
||||
menu.add_menu_item(new beestat.component.menu_item()
|
||||
.set_text('Download Profile')
|
||||
.set_icon('code_tags')
|
||||
.set_callback(function() {
|
||||
var profile = self.get_selected_profile_();
|
||||
var blob = new Blob(
|
||||
[JSON.stringify(profile, null, 2)],
|
||||
{'type': 'application/json'}
|
||||
);
|
||||
var a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = 'Temperature Profile - ' +
|
||||
moment(profile.metadata.generated_at).format('YYYY-MM-DD') +
|
||||
'.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
}));
|
||||
|
||||
menu.add_menu_item(new beestat.component.menu_item()
|
||||
.set_text('More Info')
|
||||
.set_icon('information')
|
||||
.set_callback(function() {
|
||||
new beestat.component.modal.temperature_profiles_info().render();
|
||||
new beestat.component.modal.temperature_profiles_info(self.get_selected_profile_()).render();
|
||||
}));
|
||||
}
|
||||
|
||||
@ -384,6 +507,46 @@ beestat.component.card.temperature_profiles.prototype.get_profile_extremes_ = fu
|
||||
return extremes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all historical profiles for this thermostat, sorted by generated_at ascending.
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
beestat.component.card.temperature_profiles.prototype.get_profiles_ = function() {
|
||||
if (beestat.cache.profile === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return Object.values(beestat.cache.profile)
|
||||
.filter(function(p) {
|
||||
return p.thermostat_id === self.thermostat_id_;
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
return a.profile.metadata.generated_at > b.profile.metadata.generated_at ? 1 : -1;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the currently selected profile object (the one to render on the chart).
|
||||
* Falls back to the current thermostat profile when no historical selection.
|
||||
*
|
||||
* @return {object|null}
|
||||
*/
|
||||
beestat.component.card.temperature_profiles.prototype.get_selected_profile_ = function() {
|
||||
var profiles = this.get_profiles_();
|
||||
|
||||
if (
|
||||
this.profile_index_ !== undefined &&
|
||||
profiles.length > 0 &&
|
||||
profiles[this.profile_index_] !== undefined
|
||||
) {
|
||||
return profiles[this.profile_index_].profile;
|
||||
}
|
||||
|
||||
return beestat.cache.thermostat[this.thermostat_id_].profile;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get whether or not there is any data to be displayed.
|
||||
*
|
||||
|
||||
@ -1,13 +1,38 @@
|
||||
/**
|
||||
* Temperature Profiles Details
|
||||
*
|
||||
* @param {object} profile The profile object to display info for.
|
||||
*/
|
||||
beestat.component.modal.temperature_profiles_info = function() {
|
||||
beestat.component.modal.temperature_profiles_info = function(profile) {
|
||||
this.profile_ = profile;
|
||||
beestat.component.modal.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.modal.temperature_profiles_info, beestat.component.modal);
|
||||
|
||||
beestat.component.modal.temperature_profiles_info.prototype.decorate_contents_ = function(parent) {
|
||||
const thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
||||
var date_container = $.createElement('div')
|
||||
.style({
|
||||
'margin-bottom': '16px',
|
||||
'font-weight': beestat.style.font_weight.light
|
||||
});
|
||||
var generated_at_m = moment(this.profile_.metadata.generated_at);
|
||||
var duration_weeks = Math.round(this.profile_.metadata.duration / 7);
|
||||
var duration_text = ' from the past';
|
||||
if (duration_weeks === 0) {
|
||||
duration_text += ' few days';
|
||||
} else if (duration_weeks === 1) {
|
||||
duration_text += ' week';
|
||||
} else if (duration_weeks >= 52) {
|
||||
duration_text += ' year';
|
||||
} else {
|
||||
duration_text += ' ' + duration_weeks + ' weeks';
|
||||
}
|
||||
duration_text += ' of data';
|
||||
|
||||
date_container.innerText(
|
||||
'Generated ' + generated_at_m.format('MMM Do, YYYY @ h a') + duration_text + ' (updated weekly).'
|
||||
);
|
||||
parent.appendChild(date_container);
|
||||
|
||||
const container = $.createElement('div')
|
||||
.style({
|
||||
@ -18,6 +43,7 @@ beestat.component.modal.temperature_profiles_info.prototype.decorate_contents_ =
|
||||
parent.appendChild(container);
|
||||
|
||||
const fields = [];
|
||||
var self = this;
|
||||
|
||||
[
|
||||
'heat_1',
|
||||
@ -28,8 +54,8 @@ beestat.component.modal.temperature_profiles_info.prototype.decorate_contents_ =
|
||||
'cool_2',
|
||||
'resist'
|
||||
].forEach(function(type) {
|
||||
if (thermostat.profile.temperature[type] !== null) {
|
||||
const profile = thermostat.profile.temperature[type];
|
||||
if (self.profile_.temperature[type] !== null) {
|
||||
const profile = self.profile_.temperature[type];
|
||||
|
||||
// Convert the data to Celsius if necessary
|
||||
const deltas_converted = {};
|
||||
|
||||
@ -141,6 +141,13 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
|
||||
'session'
|
||||
);
|
||||
|
||||
api.add_call(
|
||||
'profile',
|
||||
'read_id',
|
||||
{},
|
||||
'profile'
|
||||
);
|
||||
|
||||
api.set_callback(function(response) {
|
||||
beestat.cache.set('user', response.user);
|
||||
|
||||
@ -161,6 +168,7 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
|
||||
beestat.cache.set('runtime_thermostat_summary', response.runtime_thermostat_summary);
|
||||
beestat.cache.set('stripe_event', response.stripe_event);
|
||||
beestat.cache.set('session', response.session);
|
||||
beestat.cache.set('profile', response.profile);
|
||||
|
||||
// Send you to the no thermostats layer if none were returned.
|
||||
if(Object.keys(response.thermostat).length === 0) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user