From 2cbd769f9229b5c49d32a1aa42b0cc5ccaff914d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20W?= Date: Tue, 26 Jan 2021 19:34:37 +0100 Subject: [PATCH] feat(header): More header options and title support (#8) * feat(header): More header options and title support * fix(header): Header should be off by default * doc(header): New header options Fixes #5 --- .devcontainer/ui-lovelace.yaml | 10 ++++++- README.md | 4 ++- src/apex-layouts.ts | 40 +++++++++++++++----------- src/apexcharts-card.ts | 51 ++++++++++++++++++++++++++++------ src/styles.ts | 44 ++++++++++++++++++++++++----- src/types-config-ti.ts | 3 ++ src/types-config.ts | 3 ++ src/utils.ts | 28 +++++++++++-------- 8 files changed, 137 insertions(+), 46 deletions(-) diff --git a/.devcontainer/ui-lovelace.yaml b/.devcontainer/ui-lovelace.yaml index 5e7bea7..65520d9 100644 --- a/.devcontainer/ui-lovelace.yaml +++ b/.devcontainer/ui-lovelace.yaml @@ -56,7 +56,10 @@ views: - type: custom:apexcharts-card hours_to_show: 1 header: - show: false + title: Test Aggregate + show: true + show_states: true + colorize_states: true series: - entity: sensor.random0_100 name: AVG @@ -96,6 +99,8 @@ views: - type: custom:apexcharts-card hours_to_show: 4 + header: + title: Test series: - entity: sensor.humidity type: area @@ -109,6 +114,9 @@ views: group_by: func: avg duration: 1h + - type: sensor + entity: sensor.humidity + graph: line - type: custom:apexcharts-card hours_to_show: 6 diff --git a/README.md b/README.md index 52ba8a6..525584d 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,10 @@ The card stricly validates all the options available (but not for the `apex_conf | Name | Type | Default | Since | Description | | ---- | :--: | :-----: | :---: | ----------- | -| `show` | boolean | `true` | v1.0.0 | Show or hide the header | +| `show` | boolean | `false` | v1.0.0 | Show or hide the header | | `floating` | boolean | `false` | v1.0.0 | Makes the header float above the graph. Positionning will be supported later | +| `show_states` | boolean | `false` | NEXT_VERSION | Show or hide the states in the header | +| `colorize_states` | boolean | `false` | NEXT_VERSION | Colorize the states based on the color of the serie | ### `group_by` Options diff --git a/src/apex-layouts.ts b/src/apex-layouts.ts index 82259b8..a3f3bc8 100644 --- a/src/apex-layouts.ts +++ b/src/apex-layouts.ts @@ -27,7 +27,7 @@ export function getLayoutConfig(config: ChartCardConfig, hass: HomeAssistant | u }, series: config?.series.map((serie, index) => { return { - name: computeName(index, config, hass?.states), + name: computeName(index, config, undefined, hass?.states[serie.entity]), type: serie.type, data: [], }; @@ -53,6 +53,21 @@ export function getLayoutConfig(config: ChartCardConfig, hass: HomeAssistant | u return moment(new Date(val)).format('MMM Do, HH:mm:ss'); }, }, + y: { + formatter: function (_, opts, conf = config, hass2 = hass) { + let value = opts.w.globals.series[opts.seriesIndex].slice(-1)[0]; + if (value !== null && typeof value === 'number' && !Number.isInteger(value)) { + value = (value as number).toFixed(1); + } + const uom = computeUom( + opts.seriesIndex, + conf, + undefined, + hass2?.states[conf.series[opts.seriesIndex].entity], + ); + return [`${value} ${uom}`]; + }, + }, fixed: { enabled: true, postion: 'topRight', @@ -60,21 +75,14 @@ export function getLayoutConfig(config: ChartCardConfig, hass: HomeAssistant | u }, legend: { formatter: function (_, opts, conf = config, hass2 = hass) { - return [ - `${computeName(opts.seriesIndex, conf, undefined, hass2?.states[conf.series[opts.seriesIndex].entity])}:`, - `${ - opts.w.globals.series[opts.seriesIndex].slice(-1).length !== 0 - ? opts.w.globals.series[opts.seriesIndex].slice(-1)[0].toFixed(1) - : opts.w.globals.series[opts.seriesIndex].slice(-1) - } - ${computeUom( - opts.seriesIndex, - conf, - undefined, - hass2?.states[conf.series[opts.seriesIndex].entity], - )} - `, - ]; + const name = + computeName(opts.seriesIndex, conf, undefined, hass2?.states[conf.series[opts.seriesIndex].entity]) + ':'; + let value = opts.w.globals.series[opts.seriesIndex].slice(-1)[0]; + if (value !== null && typeof value === 'number' && !Number.isInteger(value)) { + value = (value as number).toFixed(1); + } + const uom = computeUom(opts.seriesIndex, conf, undefined, hass2?.states[conf.series[opts.seriesIndex].entity]); + return [name, `${value} ${uom}`]; }, }, stroke: { diff --git a/src/apexcharts-card.ts b/src/apexcharts-card.ts index 4f7d71a..14ebf88 100644 --- a/src/apexcharts-card.ts +++ b/src/apexcharts-card.ts @@ -59,13 +59,13 @@ class ChartsCard extends LitElement { private _loaded = false; - @property() private _updating = false; + @property({ type: Boolean }) private _updating = false; private _graphs: (GraphEntry | undefined)[] | undefined; - @property() private _config?: ChartCardConfig; + @property({ attribute: false }) private _config?: ChartCardConfig; - @property() private _entities: HassEntity[] = []; + private _entities: HassEntity[] = []; private _interval?: number | null; @@ -73,6 +73,8 @@ class ChartsCard extends LitElement { private _colors?: string[]; + @property({ attribute: false }) private _lastState: (number | string | null)[] = []; + public connectedCallback() { super.connectedCallback(); if (this._config && this._hass && !this._loaded) { @@ -153,7 +155,6 @@ class ChartsCard extends LitElement { cache: true, useCompress: false, show: { loading: true }, - header: { show: true }, }, JSON.parse(JSON.stringify(config)), ); @@ -252,11 +253,32 @@ class ChartsCard extends LitElement { }; return html` + `; + } + + private _renderStates(): TemplateResult { + return html` +
+ ${this._config?.series.map((_, index) => { + return html` +
+
+ ${this._lastState?.[index] === 0 ? 0 : this._lastState?.[index] || 'N/A'} + ${computeUom(index, this._config, this._entities)} +
+
${computeName(index, this._config, this._entities)}
+
+ `; + })}
`; } @@ -288,6 +310,16 @@ class ChartsCard extends LitElement { series: this._graphs.map((graph) => { if (!graph || graph.history.length === 0) return { data: [] }; const index = graph.index; + if (graph.history.length > 0) { + this._lastState[index] = graph.history[graph.history.length - 1][1]; + if ( + this._lastState[index] !== null && + typeof this._lastState[index] === 'number' && + !Number.isInteger(this._lastState[index]) + ) { + this._lastState[index] = (this._lastState[index] as number).toFixed(1); + } + } return { data: this._config?.series[index].extend_to_end && this._config?.series[index].type !== 'column' @@ -302,6 +334,7 @@ class ChartsCard extends LitElement { }, colors: computeColors(this._colors), }; + this._lastState = [...this._lastState]; this._apexChart?.updateOptions(graphData, false, false); } catch (err) { log(err); diff --git a/src/styles.ts b/src/styles.ts index e4ea82e..2195e58 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -12,7 +12,7 @@ export const styles: CSSResult = css` .wrapper { display: grid; - grid-template-areas: "header" "graph"; + grid-template-areas: 'header' 'graph'; grid-template-columns: 1fr; grid-template-rows: min-content 1fr; } @@ -28,28 +28,58 @@ export const styles: CSSResult = css` } #header { - padding-top: 10px; - padding-left: 10px; + padding: 8px 16px 0px; grid-area: header; } #header.floating { position: absolute; top: 0px; left: 0px; + right: 0px; } - #header__title > #state { + #header__title { + color: var(--secondary-text-color); + font-size: 16px; + font-weight: 500; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + padding-bottom: 5px; + } + + #header__states { + display: flex; + justify-content: space-between; + flex-flow: row wrap; + margin: -5px; + } + + #header__states > * { + margin: 5px; + } + + #states__state { + flex: 0 0 10%; + } + + #state__value > #state { font-size: 1.8em; font-weight: 500; } - #header__title > #uom { + + #state__value > #uom { font-size: 1em; font-weight: 400; opacity: 0.8; } - #header__subtitle { + + #state__name { font-size: 0.8em; font-weight: 300; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } /* Apex Charts Default CSS */ @@ -318,7 +348,7 @@ export const styles: CSSResult = css` opacity: 0; padding: 4px 10px; pointer-events: none; - color: var(--primary-text-color) + color: var(--primary-text-color); font-size: 13px; text-align: center; border-radius: 2px; diff --git a/src/types-config-ti.ts b/src/types-config-ti.ts index fa5c48f..c9e57a9 100644 --- a/src/types-config-ti.ts +++ b/src/types-config-ti.ts @@ -41,6 +41,9 @@ export const GroupByFunc = t.union(t.lit('raw'), t.lit('avg'), t.lit('min'), t.l export const ChartCardHeaderExternalConfig = t.iface([], { "show": t.opt("boolean"), "floating": t.opt("boolean"), + "title": t.opt("string"), + "show_states": t.opt("boolean"), + "colorize_states": t.opt("boolean"), }); const exportedTypeSuite: t.ITypeSuite = { diff --git a/src/types-config.ts b/src/types-config.ts index 0310df9..474d133 100644 --- a/src/types-config.ts +++ b/src/types-config.ts @@ -36,4 +36,7 @@ export type GroupByFunc = 'raw' | 'avg' | 'min' | 'max' | 'last' | 'first' | 'su export interface ChartCardHeaderExternalConfig { show?: boolean; floating?: boolean; + title?: string; + show_states?: boolean; + colorize_states?: boolean; } diff --git a/src/utils.ts b/src/utils.ts index dd5543f..ff3b835 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -67,8 +67,8 @@ export function computeName( } else if (entities) { return ( config.series[index].name || - entities[config.series[index].entity]?.attributes?.friendly_name || - entities[entities[config.series[index].entity]]?.entity_id || + entities[index]?.attributes?.friendly_name || + entities[entities[index]]?.entity_id || '' ); } @@ -85,7 +85,7 @@ export function computeUom( if (entity) { return config.series[index].unit || entity.attributes?.unit_of_measurement || ''; } else if (entities) { - return config.series[index].unit || entities[config.series[index].entity]?.attributes?.unit_of_measurement || ''; + return config.series[index].unit || entities[index]?.attributes?.unit_of_measurement || ''; } return ''; } @@ -93,14 +93,18 @@ export function computeUom( export function computeColors(colors: string[] | undefined): string[] { if (!colors) return []; return colors.map((color) => { - if (color[0] === '#') { - return color; - } else if (color.substring(0, 3) === 'var') { - return new TinyColor( - window.getComputedStyle(document.documentElement).getPropertyValue(color.substring(4).slice(0, -1)).trim(), - ).toHexString(); - } else { - return new TinyColor(color).toHexString(); - } + return computeColor(color); }); } + +export function computeColor(color: string): string { + if (color[0] === '#') { + return color; + } else if (color.substring(0, 3) === 'var') { + return new TinyColor( + window.getComputedStyle(document.documentElement).getPropertyValue(color.substring(4).slice(0, -1)).trim(), + ).toHexString(); + } else { + return new TinyColor(color).toHexString(); + } +}