From c5987f8df0589b556235379d2ced172fee6facbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wiedemann?= Date: Fri, 22 Jan 2021 13:54:18 +0000 Subject: [PATCH] feat(card): Support for pre-defined layouts --- .devcontainer/ui-lovelace.yaml | 31 +++++++ src/apex-layouts.ts | 145 +++++++++++++++++++++++++++++++++ src/apexcharts-card.ts | 94 ++------------------- src/styles.ts | 8 ++ src/types.ts | 1 + src/utils.ts | 30 +++++++ 6 files changed, 224 insertions(+), 85 deletions(-) create mode 100644 src/apex-layouts.ts diff --git a/.devcontainer/ui-lovelace.yaml b/.devcontainer/ui-lovelace.yaml index 7e26a06..2ef4fde 100644 --- a/.devcontainer/ui-lovelace.yaml +++ b/.devcontainer/ui-lovelace.yaml @@ -1,6 +1,36 @@ views: - title: Main cards: + - type: grid + title: Test Grid Square + square: true + columns: 2 + cards: + - type: custom:apexcharts-card + stacked: true + series: + - entity: sensor.random_0_1000 + name: test1 + type: area + curve: straight + - entity: sensor.random_0_1000 + name: test2 + type: area + hours_to_show: 0.20 + cache: true + layout: minimal + - type: custom:apexcharts-card + stacked: true + series: + - entity: sensor.random_0_1000 + name: test1 + type: bar + - entity: sensor.random_0_1000 + name: test2 + type: bar + hours_to_show: 0.25 + cache: true + - type: custom:apexcharts-card stacked: true series: @@ -13,6 +43,7 @@ views: type: area hours_to_show: 0.20 cache: true + layout: minimal - type: custom:apexcharts-card stacked: true diff --git a/src/apex-layouts.ts b/src/apex-layouts.ts new file mode 100644 index 0000000..c7b2ab8 --- /dev/null +++ b/src/apex-layouts.ts @@ -0,0 +1,145 @@ +import moment from 'moment'; +import { ChartCardConfig } from './types'; +import { getMilli, mergeDeep } from './utils'; + +export function getLayoutConfig(config: ChartCardConfig): unknown { + const def = { + chart: { + stacked: config?.stacked, + type: 'line', + foreColor: 'var(--primary-text-color)', + width: '100%', + // animations: { + // enabled: true, + // easing: 'linear', + // dynamicAnimation: { + // speed: 1000, + // }, + // }, + zoom: { + enabled: false, + }, + toolbar: { + show: false, + }, + }, + grid: { + strokeDashArray: 3, + }, + title: { + text: config?.series[0].name || config?.series[0].entity, + align: 'left', + floating: false, + // offsetX: 10, + style: { + fontSize: '20px', + fontWeight: '500', + fontFamily: 'var(--paper-font-body1_-_font-family)', + // color: '#263238' + }, + }, + subtitle: { + text: undefined, + align: 'right', + floating: true, + offsetY: 0, + margin: 0, + style: { + fontSize: '40px', + fontWeight: '300', + fontFamily: 'var(--paper-font-body1_-_font-family)', + // color: '#9699a2' + }, + }, + series: config?.series.map((serie) => { + return { + name: serie.name || serie.entity, + type: serie.type || 'line', + data: [], + }; + }), + xaxis: { + type: 'datetime', + range: getMilli(config.hours_to_show), + labels: { + datetimeUTC: false, + }, + }, + tooltip: { + x: { + formatter: + config.hours_to_show < 24 + ? function (val) { + return moment(new Date(val)).format('HH:mm:ss'); + } + : function (val) { + return moment(new Date(val)).format('MMM Do, HH:mm:ss'); + }, + }, + fixed: { + enabled: true, + postion: 'topRight', + }, + }, + stroke: { + curve: config.series.map((serie) => { + return serie.curve || 'smooth'; + }), + lineCap: 'round', + }, + noData: { + text: 'Loading...', + }, + }; + + let conf = {}; + switch (config.layout) { + case 'minimal': + conf = { + chart: { + offsetY: 15, + parentHeightOffset: 0, + }, + grid: { + show: false, + padding: { + left: 0, + right: 0, + }, + }, + xaxis: { + labels: { + show: false, + }, + axisBorder: { + show: false, + }, + axisTicks: { + show: false, + }, + crosshairs: { + show: true, + }, + tooltip: { + enabled: false, + }, + }, + yaxis: { + show: false, + showAlways: true, + tooltip: { + enabled: true, + }, + }, + legend: { + position: 'top', + }, + }; + break; + + default: + break; + } + + return mergeDeep(def, conf); +} diff --git a/src/apexcharts-card.ts b/src/apexcharts-card.ts index e2ae4d8..66596af 100644 --- a/src/apexcharts-card.ts +++ b/src/apexcharts-card.ts @@ -7,7 +7,7 @@ import { compress, decompress, getMilli, log } from './utils'; import ApexCharts from 'apexcharts'; import { styles } from './styles'; import { HassEntity } from 'home-assistant-js-websocket'; -import moment from 'moment'; +import { getLayoutConfig } from './apex-layouts'; /* eslint no-console: 0 */ console.info( @@ -121,7 +121,9 @@ class ChartsCard extends LitElement { return html` -
+
+
+
`; } @@ -132,86 +134,7 @@ class ChartsCard extends LitElement { if (!this._apexChart && this.shadowRoot && this._config) { this._loaded = true; const graph = this.shadowRoot.querySelector('#graph'); - this._apexChart = new ApexCharts(graph, { - chart: { - stacked: this._config?.stacked, - type: 'line', - foreColor: 'var(--primary-text-color)', - // animations: { - // enabled: true, - // easing: 'linear', - // dynamicAnimation: { - // speed: 1000, - // }, - // }, - zoom: { - enabled: false, - }, - toolbar: { - show: false, - }, - }, - title: { - text: this._config?.series[0].name || this._config?.series[0].entity, - align: 'left', - floating: false, - // offsetX: 10, - style: { - fontSize: '20px', - fontWeight: '500', - fontFamily: 'var(--paper-font-body1_-_font-family)', - // color: '#263238' - }, - }, - subtitle: { - text: undefined, - align: 'right', - floating: true, - offsetY: 0, - margin: 0, - style: { - fontSize: '40px', - fontWeight: '300', - fontFamily: 'var(--paper-font-body1_-_font-family)', - // color: '#9699a2' - }, - }, - series: this._config?.series.map((serie) => { - return { - name: serie.name || serie.entity, - type: serie.type || 'line', - data: [], - }; - }), - xaxis: { - type: 'datetime', - range: getMilli(this._config.hours_to_show), - labels: { - datetimeUTC: false, - }, - }, - tooltip: { - x: { - formatter: - this._config.hours_to_show < 24 - ? function (val) { - return moment(new Date(val)).format('HH:mm:ss'); - } - : function (val) { - return moment(new Date(val)).format('MMM Do, HH:mm:ss'); - }, - }, - }, - stroke: { - curve: this._config.series.map((serie) => { - return serie.curve || 'smooth'; - }), - lineCap: 'round', - }, - noData: { - text: 'Loading...', - }, - }); + this._apexChart = new ApexCharts(graph, getLayoutConfig(this._config)); this._apexChart.render(); } } @@ -245,9 +168,10 @@ class ChartsCard extends LitElement { const graphData = { series: this._history.map((history, index) => { return { - data: this._config?.series[index].extend_to_end - ? [...history?.data, ...[[end.getTime(), history?.data.slice(-1)[0][1]]]] - : history?.data, + data: + this._config?.series[index].extend_to_end && this._config?.series[index].type !== 'bar' + ? [...history?.data, ...[[end.getTime(), history?.data.slice(-1)[0][1]]]] + : history?.data, }; }), subtitle: { diff --git a/src/styles.ts b/src/styles.ts index 45083c9..a5b3d30 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -5,6 +5,14 @@ export const styles: CSSResult = css` display: block; } + ha-card { + overflow: hidden; + height: 100%; + } + #wrapper { + overflow: hidden; + height: 100%; + } #graph { height: 100%; } diff --git a/src/types.ts b/src/types.ts index 5c17588..883d56a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ export interface ChartCardExternalConfig { hours_to_show?: number; cache?: boolean; stacked?: boolean; + layout?: string; } export interface ChartCardSeriesExternalConfig { diff --git a/src/utils.ts b/src/utils.ts index ede54f8..68e3ddd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -16,3 +16,33 @@ export function log(message): void { // eslint-disable-next-line no-console console.warn('apexcharts-card: ', message); } + +/** + * Performs a deep merge of `source` into `target`. + * Mutates `target` only but not its objects and arrays. + * + * @author inspired by [jhildenbiddle](https://stackoverflow.com/a/48218209). + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any +export function mergeDeep(target: any, source: any): any { + const isObject = (obj) => obj && typeof obj === 'object'; + + if (!isObject(target) || !isObject(source)) { + return source; + } + + Object.keys(source).forEach((key) => { + const targetValue = target[key]; + const sourceValue = source[key]; + + if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { + target[key] = targetValue.concat(sourceValue); + } else if (isObject(targetValue) && isObject(sourceValue)) { + target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue); + } else { + target[key] = sourceValue; + } + }); + + return target; +}