diff --git a/gallery/src/demos/demo-hui-gauge-card.js b/gallery/src/demos/demo-hui-gauge-card.js new file mode 100644 index 0000000000..20d37bf5d0 --- /dev/null +++ b/gallery/src/demos/demo-hui-gauge-card.js @@ -0,0 +1,83 @@ +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import '../components/demo-cards.js'; + +const CONFIGS = [ + { + heading: 'Basic example', + config: ` +- type: gauge + entity: sensor.brightness + ` + }, + { + heading: 'With title', + config: ` +- type: gauge + title: Humidity + entity: sensor.outside_humidity + ` + }, + { + heading: 'Custom Unit of Measurement', + config: ` +- type: gauge + entity: sensor.outside_temperature + unit_of_measurement: C + ` + }, + { + heading: 'Setting Severity Levels', + config: ` +- type: gauge + entity: sensor.brightness + severity: + red: 32 + green: 0 + yellow: 23 + ` + }, + { + heading: 'Setting Min and Max Values', + config: ` +- type: gauge + entity: sensor.brightness + min: 0 + max: 38 + ` + }, + { + heading: 'Invalid Entity', + config: ` +- type: gauge + entity: sensor.invalid_entity + ` + }, + { + heading: 'Non-Numeric Value', + config: ` +- type: gauge + entity: plant.bonsai + ` + }, +]; + +class DemoGaugeEntity extends PolymerElement { + static get template() { + return html` + + `; + } + + static get properties() { + return { + _configs: { + type: Object, + value: CONFIGS + } + }; + } +} + +customElements.define('demo-hui-gauge-card', DemoGaugeEntity); diff --git a/src/panels/lovelace/cards/hui-gauge-card.js b/src/panels/lovelace/cards/hui-gauge-card.js new file mode 100644 index 0000000000..c22c08f5e1 --- /dev/null +++ b/src/panels/lovelace/cards/hui-gauge-card.js @@ -0,0 +1,201 @@ +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import '../../../components/ha-card.js'; + +import EventsMixin from '../../../mixins/events-mixin.js'; + +/* + * @appliesMixin EventsMixin + */ +class HuiGaugeCard extends EventsMixin(PolymerElement) { + static get template() { + return html` + + +
+
+
+
+
+
[[_computeStateDisplay(_stateObj)]]
+
[[_computeTitle(_stateObj)]]
+
+
+
+ `; + } + + static get properties() { + return { + hass: { + type: Object + }, + _config: Object, + _stateObj: { + type: Object, + computed: '_computeStateObj(hass.states, _config.entity)', + observer: '_stateObjChanged' + }, + }; + } + + getCardSize() { + return 1; + } + + setConfig(config) { + if (!config || !config.entity) throw new Error('Invalid card configuration'); + this._config = Object.assign({ min: 0, max: 100 }, config); + } + + _computeStateObj(states, entityId) { + return states && entityId in states ? states[entityId] : null; + } + + _stateObjChanged(stateObj) { + if (!stateObj || isNaN(stateObj.state)) return; + + const config = this._config; + const turn = this._translateTurn(stateObj.state, config) / 10; + + this.$.gauge.style.transform = `rotate(${turn}turn)`; + this.$.gauge.style.backgroundColor = this._computeSeverity(stateObj.state, config.severity); + } + + _computeStateDisplay(stateObj) { + if (!stateObj || isNaN(stateObj.state)) return ''; + const unitOfMeasurement = this._config.unit_of_measurement || stateObj.attributes.unit_of_measurement || ''; + return `${stateObj.state} ${unitOfMeasurement}`; + } + + _computeTitle(stateObj) { + if (!stateObj) { + this.$.title.className = 'not-found'; + return 'Entity not available: ' + this._config.entity; + } + if (isNaN(stateObj.state)) { + this.$.title.className = 'not-found'; + return 'Entity is non-numeric: ' + this._config.entity; + } + this.$.title.className = ''; + return this._config.title; + } + + _computeSeverity(stateValue, sections) { + const numberValue = Number(stateValue); + const severityMap = { + red: 'var(--label-badge-red)', + green: 'var(--label-badge-green)', + yellow: 'var(--label-badge-yellow)', + normal: 'var(--label-badge-blue)', + }; + + if (!sections) return severityMap.normal; + + const sectionsArray = Object.keys(sections); + const sortable = sectionsArray.map(severity => [severity, sections[severity]]); + + for (var i = 0; i < sortable.length; i++) { + if (severityMap[sortable[i][0]] == null || isNaN(sortable[i][1])) { + return severityMap.normal; + } + } + sortable.sort((a, b) => a[1] - b[1]); + + if (numberValue >= sortable[0][1] && numberValue < sortable[1][1]) { + return severityMap[sortable[0][0]]; + } + if (numberValue >= sortable[1][1] && numberValue < sortable[2][1]) { + return severityMap[sortable[1][0]]; + } + if (numberValue >= sortable[2][1]) { + return severityMap[sortable[2][0]]; + } + return severityMap.normal; + } + + _translateTurn(value, config) { + return 5 * (value - config.min) / (config.max - config.min); + } + + _handleClick() { + this.fire('hass-more-info', { entityId: this._config.entity }); + } +} + +customElements.define('hui-gauge-card', HuiGaugeCard); diff --git a/src/panels/lovelace/common/create-card-element.js b/src/panels/lovelace/common/create-card-element.js index 0975aef47f..41a8eac7ca 100644 --- a/src/panels/lovelace/common/create-card-element.js +++ b/src/panels/lovelace/common/create-card-element.js @@ -19,6 +19,7 @@ import '../cards/hui-plant-status-card.js'; import '../cards/hui-sensor-card.js'; import '../cards/hui-vertical-stack-card.js'; import '../cards/hui-weather-forecast-card'; +import '../cards/hui-gauge-card.js'; import createErrorCardConfig from './create-error-card-config.js'; @@ -27,6 +28,7 @@ const CARD_TYPES = new Set([ 'entities', 'entity-filter', 'error', + 'gauge', 'glance', 'history-graph', 'horizontal-stack',