From c57278b59becef474264d2e0bf76a18a312a377f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20W?= Date: Mon, 24 May 2021 20:23:12 +0200 Subject: [PATCH] feat: soft bounds and extended bounds for yaxis min/max (#161) * feat: soft bounds and extended bounds for yaxis min/max * doc: add new min/max format documentation * doc: slight doc improvment --- .devcontainer/ui-lovelace.yaml | 2 + README.md | 20 +++++++++- src/apexcharts-card.ts | 70 +++++++++++++++++++++++++++++++--- src/types-config.ts | 4 +- src/types.ts | 9 +++++ 5 files changed, 96 insertions(+), 9 deletions(-) diff --git a/.devcontainer/ui-lovelace.yaml b/.devcontainer/ui-lovelace.yaml index d623f77..2b11092 100644 --- a/.devcontainer/ui-lovelace.yaml +++ b/.devcontainer/ui-lovelace.yaml @@ -1010,6 +1010,8 @@ views: apex_config: tickAmount: 4 - id: second + max: '|+100|' + min: ~100 opposite: true show: true apex_config: diff --git a/README.md b/README.md index 22e2a4c..41ced7a 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ However, some things might be broken :grin: - [`transform` Option](#transform-option) - [`data_generator` Option](#data_generator-option) - [`yaxis` Options. Multi-Y axis](#yaxis-options-multi-y-axis) + - [Min/Max Format](#minmax-format) + - [Example](#example) - [Apex Charts Options Example](#apex-charts-options-example) - [Layouts](#layouts) - [Configuration Templates](#configuration-templates) @@ -434,10 +436,24 @@ You can have as many y-axis as there are series defined in your configuration or | :white_check_mark: `id` | string | | NEXT_VERSION | The identification name of the yaxis used to map it to a serie. Needs to be unique. | | `show` | boolean | `true` | NEXT_VERSION | Whether to show or not the axis on the chart | | `opposite` | boolean | `false` | NEXT_VERSION | If `true`, the axis will be shown on the right side of the chart | -| `min` | `auto` or number | `auto` | NEXT_VERSION | If undefined or `auto`, the `min` of the yaxis will be automatically calculated based on the min value of all the series associated to this axis. If a number is set, the min will be forced to this number | -| `max` | `auto` or number | `auto` | NEXT_VERSION | If undefined or `auto`, the `min` of the yaxis will be automatically calculated based on the max value of all the series associated to this axis. If a number is set, the max will be forced to this number | +| `min` | `auto`, number or string | `auto` | NEXT_VERSION | If undefined or `auto`, the `min` of the yaxis will be automatically calculated based on the min value of all the series associated to this axis. See [below](#minmax-format) for other formats. | +| `max` | `auto`, number or string | `auto` | NEXT_VERSION | If undefined or `auto`, the `min` of the yaxis will be automatically calculated based on the max value of all the series associated to this axis. See [below](#minmax-format) for other formats. | | `apex_config` | object | | NEXT_VERSION | Any configuration from https://apexcharts.com/docs/options/yaxis/, except `min`, `max`, `show` and `opposite` | +#### Min/Max Format + +`min` and `max` support multiple types of format: +* not set or `auto` (this is the default): if it is set to `auto`, the min or max will be automatically calculated +* any number: if a number is set, the min or max will be fixed on the y-axis +* `~90`: if the format is `~` followed by a number, the min or max will be defined as a soft bounds + * `min: ~90` and the min of the data in the series is `120`: the y-axis min value will be `90` + * `min: ~90` and the min of the data in the series is `60`: the y-axis min value will be `60` +* `'|+20|'` or `'|-20|'`: This will add/remove the value between `| |` from the min/max + * `min: '|-20|'`: The min of the data in the series is `32`, the y-axis min will be `12` (= `32 - 20`) + * `max: '|+10|'`: The max of the data in the series is `32`, the y-axis max will be `42` (= `32 + 10`) + +#### Example + In this example, we have 2 sensors: * `sensor.random0_100`: goes from `0` to `100` * `sensor.random_0_1000`: goes from `0` to `1000` diff --git a/src/apexcharts-card.ts b/src/apexcharts-card.ts index b6cfe7f..ba0d12b 100644 --- a/src/apexcharts-card.ts +++ b/src/apexcharts-card.ts @@ -9,6 +9,7 @@ import { EntityCachePoints, EntityEntryCache, HistoryPoint, + minmax_type, } from './types'; import { getLovelace, HomeAssistant } from 'custom-card-helpers'; import localForage from 'localforage'; @@ -358,6 +359,10 @@ class ChartsCard extends LitElement { yaxis: yAxisConfig, }; } + this._yAxisConfig?.forEach((yaxis) => { + [yaxis.min, yaxis.min_type] = this._getTypeOfMinMax(yaxis.min); + [yaxis.max, yaxis.max_type] = this._getTypeOfMinMax(yaxis.max); + }); } this._graphs = this._config.series.map((serie, index) => { serie.index = index; @@ -957,7 +962,7 @@ class ChartsCard extends LitElement { private _computeYAxisAutoMinMax(start: Date, end: Date) { if (!this._config) return; this._yAxisConfig?.map((yaxis) => { - if (typeof yaxis.min !== 'number' || typeof yaxis.max !== 'number') { + if (yaxis.min_type !== minmax_type.FIXED || yaxis.max_type !== minmax_type.FIXED) { const minMax = yaxis.series_id?.map((id) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const lMinMax = this._graphs![id]?.minMaxWithTimestamp(start.getTime(), end.getTime()); @@ -986,16 +991,71 @@ class ChartsCard extends LitElement { } }); yaxis.series_id?.forEach((id) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (yaxis.min === undefined || yaxis.min === 'auto') this._config!.apex_config!.yaxis![id].min = min; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (yaxis.max === undefined || yaxis.max === 'auto') this._config!.apex_config!.yaxis![id].max = max; + if (min !== null) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._config!.apex_config!.yaxis![id].min = this._getMinMaxBasedOnType( + true, + min, + yaxis.min as number, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + yaxis.min_type!, + ); + } + if (max !== null) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._config!.apex_config!.yaxis![id].max = this._getMinMaxBasedOnType( + false, + max, + yaxis.max as number, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + yaxis.max_type!, + ); + } }); } }); return this._config?.apex_config?.yaxis; } + private _getMinMaxBasedOnType(isMin: boolean, value: number, configMinMax: number, type: minmax_type): number { + switch (type) { + case minmax_type.AUTO: + return value; + case minmax_type.SOFT: + if ((isMin && value > configMinMax) || (!isMin && value < configMinMax)) { + return configMinMax; + } else { + return value; + } + case minmax_type.ABSOLUTE: + return value + configMinMax; + default: + return value; + } + } + + private _getTypeOfMinMax(value?: 'auto' | number | string): [number | undefined, minmax_type] { + const regexFloat = /[+-]?\d+(\.\d+)?/g; + if (typeof value === 'number') { + return [value, minmax_type.FIXED]; + } else if (value === undefined || value === 'auto') { + return [undefined, minmax_type.AUTO]; + } + if (typeof value === 'string' && value !== 'auto') { + const matched = value.match(regexFloat); + if (!matched || matched.length !== 1) { + throw new Error(`Bad yaxis min/max format: ${value}`); + } + const floatValue = parseFloat(matched[0]); + if (value.startsWith('~')) { + return [floatValue, minmax_type.SOFT]; + } else if (value.startsWith('|') && value.endsWith('|')) { + return [floatValue, minmax_type.ABSOLUTE]; + } + } + throw new Error(`Bad yaxis min/max format: ${value}`); + } + private _computeChartColors(brush: boolean): (string | (({ value }) => string))[] { const defaultColors: (string | (({ value }) => string))[] = computeColors(brush ? this._brushColors : this._colors); const series = brush ? this._config?.series_in_brush : this._config?.series_in_graph; diff --git a/src/types-config.ts b/src/types-config.ts index c2904ee..75b38da 100644 --- a/src/types-config.ts +++ b/src/types-config.ts @@ -134,8 +134,8 @@ export interface ChartCardYAxisExternal { id: string; show?: boolean; opposite?: boolean; - min?: 'auto' | number; - max?: 'auto' | number; + min?: 'auto' | number | string; + max?: 'auto' | number | string; // eslint-disable-next-line @typescript-eslint/no-explicit-any apex_config?: any; } diff --git a/src/types.ts b/src/types.ts index 571dc52..6e0052e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -68,4 +68,13 @@ export type HistoryBuckets = Array; export interface ChartCardYAxis extends ChartCardYAxisExternal { series_id?: number[]; + min_type?: minmax_type; + max_type?: minmax_type; +} + +export enum minmax_type { + AUTO, + FIXED, + SOFT, + ABSOLUTE, }