1
0
mirror of https://github.com/beestat/app.git synced 2025-05-24 02:14:03 -04:00

Fixed #165 - Convert Temperature Profiles to chart2

Also fixed #173 - Runtime Summary - No title/subtitle or filename on export.
This commit is contained in:
Jon Ziebell 2019-10-24 21:41:19 -04:00
parent 9992abc592
commit 43e547198b
11 changed files with 455 additions and 253 deletions

View File

@ -29,6 +29,9 @@ class thermostat_group extends cora\crud {
public static $converged = [
'temperature_profile' => [
'type' => 'json'
],
'weather' => [
'type' => 'json'
]
];
@ -314,7 +317,8 @@ class thermostat_group extends cora\crud {
'property_age',
'property_square_feet',
'property_stories',
'property_structure_type'
'property_structure_type',
'weather'
];
$thermostats = $this->api(
@ -377,6 +381,11 @@ class thermostat_group extends cora\crud {
$final_attributes[$attribute] = $system_type;
}
break;
default:
// Stuff that doesn't really matter (weather); just pick the last
// one.
$final_attributes[$attribute] = $thermostat[$attribute];
break;
}
}
}

View File

@ -21,7 +21,9 @@ beestat.setting = function(key, opt_value, opt_callback) {
'runtime_thermostat_summary_gap_fill': true,
'comparison_region': 'global',
'comparison_property_type': 'similar'
'comparison_property_type': 'similar',
'temperature_unit': '°F'
};
if (user.json_settings === null) {

View File

@ -279,3 +279,22 @@ beestat.series.calendar_event_other = {
'name': 'Other',
'color': beestat.style.color.gray.base
};
// Temperature Profiles
beestat.series.indoor_heat_delta = {
'name': 'Indoor Heat Δ',
'color': beestat.series.compressor_heat_1.color
};
beestat.series.indoor_heat_delta_raw = beestat.series.indoor_heat_delta;
beestat.series.indoor_cool_delta = {
'name': 'Indoor Cool Δ',
'color': beestat.series.compressor_cool_1.color
};
beestat.series.indoor_cool_delta_raw = beestat.series.indoor_cool_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;

View File

@ -50,10 +50,7 @@ beestat.extend(beestat.component.card.runtime_thermostat_summary, beestat.compon
*/
beestat.component.card.runtime_thermostat_summary.prototype.decorate_contents_ = function(parent) {
var data = this.get_data_();
this.chart_ = new beestat.component.chart2.runtime_thermostat_summary(
this.thermostat_id_,
data
);
this.chart_ = new beestat.component.chart2.runtime_thermostat_summary(data);
this.chart_.render(parent);
var sync_progress = beestat.get_sync_progress(this.thermostat_id_);
@ -117,7 +114,11 @@ beestat.component.card.runtime_thermostat_summary.prototype.get_data_ = function
'x': [],
'series': {},
'metadata': {
'series': {}
'series': {},
'chart': {
'title': this.get_title_(),
'subtitle': this.get_subtitle_()
}
}
};

View File

@ -1,7 +1,12 @@
/**
* Temperature profiles.
*
* @param {number} thermostat_group_id The thermostat_group_id this card is
* displaying data for.
*/
beestat.component.card.temperature_profiles = function() {
beestat.component.card.temperature_profiles = function(thermostat_group_id) {
this.thermostat_group_id_ = thermostat_group_id;
beestat.component.card.apply(this, arguments);
};
beestat.extend(beestat.component.card.temperature_profiles, beestat.component.card);
@ -12,15 +17,36 @@ beestat.extend(beestat.component.card.temperature_profiles, beestat.component.ca
* @param {rocket.Elements} parent
*/
beestat.component.card.temperature_profiles.prototype.decorate_contents_ = function(parent) {
var self = this;
var data = this.get_data_();
this.chart_ = new beestat.component.chart2.temperature_profiles(data);
this.chart_.render(parent);
};
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
/**
* Get all of the series data.
*
* @return {object} The series data.
*/
beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
var thermostat_group = beestat.cache.thermostat_group[
thermostat.thermostat_group_id
this.thermostat_group_id_
];
this.chart_ = new beestat.component.chart();
this.chart_.options.chart.height = 300;
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.temperature_profile === null
@ -28,10 +54,6 @@ beestat.component.card.temperature_profiles.prototype.decorate_contents_ = funct
this.chart_.render(parent);
this.show_loading_('Calculating');
} else {
// var x_categories = [];
var trendlines = {};
var raw = {};
// Global x range.
var x_min = Infinity;
var x_max = -Infinity;
@ -69,8 +91,8 @@ beestat.component.card.temperature_profiles.prototype.decorate_contents_ = funct
x_min = Math.min(x_min, this_x_min);
x_max = Math.max(x_max, this_x_max);
trendlines[type] = [];
raw[type] = [];
data.series['trendline_' + type] = [];
data.series['raw_' + type] = [];
/**
* Data is stored internally as °F with 1 value per degree. That data
@ -87,7 +109,7 @@ beestat.component.card.temperature_profiles.prototype.decorate_contents_ = funct
*/
var increment;
var fixed;
if (thermostat.temperature_unit === '°F') {
if (beestat.setting('temperature_unit') === '°F') {
increment = 1;
fixed = 0;
} else {
@ -99,240 +121,27 @@ beestat.component.card.temperature_profiles.prototype.decorate_contents_ = funct
var y = (linear_trendline.slope * x_fixed) +
linear_trendline.intercept;
trendlines[type].push([
data.series['trendline_' + type].push([
parseFloat(x_fixed),
y
]);
if (profile.deltas[x_fixed] !== undefined) {
raw[type].push([
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;
}
}
}
/*
* Set y_min and y_max to be equal but opposite so the graph is always
* centered.
*/
var absolute_y_max = Math.max(Math.abs(y_min), Math.abs(y_max));
y_min = absolute_y_max * -1;
y_max = absolute_y_max;
// Chart
this.chart_.options.exporting.chartOptions.title.text = this.get_title_();
this.chart_.options.exporting.chartOptions.subtitle.text = this.get_subtitle_();
this.chart_.options.chart.backgroundColor = beestat.style.color.bluegray.base;
this.chart_.options.exporting.filename = 'Temperature Profiles';
this.chart_.options.chart.zoomType = null;
this.chart_.options.plotOptions.series.connectNulls = true;
this.chart_.options.legend = {'enabled': false};
this.chart_.options.xAxis = {
'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': function() {
return this.value + thermostat.temperature_unit;
}
},
'plotLines': [
{
'color': beestat.series.outdoor_temperature.color,
'dashStyle': 'ShortDash',
'width': 1,
'value': beestat.temperature(thermostat.weather.temperature),
'zIndex': 2
}
]
};
this.chart_.options.yAxis = [
{
'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 + thermostat.temperature_unit;
}
},
'min': y_min,
'max': y_max,
'plotLines': [
{
'color': beestat.style.color.bluegray.light,
'dashStyle': 'solid',
'width': 3,
'value': 0,
'zIndex': 1
}
]
}
];
this.chart_.options.tooltip = {
'shared': true,
'useHTML': true,
'borderWidth': 0,
'shadow': false,
'backgroundColor': null,
'followPointer': true,
'crosshairs': {
'width': 1,
'zIndex': 100,
'color': beestat.style.color.gray.light,
'dashStyle': 'shortDot',
'snap': false
},
'positioner': function(tooltip_width, tooltip_height, point) {
return beestat.component.chart.tooltip_positioner(
self.chart_.get_chart(),
tooltip_width,
tooltip_height,
point
);
},
'formatter': 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'
}) + ' / hour';
if (series.name.indexOf('Raw') === -1) {
section.push({
'label': series.name,
'value': value,
'color': series.color
});
}
});
sections.push(section);
return beestat.component.chart.tooltip_formatter(
'Outdoor Temp: ' +
beestat.temperature({
'temperature': this.x,
'round': 0,
'units': true,
'convert': false
}),
sections
);
}
};
this.chart_.options.series = [];
// Trendline data
this.chart_.options.series.push({
'data': trendlines.heat,
'name': 'Indoor Heat Δ',
'color': beestat.series.compressor_heat_1.color,
'marker': {
'enabled': false,
'states': {'hover': {'enabled': false}}
},
'type': 'line',
'lineWidth': 2,
'states': {'hover': {'lineWidthPlus': 0}}
});
// Trendline data
this.chart_.options.series.push({
'data': trendlines.cool,
'name': 'Indoor Cool Δ',
'color': beestat.series.compressor_cool_1.color,
'marker': {
'enabled': false,
'states': {'hover': {'enabled': false}}
},
'type': 'line',
'lineWidth': 2,
'states': {'hover': {'lineWidthPlus': 0}}
});
// Trendline data
this.chart_.options.series.push({
'data': trendlines.resist,
'name': 'Indoor Δ',
'color': beestat.style.color.gray.dark,
'marker': {
'enabled': false,
'states': {'hover': {'enabled': false}}
},
'type': 'line',
'lineWidth': 2,
'states': {'hover': {'lineWidthPlus': 0}}
});
// Raw data
this.chart_.options.series.push({
'data': raw.heat,
'name': 'Heat Raw',
'color': beestat.series.compressor_heat_1.color,
'dashStyle': 'ShortDot',
'marker': {
'enabled': false,
'states': {'hover': {'enabled': false}}
},
'type': 'spline',
'lineWidth': 1,
'states': {'hover': {'lineWidthPlus': 0}}
});
// Raw data
this.chart_.options.series.push({
'data': raw.cool,
'name': 'Cool Raw',
'color': beestat.series.compressor_cool_1.color,
'dashStyle': 'ShortDot',
'marker': {
'enabled': false,
'states': {'hover': {'enabled': false}}
},
'type': 'spline',
'lineWidth': 1,
'states': {'hover': {'lineWidthPlus': 0}}
});
// Raw data
this.chart_.options.series.push({
'data': raw.resist,
'name': 'Resist Raw',
'color': beestat.style.color.gray.dark,
'dashStyle': 'ShortDot',
'marker': {
'enabled': false,
'states': {'hover': {'enabled': false}}
},
'type': 'spline',
'lineWidth': 1,
'states': {'hover': {'lineWidthPlus': 0}}
});
this.chart_.render(parent);
}
return data;
};
/**

View File

@ -1,14 +1,12 @@
/**
* Runtime thermostat summary chart.
*
* @param {number} thermostat_id The thermostat_id this chart is showing data
* for.
* @param {object} data The chart data.
*/
beestat.component.chart2.runtime_thermostat_summary = function(thermostat_id, data) {
beestat.component.chart2.apply(this, arguments);
this.thermostat_id_ = thermostat_id;
beestat.component.chart2.runtime_thermostat_summary = function(data) {
this.data_ = data;
beestat.component.chart2.apply(this, arguments);
};
beestat.extend(beestat.component.chart2.runtime_thermostat_summary, beestat.component.chart2);
@ -189,7 +187,7 @@ beestat.component.chart2.runtime_thermostat_summary.prototype.get_options_yAxis_
'color': beestat.style.color.gray.base
},
'formatter': function() {
return this.value + beestat.cache.thermostat[self.thermostat_id_].temperature_unit;
return this.value + beestat.setting('temperature_unit');
}
}
}

