mirror of
https://github.com/beestat/app.git
synced 2025-05-24 02:14:03 -04:00
Time to improvements
This commit is contained in:
parent
7a26405dea
commit
85ebe47737
@ -255,6 +255,9 @@ class profile extends cora\crud {
|
||||
$degree_days_base_temperature = 65;
|
||||
$degree_days = [];
|
||||
$begin_runtime = [];
|
||||
$extreme_times = [
|
||||
'high' => []
|
||||
];
|
||||
|
||||
while($current_timestamp <= $end_timestamp) {
|
||||
// Get a new chunk of data.
|
||||
@ -290,19 +293,81 @@ class profile extends cora\crud {
|
||||
// consistently represented instead of having to do this logic
|
||||
// throughout the generator.
|
||||
$runtime = [];
|
||||
$degree_days_date = date('Y-m-d', $current_timestamp);
|
||||
$degree_days_temperatures = [];
|
||||
|
||||
// $local_datetime = get_local_datetime(
|
||||
// date('Y-m-d H:i:s', $current_timestamp),
|
||||
// $thermostat['time_zone']
|
||||
// );
|
||||
|
||||
// $process_date = date('Y-m-d', $current_timestamp);
|
||||
$process_date = get_local_datetime(
|
||||
date('Y-m-d H:i:s', $current_timestamp),
|
||||
$thermostat['time_zone'],
|
||||
'Y-m-d'
|
||||
);
|
||||
$process_date_temperatures = [];
|
||||
|
||||
// $extreme_times_date = date('Y-m-d', $current_timestamp);
|
||||
|
||||
while($row = $result->fetch_assoc()) {
|
||||
$timestamp = strtotime($row['timestamp']);
|
||||
$date = date('Y-m-d', $timestamp);
|
||||
// $date = date('Y-m-d', $timestamp);
|
||||
$date = get_local_datetime(
|
||||
date('Y-m-d H:i:s', $timestamp),
|
||||
$thermostat['time_zone'],
|
||||
'Y-m-d'
|
||||
);
|
||||
|
||||
// Degree days
|
||||
if($date !== $degree_days_date) {
|
||||
$degree_days[] = (array_mean($degree_days_temperatures) / 10) - $degree_days_base_temperature;
|
||||
$degree_days_date = $date;
|
||||
$degree_days_temperatures = [];
|
||||
if($date !== $process_date) {
|
||||
|
||||
// Degree days
|
||||
$degree_days[] = (array_mean(array_column($process_date_temperatures, 'outdoor_temperature')) / 10) - $degree_days_base_temperature;
|
||||
|
||||
// Max temp
|
||||
$extreme_times_width = 10;
|
||||
|
||||
// Sort the temperatures in descending order and pick the top
|
||||
usort($process_date_temperatures, function($a, $b) {
|
||||
return $b['outdoor_temperature'] <=> $a['outdoor_temperature'];
|
||||
});
|
||||
|
||||
$highest_temperatures = array_slice($process_date_temperatures, 0, $extreme_times_width);
|
||||
// print_r($highest_temperatures);
|
||||
|
||||
// Calculate the average timestamp
|
||||
$average_timestamp = array_sum(array_column($highest_temperatures, 'timestamp')) / count($highest_temperatures);
|
||||
|
||||
// Save the average timestamp to $extreme_times using the day number of the year
|
||||
$extreme_times['high'][date('z', $timestamp)] = date('H:i', $average_timestamp);
|
||||
|
||||
$process_date = $date;
|
||||
$process_date_temperatures = [];
|
||||
}
|
||||
$degree_days_temperatures[] = $row['outdoor_temperature'];
|
||||
$process_date_temperatures[] = [
|
||||
'outdoor_temperature' => $row['outdoor_temperature'],
|
||||
'timestamp' => $timestamp,
|
||||
'tmp_timestamp' => date('c', $timestamp)
|
||||
];
|
||||
|
||||
// Extreme times
|
||||
// if($date !== $extreme_times_date) {
|
||||
// $degree_days[] = (array_mean($process_date_temperatures) / 10) - $degree_days_base_temperature;
|
||||
// $extreme_times_date = $date;
|
||||
// $process_date_temperatures = [];
|
||||
// }
|
||||
// $extreme_times_temperatures[] = $row['outdoor_temperature'];
|
||||
|
||||
/**
|
||||
* Store an extreme_date just like degree days date (or probably
|
||||
* just use the same value), then put all of the outdoor weather
|
||||
* data into an array. When the date changes, process the data. Note
|
||||
* that I should be storing the past 7 days of data in this array.
|
||||
*
|
||||
* Processing the data should find the top X values and then
|
||||
* calculate the average date of them (not midpoint). Do that for
|
||||
* the past 7 days and average those together, then remove one day
|
||||
* of results from the array.
|
||||
*/
|
||||
|
||||
if($first_timestamp === null) {
|
||||
$first_timestamp = $row['timestamp'];
|
||||
@ -969,6 +1034,9 @@ class profile extends cora\crud {
|
||||
'latitude' => $thermostat_database['address_latitude'],
|
||||
'longitude' => $thermostat_database['address_longitude']
|
||||
],
|
||||
'extreme_times' => [
|
||||
'high' => $extreme_times['high']
|
||||
],
|
||||
'metadata' => [
|
||||
'generated_at' => date('c'),
|
||||
'duration' => $first_timestamp === null ? null : round((time() - strtotime($first_timestamp)) / 86400),
|
||||
|
@ -16,7 +16,7 @@ class user extends cora\crud {
|
||||
'sync_patreon_status',
|
||||
'unlink_patreon_account',
|
||||
],
|
||||
'public' => []
|
||||
'public' => ['force_log_in']
|
||||
];
|
||||
|
||||
/**
|
||||
|
267
js/beestat/time_to_detail.js
Normal file
267
js/beestat/time_to_detail.js
Normal file
@ -0,0 +1,267 @@
|
||||
beestat.time_to_detail = {};
|
||||
|
||||
/**
|
||||
* Get a bunch of data for the current time_to_detail rows. Includes
|
||||
* basically everything you need to make a cool chart.
|
||||
*
|
||||
* @param {number} thermostat_id The thermostat_id to get data for.
|
||||
*
|
||||
* @return {object} The data.
|
||||
*/
|
||||
beestat.time_to_detail.get_data = function(thermostat_id) {
|
||||
const thermostat = beestat.cache.thermostat[thermostat_id];
|
||||
const operating_mode = beestat.thermostat.get_operating_mode(
|
||||
thermostat.thermostat_id
|
||||
);
|
||||
const linear_trendline = thermostat.profile.temperature[operating_mode].linear_trendline;
|
||||
|
||||
// Convert "heat_1" etc to "heat"
|
||||
const simplified_operating_mode = operating_mode.replace(/[_\d]|auxiliary/g, '');
|
||||
|
||||
var data = {
|
||||
'x': [],
|
||||
'series': {},
|
||||
'metadata': {
|
||||
'series': {},
|
||||
'chart': {
|
||||
'setpoint_reached_m': null
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize a bunch of stuff.
|
||||
[
|
||||
'outdoor_temperature',
|
||||
'indoor_temperature',
|
||||
'indoor_cool_1_delta',
|
||||
'indoor_cool_2_delta',
|
||||
'indoor_heat_1_delta',
|
||||
'indoor_heat_2_delta',
|
||||
'indoor_auxiliary_heat_1_delta',
|
||||
'indoor_auxiliary_heat_2_delta',
|
||||
'setpoint_heat',
|
||||
'setpoint_cool'
|
||||
].forEach(function(series_code) {
|
||||
data.series[series_code] = [];
|
||||
data.metadata.series[series_code] = {
|
||||
'active': false,
|
||||
'data': {}
|
||||
};
|
||||
|
||||
if (beestat.series[series_code] !== undefined) {
|
||||
data.metadata.series[series_code].name = beestat.series[series_code].name;
|
||||
} else {
|
||||
data.metadata.series[series_code].name = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize a bunch of stuff.
|
||||
data.metadata.series.outdoor_temperature.active = true;
|
||||
data.metadata.series.indoor_temperature.active = true;
|
||||
// [
|
||||
// 'outdoor_temperature',
|
||||
// 'indoor_temperature'
|
||||
// ].forEach(function(series_code) {
|
||||
// data.metadata.series[series_code].active = true;
|
||||
// });
|
||||
|
||||
begin_m = moment();
|
||||
end_m = moment().add(12, 'hour');
|
||||
|
||||
// Loop.
|
||||
let current_m = begin_m.clone();
|
||||
let current_indoor_temperature = thermostat.temperature;
|
||||
let current_outdoor_temperature;
|
||||
// let current_indoor_cool_1_delta;
|
||||
let current_setpoint_heat;
|
||||
let current_setpoint_cool;
|
||||
while (
|
||||
current_m.isSameOrAfter(end_m) === false
|
||||
) {
|
||||
data.x.push(current_m.clone());
|
||||
|
||||
current_outdoor_temperature = beestat.time_to_detail.predict_outdoor_temperature(
|
||||
thermostat_id,
|
||||
// thermostat.weather.temperature,
|
||||
// begin_m,
|
||||
current_m
|
||||
);
|
||||
current_indoor_temperature = beestat.time_to_detail.predict_indoor_temperature(
|
||||
thermostat_id,
|
||||
current_indoor_temperature,
|
||||
current_outdoor_temperature,
|
||||
current_m.clone().subtract(1, 'minute'),
|
||||
current_m
|
||||
);
|
||||
// current_indoor_temperature = prediction.indoor_temperature;
|
||||
// current_degrees_per_hour = prediction.degrees_per_hour;
|
||||
|
||||
data.series.outdoor_temperature.push(current_outdoor_temperature);
|
||||
data.metadata.series.outdoor_temperature.data[current_m.valueOf()] = current_outdoor_temperature;
|
||||
|
||||
data.series.indoor_temperature.push(current_indoor_temperature);
|
||||
data.metadata.series.indoor_temperature.data[current_m.valueOf()] = current_indoor_temperature;
|
||||
|
||||
const setpoint = beestat.time_to_detail.get_setpoint(thermostat_id, current_m);
|
||||
[
|
||||
'cool',
|
||||
'heat',
|
||||
].forEach(function(this_operating_mode) {
|
||||
if(this_operating_mode === simplified_operating_mode) {
|
||||
data.metadata.series[`setpoint_${this_operating_mode}`].active = true;
|
||||
data.series[`setpoint_${this_operating_mode}`].push(setpoint[this_operating_mode]);
|
||||
data.metadata.series[`setpoint_${this_operating_mode}`].data[current_m.valueOf()] = setpoint[this_operating_mode];
|
||||
}
|
||||
});
|
||||
|
||||
[
|
||||
'cool_1',
|
||||
'cool_2',
|
||||
'heat_1',
|
||||
'heat_2',
|
||||
'auxiliary_heat_1',
|
||||
'auxiliary_heat_2'
|
||||
].forEach(function(operating_mode) {
|
||||
if(
|
||||
operating_mode.includes(simplified_operating_mode) === true &&
|
||||
thermostat.profile.temperature[operating_mode] !== null
|
||||
) {
|
||||
const linear_trendline = thermostat.profile.temperature[operating_mode].linear_trendline;
|
||||
const degrees_per_hour = (linear_trendline.slope * current_outdoor_temperature) + linear_trendline.intercept;
|
||||
|
||||
data.metadata.series[`indoor_${operating_mode}_delta`].active = true;
|
||||
data.series[`indoor_${operating_mode}_delta`].push(degrees_per_hour);
|
||||
data.metadata.series[`indoor_${operating_mode}_delta`].data[current_m.valueOf()] = degrees_per_hour;
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
current_indoor_temperature <= setpoint[simplified_operating_mode] &&
|
||||
data.metadata.chart.setpoint_reached_m === null
|
||||
) {
|
||||
data.metadata.chart.setpoint_reached_m = current_m.clone();
|
||||
|
||||
// Redefine the end to go 25% further than we have already.
|
||||
end_m = begin_m.clone().add((current_m.diff(begin_m, 'minutes') * 1.25), 'minutes');
|
||||
}
|
||||
|
||||
current_m.add(1, 'minute');
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Predict outdoor temperature using a simple sine wave.
|
||||
*
|
||||
* @param {number} thermostat_id The thermostat_id
|
||||
* @param {moment} current_m Timestamp to predict for
|
||||
*
|
||||
* @return {number} Predicted outdoor temperature
|
||||
*/
|
||||
beestat.time_to_detail.predict_outdoor_temperature = function(thermostat_id, current_m) {
|
||||
const thermostat = beestat.cache.thermostat[thermostat_id];
|
||||
|
||||
const t = (current_m.hours() * 60) + current_m.minutes();
|
||||
|
||||
// Period and frequency constants; one day (in minutes)
|
||||
const period = 1440;
|
||||
const frequency = (2 * Math.PI) / period;
|
||||
|
||||
// Determine the phase shift based on the warmest time of the day.
|
||||
const desired_t_max = beestat.time_to_detail.get_extreme_high_time(thermostat_id, current_m);
|
||||
const default_t_max = period / 4;
|
||||
const phase_shift = desired_t_max - default_t_max;
|
||||
|
||||
// Determine the amplitude and y_offset based on the predicted high and low
|
||||
// temps.
|
||||
// TODO: the low could actually be wrong if the predicted low isn't actually the low of the entire 24h day
|
||||
const temperature_high = thermostat.weather.temperature_high;
|
||||
const temperature_low = thermostat.weather.temperature_low;
|
||||
const amplitude = (temperature_high - temperature_low) / 2;
|
||||
const y_offset = (temperature_high + temperature_low) / 2;
|
||||
|
||||
return amplitude * Math.sin(frequency * (t - phase_shift)) + y_offset;
|
||||
}
|
||||
|
||||
beestat.time_to_detail.get_extreme_high_time = function(thermostat_id, current_m) {
|
||||
const count = 30;
|
||||
const thermostat = beestat.cache.thermostat[thermostat_id];
|
||||
|
||||
const day_of_year = current_m.dayOfYear(); // 1-indexed
|
||||
let extreme_high_times = [];
|
||||
|
||||
// Function to get extreme high time for a given day of year
|
||||
const get_extreme_high_time_for_day = function(day_of_year) {
|
||||
return thermostat.profile.extreme_times.high[
|
||||
((day_of_year - 1) + 365) % 365
|
||||
];
|
||||
};
|
||||
|
||||
// Check from -14 to +7 days
|
||||
for (let i = (-count); i <= (count / 2); i++) {
|
||||
const target_day_of_year = day_of_year + i;
|
||||
const extreme_time = get_extreme_high_time_for_day(target_day_of_year);
|
||||
if (extreme_time !== undefined) {
|
||||
extreme_high_times.push(extreme_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Take the last 15 values, or fewer if not enough
|
||||
extreme_high_times = extreme_high_times.slice(-count);
|
||||
|
||||
|
||||
// Convert to minutes
|
||||
const extreme_high_minutes = extreme_high_times.map(function(extreme_high_time) {
|
||||
const extreme_high_time_m = moment(extreme_high_time, 'HH:mm');
|
||||
return extreme_high_time_m.hours() * 60 + extreme_high_time_m.minutes();
|
||||
});
|
||||
|
||||
// TODO: Remove outliers here if desired
|
||||
|
||||
const average_extreme_high_minutes = extreme_high_minutes.reduce(
|
||||
function(sum, value) {
|
||||
return (sum + value);
|
||||
},
|
||||
0
|
||||
) / extreme_high_times.length;
|
||||
|
||||
return average_extreme_high_minutes;
|
||||
};
|
||||
|
||||
|
||||
beestat.time_to_detail.predict_indoor_temperature = function(thermostat_id, begin_indoor_temperature, outdoor_temperature, begin_m, current_m) {
|
||||
const thermostat = beestat.cache.thermostat[thermostat_id];
|
||||
// const operating_mode = beestat.thermostat.get_operating_mode(
|
||||
// thermostat.thermostat_id
|
||||
// );
|
||||
const operating_mode = 'cool_1';
|
||||
const linear_trendline = thermostat.profile.temperature[operating_mode].linear_trendline;
|
||||
const degrees_per_hour = (linear_trendline.slope * outdoor_temperature) + linear_trendline.intercept;
|
||||
const degrees_per_minute = degrees_per_hour / 60;
|
||||
|
||||
// const prediction = {
|
||||
// 'indoor_temperature': begin_indoor_temperature + (degrees_per_minute * current_m.diff(begin_m, 'minutes'))
|
||||
// };
|
||||
// prediction[`indoor_${operating_mode}_delta`] = degrees_per_hour;
|
||||
|
||||
return begin_indoor_temperature + (degrees_per_minute * current_m.diff(begin_m, 'minutes'));
|
||||
}
|
||||
|
||||
beestat.time_to_detail.get_setpoint = function(thermostat_id, current_m) {
|
||||
const thermostat = beestat.cache.thermostat[thermostat_id];
|
||||
|
||||
const climates_by_climate_ref = [];
|
||||
thermostat.program.climates.forEach(function(climate) {
|
||||
climates_by_climate_ref[climate.climateRef] = climate;
|
||||
})
|
||||
|
||||
const ecobee_day = current_m.day();
|
||||
const ecobee_half_hour = Math.floor((current_m.hour() * 60 + current_m.minute()) / 30);
|
||||
const schedule = thermostat.program.schedule[ecobee_day][ecobee_half_hour];
|
||||
|
||||
return {
|
||||
'heat': climates_by_climate_ref[schedule].heatTemp,
|
||||
'cool': climates_by_climate_ref[schedule].coolTemp
|
||||
};
|
||||
}
|
@ -346,7 +346,6 @@ beestat.component.card.system.prototype.decorate_time_to_temperature_ = function
|
||||
const container = $.createElement('div').style({
|
||||
'background': beestat.style.color.bluegray.dark,
|
||||
'padding': beestat.style.size.gutter / 2,
|
||||
'text-align': 'center',
|
||||
'margin-top': beestat.style.size.gutter,
|
||||
'border-radius': beestat.style.size.border_radius
|
||||
});
|
||||
@ -365,13 +364,13 @@ beestat.component.card.system.prototype.decorate_time_to_temperature_ = function
|
||||
const outdoor_temperature = thermostat.weather.temperature;
|
||||
const degrees_per_hour = (linear_trendline.slope * outdoor_temperature) + linear_trendline.intercept;
|
||||
|
||||
header_text += ' (' +
|
||||
beestat.temperature({
|
||||
'temperature': degrees_per_hour,
|
||||
'delta': true,
|
||||
'units': true
|
||||
}) +
|
||||
' / h)';
|
||||
// header_text += ' (' +
|
||||
// beestat.temperature({
|
||||
// 'temperature': degrees_per_hour,
|
||||
// 'delta': true,
|
||||
// 'units': true
|
||||
// }) +
|
||||
// ' / h)';
|
||||
|
||||
if (
|
||||
(
|
||||
@ -417,12 +416,55 @@ beestat.component.card.system.prototype.decorate_time_to_temperature_ = function
|
||||
}
|
||||
}
|
||||
|
||||
container.appendChild(
|
||||
const grid = $.createElement('div')
|
||||
.style({
|
||||
'display': 'grid',
|
||||
'grid-template-columns': '3fr 1fr', // 75% for left, 25% for right
|
||||
'grid-gap': `${beestat.style.size.gutter}px`,
|
||||
'align-items': 'center', // Center content vertically
|
||||
});
|
||||
|
||||
const left = $.createElement('div');
|
||||
// .style({
|
||||
// 'overflow-wrap': 'break-word', // Allow wrapping if the content doesn't fit
|
||||
// 'word-wrap': 'break-word',
|
||||
// 'word-break': 'break-word',
|
||||
// });
|
||||
|
||||
left.appendChild(
|
||||
$.createElement('div')
|
||||
.style('font-weight', 'bold')
|
||||
.innerText(header_text)
|
||||
);
|
||||
container.appendChild($.createElement('div').innerText(text));
|
||||
left.appendChild(
|
||||
$.createElement('div')
|
||||
.innerText(text)
|
||||
);
|
||||
|
||||
const right = $.createElement('div')
|
||||
.style({
|
||||
'text-align': 'right', // Right-align the content of the right column
|
||||
});
|
||||
|
||||
var cancel = new beestat.component.tile()
|
||||
.set_icon('chart_line')
|
||||
.set_shadow(false)
|
||||
.set_background_hover_color('#fff')
|
||||
.set_text_hover_color(beestat.style.color.bluegray.dark)
|
||||
.set_text('Detail')
|
||||
.addEventListener('click', function () {
|
||||
(new beestat.component.modal.time_to_detail()).render();
|
||||
})
|
||||
.render(right);
|
||||
|
||||
grid.appendChild(left);
|
||||
grid.appendChild(right);
|
||||
|
||||
container.appendChild(grid);
|
||||
|
||||
// setTimeout(function() {
|
||||
// (new beestat.component.modal.time_to_detail()).render();
|
||||
// }, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -511,6 +511,7 @@ beestat.component.card.three_d.prototype.decorate_controls_ = function(parent) {
|
||||
address.normalized.metadata.latitude,
|
||||
address.normalized.metadata.longitude
|
||||
);
|
||||
console.info(times);
|
||||
|
||||
const sunrise_m = moment(times.sunrise);
|
||||
const sunrise_percentage = ((sunrise_m.hours() * 60) + sunrise_m.minutes()) / 1440 * 100;
|
||||
|
@ -211,6 +211,7 @@ beestat.component.chart.prototype.get_options_chart_ = function() {
|
||||
},
|
||||
'spacing': this.get_options_chart_spacing_(),
|
||||
// For consistent left spacing on charts with no y-axis values
|
||||
'marginTop': this.get_options_chart_marginTop_(),
|
||||
'marginLeft': this.get_options_chart_marginLeft_(),
|
||||
'marginRight': this.get_options_chart_marginRight_(),
|
||||
'marginBottom': this.get_options_chart_marginBottom_(),
|
||||
@ -230,6 +231,15 @@ beestat.component.chart.prototype.get_options_chart_ = function() {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the top margin for the chart.
|
||||
*
|
||||
* @return {number} The top margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.prototype.get_options_chart_marginTop_ = function() {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the left margin for the chart.
|
||||
*
|
||||
|
313
js/component/chart/time_to_detail.js
Normal file
313
js/component/chart/time_to_detail.js
Normal file
@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Runtime thermostat detail temperature chart.
|
||||
*
|
||||
* @param {object} data The chart data.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail = function(data) {
|
||||
this.data_ = data;
|
||||
|
||||
beestat.component.chart.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.chart.time_to_detail, beestat.component.chart);
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_.
|
||||
*
|
||||
* @return {object} The xAxis options.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail.prototype.get_options_xAxis_ = function() {
|
||||
return {
|
||||
'categories': this.data_.x,
|
||||
'lineColor': beestat.style.color.bluegray.light,
|
||||
'tickLength': 0,
|
||||
'labels': {
|
||||
'style': {
|
||||
'color': beestat.style.color.gray.base,
|
||||
'font-size': '12px'
|
||||
},
|
||||
'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': beestat.time(
|
||||
this.data_.metadata.chart.setpoint_reached_m.diff(moment(), 'second'),
|
||||
) + ' (' + this.data_.metadata.chart.setpoint_reached_m.format('h:mm a') + ')'
|
||||
},
|
||||
'value': this.data_.metadata.chart.setpoint_reached_m.diff(moment(), 'minute'),
|
||||
'zIndex': 2
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_labels_formatter_.
|
||||
*
|
||||
* @return {Function} xAxis labels formatter.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail.prototype.get_options_xAxis_labels_formatter_ = function() {
|
||||
var current_day;
|
||||
var current_time;
|
||||
|
||||
return function() {
|
||||
var day = this.value.format('ddd');
|
||||
|
||||
var time = this.value.clone();
|
||||
var minutes = time.minutes();
|
||||
var rounded_minutes = Math.round(minutes / 5) * 5;
|
||||
time.minutes(rounded_minutes).seconds(0);
|
||||
time = time.format('h:mm');
|
||||
|
||||
var label_parts = [];
|
||||
if (day !== current_day) {
|
||||
label_parts.push(day);
|
||||
}
|
||||
if (time !== current_time) {
|
||||
label_parts.push(time);
|
||||
}
|
||||
current_day = day;
|
||||
current_time = time;
|
||||
|
||||
return label_parts.join(' ');
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_series_.
|
||||
*
|
||||
* @return {Array} All of the series to display on the chart.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail.prototype.get_options_series_ = function() {
|
||||
var self = this;
|
||||
var series = [];
|
||||
|
||||
// Indoor/Outdoor Temperature
|
||||
[
|
||||
'indoor_temperature',
|
||||
'outdoor_temperature',
|
||||
].forEach(function(series_code) {
|
||||
if (self.data_.metadata.series[series_code].active === true) {
|
||||
series.push({
|
||||
'name': series_code,
|
||||
'data': self.data_.series[series_code],
|
||||
'color': beestat.series[series_code].color,
|
||||
'yAxis': (series_code === 'indoor_temperature') ? 0 : 1,
|
||||
'type': 'spline',
|
||||
'dashStyle': (series_code === 'indoor_temperature') ? 'Solid' : 'ShortDash',
|
||||
'lineWidth': (series_code === 'indoor_temperature') ? 2 : 1
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Setpoint Heat/Cool
|
||||
[
|
||||
'setpoint_heat',
|
||||
'setpoint_cool'
|
||||
].forEach(function(series_code) {
|
||||
if (self.data_.metadata.series[series_code].active === true) {
|
||||
series.push({
|
||||
'name': series_code,
|
||||
'data': self.data_.series[series_code],
|
||||
'color': beestat.series[series_code].color,
|
||||
'yAxis': 0,
|
||||
'type': 'line',
|
||||
'lineWidth': 1,
|
||||
'step': 'right',
|
||||
'className': 'crisp_edges'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_yAxis_.
|
||||
*
|
||||
* @return {Array} The y-axis options.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail.prototype.get_options_yAxis_ = function() {
|
||||
return [
|
||||
// Indoor Temperature
|
||||
{
|
||||
'gridLineColor': beestat.style.color.bluegray.light,
|
||||
'gridLineDashStyle': 'longdash',
|
||||
'allowDecimals': false,
|
||||
'title': {'text': null},
|
||||
'labels': {
|
||||
'style': {
|
||||
'color': beestat.style.color.gray.base,
|
||||
'fontSize': '11px'
|
||||
},
|
||||
'formatter': function() {
|
||||
return this.value + beestat.setting('units.temperature');
|
||||
}
|
||||
}
|
||||
},
|
||||
// Outdoor Temperature
|
||||
{
|
||||
'gridLineColor': beestat.style.color.bluegray.light,
|
||||
'opposite': true,
|
||||
'gridLineDashStyle': 'longdash',
|
||||
'allowDecimals': false,
|
||||
'title': {'text': null},
|
||||
'labels': {
|
||||
'style': {
|
||||
'color': beestat.style.color.gray.base,
|
||||
'fontSize': '11px'
|
||||
},
|
||||
'formatter': function() {
|
||||
return this.value + beestat.setting('units.temperature');
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_tooltip_formatter_.
|
||||
*
|
||||
* @return {Function} The tooltip formatter.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail.prototype.get_options_tooltip_formatter_ = function() {
|
||||
var self = this;
|
||||
|
||||
return function() {
|
||||
var points = [];
|
||||
var x = this.x;
|
||||
|
||||
var sections = [];
|
||||
var groups = {
|
||||
'data': [],
|
||||
'delta': []
|
||||
};
|
||||
|
||||
// Add some other stuff.
|
||||
[
|
||||
'indoor_temperature',
|
||||
'outdoor_temperature',
|
||||
'setpoint_heat',
|
||||
'setpoint_cool',
|
||||
'indoor_cool_1_delta',
|
||||
'indoor_cool_2_delta',
|
||||
'indoor_heat_1_delta',
|
||||
'indoor_heat_2_delta',
|
||||
'indoor_auxiliary_heat_1_delta',
|
||||
// 'indoor_auxiliary_heat_2_delta'
|
||||
].forEach(function(series_code) {
|
||||
if (
|
||||
self.data_.metadata.series[series_code].active === true
|
||||
) {
|
||||
points.push({
|
||||
'series_code': series_code,
|
||||
'value': self.data_.metadata.series[series_code].data[x.valueOf()],
|
||||
'color': beestat.series[series_code].color
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
points.forEach(function(point) {
|
||||
var label;
|
||||
var value;
|
||||
var color;
|
||||
var group;
|
||||
|
||||
if (
|
||||
point.series_code.includes('temperature') === true ||
|
||||
point.series_code.includes('setpoint') === true
|
||||
) {
|
||||
group = 'data';
|
||||
label = beestat.series[point.series_code].name;
|
||||
color = beestat.series[point.series_code].color;
|
||||
value = point.value;
|
||||
|
||||
value = beestat.temperature({
|
||||
'temperature': value,
|
||||
'input_temperature_unit': beestat.setting('units.temperature'),
|
||||
'units': true
|
||||
});
|
||||
} else if(point.series_code.includes('delta') === true) {
|
||||
group = 'delta';
|
||||
label = beestat.series[point.series_code].name;
|
||||
color = beestat.series[point.series_code].color;
|
||||
value = point.value;
|
||||
|
||||
value = beestat.temperature({
|
||||
'temperature': point.value,
|
||||
'units': true,
|
||||
'input_temperature_unit': beestat.setting('units.temperature'),
|
||||
'delta': true,
|
||||
'type': 'string'
|
||||
}) + ' / h';
|
||||
|
||||
if (point.value.toFixed(1) > 0) {
|
||||
value = '+' + value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
groups[group].push({
|
||||
'label': label,
|
||||
'value': value,
|
||||
'color': color
|
||||
});
|
||||
});
|
||||
|
||||
sections.push(groups.data);
|
||||
sections.push(groups.delta);
|
||||
|
||||
var title = this.x.format('ddd, MMM D @ h:mma');
|
||||
|
||||
return self.tooltip_formatter_helper_(
|
||||
title,
|
||||
sections
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the height of the chart.
|
||||
*
|
||||
* @return {number} The height of the chart.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail.prototype.get_options_chart_height_ = function() {
|
||||
return 350;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the top margin for the chart.
|
||||
*
|
||||
* @return {number} The top margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail.prototype.get_options_chart_marginTop_ = function() {
|
||||
return 20;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the left margin for the chart.
|
||||
*
|
||||
* @return {number} The left margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail.prototype.get_options_chart_marginLeft_ = function() {
|
||||
return 45;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the right margin for the chart.
|
||||
*
|
||||
* @return {number} The right margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.time_to_detail.prototype.get_options_chart_marginRight_ = function() {
|
||||
return 45;
|
||||
};
|
44
js/component/modal/time_to_detail.js
Normal file
44
js/component/modal/time_to_detail.js
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Current time_to_detail.
|
||||
*/
|
||||
beestat.component.modal.time_to_detail = function() {
|
||||
var self = this;
|
||||
|
||||
beestat.dispatcher.addEventListener(
|
||||
'cache.thermostat',
|
||||
function() {
|
||||
self.rerender();
|
||||
}
|
||||
);
|
||||
|
||||
beestat.component.modal.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.modal.time_to_detail, beestat.component.modal);
|
||||
|
||||
/**
|
||||
* Decorate
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.time_to_detail.prototype.decorate_contents_ = function(parent) {
|
||||
new beestat.component.chart.time_to_detail(
|
||||
beestat.time_to_detail.get_data(beestat.setting('thermostat_id'))
|
||||
).render(parent);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the title of the chart.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
beestat.component.modal.time_to_detail.prototype.get_title_ = function() {
|
||||
const thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
||||
const operating_mode = beestat.thermostat.get_operating_mode(
|
||||
thermostat.thermostat_id
|
||||
);
|
||||
|
||||
// Convert "heat_1" etc to "heat"
|
||||
const simplified_operating_mode = operating_mode.replace(/[_\d]|auxiliary/g, '');
|
||||
|
||||
return 'Time to ' + simplified_operating_mode;
|
||||
};
|
@ -56,6 +56,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
||||
echo '<script src="/js/beestat/math.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/beestat/platform.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/beestat/text_dimensions.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/beestat/time_to_detail.js"></script>' . PHP_EOL;
|
||||
|
||||
// Layer
|
||||
echo '<script src="/js/layer.js"></script>' . PHP_EOL;
|
||||
@ -112,6 +113,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
||||
echo '<script src="/js/component/chart/voc_concentration.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/chart/co2_concentration.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/chart/air_quality.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/chart/time_to_detail.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/header.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/icon.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/layout.js"></script>' . PHP_EOL;
|
||||
@ -144,6 +146,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
||||
echo '<script src="/js/component/modal/floor_plan_elevation_help.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/modal/visualize_custom.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/modal/temperature_profiles_info.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/modal/time_to_detail.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/input.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/input/text.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/input/checkbox.js"></script>' . PHP_EOL;
|
||||
|
Loading…
x
Reference in New Issue
Block a user