mirror of
https://github.com/beestat/app.git
synced 2025-07-09 03:04:07 -04:00
Added air quality for Patrons
This commit is contained in:
parent
91945d7d8e
commit
2da6822ce2
@ -776,10 +776,31 @@ class runtime extends cora\api {
|
||||
foreach($sensor['capability'] as $capability) {
|
||||
if(
|
||||
$capability['id'] == $capability_identifier &&
|
||||
in_array($capability['type'], ['temperature', 'occupancy']) === true &&
|
||||
$value !== null
|
||||
) {
|
||||
$datas[$sensor['sensor_id']][$capability['type']] = ($capability['type'] === 'temperature') ? ($value * 10) : $value;
|
||||
switch($capability['type']) {
|
||||
case 'temperature':
|
||||
$datas[$sensor['sensor_id']]['temperature'] = ($value * 10);
|
||||
break;
|
||||
case 'occupancy':
|
||||
$datas[$sensor['sensor_id']]['occupancy'] = $value;
|
||||
break;
|
||||
case 'airPressure':
|
||||
$datas[$sensor['sensor_id']]['air_pressure'] = $value;
|
||||
break;
|
||||
case 'airQuality':
|
||||
$datas[$sensor['sensor_id']]['air_quality'] = $value;
|
||||
break;
|
||||
case 'airQualityAccuracy':
|
||||
$datas[$sensor['sensor_id']]['air_quality_accuracy'] = $value;
|
||||
break;
|
||||
case 'vocPPM':
|
||||
$datas[$sensor['sensor_id']]['voc_concentration'] = $value;
|
||||
break;
|
||||
case 'co2PPM':
|
||||
$datas[$sensor['sensor_id']]['co2_concentration'] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -422,6 +422,7 @@ input[type=checkbox] {
|
||||
.icon.network_strength_4:before { content: "\F08FA"; }
|
||||
.icon.network_strength_off:before { content: "\F08FC"; }
|
||||
.icon.numeric_1_box:before { content: "\F03A4"; }
|
||||
.icon.numeric_4_box:before { content: "\F03AD"; }
|
||||
.icon.numeric_3_box:before { content: "\F03AA"; }
|
||||
.icon.numeric_7_box:before { content: "\F03B6"; }
|
||||
.icon.patreon:before { content: "\F0882"; }
|
||||
|
@ -61,6 +61,7 @@ window.addEventListener('resize', rocket.throttle(100, function() {
|
||||
var breakpoints = [
|
||||
600,
|
||||
650,
|
||||
800,
|
||||
1000
|
||||
];
|
||||
|
||||
|
@ -6,10 +6,13 @@ beestat.runtime_sensor = {};
|
||||
*
|
||||
* @param {number} thermostat_id The thermostat_id to get data for.
|
||||
* @param {object} range Range settings.
|
||||
* @param {string} key The key to pull the data from inside
|
||||
* beestat.cache.data. This exists because runtime_sensor data exists in
|
||||
* multiple spots.
|
||||
*
|
||||
* @return {object} The data.
|
||||
*/
|
||||
beestat.runtime_sensor.get_data = function(thermostat_id, range) {
|
||||
beestat.runtime_sensor.get_data = function(thermostat_id, range, key) {
|
||||
var data = {
|
||||
'x': [],
|
||||
'series': {},
|
||||
@ -21,6 +24,33 @@ beestat.runtime_sensor.get_data = function(thermostat_id, range) {
|
||||
}
|
||||
};
|
||||
|
||||
var colors = [
|
||||
beestat.style.color.blue.base,
|
||||
beestat.style.color.red.base,
|
||||
beestat.style.color.yellow.base,
|
||||
beestat.style.color.green.base,
|
||||
beestat.style.color.orange.base,
|
||||
beestat.style.color.bluegreen.base,
|
||||
beestat.style.color.purple.base,
|
||||
beestat.style.color.lightblue.base,
|
||||
beestat.style.color.blue.light,
|
||||
beestat.style.color.red.light,
|
||||
beestat.style.color.yellow.light,
|
||||
beestat.style.color.green.light,
|
||||
beestat.style.color.orange.light,
|
||||
beestat.style.color.bluegreen.light,
|
||||
beestat.style.color.purple.light,
|
||||
beestat.style.color.lightblue.light,
|
||||
beestat.style.color.blue.dark,
|
||||
beestat.style.color.red.dark,
|
||||
beestat.style.color.yellow.dark,
|
||||
beestat.style.color.green.dark,
|
||||
beestat.style.color.orange.dark,
|
||||
beestat.style.color.bluegreen.dark,
|
||||
beestat.style.color.purple.dark,
|
||||
beestat.style.color.lightblue.dark
|
||||
];
|
||||
|
||||
// Duration objects. These are passed by reference into the metadata.
|
||||
var durations = {};
|
||||
|
||||
@ -31,10 +61,19 @@ beestat.runtime_sensor.get_data = function(thermostat_id, range) {
|
||||
data.metadata.sensors = sensors;
|
||||
|
||||
// Set up the series_codes.
|
||||
sensors.forEach(function(sensor) {
|
||||
const sensor_series_colors = {};
|
||||
sensors.forEach(function(sensor, i) {
|
||||
if (sensor.thermostat_id === thermostat_id) {
|
||||
series_codes.push('temperature_' + sensor.sensor_id);
|
||||
series_codes.push('occupancy_' + sensor.sensor_id);
|
||||
|
||||
sensor_series_colors[sensor.sensor_id] = colors[i];
|
||||
|
||||
if (sensor.type === 'thermostat') {
|
||||
series_codes.push('air_quality_' + sensor.sensor_id);
|
||||
series_codes.push('voc_concentration_' + sensor.sensor_id);
|
||||
series_codes.push('co2_concentration_' + sensor.sensor_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -53,9 +92,16 @@ beestat.runtime_sensor.get_data = function(thermostat_id, range) {
|
||||
};
|
||||
if (series_code === 'dummy') {
|
||||
data.metadata.series[series_code].name = null;
|
||||
} else if (series_code.includes('air_quality_') === true) {
|
||||
data.metadata.series[series_code].name = 'AQ';
|
||||
} else if (series_code.includes('voc_concentration_') === true) {
|
||||
data.metadata.series[series_code].name = 'TVOC';
|
||||
} else if (series_code.includes('co2_concentration_') === true) {
|
||||
data.metadata.series[series_code].name = 'CO2';
|
||||
} else {
|
||||
var sensor_id = series_code.replace(/[^0-9]/g, '');
|
||||
data.metadata.series[series_code].name = beestat.cache.sensor[sensor_id].name;
|
||||
data.metadata.series[series_code].color = sensor_series_colors[sensor_id];
|
||||
}
|
||||
|
||||
durations[series_code] = {'seconds': 0};
|
||||
@ -83,7 +129,7 @@ beestat.runtime_sensor.get_data = function(thermostat_id, range) {
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
|
||||
var runtime_sensors = beestat.runtime_sensor.get_runtime_sensors_by_date_();
|
||||
var runtime_sensors = beestat.runtime_sensor.get_runtime_sensors_by_date_(key);
|
||||
|
||||
// Loop.
|
||||
var current_m = begin_m;
|
||||
@ -100,6 +146,12 @@ beestat.runtime_sensor.get_data = function(thermostat_id, range) {
|
||||
if (runtime_sensor === undefined) {
|
||||
data.series['temperature_' + sensor.sensor_id].push(null);
|
||||
data.series['occupancy_' + sensor.sensor_id].push(null);
|
||||
|
||||
if (sensor.type === 'thermostat') {
|
||||
data.series['air_quality_' + sensor.sensor_id].push(null);
|
||||
data.series['voc_concentration_' + sensor.sensor_id].push(null);
|
||||
data.series['co2_concentration_' + sensor.sensor_id].push(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -108,6 +160,20 @@ beestat.runtime_sensor.get_data = function(thermostat_id, range) {
|
||||
data.metadata.series['temperature_' + runtime_sensor.sensor_id].active = true;
|
||||
data.metadata.series['temperature_' + runtime_sensor.sensor_id].data[current_m.valueOf()] = temperature;
|
||||
|
||||
if (sensor.type === 'thermostat') {
|
||||
data.series['air_quality_' + runtime_sensor.sensor_id].push(runtime_sensor.air_quality);
|
||||
data.metadata.series['air_quality_' + runtime_sensor.sensor_id].active = true;
|
||||
data.metadata.series['air_quality_' + runtime_sensor.sensor_id].data[current_m.valueOf()] = runtime_sensor.air_quality;
|
||||
|
||||
data.series['voc_concentration_' + runtime_sensor.sensor_id].push(runtime_sensor.voc_concentration);
|
||||
data.metadata.series['voc_concentration_' + runtime_sensor.sensor_id].active = true;
|
||||
data.metadata.series['voc_concentration_' + runtime_sensor.sensor_id].data[current_m.valueOf()] = runtime_sensor.voc_concentration;
|
||||
|
||||
data.series['co2_concentration_' + runtime_sensor.sensor_id].push(runtime_sensor.co2_concentration);
|
||||
data.metadata.series['co2_concentration_' + runtime_sensor.sensor_id].active = true;
|
||||
data.metadata.series['co2_concentration_' + runtime_sensor.sensor_id].data[current_m.valueOf()] = runtime_sensor.co2_concentration;
|
||||
}
|
||||
|
||||
if (runtime_sensor.occupancy === true) {
|
||||
let swimlane_properties =
|
||||
beestat.component.chart.runtime_sensor_detail_occupancy.get_swimlane_properties(
|
||||
@ -139,6 +205,12 @@ beestat.runtime_sensor.get_data = function(thermostat_id, range) {
|
||||
if (sensor.thermostat_id === thermostat_id) {
|
||||
data.series['temperature_' + sensor.sensor_id].push(null);
|
||||
data.series['occupancy_' + sensor.sensor_id].push(null);
|
||||
|
||||
if (sensor.type === 'thermostat') {
|
||||
data.series['air_quality_' + sensor.sensor_id].push(null);
|
||||
data.series['voc_concentration_' + sensor.sensor_id].push(null);
|
||||
data.series['co2_concentration_' + sensor.sensor_id].push(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -152,12 +224,16 @@ beestat.runtime_sensor.get_data = function(thermostat_id, range) {
|
||||
/**
|
||||
* Get all the runtime_sensor rows indexed by date.
|
||||
*
|
||||
* @param {string} key The key to pull the data from inside
|
||||
* beestat.cache.data. This exists because runtime_sensor data exists in
|
||||
* multiple spots.
|
||||
*
|
||||
* @return {array} The runtime_sensor rows.
|
||||
*/
|
||||
beestat.runtime_sensor.get_runtime_sensors_by_date_ = function() {
|
||||
beestat.runtime_sensor.get_runtime_sensors_by_date_ = function(key) {
|
||||
var runtime_sensors = {};
|
||||
if (beestat.cache.runtime_sensor !== undefined) {
|
||||
beestat.cache.runtime_sensor.forEach(function(runtime_sensor) {
|
||||
if (beestat.cache.data[key] !== undefined) {
|
||||
beestat.cache.data[key].forEach(function(runtime_sensor) {
|
||||
var timestamp = [moment(runtime_sensor.timestamp).valueOf()];
|
||||
if (runtime_sensors[timestamp] === undefined) {
|
||||
runtime_sensors[timestamp] = {};
|
||||
|
@ -39,6 +39,20 @@ beestat.setting = function(argument_1, opt_value, opt_callback) {
|
||||
'runtime_sensor_detail_range_static_end': moment().format('MM/DD/YYYY'),
|
||||
'runtime_sensor_detail_range_dynamic': 3,
|
||||
|
||||
'air_quality_detail_range_type': 'dynamic',
|
||||
'air_quality_detail_range_static_begin': moment()
|
||||
.subtract(3, 'day')
|
||||
.format('MM/DD/YYYY'),
|
||||
'air_quality_detail_range_static_end': moment().format('MM/DD/YYYY'),
|
||||
'air_quality_detail_range_dynamic': 3,
|
||||
|
||||
'voc_summary_range_type': 'dynamic',
|
||||
'voc_summary_range_static_begin': moment()
|
||||
.subtract(28, 'day')
|
||||
.format('MM/DD/YYYY'),
|
||||
'voc_summary_range_static_end': moment().format('MM/DD/YYYY'),
|
||||
'voc_summary_range_dynamic': 30,
|
||||
|
||||
'runtime_thermostat_summary_time_count': 0,
|
||||
'runtime_thermostat_summary_time_period': 'all',
|
||||
'runtime_thermostat_summary_group_by': 'month',
|
||||
|
@ -323,3 +323,19 @@ beestat.series.indoor_resist_delta = {
|
||||
'color': beestat.style.color.gray.dark
|
||||
};
|
||||
beestat.series.indoor_resist_delta_raw = beestat.series.indoor_resist_delta;
|
||||
|
||||
// Air Quality
|
||||
beestat.series.air_quality = {
|
||||
'name': 'Air Quality',
|
||||
'color': beestat.style.color.gray.base
|
||||
};
|
||||
|
||||
beestat.series.voc_concentration = {
|
||||
'name': 'TVOC',
|
||||
'color': beestat.style.color.yellow.dark
|
||||
};
|
||||
|
||||
beestat.series.co2_concentration = {
|
||||
'name': 'CO₂',
|
||||
'color': beestat.style.color.blue.base
|
||||
};
|
||||
|
411
js/component/card/air_quality_detail.js
Normal file
411
js/component/card/air_quality_detail.js
Normal file
@ -0,0 +1,411 @@
|
||||
/**
|
||||
* Air Quality card. Shows a chart with comfort profiles, occupancy, and air
|
||||
* quality data.
|
||||
*
|
||||
* @param {number} thermostat_id The thermostat_id this card is displaying
|
||||
* data for
|
||||
*/
|
||||
beestat.component.card.air_quality_detail = function(thermostat_id) {
|
||||
var self = this;
|
||||
|
||||
this.thermostat_id_ = thermostat_id;
|
||||
|
||||
/*
|
||||
* When a setting is changed clear all of the data. Then rerender which will
|
||||
* trigger the loading state. Also do this when the cache changes.
|
||||
*
|
||||
* 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 change_function = beestat.debounce(function() {
|
||||
self.get_data_(true);
|
||||
self.rerender();
|
||||
}, 10);
|
||||
|
||||
beestat.dispatcher.addEventListener(
|
||||
[
|
||||
'setting.air_quality_detail_range_type',
|
||||
'setting.air_quality_detail_range_dynamic',
|
||||
'cache.data.air_quality_detail__runtime_thermostat',
|
||||
'cache.data.air_quality_detail__runtime_sensor'
|
||||
],
|
||||
change_function
|
||||
);
|
||||
|
||||
beestat.component.card.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.card.air_quality_detail, beestat.component.card);
|
||||
|
||||
/**
|
||||
* Decorate
|
||||
*
|
||||
* @param {rocket.ELements} parent
|
||||
*/
|
||||
beestat.component.card.air_quality_detail.prototype.decorate_contents_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
this.charts_ = {
|
||||
'occupancy': new beestat.component.chart.runtime_sensor_detail_occupancy(
|
||||
this.get_data_()
|
||||
),
|
||||
'air_quality': new beestat.component.chart.air_quality(
|
||||
this.get_data_()
|
||||
),
|
||||
'voc_concentration': new beestat.component.chart.voc_concentration(
|
||||
this.get_data_()
|
||||
),
|
||||
'co2_concentration': new beestat.component.chart.co2_concentration(
|
||||
this.get_data_()
|
||||
)
|
||||
};
|
||||
|
||||
var container = $.createElement('div').style({
|
||||
'position': 'relative'
|
||||
});
|
||||
parent.appendChild(container);
|
||||
|
||||
var chart_container = $.createElement('div');
|
||||
container.appendChild(chart_container);
|
||||
|
||||
this.charts_.occupancy.render(chart_container);
|
||||
|
||||
chart_container.appendChild($.createElement('p').innerText('Air Quality'));
|
||||
this.charts_.air_quality.render(chart_container);
|
||||
|
||||
chart_container.appendChild($.createElement('p').innerText('TVOC Concentration'));
|
||||
this.charts_.voc_concentration.render(chart_container);
|
||||
|
||||
chart_container.appendChild($.createElement('p').innerText('CO₂ Concentration'));
|
||||
this.charts_.co2_concentration.render(chart_container);
|
||||
|
||||
// this.charts_.x_axis.render(chart_container);
|
||||
|
||||
// Sync extremes and crosshair.
|
||||
Object.values(this.charts_).forEach(function(source_chart) {
|
||||
Object.values(self.charts_).forEach(function(target_chart) {
|
||||
target_chart.sync_extremes(source_chart);
|
||||
target_chart.sync_crosshair(source_chart);
|
||||
});
|
||||
});
|
||||
|
||||
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||
|
||||
var required_begin;
|
||||
var required_end;
|
||||
if (beestat.setting('air_quality_detail_range_type') === 'dynamic') {
|
||||
required_begin = moment()
|
||||
.subtract(
|
||||
beestat.setting('air_quality_detail_range_dynamic'),
|
||||
'day'
|
||||
)
|
||||
.second(0);
|
||||
|
||||
required_end = moment()
|
||||
.subtract(1, 'hour')
|
||||
.second(0);
|
||||
} else {
|
||||
required_begin = moment(
|
||||
beestat.setting('air_quality_detail_range_static_begin') + ' 00:00:00'
|
||||
);
|
||||
required_end = moment(
|
||||
beestat.setting('air_quality_detail_range_static_end') + ' 23:59:59'
|
||||
);
|
||||
}
|
||||
|
||||
// Don't go before there's data.
|
||||
required_begin = moment.max(
|
||||
required_begin,
|
||||
moment.utc(thermostat.first_connected)
|
||||
);
|
||||
|
||||
// Don't go after now.
|
||||
required_end = moment.min(
|
||||
required_end,
|
||||
moment().subtract(1, 'hour')
|
||||
);
|
||||
|
||||
/**
|
||||
* If the needed data exists in the database and the runtime_sensor
|
||||
* cache is empty, then query the data. If the needed data does not exist in
|
||||
* the database, check every 2 seconds until it does.
|
||||
*/
|
||||
if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) {
|
||||
if (beestat.cache.data.air_quality_detail__runtime_sensor === undefined) {
|
||||
this.show_loading_('Fetching');
|
||||
|
||||
var value;
|
||||
var operator;
|
||||
|
||||
if (beestat.setting('air_quality_detail_range_type') === 'dynamic') {
|
||||
value = required_begin.format();
|
||||
operator = '>=';
|
||||
} else {
|
||||
value = [
|
||||
required_begin.format(),
|
||||
required_end.format()
|
||||
];
|
||||
operator = 'between';
|
||||
}
|
||||
|
||||
var api_call = new beestat.api();
|
||||
beestat.sensor.get_sorted().forEach(function(sensor) {
|
||||
if (sensor.thermostat_id === self.thermostat_id_) {
|
||||
api_call.add_call(
|
||||
'runtime_sensor',
|
||||
'read',
|
||||
{
|
||||
'attributes': {
|
||||
'sensor_id': sensor.sensor_id,
|
||||
'timestamp': {
|
||||
'value': value,
|
||||
'operator': operator
|
||||
}
|
||||
}
|
||||
},
|
||||
'runtime_sensor_' + sensor.sensor_id
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
api_call.add_call(
|
||||
'runtime_thermostat',
|
||||
'read',
|
||||
{
|
||||
'attributes': {
|
||||
'thermostat_id': thermostat.thermostat_id,
|
||||
'timestamp': {
|
||||
'value': value,
|
||||
'operator': operator
|
||||
}
|
||||
}
|
||||
},
|
||||
'runtime_thermostat'
|
||||
);
|
||||
|
||||
api_call.set_callback(function(response) {
|
||||
var runtime_sensors = [];
|
||||
for (var alias in response) {
|
||||
var r = response[alias];
|
||||
if (alias === 'runtime_thermostat') {
|
||||
beestat.cache.set('data.air_quality_detail__runtime_thermostat', r);
|
||||
} else {
|
||||
runtime_sensors = runtime_sensors.concat(r);
|
||||
}
|
||||
}
|
||||
beestat.cache.set('data.air_quality_detail__runtime_sensor', runtime_sensors);
|
||||
});
|
||||
|
||||
api_call.send();
|
||||
} else if (this.has_data_() === false) {
|
||||
chart_container.style('filter', 'blur(3px)');
|
||||
var no_data = $.createElement('div');
|
||||
no_data.style({
|
||||
'position': 'absolute',
|
||||
'top': 0,
|
||||
'left': 0,
|
||||
'width': '100%',
|
||||
'height': '100%',
|
||||
'display': 'flex',
|
||||
'flex-direction': 'column',
|
||||
'justify-content': 'center',
|
||||
'text-align': 'center'
|
||||
});
|
||||
no_data.innerText('No data to display');
|
||||
container.appendChild(no_data);
|
||||
}
|
||||
} else {
|
||||
this.show_loading_('Syncing');
|
||||
window.setTimeout(function() {
|
||||
new beestat.api()
|
||||
.add_call(
|
||||
'thermostat',
|
||||
'read_id',
|
||||
{
|
||||
'attributes': {
|
||||
'inactive': 0
|
||||
}
|
||||
},
|
||||
'thermostat'
|
||||
)
|
||||
.set_callback(function(response) {
|
||||
beestat.cache.set('thermostat', response);
|
||||
self.rerender();
|
||||
})
|
||||
.send();
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate the menu
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.card.air_quality_detail.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('Past 1 Day')
|
||||
.set_icon('numeric_1_box')
|
||||
.set_callback(function() {
|
||||
if (
|
||||
beestat.setting('air_quality_detail_range_dynamic') !== 1 ||
|
||||
beestat.setting('air_quality_detail_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('data.air_quality_detail__runtime_sensor');
|
||||
beestat.setting({
|
||||
'air_quality_detail_range_dynamic': 1,
|
||||
'air_quality_detail_range_type': 'dynamic'
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
menu.add_menu_item(new beestat.component.menu_item()
|
||||
.set_text('Past 3 Days')
|
||||
.set_icon('numeric_3_box')
|
||||
.set_callback(function() {
|
||||
if (
|
||||
beestat.setting('air_quality_detail_range_dynamic') !== 3 ||
|
||||
beestat.setting('air_quality_detail_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('data.air_quality_detail__runtime_sensor');
|
||||
beestat.setting({
|
||||
'air_quality_detail_range_dynamic': 3,
|
||||
'air_quality_detail_range_type': 'dynamic'
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
menu.add_menu_item(new beestat.component.menu_item()
|
||||
.set_text('Past 7 Days')
|
||||
.set_icon('numeric_7_box')
|
||||
.set_callback(function() {
|
||||
if (
|
||||
beestat.setting('air_quality_detail_range_dynamic') !== 7 ||
|
||||
beestat.setting('air_quality_detail_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('data.air_quality_detail__runtime_sensor');
|
||||
beestat.setting({
|
||||
'air_quality_detail_range_dynamic': 7,
|
||||
'air_quality_detail_range_type': 'dynamic'
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
menu.add_menu_item(new beestat.component.menu_item()
|
||||
.set_text('Custom')
|
||||
.set_icon('calendar_edit')
|
||||
.set_callback(function() {
|
||||
(new beestat.component.modal.air_quality_detail_custom()).render();
|
||||
}));
|
||||
|
||||
if (this.has_data_() === true) {
|
||||
menu.add_menu_item(new beestat.component.menu_item()
|
||||
.set_text('Reset Zoom')
|
||||
.set_icon('magnify_minus')
|
||||
.set_callback(function() {
|
||||
self.charts_.air_quality.reset_zoom();
|
||||
}));
|
||||
}
|
||||
|
||||
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/2685b4aae86a4f5d80dd0a4e5dd88201');
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether or not there is data to display on the chart.
|
||||
*
|
||||
* @return {boolean} Whether or not there is data to display on the chart.
|
||||
*/
|
||||
beestat.component.card.air_quality_detail.prototype.has_data_ = function() {
|
||||
var data = this.get_data_();
|
||||
for (var series_code in data.metadata.series) {
|
||||
if (
|
||||
series_code !== 'dummy' &&
|
||||
data.metadata.series[series_code].active === true
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get data. This doesn't directly or indirectly make any API calls, but it
|
||||
* caches the data so it doesn't have to loop over everything more than once.
|
||||
*
|
||||
* @param {boolean} force Force get the data?
|
||||
*
|
||||
* @return {object} The data.
|
||||
*/
|
||||
beestat.component.card.air_quality_detail.prototype.get_data_ = function(force) {
|
||||
if (this.data_ === undefined || force === true) {
|
||||
var range = {
|
||||
'type': beestat.setting('air_quality_detail_range_type'),
|
||||
'dynamic': beestat.setting('air_quality_detail_range_dynamic'),
|
||||
'static_begin': beestat.setting('air_quality_detail_range_static_begin'),
|
||||
'static_end': beestat.setting('air_quality_detail_range_static_end')
|
||||
};
|
||||
|
||||
var sensor_data = beestat.runtime_sensor.get_data(
|
||||
this.thermostat_id_,
|
||||
range,
|
||||
'air_quality_detail__runtime_sensor'
|
||||
);
|
||||
var thermostat_data = beestat.runtime_thermostat.get_data(
|
||||
this.thermostat_id_,
|
||||
range,
|
||||
'air_quality_detail__runtime_thermostat'
|
||||
);
|
||||
|
||||
this.data_ = sensor_data;
|
||||
|
||||
Object.assign(this.data_.series, thermostat_data.series);
|
||||
Object.assign(this.data_.metadata.series, thermostat_data.metadata.series);
|
||||
|
||||
this.data_.metadata.chart.title = this.get_title_();
|
||||
this.data_.metadata.chart.subtitle = this.get_subtitle_();
|
||||
}
|
||||
|
||||
return this.data_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the title of the card.
|
||||
*
|
||||
* @return {string} Title
|
||||
*/
|
||||
beestat.component.card.air_quality_detail.prototype.get_title_ = function() {
|
||||
return 'Air Quality Detail';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the subtitle of the card.
|
||||
*
|
||||
* @return {string} Subtitle
|
||||
*/
|
||||
beestat.component.card.air_quality_detail.prototype.get_subtitle_ = function() {
|
||||
if (beestat.setting('air_quality_detail_range_type') === 'dynamic') {
|
||||
var s = (beestat.setting('air_quality_detail_range_dynamic') > 1) ? 's' : '';
|
||||
|
||||
return 'Past ' +
|
||||
beestat.setting('air_quality_detail_range_dynamic') +
|
||||
' day' +
|
||||
s;
|
||||
}
|
||||
|
||||
var begin = moment(beestat.setting('air_quality_detail_range_static_begin'))
|
||||
.format('MMM D, YYYY');
|
||||
var end = moment(beestat.setting('air_quality_detail_range_static_end'))
|
||||
.format('MMM D, YYYY');
|
||||
|
||||
return begin + ' to ' + end;
|
||||
};
|
@ -13,5 +13,5 @@ beestat.extend(beestat.component.card.early_access, beestat.component.card);
|
||||
*/
|
||||
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! ⤵'));
|
||||
parent.appendChild($.createElement('p').innerText('Welcome to the early access release for Air Quality in beestat! Please let me know if you have any feedback or issues.'));
|
||||
};
|
||||
|
@ -27,8 +27,8 @@ beestat.component.card.runtime_sensor_detail = function(thermostat_id) {
|
||||
[
|
||||
'setting.runtime_sensor_detail_range_type',
|
||||
'setting.runtime_sensor_detail_range_dynamic',
|
||||
'cache.data.runtime_thermostat_sensor_detail',
|
||||
'cache.data.runtime_sensor'
|
||||
'cache.data.runtime_sensor_detail__runtime_thermostat',
|
||||
'cache.data.runtime_sensor_detail__runtime_sensor'
|
||||
],
|
||||
change_function
|
||||
);
|
||||
@ -126,12 +126,12 @@ beestat.component.card.runtime_sensor_detail.prototype.decorate_contents_ = func
|
||||
);
|
||||
|
||||
/**
|
||||
* If the needed data exists in the database and the runtime_sensor
|
||||
* cache is empty, then query the data. If the needed data does not exist in
|
||||
* the database, check every 2 seconds until it does.
|
||||
* If the needed data exists in the database and the runtime_sensor cache is
|
||||
* empty, then query the data. If the needed data does not exist in the
|
||||
* database, check every 2 seconds until it does.
|
||||
*/
|
||||
if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) {
|
||||
if (beestat.cache.runtime_sensor === undefined) {
|
||||
if (beestat.cache.data.runtime_sensor_detail__runtime_sensor === undefined) {
|
||||
this.show_loading_('Fetching');
|
||||
|
||||
var value;
|
||||
@ -188,12 +188,12 @@ beestat.component.card.runtime_sensor_detail.prototype.decorate_contents_ = func
|
||||
for (var alias in response) {
|
||||
var r = response[alias];
|
||||
if (alias === 'runtime_thermostat') {
|
||||
beestat.cache.set('data.runtime_thermostat_sensor_detail', r);
|
||||
beestat.cache.set('data.runtime_sensor_detail__runtime_thermostat', r);
|
||||
} else {
|
||||
runtime_sensors = runtime_sensors.concat(r);
|
||||
}
|
||||
}
|
||||
beestat.cache.set('runtime_sensor', runtime_sensors);
|
||||
beestat.cache.set('data.runtime_sensor_detail__runtime_sensor', runtime_sensors);
|
||||
});
|
||||
|
||||
api_call.send();
|
||||
@ -255,7 +255,7 @@ beestat.component.card.runtime_sensor_detail.prototype.decorate_top_right_ = fun
|
||||
beestat.setting('runtime_sensor_detail_range_dynamic') !== 1 ||
|
||||
beestat.setting('runtime_sensor_detail_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('runtime_sensor');
|
||||
beestat.cache.delete('data.runtime_sensor_detail__runtime_sensor');
|
||||
beestat.setting({
|
||||
'runtime_sensor_detail_range_dynamic': 1,
|
||||
'runtime_sensor_detail_range_type': 'dynamic'
|
||||
@ -271,7 +271,7 @@ beestat.component.card.runtime_sensor_detail.prototype.decorate_top_right_ = fun
|
||||
beestat.setting('runtime_sensor_detail_range_dynamic') !== 3 ||
|
||||
beestat.setting('runtime_sensor_detail_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('runtime_sensor');
|
||||
beestat.cache.delete('data.runtime_sensor_detail__runtime_sensor');
|
||||
beestat.setting({
|
||||
'runtime_sensor_detail_range_dynamic': 3,
|
||||
'runtime_sensor_detail_range_type': 'dynamic'
|
||||
@ -287,7 +287,7 @@ beestat.component.card.runtime_sensor_detail.prototype.decorate_top_right_ = fun
|
||||
beestat.setting('runtime_sensor_detail_range_dynamic') !== 7 ||
|
||||
beestat.setting('runtime_sensor_detail_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('runtime_sensor');
|
||||
beestat.cache.delete('data.runtime_sensor_detail__runtime_sensor');
|
||||
beestat.setting({
|
||||
'runtime_sensor_detail_range_dynamic': 7,
|
||||
'runtime_sensor_detail_range_type': 'dynamic'
|
||||
@ -362,11 +362,15 @@ beestat.component.card.runtime_sensor_detail.prototype.get_data_ = function(forc
|
||||
'static_end': beestat.setting('runtime_sensor_detail_range_static_end')
|
||||
};
|
||||
|
||||
var sensor_data = beestat.runtime_sensor.get_data(this.thermostat_id_, range);
|
||||
var sensor_data = beestat.runtime_sensor.get_data(
|
||||
this.thermostat_id_,
|
||||
range,
|
||||
'runtime_sensor_detail__runtime_sensor'
|
||||
);
|
||||
var thermostat_data = beestat.runtime_thermostat.get_data(
|
||||
this.thermostat_id_,
|
||||
range,
|
||||
'runtime_thermostat_sensor_detail'
|
||||
'runtime_sensor_detail__runtime_thermostat'
|
||||
);
|
||||
|
||||
this.data_ = sensor_data;
|
||||
|
@ -27,7 +27,7 @@ beestat.component.card.runtime_thermostat_detail = function(thermostat_id) {
|
||||
[
|
||||
'setting.runtime_thermostat_detail_range_type',
|
||||
'setting.runtime_thermostat_detail_range_dynamic',
|
||||
'cache.data.runtime_thermostat_thermostat_detail',
|
||||
'cache.data.runtime_thermostat_detail__runtime_thermostat',
|
||||
'cache.thermostat'
|
||||
],
|
||||
change_function
|
||||
@ -150,7 +150,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_contents_ =
|
||||
* the database, check every 2 seconds until it does.
|
||||
*/
|
||||
if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) {
|
||||
if (beestat.cache.data.runtime_thermostat_thermostat_detail === undefined) {
|
||||
if (beestat.cache.data.runtime_thermostat_detail__runtime_thermostat === undefined) {
|
||||
this.show_loading_('Fetching');
|
||||
|
||||
var value;
|
||||
@ -182,7 +182,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_contents_ =
|
||||
}
|
||||
)
|
||||
.set_callback(function(response) {
|
||||
beestat.cache.set('data.runtime_thermostat_thermostat_detail', response);
|
||||
beestat.cache.set('data.runtime_thermostat_detail__runtime_thermostat', response);
|
||||
})
|
||||
.send();
|
||||
} else if (this.has_data_() === false) {
|
||||
@ -242,7 +242,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_top_right_ =
|
||||
beestat.setting('runtime_thermostat_detail_range_dynamic') !== 1 ||
|
||||
beestat.setting('runtime_thermostat_detail_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('data.runtime_thermostat_thermostat_detail');
|
||||
beestat.cache.delete('data.runtime_thermostat_detail__runtime_thermostat');
|
||||
beestat.setting({
|
||||
'runtime_thermostat_detail_range_dynamic': 1,
|
||||
'runtime_thermostat_detail_range_type': 'dynamic'
|
||||
@ -258,7 +258,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_top_right_ =
|
||||
beestat.setting('runtime_thermostat_detail_range_dynamic') !== 3 ||
|
||||
beestat.setting('runtime_thermostat_detail_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('data.runtime_thermostat_thermostat_detail');
|
||||
beestat.cache.delete('data.runtime_thermostat_detail__runtime_thermostat');
|
||||
beestat.setting({
|
||||
'runtime_thermostat_detail_range_dynamic': 3,
|
||||
'runtime_thermostat_detail_range_type': 'dynamic'
|
||||
@ -274,7 +274,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_top_right_ =
|
||||
beestat.setting('runtime_thermostat_detail_range_dynamic') !== 7 ||
|
||||
beestat.setting('runtime_thermostat_detail_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('data.runtime_thermostat_thermostat_detail');
|
||||
beestat.cache.delete('data.runtime_thermostat_detail__runtime_thermostat');
|
||||
beestat.setting({
|
||||
'runtime_thermostat_detail_range_dynamic': 7,
|
||||
'runtime_thermostat_detail_range_type': 'dynamic'
|
||||
@ -352,7 +352,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.get_data_ = function(
|
||||
this.data_ = beestat.runtime_thermostat.get_data(
|
||||
this.thermostat_id_,
|
||||
range,
|
||||
'runtime_thermostat_thermostat_detail'
|
||||
'runtime_thermostat_detail__runtime_thermostat'
|
||||
);
|
||||
|
||||
this.data_.metadata.chart.title = this.get_title_();
|
||||
|
393
js/component/card/voc_summary.js
Normal file
393
js/component/card/voc_summary.js
Normal file
@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Air Quality card. Shows a chart with comfort profiles, occupancy, and air
|
||||
* quality data.
|
||||
*
|
||||
* @param {number} thermostat_id The thermostat_id this card is displaying
|
||||
* data for
|
||||
*/
|
||||
beestat.component.card.voc_summary = function(thermostat_id) {
|
||||
var self = this;
|
||||
|
||||
this.thermostat_id_ = thermostat_id;
|
||||
|
||||
/*
|
||||
* When a setting is changed clear all of the data. Then rerender which will
|
||||
* trigger the loading state. Also do this when the cache changes.
|
||||
*
|
||||
* 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 change_function = beestat.debounce(function() {
|
||||
self.rerender();
|
||||
}, 10);
|
||||
|
||||
beestat.dispatcher.addEventListener(
|
||||
[
|
||||
'setting.voc_summary_range_type',
|
||||
'setting.voc_summary_range_dynamic',
|
||||
'cache.data.voc_summary'
|
||||
],
|
||||
change_function
|
||||
);
|
||||
|
||||
beestat.component.card.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.card.voc_summary, beestat.component.card);
|
||||
|
||||
beestat.component.card.voc_summary.prototype.rerender_on_breakpoint_ = true;
|
||||
|
||||
/**
|
||||
* Decorate
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.card.voc_summary.prototype.decorate_contents_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
var container = $.createElement('div').style({
|
||||
'position': 'relative'
|
||||
});
|
||||
parent.appendChild(container);
|
||||
|
||||
var chart_container = $.createElement('div');
|
||||
container.appendChild(chart_container);
|
||||
|
||||
this.decorate_chart_(chart_container);
|
||||
|
||||
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||
|
||||
var required_begin;
|
||||
var required_end;
|
||||
if (beestat.setting('voc_summary_range_type') === 'dynamic') {
|
||||
required_begin = moment()
|
||||
.subtract(
|
||||
beestat.setting('voc_summary_range_dynamic'),
|
||||
'day'
|
||||
)
|
||||
.second(0);
|
||||
|
||||
required_end = moment()
|
||||
.subtract(1, 'hour')
|
||||
.second(0);
|
||||
} else {
|
||||
required_begin = moment(
|
||||
beestat.setting('voc_summary_range_static_begin') + ' 00:00:00'
|
||||
);
|
||||
required_end = moment(
|
||||
beestat.setting('voc_summary_range_static_end') + ' 23:59:59'
|
||||
);
|
||||
}
|
||||
|
||||
// Don't go before there's data.
|
||||
required_begin = moment.max(
|
||||
required_begin,
|
||||
moment.utc(thermostat.first_connected)
|
||||
);
|
||||
|
||||
// Don't go after now.
|
||||
required_end = moment.min(
|
||||
required_end,
|
||||
moment().subtract(1, 'hour')
|
||||
);
|
||||
|
||||
/**
|
||||
* If the needed data exists in the database and the runtime_sensor
|
||||
* cache is empty, then query the data. If the needed data does not exist in
|
||||
* the database, check every 2 seconds until it does.
|
||||
*/
|
||||
if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) {
|
||||
if (beestat.cache.data.voc_summary === undefined) {
|
||||
this.show_loading_('Fetching');
|
||||
|
||||
var value;
|
||||
var operator;
|
||||
|
||||
if (beestat.setting('voc_summary_range_type') === 'dynamic') {
|
||||
value = required_begin.format();
|
||||
operator = '>=';
|
||||
} else {
|
||||
value = [
|
||||
required_begin.format(),
|
||||
required_end.format()
|
||||
];
|
||||
operator = 'between';
|
||||
}
|
||||
|
||||
var api_call = new beestat.api();
|
||||
beestat.sensor.get_sorted().forEach(function(sensor) {
|
||||
if (
|
||||
sensor.thermostat_id === self.thermostat_id_ &&
|
||||
sensor.type === 'thermostat'
|
||||
) {
|
||||
api_call.add_call(
|
||||
'runtime_sensor',
|
||||
'read',
|
||||
{
|
||||
'attributes': {
|
||||
'sensor_id': sensor.sensor_id,
|
||||
'timestamp': {
|
||||
'value': value,
|
||||
'operator': operator
|
||||
}
|
||||
}
|
||||
},
|
||||
'runtime_sensor_' + sensor.sensor_id
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
api_call.set_callback(function(response) {
|
||||
var runtime_sensors = [];
|
||||
for (var alias in response) {
|
||||
var r = response[alias];
|
||||
runtime_sensors = runtime_sensors.concat(r);
|
||||
}
|
||||
beestat.cache.set('data.voc_summary', runtime_sensors);
|
||||
});
|
||||
|
||||
api_call.send();
|
||||
} else if (this.has_data_() === false) {
|
||||
chart_container.style('filter', 'blur(3px)');
|
||||
var no_data = $.createElement('div');
|
||||
no_data.style({
|
||||
'position': 'absolute',
|
||||
'top': 0,
|
||||
'left': 0,
|
||||
'width': '100%',
|
||||
'height': '100%',
|
||||
'display': 'flex',
|
||||
'flex-direction': 'column',
|
||||
'justify-content': 'center',
|
||||
'text-align': 'center'
|
||||
});
|
||||
no_data.innerText('No data to display');
|
||||
container.appendChild(no_data);
|
||||
}
|
||||
} else {
|
||||
this.show_loading_('Syncing');
|
||||
window.setTimeout(function() {
|
||||
new beestat.api()
|
||||
.add_call(
|
||||
'thermostat',
|
||||
'read_id',
|
||||
{
|
||||
'attributes': {
|
||||
'inactive': 0
|
||||
}
|
||||
},
|
||||
'thermostat'
|
||||
)
|
||||
.set_callback(function(response) {
|
||||
beestat.cache.set('thermostat', response);
|
||||
self.rerender();
|
||||
})
|
||||
.send();
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate chart
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.card.voc_summary.prototype.decorate_chart_ = function(parent) {
|
||||
var grid_data = {};
|
||||
for (var runtime_sensor_id in beestat.cache.data.voc_summary) {
|
||||
var runtime_sensor = beestat.cache.data.voc_summary[runtime_sensor_id];
|
||||
var key = moment(runtime_sensor.timestamp).format('d_H');
|
||||
if (grid_data[key] === undefined) {
|
||||
grid_data[key] = [];
|
||||
}
|
||||
if (runtime_sensor.voc_concentration !== null) {
|
||||
grid_data[key].push(runtime_sensor.voc_concentration);
|
||||
}
|
||||
}
|
||||
|
||||
var table = $.createElement('table');
|
||||
table.style({
|
||||
'table-layout': 'fixed',
|
||||
'border-collapse': 'collapse',
|
||||
'width': '100%'
|
||||
});
|
||||
|
||||
var tr;
|
||||
var td;
|
||||
|
||||
tr = $.createElement('tr');
|
||||
tr.appendChild($.createElement('td').style({'width': '50px'}));
|
||||
table.appendChild(tr);
|
||||
|
||||
var days_of_week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
|
||||
var day;
|
||||
var hour;
|
||||
|
||||
// Header row
|
||||
for (hour = 0; hour < 24; hour++) {
|
||||
var meridiem = hour >= 12 ? 'p' : 'a';
|
||||
var new_hour = (hour % 12) || 12;
|
||||
|
||||
tr.appendChild(
|
||||
$.createElement('td')
|
||||
.innerText(new_hour + (beestat.width > 700 ? meridiem : ''))
|
||||
.style({
|
||||
'text-align': 'center',
|
||||
'font-size': beestat.style.font_size.small
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (day = 0; day < 7; day++) {
|
||||
tr = $.createElement('tr');
|
||||
table.appendChild(tr);
|
||||
|
||||
td = $.createElement('td')
|
||||
.innerHTML(days_of_week[day]);
|
||||
tr.appendChild(td);
|
||||
|
||||
for (hour = 0; hour < 24; hour++) {
|
||||
var cell_value = grid_data[day + '_' + hour];
|
||||
td = $.createElement('td');
|
||||
var background = beestat.style.color.bluegray.light;
|
||||
|
||||
if (cell_value !== undefined) {
|
||||
var average = grid_data[day + '_' + hour].reduce(function(a, b) {
|
||||
return a + b;
|
||||
}) / grid_data[day + '_' + hour].length;
|
||||
|
||||
td.setAttribute('title', Math.round(average) + ' ppb');
|
||||
|
||||
if (average < 1) {
|
||||
background = beestat.style.color.bluegray.light;
|
||||
} else if (average < 75) {
|
||||
background = beestat.style.color.green.light;
|
||||
} else if (average < 150) {
|
||||
background = beestat.style.color.green.base;
|
||||
} else if (average < 225) {
|
||||
background = beestat.style.color.green.dark;
|
||||
} else if (average < 375) {
|
||||
background = beestat.style.color.yellow.light;
|
||||
} else if (average < 525) {
|
||||
background = beestat.style.color.yellow.base;
|
||||
} else if (average < 675) {
|
||||
background = beestat.style.color.yellow.dark;
|
||||
} else if (average < 925) {
|
||||
background = beestat.style.color.orange.light;
|
||||
} else if (average < 1175) {
|
||||
background = beestat.style.color.orange.base;
|
||||
} else if (average < 1425) {
|
||||
background = beestat.style.color.orange.dark;
|
||||
} else if (average < 1675) {
|
||||
background = beestat.style.color.red.light;
|
||||
} else if (average < 1925) {
|
||||
background = beestat.style.color.red.base;
|
||||
} else if (average < 2175) {
|
||||
background = beestat.style.color.red.dark;
|
||||
} else {
|
||||
background = beestat.style.color.red.dark;
|
||||
}
|
||||
}
|
||||
|
||||
td.style({
|
||||
'height': '20px',
|
||||
'background': background
|
||||
});
|
||||
|
||||
tr.appendChild(td);
|
||||
}
|
||||
}
|
||||
|
||||
parent.appendChild(table);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate the menu
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.card.voc_summary.prototype.decorate_top_right_ = function(parent) {
|
||||
var menu = (new beestat.component.menu()).render(parent);
|
||||
|
||||
menu.add_menu_item(new beestat.component.menu_item()
|
||||
.set_text('Past 1 Week')
|
||||
.set_icon('numeric_1_box')
|
||||
.set_callback(function() {
|
||||
if (
|
||||
beestat.setting('voc_summary_range_dynamic') !== 7 ||
|
||||
beestat.setting('voc_summary_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('data.voc_summary');
|
||||
beestat.setting({
|
||||
'voc_summary_range_dynamic': 7,
|
||||
'voc_summary_range_type': 'dynamic'
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
menu.add_menu_item(new beestat.component.menu_item()
|
||||
.set_text('Past 4 Weeks')
|
||||
.set_icon('numeric_4_box')
|
||||
.set_callback(function() {
|
||||
if (
|
||||
beestat.setting('voc_summary_range_dynamic') !== 28 ||
|
||||
beestat.setting('voc_summary_range_type') !== 'dynamic'
|
||||
) {
|
||||
beestat.cache.delete('data.voc_summary');
|
||||
beestat.setting({
|
||||
'voc_summary_range_dynamic': 28,
|
||||
'voc_summary_range_type': 'dynamic'
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
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/25a1a894a7f4432ead5831bef48770bd');
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether or not there is data to display on the chart.
|
||||
*
|
||||
* @return {boolean} Whether or not there is data to display on the chart.
|
||||
*/
|
||||
beestat.component.card.voc_summary.prototype.has_data_ = function() {
|
||||
return beestat.cache.data.voc_summary &&
|
||||
beestat.cache.data.voc_summary.length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the title of the card.
|
||||
*
|
||||
* @return {string} Title
|
||||
*/
|
||||
beestat.component.card.voc_summary.prototype.get_title_ = function() {
|
||||
return 'TVOC Summary';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the subtitle of the card.
|
||||
*
|
||||
* @return {string} Subtitle
|
||||
*/
|
||||
beestat.component.card.voc_summary.prototype.get_subtitle_ = function() {
|
||||
if (beestat.setting('voc_summary_range_type') === 'dynamic') {
|
||||
var s = ((beestat.setting('voc_summary_range_dynamic') / 7) > 1) ? 's' : '';
|
||||
|
||||
return 'Past ' +
|
||||
(beestat.setting('voc_summary_range_dynamic') / 7) +
|
||||
' week' +
|
||||
s;
|
||||
}
|
||||
|
||||
var begin = moment(beestat.setting('voc_summary_range_static_begin'))
|
||||
.format('MMM D, YYYY');
|
||||
var end = moment(beestat.setting('voc_summary_range_static_end'))
|
||||
.format('MMM D, YYYY');
|
||||
|
||||
return begin + ' to ' + end;
|
||||
};
|
@ -182,6 +182,7 @@ beestat.component.chart.prototype.get_options_chart_ = function() {
|
||||
// For consistent left spacing on charts with no y-axis values
|
||||
'marginLeft': this.get_options_chart_marginLeft_(),
|
||||
'marginRight': this.get_options_chart_marginRight_(),
|
||||
'marginBottom': this.get_options_chart_marginBottom_(),
|
||||
'zoomType': this.get_options_chart_zoomType_(),
|
||||
'panning': true,
|
||||
'panKey': 'ctrl',
|
||||
@ -214,6 +215,15 @@ beestat.component.chart.prototype.get_options_chart_marginRight_ = function() {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the bottom margin for the chart.
|
||||
*
|
||||
* @return {number} The right margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.prototype.get_options_chart_marginBottom_ = function() {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the spacing for the chart.
|
||||
*
|
||||
@ -578,15 +588,17 @@ beestat.component.chart.prototype.tooltip_formatter_helper_ = function(title, se
|
||||
'box-shadow': '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)'
|
||||
});
|
||||
|
||||
var title_div = $.createElement('div')
|
||||
.style({
|
||||
'font-weight': beestat.style.font_weight.bold,
|
||||
'font-size': beestat.style.font_size.large,
|
||||
'margin-bottom': beestat.style.size.gutter / 4,
|
||||
'color': beestat.style.color.gray.light
|
||||
})
|
||||
.innerText(title);
|
||||
tooltip.appendChild(title_div);
|
||||
if (title !== null) {
|
||||
var title_div = $.createElement('div')
|
||||
.style({
|
||||
'font-weight': beestat.style.font_weight.bold,
|
||||
'font-size': beestat.style.font_size.large,
|
||||
'margin-bottom': beestat.style.size.gutter / 4,
|
||||
'color': beestat.style.color.gray.light
|
||||
})
|
||||
.innerText(title);
|
||||
tooltip.appendChild(title_div);
|
||||
}
|
||||
|
||||
var table = $.createElement('table')
|
||||
.setAttribute({
|
||||
|
168
js/component/chart/air_quality.js
Normal file
168
js/component/chart/air_quality.js
Normal file
@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Air Quality chart.
|
||||
*
|
||||
* @param {object} data The chart data.
|
||||
*/
|
||||
beestat.component.chart.air_quality = function(data) {
|
||||
this.data_ = data;
|
||||
|
||||
beestat.component.chart.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.chart.air_quality, beestat.component.chart);
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_labels_formatter_.
|
||||
*
|
||||
* @return {Function} xAxis labels formatter.
|
||||
*/
|
||||
beestat.component.chart.air_quality.prototype.get_options_xAxis_labels_formatter_ = function() {
|
||||
return function() {
|
||||
return null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_series_.
|
||||
*
|
||||
* @return {Array} All of the series to display on the chart.
|
||||
*/
|
||||
beestat.component.chart.air_quality.prototype.get_options_series_ = function() {
|
||||
var self = this;
|
||||
var series = [];
|
||||
|
||||
// Sensors
|
||||
this.data_.metadata.sensors.forEach(function(sensor) {
|
||||
if (sensor.type === 'thermostat') {
|
||||
series.push({
|
||||
'name': 'air_quality_' + sensor.sensor_id,
|
||||
'data': self.data_.series['air_quality_' + sensor.sensor_id],
|
||||
'color': beestat.series.air_quality.color,
|
||||
'yAxis': 0,
|
||||
'type': 'spline',
|
||||
'lineWidth': 1
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
series.push({
|
||||
'name': '',
|
||||
'data': self.data_.series.dummy,
|
||||
'yAxis': 0,
|
||||
'type': 'line',
|
||||
'lineWidth': 0,
|
||||
'showInLegend': false
|
||||
});
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_yAxis_.
|
||||
*
|
||||
* @return {Array} The y-axis options.
|
||||
*/
|
||||
beestat.component.chart.air_quality.prototype.get_options_yAxis_ = function() {
|
||||
return [
|
||||
{
|
||||
'gridLineColor': beestat.style.color.bluegray.light,
|
||||
'gridLineDashStyle': 'longdash',
|
||||
'allowDecimals': false,
|
||||
'title': {'text': null},
|
||||
'labels': {
|
||||
'style': {'color': beestat.style.color.gray.base},
|
||||
'formatter': function() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_tooltip_formatter_.
|
||||
*
|
||||
* @return {Function} The tooltip formatter.
|
||||
*/
|
||||
beestat.component.chart.air_quality.prototype.get_options_tooltip_formatter_ = function() {
|
||||
var self = this;
|
||||
|
||||
return function() {
|
||||
var x = this.x;
|
||||
|
||||
var sections = [];
|
||||
var groups = {
|
||||
'data': []
|
||||
};
|
||||
|
||||
$.values(beestat.cache.sensor).forEach(function(sensor) {
|
||||
if (
|
||||
sensor.thermostat_id === beestat.setting('thermostat_id') &&
|
||||
sensor.type === 'thermostat'
|
||||
) {
|
||||
groups.data.push({
|
||||
'label': beestat.series.air_quality.name,
|
||||
'value': (self.data_.metadata.series['air_quality_' + sensor.sensor_id].data[x.valueOf()]),
|
||||
'color': beestat.series.air_quality.color
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
sections.push(groups.data);
|
||||
|
||||
var title = this.x.format('ddd, MMM D @ h:mma');
|
||||
|
||||
return self.tooltip_formatter_helper_(
|
||||
title,
|
||||
sections
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the tooltip positioner y value.
|
||||
*
|
||||
* @param {number} tooltip_width Tooltip width.
|
||||
* @param {number} tooltip_height Tooltip height.
|
||||
* @param {point} point Highcharts current point.
|
||||
*
|
||||
* @return {number} The tooltip y value.
|
||||
*/
|
||||
beestat.component.chart.air_quality.prototype.get_options_tooltip_positioner_y_ = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the height of the chart.
|
||||
*
|
||||
* @return {number} The height of the chart.
|
||||
*/
|
||||
beestat.component.chart.air_quality.prototype.get_options_chart_height_ = function() {
|
||||
return 75;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the left margin for the chart.
|
||||
*
|
||||
* @return {number} The left margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.air_quality.prototype.get_options_chart_marginLeft_ = function() {
|
||||
return 60;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the legend enabled options.
|
||||
*
|
||||
* @return {Function} The legend enabled options.
|
||||
*/
|
||||
beestat.component.chart.air_quality.prototype.get_options_legend_enabled_ = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the bottom margin for the chart.
|
||||
*
|
||||
* @return {number} The right margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.air_quality.prototype.get_options_chart_marginBottom_ = function() {
|
||||
return 10;
|
||||
};
|
174
js/component/chart/co2_concentration.js
Normal file
174
js/component/chart/co2_concentration.js
Normal file
@ -0,0 +1,174 @@
|
||||
/**
|
||||
* CO2 Concentration chart.
|
||||
*
|
||||
* @param {object} data The chart data.
|
||||
*/
|
||||
beestat.component.chart.co2_concentration = function(data) {
|
||||
this.data_ = data;
|
||||
|
||||
beestat.component.chart.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.chart.co2_concentration, beestat.component.chart);
|
||||
|
||||
/**
|
||||
* Override for get_options_series_.
|
||||
*
|
||||
* @return {Array} All of the series to display on the chart.
|
||||
*/
|
||||
beestat.component.chart.co2_concentration.prototype.get_options_series_ = function() {
|
||||
var self = this;
|
||||
var series = [];
|
||||
|
||||
// Sensors
|
||||
this.data_.metadata.sensors.forEach(function(sensor) {
|
||||
if (sensor.type === 'thermostat') {
|
||||
series.push({
|
||||
'name': 'co2_concentration_' + sensor.sensor_id,
|
||||
'data': self.data_.series['co2_concentration_' + sensor.sensor_id],
|
||||
'color': beestat.series.co2_concentration.color,
|
||||
'yAxis': 0,
|
||||
'type': 'spline',
|
||||
'lineWidth': 1
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
series.push({
|
||||
'name': '',
|
||||
'data': self.data_.series.dummy,
|
||||
'yAxis': 0,
|
||||
'type': 'line',
|
||||
'lineWidth': 0,
|
||||
'showInLegend': false
|
||||
});
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_yAxis_.
|
||||
*
|
||||
* @return {Array} The y-axis options.
|
||||
*/
|
||||
beestat.component.chart.co2_concentration.prototype.get_options_yAxis_ = function() {
|
||||
return [
|
||||
{
|
||||
'gridLineColor': beestat.style.color.bluegray.light,
|
||||
'gridLineDashStyle': 'longdash',
|
||||
'allowDecimals': false,
|
||||
'title': {'text': null},
|
||||
'labels': {
|
||||
'style': {'color': beestat.style.color.gray.base},
|
||||
'formatter': function() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_tooltip_formatter_.
|
||||
*
|
||||
* @return {Function} The tooltip formatter.
|
||||
*/
|
||||
beestat.component.chart.co2_concentration.prototype.get_options_tooltip_formatter_ = function() {
|
||||
var self = this;
|
||||
|
||||
return function() {
|
||||
var x = this.x;
|
||||
|
||||
var sections = [];
|
||||
var groups = {
|
||||
'data': []
|
||||
};
|
||||
|
||||
$.values(beestat.cache.sensor).forEach(function(sensor) {
|
||||
if (
|
||||
sensor.thermostat_id === beestat.setting('thermostat_id') &&
|
||||
sensor.type === 'thermostat'
|
||||
) {
|
||||
groups.data.push({
|
||||
'label': beestat.series.co2_concentration.name,
|
||||
'value': (self.data_.metadata.series['co2_concentration_' + sensor.sensor_id].data[x.valueOf()]) + ' ppm',
|
||||
'color': beestat.series.co2_concentration.color
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
sections.push(groups.data);
|
||||
|
||||
return self.tooltip_formatter_helper_(
|
||||
null,
|
||||
sections
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the tooltip positioner y value.
|
||||
*
|
||||
* @param {number} tooltip_width Tooltip width.
|
||||
* @param {number} tooltip_height Tooltip height.
|
||||
* @param {point} point Highcharts current point.
|
||||
*
|
||||
* @return {number} The tooltip y value.
|
||||
*/
|
||||
beestat.component.chart.co2_concentration.prototype.get_options_tooltip_positioner_y_ = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the height of the chart.
|
||||
*
|
||||
* @return {number} The height of the chart.
|
||||
*/
|
||||
beestat.component.chart.co2_concentration.prototype.get_options_chart_height_ = function() {
|
||||
return 135;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the left margin for the chart.
|
||||
*
|
||||
* @return {number} The left margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.co2_concentration.prototype.get_options_chart_marginLeft_ = function() {
|
||||
return 60;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the legend enabled options.
|
||||
*
|
||||
* @return {Function} The legend enabled options.
|
||||
*/
|
||||
beestat.component.chart.co2_concentration.prototype.get_options_legend_enabled_ = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_labels_formatter_.
|
||||
*
|
||||
* @return {Function} xAxis labels formatter.
|
||||
*/
|
||||
beestat.component.chart.co2_concentration.prototype.get_options_xAxis_labels_formatter_ = function() {
|
||||
var current_day;
|
||||
var current_hour;
|
||||
|
||||
return function() {
|
||||
var hour = this.value.format('ha');
|
||||
var day = this.value.format('ddd');
|
||||
|
||||
var label_parts = [];
|
||||
if (day !== current_day) {
|
||||
label_parts.push(day);
|
||||
}
|
||||
if (hour !== current_hour) {
|
||||
label_parts.push(hour);
|
||||
}
|
||||
|
||||
current_hour = hour;
|
||||
current_day = day;
|
||||
|
||||
return label_parts.join(' ');
|
||||
};
|
||||
};
|
@ -30,33 +30,6 @@ beestat.component.chart.runtime_sensor_detail_occupancy.prototype.get_options_se
|
||||
var self = this;
|
||||
var series = [];
|
||||
|
||||
var colors = [
|
||||
beestat.style.color.blue.base,
|
||||
beestat.style.color.red.base,
|
||||
beestat.style.color.yellow.base,
|
||||
beestat.style.color.green.base,
|
||||
beestat.style.color.orange.base,
|
||||
beestat.style.color.bluegreen.base,
|
||||
beestat.style.color.purple.base,
|
||||
beestat.style.color.lightblue.base,
|
||||
beestat.style.color.blue.light,
|
||||
beestat.style.color.red.light,
|
||||
beestat.style.color.yellow.light,
|
||||
beestat.style.color.green.light,
|
||||
beestat.style.color.orange.light,
|
||||
beestat.style.color.bluegreen.light,
|
||||
beestat.style.color.purple.light,
|
||||
beestat.style.color.lightblue.light,
|
||||
beestat.style.color.blue.dark,
|
||||
beestat.style.color.red.dark,
|
||||
beestat.style.color.yellow.dark,
|
||||
beestat.style.color.green.dark,
|
||||
beestat.style.color.orange.dark,
|
||||
beestat.style.color.bluegreen.dark,
|
||||
beestat.style.color.purple.dark,
|
||||
beestat.style.color.lightblue.dark
|
||||
];
|
||||
|
||||
/**
|
||||
* This chart does not need the entire dummy series, but it does need the
|
||||
* first series to have *some* non-null data or Highcharts does not find a
|
||||
@ -77,7 +50,7 @@ beestat.component.chart.runtime_sensor_detail_occupancy.prototype.get_options_se
|
||||
series.push({
|
||||
'name': 'occupancy_' + sensor.sensor_id,
|
||||
'data': self.data_.series['occupancy_' + sensor.sensor_id],
|
||||
'color': colors[i],
|
||||
'color': self.data_.metadata.series['occupancy_' + sensor.sensor_id].color,
|
||||
'yAxis': 0,
|
||||
'type': 'line',
|
||||
'lineWidth': beestat.component.chart.runtime_sensor_detail_occupancy.get_swimlane_properties(self.data_.metadata.sensors.length, 1).line_width,
|
||||
|
@ -60,39 +60,12 @@ beestat.component.chart.runtime_sensor_detail_temperature.prototype.get_options_
|
||||
var self = this;
|
||||
var series = [];
|
||||
|
||||
var colors = [
|
||||
beestat.style.color.blue.base,
|
||||
beestat.style.color.red.base,
|
||||
beestat.style.color.yellow.base,
|
||||
beestat.style.color.green.base,
|
||||
beestat.style.color.orange.base,
|
||||
beestat.style.color.bluegreen.base,
|
||||
beestat.style.color.purple.base,
|
||||
beestat.style.color.lightblue.base,
|
||||
beestat.style.color.blue.light,
|
||||
beestat.style.color.red.light,
|
||||
beestat.style.color.yellow.light,
|
||||
beestat.style.color.green.light,
|
||||
beestat.style.color.orange.light,
|
||||
beestat.style.color.bluegreen.light,
|
||||
beestat.style.color.purple.light,
|
||||
beestat.style.color.lightblue.light,
|
||||
beestat.style.color.blue.dark,
|
||||
beestat.style.color.red.dark,
|
||||
beestat.style.color.yellow.dark,
|
||||
beestat.style.color.green.dark,
|
||||
beestat.style.color.orange.dark,
|
||||
beestat.style.color.bluegreen.dark,
|
||||
beestat.style.color.purple.dark,
|
||||
beestat.style.color.lightblue.dark
|
||||
];
|
||||
|
||||
// Sensors
|
||||
this.data_.metadata.sensors.forEach(function(sensor, i) {
|
||||
this.data_.metadata.sensors.forEach(function(sensor) {
|
||||
series.push({
|
||||
'name': 'temperature_' + sensor.sensor_id,
|
||||
'data': self.data_.series['temperature_' + sensor.sensor_id],
|
||||
'color': colors[i],
|
||||
'color': self.data_.metadata.series['temperature_' + sensor.sensor_id].color,
|
||||
'yAxis': 0,
|
||||
'type': 'spline',
|
||||
'lineWidth': 1
|
||||
|
168
js/component/chart/voc_concentration.js
Normal file
168
js/component/chart/voc_concentration.js
Normal file
@ -0,0 +1,168 @@
|
||||
/**
|
||||
* VOC Concentration chart.
|
||||
*
|
||||
* @param {object} data The chart data.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration = function(data) {
|
||||
this.data_ = data;
|
||||
|
||||
beestat.component.chart.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.chart.voc_concentration, beestat.component.chart);
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_labels_formatter_.
|
||||
*
|
||||
* @return {Function} xAxis labels formatter.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration.prototype.get_options_xAxis_labels_formatter_ = function() {
|
||||
return function() {
|
||||
return null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_series_.
|
||||
*
|
||||
* @return {Array} All of the series to display on the chart.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration.prototype.get_options_series_ = function() {
|
||||
var self = this;
|
||||
var series = [];
|
||||
|
||||
// Sensors
|
||||
this.data_.metadata.sensors.forEach(function(sensor, i) {
|
||||
if (sensor.type === 'thermostat') {
|
||||
series.push({
|
||||
'name': 'voc_concentration_' + sensor.sensor_id,
|
||||
'data': self.data_.series['voc_concentration_' + sensor.sensor_id],
|
||||
'color': beestat.series.voc_concentration.color,
|
||||
'yAxis': 0,
|
||||
'type': 'spline',
|
||||
'lineWidth': 1
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
series.push({
|
||||
'name': '',
|
||||
'data': self.data_.series.dummy,
|
||||
'yAxis': 0,
|
||||
'type': 'line',
|
||||
'lineWidth': 0,
|
||||
'showInLegend': false
|
||||
});
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_yAxis_.
|
||||
*
|
||||
* @return {Array} The y-axis options.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration.prototype.get_options_yAxis_ = function() {
|
||||
return [
|
||||
{
|
||||
'gridLineColor': beestat.style.color.bluegray.light,
|
||||
'gridLineDashStyle': 'longdash',
|
||||
'allowDecimals': false,
|
||||
'title': {'text': null},
|
||||
'labels': {
|
||||
'style': {'color': beestat.style.color.gray.base},
|
||||
'formatter': function() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_tooltip_formatter_.
|
||||
*
|
||||
* @return {Function} The tooltip formatter.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration.prototype.get_options_tooltip_formatter_ = function() {
|
||||
var self = this;
|
||||
|
||||
return function() {
|
||||
var x = this.x;
|
||||
|
||||
var sections = [];
|
||||
var groups = {
|
||||
'data': []
|
||||
};
|
||||
|
||||
$.values(beestat.cache.sensor).forEach(function(sensor) {
|
||||
if (
|
||||
sensor.thermostat_id === beestat.setting('thermostat_id') &&
|
||||
sensor.type === 'thermostat'
|
||||
) {
|
||||
groups.data.push({
|
||||
'label': beestat.series.voc_concentration.name,
|
||||
'value': (self.data_.metadata.series['voc_concentration_' + sensor.sensor_id].data[x.valueOf()]) + ' ppb',
|
||||
'color': beestat.series.voc_concentration.color
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
sections.push(groups.data);
|
||||
|
||||
// var title = this.x.format('ddd, MMM D @ h:mma');
|
||||
|
||||
return self.tooltip_formatter_helper_(
|
||||
null,
|
||||
sections
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the tooltip positioner y value.
|
||||
*
|
||||
* @param {number} tooltip_width Tooltip width.
|
||||
* @param {number} tooltip_height Tooltip height.
|
||||
* @param {point} point Highcharts current point.
|
||||
*
|
||||
* @return {number} The tooltip y value.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration.prototype.get_options_tooltip_positioner_y_ = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the height of the chart.
|
||||
*
|
||||
* @return {number} The height of the chart.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration.prototype.get_options_chart_height_ = function() {
|
||||
return 75;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the left margin for the chart.
|
||||
*
|
||||
* @return {number} The left margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration.prototype.get_options_chart_marginLeft_ = function() {
|
||||
return 60;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the legend enabled options.
|
||||
*
|
||||
* @return {Function} The legend enabled options.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration.prototype.get_options_legend_enabled_ = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the bottom margin for the chart.
|
||||
*
|
||||
* @return {number} The right margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.voc_concentration.prototype.get_options_chart_marginBottom_ = function() {
|
||||
return 10;
|
||||
};
|
@ -24,6 +24,11 @@ beestat.component.header.prototype.rerender_on_breakpoint_ = true;
|
||||
beestat.component.header.prototype.decorate_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
const thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
||||
const ecobee_thermostat = beestat.cache.ecobee_thermostat[
|
||||
thermostat.ecobee_thermostat_id
|
||||
];
|
||||
|
||||
var pages;
|
||||
|
||||
pages = [
|
||||
@ -44,6 +49,17 @@ beestat.component.header.prototype.decorate_ = function(parent) {
|
||||
}
|
||||
];
|
||||
|
||||
if (
|
||||
beestat.user.has_early_access() === true &&
|
||||
ecobee_thermostat.model_number === 'aresSmart'
|
||||
) {
|
||||
pages.push({
|
||||
'layer': 'air_quality',
|
||||
'text': 'Air Quality',
|
||||
'icon': 'weather_windy'
|
||||
});
|
||||
}
|
||||
|
||||
var gutter = beestat.style.size.gutter;
|
||||
|
||||
var row = $.createElement('div').style({
|
||||
@ -89,7 +105,7 @@ beestat.component.header.prototype.decorate_ = function(parent) {
|
||||
.set_icon(page.icon)
|
||||
.set_text_color(beestat.style.color.bluegray.dark);
|
||||
|
||||
if (beestat.width > 650) {
|
||||
if (beestat.width > 800) {
|
||||
button.set_text(page.text);
|
||||
}
|
||||
|
||||
|
@ -34,12 +34,10 @@ beestat.component.input.checkbox.prototype.decorate_ = function(parent) {
|
||||
.innerText(this.label_)
|
||||
.addEventListener('click', function() {
|
||||
self.input_[0].click();
|
||||
// self.input_.checked(!self.input_.checked());
|
||||
});
|
||||
div.appendChild(text_label);
|
||||
|
||||
this.input_.addEventListener('change', function() {
|
||||
// console.log('input changed');
|
||||
self.dispatchEvent('change');
|
||||
});
|
||||
|
||||
|
374
js/component/modal/air_quality_detail_custom.js
Normal file
374
js/component/modal/air_quality_detail_custom.js
Normal file
@ -0,0 +1,374 @@
|
||||
/**
|
||||
* Custom date range for the Air Quality Detail chart.
|
||||
*/
|
||||
beestat.component.modal.air_quality_detail_custom = function() {
|
||||
beestat.component.modal.apply(this, arguments);
|
||||
this.state_.air_quality_detail_range_type = beestat.setting('air_quality_detail_range_type');
|
||||
this.state_.air_quality_detail_range_dynamic = beestat.setting('air_quality_detail_range_dynamic');
|
||||
this.state_.air_quality_detail_range_static_begin = beestat.setting('air_quality_detail_range_static_begin');
|
||||
this.state_.air_quality_detail_range_static_end = beestat.setting('air_quality_detail_range_static_end');
|
||||
this.state_.error = {
|
||||
'max_range': false,
|
||||
'invalid_range_begin': false,
|
||||
'invalid_range_end': false,
|
||||
'out_of_sync_range': false
|
||||
};
|
||||
};
|
||||
beestat.extend(beestat.component.modal.air_quality_detail_custom, beestat.component.modal);
|
||||
|
||||
/**
|
||||
* Decorate.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.air_quality_detail_custom.prototype.decorate_contents_ = function(parent) {
|
||||
parent.appendChild($.createElement('p').innerHTML('Choose a custom range to display on the Air Quality Detail chart. Max range is 7 days at a time and 90 days in the past.'));
|
||||
|
||||
this.decorate_range_type_(parent);
|
||||
|
||||
if (this.state_.air_quality_detail_range_type === 'dynamic') {
|
||||
this.decorate_range_dynamic_(parent);
|
||||
} else {
|
||||
this.decorate_range_static_(parent);
|
||||
}
|
||||
|
||||
this.decorate_error_(parent);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate the range type selector.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.air_quality_detail_custom.prototype.decorate_range_type_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
var button_group = new beestat.component.button_group();
|
||||
|
||||
button_group.add_button(new beestat.component.button()
|
||||
.set_background_hover_color(beestat.style.color.lightblue.base)
|
||||
.set_text_color('#fff')
|
||||
.set_background_color(
|
||||
this.state_.air_quality_detail_range_type === 'dynamic'
|
||||
? beestat.style.color.lightblue.base
|
||||
: beestat.style.color.bluegray.base
|
||||
)
|
||||
.set_text('Dynamic')
|
||||
.addEventListener('click', function() {
|
||||
self.state_.air_quality_detail_range_type = 'dynamic';
|
||||
self.rerender();
|
||||
}));
|
||||
|
||||
button_group.add_button(new beestat.component.button()
|
||||
.set_background_hover_color(beestat.style.color.lightblue.base)
|
||||
.set_text_color('#fff')
|
||||
.set_background_color(
|
||||
this.state_.air_quality_detail_range_type === 'static'
|
||||
? beestat.style.color.lightblue.base
|
||||
: beestat.style.color.bluegray.base
|
||||
)
|
||||
.set_text('Static')
|
||||
.addEventListener('click', function() {
|
||||
self.state_.air_quality_detail_range_type = 'static';
|
||||
self.rerender();
|
||||
}));
|
||||
|
||||
(new beestat.component.title('Range Type')).render(parent);
|
||||
var row = $.createElement('div').addClass('row');
|
||||
parent.appendChild(row);
|
||||
var column = $.createElement('div').addClass(['column column_12']);
|
||||
row.appendChild(column);
|
||||
button_group.render(column);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate the static range inputs.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.air_quality_detail_custom.prototype.decorate_range_static_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
var air_quality_detail_static_range_begin;
|
||||
var air_quality_detail_static_range_end;
|
||||
|
||||
/**
|
||||
* Check whether or not a value is outside of where data is synced.
|
||||
*/
|
||||
var check_out_of_sync_range = function() {
|
||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
||||
var min = moment.max(
|
||||
moment(thermostat.sync_begin),
|
||||
moment().subtract(1, 'month')
|
||||
);
|
||||
var max = moment(thermostat.sync_end);
|
||||
|
||||
var begin = moment.min(
|
||||
moment(air_quality_detail_static_range_begin.get_value()),
|
||||
moment(air_quality_detail_static_range_end.get_value())
|
||||
);
|
||||
|
||||
var end = moment.max(
|
||||
moment(air_quality_detail_static_range_begin.get_value() + ' 00:00:00'),
|
||||
moment(air_quality_detail_static_range_end.get_value() + ' 23:59:59')
|
||||
);
|
||||
|
||||
if (
|
||||
begin.isBefore(min) === true ||
|
||||
end.isAfter(max) === true
|
||||
) {
|
||||
self.state_.error.out_of_sync_range = true;
|
||||
} else {
|
||||
self.state_.error.out_of_sync_range = false;
|
||||
}
|
||||
};
|
||||
|
||||
air_quality_detail_static_range_begin = new beestat.component.input.text()
|
||||
.set_style({
|
||||
'width': 110,
|
||||
'text-align': 'center',
|
||||
'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
|
||||
})
|
||||
.set_attribute({
|
||||
'maxlength': 10
|
||||
})
|
||||
.set_icon('calendar')
|
||||
.set_value(this.state_.air_quality_detail_range_static_begin);
|
||||
|
||||
air_quality_detail_static_range_begin.addEventListener('blur', function() {
|
||||
var m = moment(this.get_value());
|
||||
if (m.isValid() === true) {
|
||||
self.state_.error.invalid_range_begin = false;
|
||||
|
||||
var value = m.format('M/D/YYYY');
|
||||
|
||||
var diff = Math.abs(m.diff(moment(air_quality_detail_static_range_end.get_value()), 'day')) + 1;
|
||||
if (diff > 7) {
|
||||
self.state_.error.max_range = true;
|
||||
} else {
|
||||
self.state_.error.max_range = false;
|
||||
}
|
||||
|
||||
check_out_of_sync_range();
|
||||
|
||||
self.state_.air_quality_detail_range_static_begin = value;
|
||||
self.rerender();
|
||||
} else {
|
||||
self.state_.air_quality_detail_range_static_begin = this.get_value();
|
||||
self.state_.error.invalid_range_begin = true;
|
||||
self.rerender();
|
||||
}
|
||||
});
|
||||
|
||||
air_quality_detail_static_range_end = new beestat.component.input.text()
|
||||
.set_style({
|
||||
'width': 110,
|
||||
'text-align': 'center',
|
||||
'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
|
||||
})
|
||||
.set_attribute({
|
||||
'maxlength': 10
|
||||
})
|
||||
.set_icon('calendar')
|
||||
.set_value(this.state_.air_quality_detail_range_static_end);
|
||||
|
||||
air_quality_detail_static_range_end.addEventListener('blur', function() {
|
||||
var m = moment(this.get_value());
|
||||
if (m.isValid() === true) {
|
||||
self.state_.error.invalid_range_end = false;
|
||||
|
||||
var value = m.format('M/D/YYYY');
|
||||
|
||||
var diff = Math.abs(m.diff(moment(air_quality_detail_static_range_begin.get_value()), 'day')) + 1;
|
||||
if (diff > 7) {
|
||||
self.state_.error.max_range = true;
|
||||
} else {
|
||||
self.state_.error.max_range = false;
|
||||
}
|
||||
|
||||
check_out_of_sync_range();
|
||||
|
||||
self.state_.air_quality_detail_range_static_end = value;
|
||||
self.rerender();
|
||||
} else {
|
||||
self.state_.air_quality_detail_range_static_end = this.get_value();
|
||||
self.state_.error.invalid_range_end = true;
|
||||
self.rerender();
|
||||
}
|
||||
});
|
||||
|
||||
var span;
|
||||
|
||||
var row = $.createElement('div').addClass('row');
|
||||
parent.appendChild(row);
|
||||
var column = $.createElement('div').addClass(['column column_12']);
|
||||
row.appendChild(column);
|
||||
|
||||
span = $.createElement('span').style('display', 'inline-block');
|
||||
air_quality_detail_static_range_begin.render(span);
|
||||
column.appendChild(span);
|
||||
|
||||
span = $.createElement('span')
|
||||
.style({
|
||||
'display': 'inline-block',
|
||||
'margin-left': beestat.style.size.gutter,
|
||||
'margin-right': beestat.style.size.gutter
|
||||
})
|
||||
.innerText('to');
|
||||
column.appendChild(span);
|
||||
|
||||
span = $.createElement('span').style('display', 'inline-block');
|
||||
air_quality_detail_static_range_end.render(span);
|
||||
column.appendChild(span);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate the dynamic range input.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.air_quality_detail_custom.prototype.decorate_range_dynamic_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
var air_quality_detail_range_dynamic = new beestat.component.input.text()
|
||||
.set_style({
|
||||
'width': 75,
|
||||
'text-align': 'center',
|
||||
'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
|
||||
})
|
||||
.set_attribute({
|
||||
'maxlength': 1
|
||||
})
|
||||
.set_icon('pound')
|
||||
.set_value(beestat.setting('air_quality_detail_range_dynamic'));
|
||||
|
||||
air_quality_detail_range_dynamic.addEventListener('blur', function() {
|
||||
var value = parseInt(this.get_value(), 10);
|
||||
if (isNaN(value) === true || value === 0) {
|
||||
value = 1;
|
||||
} else if (value > 7) {
|
||||
value = 7;
|
||||
}
|
||||
this.set_value(value);
|
||||
self.state_.air_quality_detail_range_dynamic = value;
|
||||
});
|
||||
|
||||
var span;
|
||||
|
||||
var row = $.createElement('div').addClass('row');
|
||||
parent.appendChild(row);
|
||||
var column = $.createElement('div').addClass(['column column_12']);
|
||||
row.appendChild(column);
|
||||
|
||||
span = $.createElement('span').style('display', 'inline-block');
|
||||
air_quality_detail_range_dynamic.render(span);
|
||||
column.appendChild(span);
|
||||
|
||||
span = $.createElement('span')
|
||||
.style({
|
||||
'display': 'inline-block',
|
||||
'margin-left': beestat.style.size.gutter
|
||||
})
|
||||
.innerText('days');
|
||||
column.appendChild(span);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate the error area.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.air_quality_detail_custom.prototype.decorate_error_ = function(parent) {
|
||||
var div = $.createElement('div').style('color', beestat.style.color.red.base);
|
||||
if (this.state_.error.max_range === true) {
|
||||
div.appendChild($.createElement('div').innerText('Max range is 7 days.'));
|
||||
}
|
||||
if (this.state_.error.invalid_range_begin === true) {
|
||||
div.appendChild($.createElement('div').innerText('Invalid begin date.'));
|
||||
}
|
||||
if (this.state_.error.invalid_range_end === true) {
|
||||
div.appendChild($.createElement('div').innerText('Invalid end date.'));
|
||||
}
|
||||
if (this.state_.error.out_of_sync_range === true) {
|
||||
div.appendChild($.createElement('div').innerText('Detail not available for this range.'));
|
||||
}
|
||||
parent.appendChild(div);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @return {string} Title
|
||||
*/
|
||||
beestat.component.modal.air_quality_detail_custom.prototype.get_title_ = function() {
|
||||
return 'Air Quality Detail - Custom Range';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the buttons that go on the bottom of this modal.
|
||||
*
|
||||
* @return {[beestat.component.button]} The buttons.
|
||||
*/
|
||||
beestat.component.modal.air_quality_detail_custom.prototype.get_buttons_ = function() {
|
||||
var self = this;
|
||||
|
||||
var cancel = new beestat.component.button()
|
||||
.set_background_color('#fff')
|
||||
.set_text_color(beestat.style.color.gray.base)
|
||||
.set_text_hover_color(beestat.style.color.red.base)
|
||||
.set_text('Cancel')
|
||||
.addEventListener('click', function() {
|
||||
self.dispose();
|
||||
});
|
||||
|
||||
var save;
|
||||
if (
|
||||
this.state_.error.max_range === true ||
|
||||
this.state_.error.invalid_range_begin === true ||
|
||||
this.state_.error.invalid_range_end === true ||
|
||||
this.state_.error.out_of_sync_range === true
|
||||
) {
|
||||
save = new beestat.component.button()
|
||||
.set_background_color(beestat.style.color.gray.base)
|
||||
.set_text_color('#fff')
|
||||
.set_text('Save');
|
||||
} else {
|
||||
save = new beestat.component.button()
|
||||
.set_background_color(beestat.style.color.green.base)
|
||||
.set_background_hover_color(beestat.style.color.green.light)
|
||||
.set_text_color('#fff')
|
||||
.set_text('Save')
|
||||
.addEventListener('click', function() {
|
||||
this
|
||||
.set_background_color(beestat.style.color.gray.base)
|
||||
.set_background_hover_color()
|
||||
.removeEventListener('click');
|
||||
|
||||
if (moment(self.state_.air_quality_detail_range_static_begin).isAfter(moment(self.state_.air_quality_detail_range_static_end)) === true) {
|
||||
var temp = self.state_.air_quality_detail_range_static_begin;
|
||||
self.state_.air_quality_detail_range_static_begin = self.state_.air_quality_detail_range_static_end;
|
||||
self.state_.air_quality_detail_range_static_end = temp;
|
||||
}
|
||||
|
||||
beestat.cache.delete('runtime_thermostat');
|
||||
beestat.cache.delete('runtime_sensor');
|
||||
beestat.setting(
|
||||
{
|
||||
'air_quality_detail_range_type': self.state_.air_quality_detail_range_type,
|
||||
'air_quality_detail_range_dynamic': self.state_.air_quality_detail_range_dynamic,
|
||||
'air_quality_detail_range_static_begin': self.state_.air_quality_detail_range_static_begin,
|
||||
'air_quality_detail_range_static_end': self.state_.air_quality_detail_range_static_end
|
||||
},
|
||||
undefined,
|
||||
function() {
|
||||
self.dispose();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
cancel,
|
||||
save
|
||||
];
|
||||
};
|
@ -46,6 +46,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
||||
echo '<script src="/js/layer/compare.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/layer/analyze.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/layer/settings.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/layer/air_quality.js"></script>' . PHP_EOL;
|
||||
|
||||
// Component
|
||||
echo '<script src="/js/component.js"></script>' . PHP_EOL;
|
||||
@ -67,6 +68,8 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
||||
echo '<script src="/js/component/card/temperature_profiles.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/card/metrics.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/card/settings.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/card/air_quality_detail.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/card/voc_summary.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/chart.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;
|
||||
@ -74,6 +77,9 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
||||
echo '<script src="/js/component/chart/runtime_thermostat_detail_equipment.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/chart/runtime_sensor_detail_temperature.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/chart/runtime_sensor_detail_occupancy.js"></script>' . PHP_EOL;
|
||||
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/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;
|
||||
@ -96,6 +102,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
||||
echo '<script src="/js/component/modal/weather.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/modal/patreon_status.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/modal/newsletter.js"></script>' . PHP_EOL;
|
||||
echo '<script src="/js/component/modal/air_quality_detail_custom.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;
|
||||
|
64
js/layer/air_quality.js
Normal file
64
js/layer/air_quality.js
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Air Quality layer.
|
||||
*/
|
||||
beestat.layer.air_quality = function() {
|
||||
beestat.layer.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.layer.air_quality, beestat.layer);
|
||||
|
||||
beestat.layer.air_quality.prototype.decorate_ = function(parent) {
|
||||
const thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
||||
|
||||
/*
|
||||
* 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('air_quality')).render(parent);
|
||||
|
||||
// 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.early_access(
|
||||
thermostat.thermostat_id
|
||||
),
|
||||
'size': 12
|
||||
}
|
||||
]);
|
||||
|
||||
cards.push([
|
||||
{
|
||||
'card': new beestat.component.card.air_quality_detail(
|
||||
thermostat.thermostat_id
|
||||
),
|
||||
'size': 12
|
||||
}
|
||||
]);
|
||||
|
||||
cards.push([
|
||||
{
|
||||
'card': new beestat.component.card.voc_summary(
|
||||
thermostat.thermostat_id
|
||||
),
|
||||
'size': 12
|
||||
}
|
||||
]);
|
||||
|
||||
(new beestat.component.layout(cards)).render(parent);
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/*
|
||||
Highcharts JS vv7.1.2 custom build (2019-07-17)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user