View File

@ -0,0 +1,287 @@
/**
* Temperature profiles chart.
*
* @param {object} data The chart data.
*/
beestat.component.chart2.temperature_profiles = function(data) {
this.data_ = data;
beestat.component.chart2.apply(this, arguments);
};
beestat.extend(beestat.component.chart2.temperature_profiles, beestat.component.chart2);
/**
* Override for get_options_xAxis_labels_formatter_.
*
* @return {Function} xAxis labels formatter.
*/
beestat.component.chart2.temperature_profiles.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.chart2.temperature_profiles.prototype.get_options_series_ = function() {
var series = [];
// Trendline data
series.push({
'data': this.data_.series.trendline_heat,
'name': 'indoor_heat_delta',
'color': beestat.series.compressor_heat_1.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,
'name': 'indoor_cool_delta',
'color': beestat.series.compressor_cool_1.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.style.color.gray.dark,
'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,
'name': 'indoor_heat_delta_raw',
'color': beestat.series.compressor_heat_1.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,
'name': 'indoor_cool_delta_raw',
'color': beestat.series.compressor_cool_1.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.style.color.gray.dark,
'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.chart2.temperature_profiles.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.chart2.temperature_profiles.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.chart2.temperature_profiles.prototype.get_options_chart_zoomType_ = function() {
return null;
};
/**
* Override for get_options_legend_.
*
* @return {object} The legend options.
*/
beestat.component.chart2.temperature_profiles.prototype.get_options_legend_ = function() {
return {
'enabled': false
};
};
/**
* Override for get_options_xAxis_.
*
* @return {object} The xAxis options.
*/
beestat.component.chart2.temperature_profiles.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_()
},
'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.chart2.temperature_profiles.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.chart2.temperature_profiles.prototype.get_options_plotOptions_series_connectNulls_ = function() {
return true;
};

