mirror of
https://github.com/beestat/app.git
synced 2026-05-24 08:02:27 -04:00
Sensor Data 🤞
Finished outstanding features, fixed lots of bugs, enabled for the general population. #17
This commit is contained in:
@@ -1,361 +0,0 @@
|
||||
/**
|
||||
* Runtime sensor detail chart.
|
||||
*
|
||||
* @param {object} data The chart data.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail = function(data) {
|
||||
this.data_ = data;
|
||||
|
||||
beestat.component.chart.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.chart.runtime_sensor_detail, beestat.component.chart);
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_labels_formatter_.
|
||||
*
|
||||
* @return {Function} xAxis labels formatter.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail.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(' ');
|
||||
};
|
||||
};
|
||||
|
||||
beestat.component.chart.runtime_sensor_detail.prototype.get_options_legend_labelFormatter_ = function() {
|
||||
var self = this;
|
||||
return function() {
|
||||
return self.data_.metadata.series[this.name].name;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_series_.
|
||||
*
|
||||
* @return {Array} All of the series to display on the chart.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail.prototype.get_options_series_ = function() {
|
||||
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.data_.metadata.sensors.forEach(function(sensor, i) {
|
||||
if (sensor.thermostat_id === beestat.setting('thermostat_id')) {
|
||||
series.push({
|
||||
'name': 'temperature_' + sensor.sensor_id,
|
||||
'data': self.data_.series['temperature_' + sensor.sensor_id],
|
||||
'color': colors[i],
|
||||
'yAxis': 0,
|
||||
'type': 'spline',
|
||||
'lineWidth': 1
|
||||
});
|
||||
|
||||
// var sensor_count = (Object.keys(self.data_.series).length - 1) / 2;
|
||||
|
||||
series.push({
|
||||
'linkedTo': ':previous',
|
||||
'name': 'occupancy_' + sensor.sensor_id,
|
||||
'data': self.data_.series['occupancy_' + sensor.sensor_id],
|
||||
'color': colors[i],
|
||||
'yAxis': 1,
|
||||
'type': 'line',
|
||||
'lineWidth': beestat.component.chart.runtime_sensor_detail.get_swimlane_properties(self.data_.metadata.sensors.length, 1).line_width,
|
||||
'linecap': 'square',
|
||||
'className': 'crisp_edges'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
series.push({
|
||||
'name': '',
|
||||
'data': self.data_.series.dummy,
|
||||
'yAxis': 1,
|
||||
'type': 'line',
|
||||
'lineWidth': 0,
|
||||
'showInLegend': false
|
||||
});
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_yAxis_.
|
||||
*
|
||||
* @return {Array} The y-axis options.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail.prototype.get_options_yAxis_ = function() {
|
||||
/**
|
||||
* Highcharts doesn't seem to respect axis behavior well so just overriding
|
||||
* it completely here.
|
||||
*/
|
||||
var y_min = Math.floor((this.data_.metadata.chart.y_min) / 5) * 5;
|
||||
var y_max = Math.ceil((this.data_.metadata.chart.y_max) / 5) * 5;
|
||||
|
||||
y_max += ((beestat.setting('temperature_unit') === '°F') ? 10 : 4);
|
||||
|
||||
var tick_positions = [];
|
||||
var tick_interval = (beestat.setting('temperature_unit') === '°F') ? 5 : 2;
|
||||
var current_tick_position =
|
||||
Math.floor(y_min / tick_interval) * tick_interval;
|
||||
while (current_tick_position <= y_max) {
|
||||
tick_positions.push(current_tick_position);
|
||||
current_tick_position += tick_interval;
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
// Temperature
|
||||
{
|
||||
'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');
|
||||
}
|
||||
},
|
||||
'tickPositions': tick_positions
|
||||
},
|
||||
|
||||
// Swimlanes
|
||||
{
|
||||
'height': 100,
|
||||
// 'top': 0,
|
||||
'min': 0,
|
||||
'max': 100,
|
||||
'reversed': true,
|
||||
'gridLineWidth': 0,
|
||||
'title': {'text': null},
|
||||
'labels': {'enabled': false},
|
||||
'plotBands': {
|
||||
'zIndex': 2,
|
||||
// 'color': 'red',
|
||||
'color': beestat.style.color.bluegray.dark,
|
||||
'from': 0,
|
||||
'to': 51
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_tooltip_formatter_.
|
||||
*
|
||||
* @return {Function} The tooltip formatter.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail.prototype.get_options_tooltip_formatter_ = function() {
|
||||
var self = this;
|
||||
|
||||
return function() {
|
||||
var sections = [];
|
||||
var group = [];
|
||||
|
||||
// Get all the point values and index them by series_code for reference.
|
||||
var values = {};
|
||||
this.points.forEach(function(point) {
|
||||
values[point.series.name] = point.y;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a couple of other properties and index them by series_code for
|
||||
* reference. This dives up to the chart itself because the tooltip shows
|
||||
* all series unless explicitly disabled and those aren't always in the
|
||||
* points array.
|
||||
*/
|
||||
var colors = {};
|
||||
var visible = {};
|
||||
self.chart_.series.forEach(function(series) {
|
||||
colors[series.name] = series.color;
|
||||
visible[series.name] = series.visible;
|
||||
});
|
||||
|
||||
for (var series_code in self.data_.series) {
|
||||
var label;
|
||||
var value;
|
||||
var color;
|
||||
|
||||
if (series_code.includes('temperature') && visible[series_code] === true) {
|
||||
label = self.data_.metadata.series[series_code].name;
|
||||
color = colors[series_code];
|
||||
if (values[series_code] === undefined) {
|
||||
value = '-';
|
||||
} else {
|
||||
value = beestat.temperature({
|
||||
'temperature': values[series_code],
|
||||
'convert': false,
|
||||
'units': true
|
||||
});
|
||||
}
|
||||
|
||||
var occupancy_key = series_code.replace('temperature', 'occupancy');
|
||||
if (values[occupancy_key] !== undefined && values[occupancy_key] !== null) {
|
||||
value += ' ●';
|
||||
}
|
||||
|
||||
group.push({
|
||||
'label': label,
|
||||
'value': value,
|
||||
'color': color
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (group.length === 0) {
|
||||
group.push({
|
||||
'label': 'No data',
|
||||
'value': '',
|
||||
'color': beestat.style.color.gray.base
|
||||
});
|
||||
}
|
||||
|
||||
sections.push(group);
|
||||
|
||||
var title = this.x.format('ddd, MMM D @ h:mma');
|
||||
|
||||
return self.tooltip_formatter_helper_(
|
||||
title,
|
||||
sections
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get properties of swimlane series.
|
||||
*
|
||||
* @param {number} count The number of swimlanes present.
|
||||
* @param {number} i Which swimlane this is.
|
||||
*
|
||||
* @return {Object} The swimlane line width and y position.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail.get_swimlane_properties = function(count, i) {
|
||||
// Available height for all swimlanes
|
||||
var height = 50;
|
||||
|
||||
// Some sensible max height if you have very few sensors.
|
||||
var max_line_width = 16;
|
||||
|
||||
// Spacing. This is arbitrary...spacing decreases to 0 after you hit 15 sensors.
|
||||
var spacing = Math.floor(15 / count);
|
||||
|
||||
// Base line width is a percentage height of the container.
|
||||
var line_width = Math.floor(height / count);
|
||||
|
||||
// Cap to a max line width.
|
||||
line_width = Math.min(line_width, max_line_width);
|
||||
|
||||
// Set y, then shift it up slightly because the width expands out from the center.
|
||||
var y = (line_width * i);
|
||||
y += Math.round((line_width / 2));
|
||||
|
||||
// Make the lines slightly less tall to create space between them.
|
||||
line_width -= spacing;
|
||||
|
||||
// Center within the swimlane area.
|
||||
var occupied_space = (line_width * count) + (spacing * count);
|
||||
var empty_space = height - occupied_space;
|
||||
// y += (empty_space / 2);
|
||||
|
||||
return {
|
||||
'line_width': line_width,
|
||||
'y': y
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This is unfortunate. Axis heights can be done in either pixels or
|
||||
* percentages. If you use percentages, it's percentage of the plot height
|
||||
* which includes the y-axis labels and the legend. These heights are
|
||||
* variable, so setting a 20% height on the swimlane axis means the axis
|
||||
* height can actually change depending on external factors. When trying to
|
||||
* accurately position lanes, this variation can mess up pixel-perfect
|
||||
* spacing.
|
||||
*
|
||||
* If you use pixels you can get more exact, but since there's no way to
|
||||
* determine the available height for the chart (plot area minus y-axis labels
|
||||
* minus legend), you're left in the dark on how high to make your "rest of
|
||||
* the space" axis. There's also no way to set the height of one axis and have
|
||||
* the other axis take the remaining space.
|
||||
*
|
||||
* So, as a workaround...I simply overlay the swimlanes on the top of a
|
||||
* full-height temperature chart. Then I draw a rectangle on top of y-axis
|
||||
* labels I want to hide so it appears to be on it's own.
|
||||
*
|
||||
* Helpful: https://www.highcharts.com/demo/renderer
|
||||
*
|
||||
* @return {object} The events list for the chart.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail.prototype.get_options_chart_events_ = function() {
|
||||
return {
|
||||
'load': function() {
|
||||
this.renderer.rect(0, 0, 30, 80)
|
||||
.attr({
|
||||
'fill': beestat.style.color.bluegray.base,
|
||||
'zIndex': 10
|
||||
})
|
||||
.add();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* See comment on get_options_chart_events_. This is done separately to
|
||||
* override the normal load event rectangle draw because on export I also add
|
||||
* padding and a title which screws up the positioning a bit.
|
||||
*
|
||||
* @return {object} The events list for the chart on export.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail.prototype.get_options_exporting_chart_events_ = function() {
|
||||
return {
|
||||
'load': function() {
|
||||
this.renderer.rect(beestat.style.size.gutter, 60, 30, 60)
|
||||
.attr({
|
||||
'fill': beestat.style.color.bluegray.base,
|
||||
'zIndex': 10
|
||||
})
|
||||
.add();
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Runtime sensor detail chart.
|
||||
*
|
||||
* @param {object} data The chart data.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_occupancy = function(data) {
|
||||
this.data_ = data;
|
||||
|
||||
beestat.component.chart.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.chart.runtime_sensor_detail_occupancy, beestat.component.chart);
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_labels_formatter_.
|
||||
*
|
||||
* @return {Function} xAxis labels formatter.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_occupancy.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.runtime_sensor_detail_occupancy.prototype.get_options_series_ = function() {
|
||||
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) {
|
||||
series.push({
|
||||
'name': 'occupancy_' + sensor.sensor_id,
|
||||
'data': self.data_.series['occupancy_' + sensor.sensor_id],
|
||||
'color': colors[i],
|
||||
'yAxis': 0,
|
||||
'type': 'line',
|
||||
'lineWidth': beestat.component.chart.runtime_sensor_detail_occupancy.get_swimlane_properties(self.data_.metadata.sensors.length, 1).line_width,
|
||||
'linecap': 'square',
|
||||
'className': 'crisp_edges'
|
||||
});
|
||||
});
|
||||
|
||||
series.push({
|
||||
'name': '',
|
||||
'data': self.data_.series.dummy,
|
||||
'yAxis': 0,
|
||||
'type': 'line',
|
||||
'lineWidth': 0
|
||||
});
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_yAxis_.
|
||||
*
|
||||
* @return {Array} The y-axis options.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_occupancy.prototype.get_options_yAxis_ = function() {
|
||||
return [
|
||||
{
|
||||
'min': 0,
|
||||
'max': 50,
|
||||
|
||||
// Keeps the chart from ending on a multiple of whatever the tick interval gets set to.
|
||||
'endOnTick': false,
|
||||
|
||||
// 'min': 0,
|
||||
// 'minRange': 100,
|
||||
// 'ceiling': 100
|
||||
|
||||
'reversed': true,
|
||||
'gridLineWidth': 0,
|
||||
'title': {'text': null},
|
||||
'labels': {'enabled': false},
|
||||
'plotBands': [
|
||||
{
|
||||
'zIndex': 2,
|
||||
'color': beestat.style.color.bluegray.dark,
|
||||
'from': 0,
|
||||
'to': 50
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get properties of swimlane series.
|
||||
*
|
||||
* @param {number} count The number of swimlanes present.
|
||||
* @param {number} i Which swimlane this is.
|
||||
*
|
||||
* @return {Object} The swimlane line width and y position.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_occupancy.get_swimlane_properties = function(count, i) {
|
||||
// Available height for all swimlanes
|
||||
var height = 50;
|
||||
|
||||
// Some sensible max height if you have very few sensors.
|
||||
var max_line_width = 16;
|
||||
|
||||
// Spacing. This is arbitrary...spacing decreases to 0 after you hit 15 sensors.
|
||||
var spacing = Math.floor(15 / count);
|
||||
spacing = Math.min(spacing, 4);
|
||||
|
||||
// Base line width is a percentage height of the container.
|
||||
var line_width = Math.floor(height / count);
|
||||
|
||||
// Cap to a max line width.
|
||||
line_width = Math.min(line_width, max_line_width);
|
||||
|
||||
// Set y, then shift it up slightly because the width expands out from the center.
|
||||
var y = (line_width * i);
|
||||
y += Math.round((line_width / 2));
|
||||
|
||||
// Make the lines slightly less tall to create space between them.
|
||||
line_width -= spacing;
|
||||
|
||||
// Center within the swimlane area.
|
||||
// var occupied_space = (line_width * count) + (spacing * count);
|
||||
// var empty_space = height - occupied_space;
|
||||
// y += (empty_space / 2);
|
||||
|
||||
return {
|
||||
'line_width': line_width,
|
||||
'y': y
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the height of the chart. For really precise charts, make sure to
|
||||
* include relevant spacing.
|
||||
*
|
||||
* @return {number} The height of the chart.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_occupancy.prototype.get_options_chart_height_ = function() {
|
||||
return 50 + this.get_options_chart_spacing_()[0] + this.get_options_chart_spacing_()[2];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the legend enabled options.
|
||||
*
|
||||
* @return {Function} The legend enabled options.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_occupancy.prototype.get_options_legend_enabled_ = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the left margin for the chart.
|
||||
*
|
||||
* @return {number} The left margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_occupancy.prototype.get_options_chart_marginLeft_ = function() {
|
||||
return 40;
|
||||
};
|
||||
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* Runtime sensor detail chart.
|
||||
*
|
||||
* @param {object} data The chart data.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_temperature = function(data) {
|
||||
this.data_ = data;
|
||||
|
||||
beestat.component.chart.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.chart.runtime_sensor_detail_temperature, beestat.component.chart);
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_labels_formatter_.
|
||||
*
|
||||
* @return {Function} xAxis labels formatter.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_temperature.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(' ');
|
||||
};
|
||||
};
|
||||
|
||||
beestat.component.chart.runtime_sensor_detail_temperature.prototype.get_options_legend_labelFormatter_ = function() {
|
||||
var self = this;
|
||||
return function() {
|
||||
return self.data_.metadata.series[this.name].name;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_series_.
|
||||
*
|
||||
* @return {Array} All of the series to display on the chart.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_temperature.prototype.get_options_series_ = function() {
|
||||
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) {
|
||||
series.push({
|
||||
'name': 'temperature_' + sensor.sensor_id,
|
||||
'data': self.data_.series['temperature_' + sensor.sensor_id],
|
||||
'color': colors[i],
|
||||
'yAxis': 0,
|
||||
'type': 'spline',
|
||||
'lineWidth': 1,
|
||||
'events': {
|
||||
'legendItemClick': function() {
|
||||
// Delay the event dispatch so the series is actually toggled to the correct visibility.
|
||||
setTimeout(function() {
|
||||
self.dispatchEvent('legend_item_click');
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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.runtime_sensor_detail_temperature.prototype.get_options_yAxis_ = function() {
|
||||
return [
|
||||
{
|
||||
'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');
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_tooltip_formatter_.
|
||||
*
|
||||
* @return {Function} The tooltip formatter.
|
||||
*/
|
||||
beestat.component.chart.runtime_sensor_detail_temperature.prototype.get_options_tooltip_formatter_ = function() {
|
||||
var self = this;
|
||||
|
||||
return function() {
|
||||
var sections = [];
|
||||
var group = [];
|
||||
|
||||
// Get all the point values and index them by series_code for reference.
|
||||
var values = {};
|
||||
this.points.forEach(function(point) {
|
||||
values[point.series.name] = point.y;
|
||||
|
||||
var occupancy_key = point.series.name.replace('temperature', 'occupancy');
|
||||
if (self.data_.metadata.series[occupancy_key] !== undefined) {
|
||||
values[occupancy_key] =
|
||||
self.data_.metadata.series[occupancy_key].data[point.x.valueOf()];
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a couple of other properties and index them by series_code for
|
||||
* reference. This dives up to the chart itself because the tooltip shows
|
||||
* all series unless explicitly disabled and those aren't always in the
|
||||
* points array.
|
||||
*/
|
||||
var colors = {};
|
||||
var visible = {};
|
||||
self.chart_.series.forEach(function(series) {
|
||||
colors[series.name] = series.color;
|
||||
visible[series.name] = series.visible;
|
||||
});
|
||||
|
||||
for (var series_code in self.data_.series) {
|
||||
var label;
|
||||
var value;
|
||||
var color;
|
||||
|
||||
if (visible[series_code] === true) {
|
||||
label = self.data_.metadata.series[series_code].name;
|
||||
color = colors[series_code];
|
||||
if (values[series_code] === undefined) {
|
||||
value = '-';
|
||||
} else {
|
||||
value = beestat.temperature({
|
||||
'temperature': values[series_code],
|
||||
'convert': false,
|
||||
'units': true
|
||||
});
|
||||
}
|
||||
|
||||
var occupancy_key = series_code.replace('temperature', 'occupancy');
|
||||
if (values[occupancy_key] !== undefined && values[occupancy_key] !== null) {
|
||||
value += ' ●';
|
||||
}
|
||||
|
||||
group.push({
|
||||
'label': label,
|
||||
'value': value,
|
||||
'color': color
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (group.length === 0) {
|
||||
group.push({
|
||||
'label': 'No data',
|
||||
'value': '',
|
||||
'color': beestat.style.color.gray.base
|
||||
});
|
||||
}
|
||||
|
||||
sections.push(group);
|
||||
|
||||
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.runtime_sensor_detail_temperature.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.runtime_sensor_detail_temperature.prototype.get_options_chart_height_ = function() {
|
||||
return 300;
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Runtime sensor detail chart.
|
||||
*
|
||||
* @param {object} data The chart data.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail_equipment = function(data) {
|
||||
this.data_ = data;
|
||||
|
||||
beestat.component.chart.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.chart.runtime_thermostat_detail_equipment, beestat.component.chart);
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_labels_formatter_.
|
||||
*
|
||||
* @return {Function} xAxis labels formatter.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail_equipment.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.runtime_thermostat_detail_equipment.prototype.get_options_series_ = function() {
|
||||
var self = this;
|
||||
var series = [];
|
||||
|
||||
[
|
||||
'calendar_event_smartrecovery',
|
||||
'calendar_event_home',
|
||||
'calendar_event_away',
|
||||
'calendar_event_sleep',
|
||||
'calendar_event_smarthome',
|
||||
'calendar_event_smartaway',
|
||||
'calendar_event_hold',
|
||||
'calendar_event_vacation',
|
||||
'calendar_event_quicksave',
|
||||
'calendar_event_other',
|
||||
'calendar_event_custom',
|
||||
'compressor_heat_1',
|
||||
'compressor_heat_2',
|
||||
'auxiliary_heat_1',
|
||||
'auxiliary_heat_2',
|
||||
'compressor_cool_1',
|
||||
'compressor_cool_2',
|
||||
'fan',
|
||||
'humidifier',
|
||||
'dehumidifier',
|
||||
'ventilator',
|
||||
'economizer'
|
||||
].forEach(function(series_code) {
|
||||
if (self.data_.metadata.series[series_code].active === true) {
|
||||
var line_width;
|
||||
if (
|
||||
series_code.includes('heat') === true ||
|
||||
series_code.includes('cool') === true
|
||||
) {
|
||||
line_width = 12;
|
||||
} else {
|
||||
line_width = 6;
|
||||
}
|
||||
|
||||
series.push({
|
||||
'name': series_code,
|
||||
'data': self.data_.series[series_code],
|
||||
'color': beestat.series[series_code].color,
|
||||
'yAxis': 0,
|
||||
'type': 'line',
|
||||
'lineWidth': line_width,
|
||||
'linecap': 'square',
|
||||
'className': 'crisp_edges'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override for get_options_yAxis_.
|
||||
*
|
||||
* @return {Array} The y-axis options.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail_equipment.prototype.get_options_yAxis_ = function() {
|
||||
return [
|
||||
{
|
||||
'min': 0,
|
||||
'max': 44,
|
||||
|
||||
// Keeps the chart from ending on a multiple of whatever the tick interval gets set to.
|
||||
'endOnTick': false,
|
||||
|
||||
'reversed': true,
|
||||
'gridLineWidth': 0,
|
||||
'title': {'text': null},
|
||||
'labels': {'enabled': false}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the height of the chart.
|
||||
*
|
||||
* @return {number} The height of the chart.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail_equipment.prototype.get_options_chart_height_ = function() {
|
||||
return 44;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the legend enabled options.
|
||||
*
|
||||
* @return {Function} The legend enabled options.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail_equipment.prototype.get_options_legend_enabled_ = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the left margin for the chart.
|
||||
*
|
||||
* @return {number} The left margin for the chart.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail_equipment.prototype.get_options_chart_marginLeft_ = function() {
|
||||
return 40;
|
||||
};
|
||||
+46
-74
@@ -3,19 +3,19 @@
|
||||
*
|
||||
* @param {object} data The chart data.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail = function(data) {
|
||||
beestat.component.chart.runtime_thermostat_detail_temperature = function(data) {
|
||||
this.data_ = data;
|
||||
|
||||
beestat.component.chart.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.chart.runtime_thermostat_detail, beestat.component.chart);
|
||||
beestat.extend(beestat.component.chart.runtime_thermostat_detail_temperature, beestat.component.chart);
|
||||
|
||||
/**
|
||||
* Override for get_options_xAxis_labels_formatter_.
|
||||
*
|
||||
* @return {Function} xAxis labels formatter.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail.prototype.get_options_xAxis_labels_formatter_ = function() {
|
||||
beestat.component.chart.runtime_thermostat_detail_temperature.prototype.get_options_xAxis_labels_formatter_ = function() {
|
||||
var current_day;
|
||||
var current_hour;
|
||||
|
||||
@@ -43,7 +43,7 @@ beestat.component.chart.runtime_thermostat_detail.prototype.get_options_xAxis_la
|
||||
*
|
||||
* @return {Array} All of the series to display on the chart.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail.prototype.get_options_series_ = function() {
|
||||
beestat.component.chart.runtime_thermostat_detail_temperature.prototype.get_options_series_ = function() {
|
||||
var self = this;
|
||||
var series = [];
|
||||
|
||||
@@ -103,60 +103,10 @@ beestat.component.chart.runtime_thermostat_detail.prototype.get_options_series_
|
||||
}
|
||||
});
|
||||
|
||||
// Swimlanes
|
||||
[
|
||||
'calendar_event_smartrecovery',
|
||||
'calendar_event_home',
|
||||
'calendar_event_away',
|
||||
'calendar_event_sleep',
|
||||
'calendar_event_smarthome',
|
||||
'calendar_event_smartaway',
|
||||
'calendar_event_hold',
|
||||
'calendar_event_vacation',
|
||||
'calendar_event_quicksave',
|
||||
'calendar_event_other',
|
||||
'calendar_event_custom',
|
||||
'compressor_heat_1',
|
||||
'compressor_heat_2',
|
||||
'auxiliary_heat_1',
|
||||
'auxiliary_heat_2',
|
||||
'compressor_cool_1',
|
||||
'compressor_cool_2',
|
||||
'fan',
|
||||
'humidifier',
|
||||
'dehumidifier',
|
||||
'ventilator',
|
||||
'economizer'
|
||||
].forEach(function(series_code) {
|
||||
if (self.data_.metadata.series[series_code].active === true) {
|
||||
var line_width;
|
||||
if (
|
||||
series_code.includes('heat') === true ||
|
||||
series_code.includes('cool') === true
|
||||
) {
|
||||
line_width = 12;
|
||||
} else {
|
||||
line_width = 6;
|
||||
}
|
||||
|
||||
series.push({
|
||||
'name': series_code,
|
||||
'data': self.data_.series[series_code],
|
||||
'color': beestat.series[series_code].color,
|
||||
'yAxis': 2,
|
||||
'type': 'line',
|
||||
'lineWidth': line_width,
|
||||
'linecap': 'square',
|
||||
'className': 'crisp_edges',
|
||||
'showInLegend': false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
series.push({
|
||||
'name': '',
|
||||
'data': self.data_.series.dummy,
|
||||
'yAxis': 2,
|
||||
'yAxis': 0,
|
||||
'type': 'line',
|
||||
'lineWidth': 0,
|
||||
'showInLegend': false
|
||||
@@ -170,7 +120,7 @@ beestat.component.chart.runtime_thermostat_detail.prototype.get_options_series_
|
||||
*
|
||||
* @return {Array} The y-axis options.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail.prototype.get_options_yAxis_ = function() {
|
||||
beestat.component.chart.runtime_thermostat_detail_temperature.prototype.get_options_yAxis_ = function() {
|
||||
/**
|
||||
* Highcharts doesn't seem to respect axis behavior well so just overriding
|
||||
* it completely here.
|
||||
@@ -190,8 +140,6 @@ beestat.component.chart.runtime_thermostat_detail.prototype.get_options_yAxis_ =
|
||||
return [
|
||||
// Temperature
|
||||
{
|
||||
'height': '80%',
|
||||
'top': '20%',
|
||||
'gridLineColor': beestat.style.color.bluegray.light,
|
||||
'gridLineDashStyle': 'longdash',
|
||||
'title': {'text': null},
|
||||
@@ -206,8 +154,6 @@ beestat.component.chart.runtime_thermostat_detail.prototype.get_options_yAxis_ =
|
||||
|
||||
// Humidity
|
||||
{
|
||||
'height': '80%',
|
||||
'top': '20%',
|
||||
'alignTicks': false,
|
||||
'gridLineColor': null,
|
||||
'opposite': true,
|
||||
@@ -223,32 +169,21 @@ beestat.component.chart.runtime_thermostat_detail.prototype.get_options_yAxis_ =
|
||||
'min': 0,
|
||||
'minRange': 100,
|
||||
'ceiling': 100
|
||||
},
|
||||
|
||||
// Swimlanes
|
||||
{
|
||||
'height': '20%',
|
||||
'top': '0%',
|
||||
'min': 0,
|
||||
'max': 100,
|
||||
'gridLineWidth': 0,
|
||||
'title': {'text': null},
|
||||
'labels': {'enabled': false}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
// https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/stock/demo/candlestick-and-volume/
|
||||
|
||||
/**
|
||||
* Override for get_options_tooltip_formatter_.
|
||||
*
|
||||
* @return {Function} The tooltip formatter.
|
||||
*/
|
||||
beestat.component.chart.runtime_thermostat_detail.prototype.get_options_tooltip_formatter_ = function() {
|
||||
beestat.component.chart.runtime_thermostat_detail_temperature.prototype.get_options_tooltip_formatter_ = function() {
|
||||
var self = this;
|
||||
|
||||
return function() {
|
||||
var self2 = this;
|
||||
|
||||
var sections = [];
|
||||
var groups = {
|
||||
'mode': [],
|
||||
@@ -256,6 +191,43 @@ beestat.component.chart.runtime_thermostat_detail.prototype.get_options_tooltip_
|
||||
'equipment': []
|
||||
};
|
||||
|
||||
// Add a bunch of fake points so they appear in the tooltip.
|
||||
[
|
||||
'compressor_heat_1',
|
||||
'compressor_heat_2',
|
||||
'auxiliary_heat_1',
|
||||
'auxiliary_heat_2',
|
||||
'compressor_cool_1',
|
||||
'compressor_cool_2',
|
||||
'fan',
|
||||
'humidifier',
|
||||
'dehumidifier',
|
||||
'ventilator',
|
||||
'economizer',
|
||||
'calendar_event_smartrecovery',
|
||||
'calendar_event_home',
|
||||
'calendar_event_away',
|
||||
'calendar_event_sleep',
|
||||
'calendar_event_smarthome',
|
||||
'calendar_event_smartaway',
|
||||
'calendar_event_hold',
|
||||
'calendar_event_vacation',
|
||||
'calendar_event_quicksave',
|
||||
'calendar_event_other',
|
||||
'calendar_event_custom'
|
||||
].forEach(function(series_code) {
|
||||
if (self.data_.metadata.series[series_code].data[self2.x.valueOf()] !== undefined) {
|
||||
self2.points.push({
|
||||
'series': {
|
||||
'name': series_code,
|
||||
'color': beestat.series[series_code].color
|
||||
},
|
||||
'x': self2.x,
|
||||
'y': self.data_.metadata.series[series_code].data[self2.x.valueOf()]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var values = {};
|
||||
this.points.forEach(function(point) {
|
||||
values[point.series.name] = point.y;
|
||||
Reference in New Issue
Block a user