mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-08 18:39:40 +00:00
287 lines
7.1 KiB
JavaScript
287 lines
7.1 KiB
JavaScript
import "@polymer/polymer/lib/utils/debounce";
|
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
/* eslint-plugin-disable lit */
|
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
import { formatDateTimeWithSeconds } from "../common/datetime/format_date_time";
|
|
import { computeDomain } from "../common/entity/compute_domain";
|
|
import { computeRTL } from "../common/util/compute_rtl";
|
|
import LocalizeMixin from "../mixins/localize-mixin";
|
|
import "./entity/ha-chart-base";
|
|
|
|
/** Binary sensor device classes for which the static colors for on/off need to be inverted.
|
|
* List the ones were "off" = good or normal state = should be rendered "green".
|
|
*/
|
|
const BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED = new Set([
|
|
"battery",
|
|
"door",
|
|
"garage_door",
|
|
"gas",
|
|
"lock",
|
|
"opening",
|
|
"problem",
|
|
"safety",
|
|
"smoke",
|
|
"window",
|
|
]);
|
|
|
|
class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
|
static get template() {
|
|
return html`
|
|
<style>
|
|
:host {
|
|
display: block;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease-in-out;
|
|
}
|
|
:host([rendered]) {
|
|
opacity: 1;
|
|
}
|
|
|
|
ha-chart-base {
|
|
direction: ltr;
|
|
}
|
|
</style>
|
|
<ha-chart-base
|
|
hass="[[hass]]"
|
|
data="[[chartData]]"
|
|
rendered="{{rendered}}"
|
|
rtl="{{rtl}}"
|
|
></ha-chart-base>
|
|
`;
|
|
}
|
|
|
|
static get properties() {
|
|
return {
|
|
hass: {
|
|
type: Object,
|
|
},
|
|
chartData: Object,
|
|
data: {
|
|
type: Object,
|
|
observer: "dataChanged",
|
|
},
|
|
names: Object,
|
|
noSingle: Boolean,
|
|
endTime: Date,
|
|
rendered: {
|
|
type: Boolean,
|
|
value: false,
|
|
reflectToAttribute: true,
|
|
},
|
|
rtl: {
|
|
reflectToAttribute: true,
|
|
computed: "_computeRTL(hass)",
|
|
},
|
|
};
|
|
}
|
|
|
|
static get observers() {
|
|
return ["dataChanged(data, endTime, localize, language)"];
|
|
}
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
this._isAttached = true;
|
|
this.drawChart();
|
|
}
|
|
|
|
dataChanged() {
|
|
this.drawChart();
|
|
}
|
|
|
|
drawChart() {
|
|
const staticColors = {
|
|
on: 1,
|
|
off: 0,
|
|
home: 1,
|
|
not_home: 0,
|
|
unavailable: "#a0a0a0",
|
|
unknown: "#606060",
|
|
idle: 2,
|
|
};
|
|
let stateHistory = this.data;
|
|
|
|
if (!this._isAttached) {
|
|
return;
|
|
}
|
|
|
|
if (!stateHistory) {
|
|
stateHistory = [];
|
|
}
|
|
|
|
const startTime = new Date(
|
|
stateHistory.reduce(
|
|
(minTime, stateInfo) =>
|
|
Math.min(minTime, new Date(stateInfo.data[0].last_changed)),
|
|
new Date()
|
|
)
|
|
);
|
|
|
|
// end time is Math.max(startTime, last_event)
|
|
let endTime =
|
|
this.endTime ||
|
|
new Date(
|
|
stateHistory.reduce(
|
|
(maxTime, stateInfo) =>
|
|
Math.max(
|
|
maxTime,
|
|
new Date(stateInfo.data[stateInfo.data.length - 1].last_changed)
|
|
),
|
|
startTime
|
|
)
|
|
);
|
|
|
|
if (endTime > new Date()) {
|
|
endTime = new Date();
|
|
}
|
|
|
|
const labels = [];
|
|
const datasets = [];
|
|
// stateHistory is a list of lists of sorted state objects
|
|
const names = this.names || {};
|
|
stateHistory.forEach((stateInfo) => {
|
|
let newLastChanged;
|
|
let prevState = null;
|
|
let locState = null;
|
|
let prevLastChanged = startTime;
|
|
const entityDisplay = names[stateInfo.entity_id] || stateInfo.name;
|
|
|
|
const invertOnOff =
|
|
computeDomain(stateInfo.entity_id) === "binary_sensor" &&
|
|
BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED.has(
|
|
this.hass.states[stateInfo.entity_id].attributes.device_class
|
|
);
|
|
|
|
const dataRow = [];
|
|
stateInfo.data.forEach((state) => {
|
|
let newState = state.state;
|
|
const timeStamp = new Date(state.last_changed);
|
|
if (newState === undefined || newState === "") {
|
|
newState = null;
|
|
}
|
|
if (timeStamp > endTime) {
|
|
// Drop datapoints that are after the requested endTime. This could happen if
|
|
// endTime is 'now' and client time is not in sync with server time.
|
|
return;
|
|
}
|
|
if (prevState !== null && newState !== prevState) {
|
|
newLastChanged = new Date(state.last_changed);
|
|
|
|
dataRow.push([
|
|
prevLastChanged,
|
|
newLastChanged,
|
|
locState,
|
|
prevState,
|
|
invertOnOff,
|
|
]);
|
|
|
|
prevState = newState;
|
|
locState = state.state_localize;
|
|
prevLastChanged = newLastChanged;
|
|
} else if (prevState === null) {
|
|
prevState = newState;
|
|
locState = state.state_localize;
|
|
prevLastChanged = new Date(state.last_changed);
|
|
}
|
|
});
|
|
|
|
if (prevState !== null) {
|
|
dataRow.push([
|
|
prevLastChanged,
|
|
endTime,
|
|
locState,
|
|
prevState,
|
|
invertOnOff,
|
|
]);
|
|
}
|
|
datasets.push({
|
|
data: dataRow,
|
|
entity_id: stateInfo.entity_id,
|
|
});
|
|
labels.push(entityDisplay);
|
|
});
|
|
|
|
const formatTooltipLabel = (item, data) => {
|
|
const values = data.datasets[item.datasetIndex].data[item.index];
|
|
|
|
const start = formatDateTimeWithSeconds(values[0], this.hass.locale);
|
|
const end = formatDateTimeWithSeconds(values[1], this.hass.locale);
|
|
const state = values[2];
|
|
|
|
return [state, start, end];
|
|
};
|
|
|
|
const formatTooltipBeforeBody = (item, data) => {
|
|
if (!this.hass.userData || !this.hass.userData.showAdvanced || !item[0]) {
|
|
return "";
|
|
}
|
|
// Extract the entity ID from the dataset.
|
|
const values = data.datasets[item[0].datasetIndex];
|
|
return values.entity_id || "";
|
|
};
|
|
|
|
const chartOptions = {
|
|
type: "timeline",
|
|
options: {
|
|
tooltips: {
|
|
callbacks: {
|
|
label: formatTooltipLabel,
|
|
beforeBody: formatTooltipBeforeBody,
|
|
},
|
|
},
|
|
scales: {
|
|
xAxes: [
|
|
{
|
|
ticks: {
|
|
major: {
|
|
fontStyle: "bold",
|
|
},
|
|
sampleSize: 5,
|
|
autoSkipPadding: 50,
|
|
maxRotation: 0,
|
|
},
|
|
categoryPercentage: undefined,
|
|
barPercentage: undefined,
|
|
time: {
|
|
format: undefined,
|
|
},
|
|
},
|
|
],
|
|
yAxes: [
|
|
{
|
|
afterSetDimensions: (yaxe) => {
|
|
yaxe.maxWidth = yaxe.chart.width * 0.18;
|
|
},
|
|
position: this._computeRTL ? "right" : "left",
|
|
categoryPercentage: undefined,
|
|
barPercentage: undefined,
|
|
time: { format: undefined },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
datasets: {
|
|
categoryPercentage: 0.8,
|
|
barPercentage: 0.9,
|
|
},
|
|
data: {
|
|
labels: labels,
|
|
datasets: datasets,
|
|
},
|
|
colors: {
|
|
staticColors: staticColors,
|
|
staticColorIndex: 3,
|
|
},
|
|
};
|
|
this.chartData = chartOptions;
|
|
}
|
|
|
|
_computeRTL(hass) {
|
|
return computeRTL(hass);
|
|
}
|
|
}
|
|
customElements.define(
|
|
"state-history-chart-timeline",
|
|
StateHistoryChartTimeline
|
|
);
|