mirror of
https://github.com/beestat/app.git
synced 2025-11-08 15:53:15 -05:00
282 lines
7.7 KiB
JavaScript
282 lines
7.7 KiB
JavaScript
/**
|
|
* 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
|
|
];
|
|
|
|
Object.values(beestat.cache.sensor).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(sensor_count, 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 sensor_count = (Object.keys(this.data_.series).length - 1) / 2;
|
|
|
|
var y_min = Math.floor((this.data_.metadata.chart.y_min - 5) / 10) * 10;
|
|
var y_max = Math.ceil((this.data_.metadata.chart.y_max + 10) / 10) * 10;
|
|
|
|
/**
|
|
* 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, setting the swimlane axis to a fixed height and
|
|
* having it sit on top of a full height axis works well enough. Adding a
|
|
* bit of padding to the primary axis prevents those values from flowing on
|
|
* top. It's not perfect because you get the main axis all the way up the
|
|
* side but it's not terrible.
|
|
*/
|
|
y_max += ((sensor_count > 8) ? 20 : 10);
|
|
|
|
var tick_positions = [];
|
|
var tick_interval = (beestat.setting('temperature_unit') === '°F') ? 10 : 5;
|
|
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': 15,
|
|
'min': 0,
|
|
'max': 100,
|
|
'reversed': true,
|
|
'gridLineWidth': 0,
|
|
'title': {'text': null},
|
|
'labels': {'enabled': false}
|
|
}
|
|
];
|
|
};
|
|
|
|
/**
|
|
* 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 = [];
|
|
|
|
var values = {};
|
|
this.points.forEach(function(point) {
|
|
values[point.series.name] = point.y;
|
|
});
|
|
|
|
this.points.forEach(function(point) {
|
|
var label;
|
|
var value;
|
|
var color;
|
|
|
|
if (point.series.name.includes('temperature') === true) {
|
|
label = self.data_.metadata.series[point.series.name].name;
|
|
color = point.series.color;
|
|
value = beestat.temperature({
|
|
'temperature': values[point.series.name],
|
|
'convert': false,
|
|
'units': true
|
|
});
|
|
|
|
var occupancy_key = point.series.name.replace('temperature', 'occupancy');
|
|
if (values[occupancy_key] !== undefined && values[occupancy_key] !== null) {
|
|
value += ' ●';
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
var height = 50;
|
|
var max_line_width = 16;
|
|
|
|
// Spacing.
|
|
var spacing = 4;
|
|
|
|
// Base line width is a percentage height of the container.
|
|
var line_width = Math.round(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;
|
|
|
|
return {
|
|
'line_width': line_width,
|
|
'y': y
|
|
};
|
|
};
|