View File

@ -105,7 +105,8 @@ beestat.component.chart2.prototype.get_options_plotOptions_ = function() {
'inactive': {
'opacity': 1
}
}
},
'connectNulls': this.get_options_plotOptions_series_connectNulls_()
},
'column': {
'pointPadding': 0,
@ -118,6 +119,15 @@ beestat.component.chart2.prototype.get_options_plotOptions_ = function() {
};
};
/**
* Get whether or not to connect nulls.
*
* @return {boolean} Whether or not to connect nulls.
*/
beestat.component.chart2.prototype.get_options_plotOptions_series_connectNulls_ = function() {
return false;
};
/**
* Get the title options.
*
@ -156,7 +166,7 @@ beestat.component.chart2.prototype.get_options_chart_ = function() {
0,
0
],
'zoomType': 'x',
'zoomType': this.get_options_chart_zoomType_(),
'panning': true,
'panKey': 'ctrl',
'backgroundColor': beestat.style.color.bluegray.base,
@ -164,10 +174,29 @@ beestat.component.chart2.prototype.get_options_chart_ = function() {
'theme': {
'display': 'none'
}
}
},
'height': this.get_options_chart_height_()
};
};
/**
* Get the height of the chart.
*
* @return {number} The height of the chart.
*/
beestat.component.chart2.prototype.get_options_chart_height_ = function() {
return null;
};
/**
* Get the zoomType option. Return null for no zoom.
*
* @return {string} The zoom type.
*/
beestat.component.chart2.prototype.get_options_chart_zoomType_ = function() {
return 'x';
};
/**
* Get the export options.
*
@ -178,14 +207,14 @@ beestat.component.chart2.prototype.get_options_exporting_ = function() {
'enabled': false,
'sourceWidth': 980,
'scale': 1,
'filename': 'beestat',
'filename': this.get_options_exporting_filename_(),
'chartOptions': {
'credits': {
'text': 'beestat.io'
},
'title': {
'align': 'left',
'text': null,
'text': this.get_options_exporting_chartOptions_title_text_(),
'margin': beestat.style.size.gutter,
'style': {
'color': '#fff',
@ -195,7 +224,7 @@ beestat.component.chart2.prototype.get_options_exporting_ = function() {
},
'subtitle': {
'align': 'left',
'text': null,
'text': this.get_options_exporting_chartOptions_subtitle_text_(),
'style': {
'color': '#fff',
'font-weight': beestat.style.font_weight.light,
@ -217,6 +246,50 @@ beestat.component.chart2.prototype.get_options_exporting_ = function() {
};
};
/**
* Get the exported chart title.
*
* @return {string} The exported chart title.
*/
beestat.component.chart2.prototype.get_options_exporting_chartOptions_title_text_ = function() {
return this.data_.metadata.chart.title;
};
/**
* Get the exported chart subtitle.
*
* @return {string} The exported chart subtitle.
*/
beestat.component.chart2.prototype.get_options_exporting_chartOptions_subtitle_text_ = function() {
return this.data_.metadata.chart.subtitle;
};
/**
* Get the exported chart filename.
*
* @return {string} The exported chart filename.
*/
beestat.component.chart2.prototype.get_options_exporting_filename_ = function() {
var title = this.get_options_exporting_chartOptions_title_text_();
var subtitle = this.get_options_exporting_chartOptions_subtitle_text_();
var filename = [];
if (title !== null) {
filename.push(title);
}
if (subtitle !== null) {
filename.push('-');
filename.push(subtitle);
}
if (filename.length === 0) {
filename.push('beestat');
}
return filename.join(' ');
};
/**
* Get the credits options.
*

View File

@ -61,6 +61,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/component/chart.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart2.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart/runtime_thermostat_summary.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart/temperature_profiles.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;

View File

@ -82,7 +82,7 @@ beestat.layer.home_comparisons.prototype.decorate_ = function(parent) {
cards.push([
{
'card': new beestat.component.card.temperature_profiles(),
'card': new beestat.component.card.temperature_profiles(thermostat_group.thermostat_group_id),
'size': 12
}
]);

View File

@ -175,6 +175,9 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
thermostat.ecobee_thermostat_id
];
// Set the active temperature unit.
beestat.setting('temperature_unit', thermostat.temperature_unit);
// Rename series if only one stage is available.
if (ecobee_thermostat.json_settings.coolStages === 1) {
beestat.series.sum_compressor_cool_1.name = 'Cool';