diff --git a/.devcontainer/ui-lovelace.yaml b/.devcontainer/ui-lovelace.yaml index 65520d9..fc179c6 100644 --- a/.devcontainer/ui-lovelace.yaml +++ b/.devcontainer/ui-lovelace.yaml @@ -13,7 +13,7 @@ views: - entity: sensor.random_0_1000 name: Sensor 2 type: area - hours_to_show: 0.20 + graph_span: 15min cache: true layout: minimal header: @@ -30,7 +30,7 @@ views: - entity: sensor.random_0_1000 name: Ram Free type: area - hours_to_show: 0.20 + graph_span: 15min cache: true layout: minimal header: @@ -45,16 +45,16 @@ views: - entity: sensor.random_0_1000 name: test2 type: column - hours_to_show: 0.25 + graph_span: 20min cache: true - type: custom:apexcharts-card - hours_to_show: 200 + graph_span: 200h series: - entity: sensor.humidity curve: straight - type: custom:apexcharts-card - hours_to_show: 1 + graph_span: 1h header: title: Test Aggregate show: true @@ -98,7 +98,7 @@ views: func: first - type: custom:apexcharts-card - hours_to_show: 4 + graph_span: 4h header: title: Test series: @@ -119,7 +119,7 @@ views: graph: line - type: custom:apexcharts-card - hours_to_show: 6 + graph_span: 6h header: show: false series: diff --git a/README.md b/README.md index 525584d..d09f3e3 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ The card stricly validates all the options available (but not for the `apex_conf | :white_check_mark: `type` | string | | v1.0.0 | `custom:apexcharts-card` | | :white_check_mark: `series` | array | | v1.0.0 | See [series](#series-options) | | `update_interval` | string | | NEXT_VERSION | By default the card updates on every state change. Setting this overrides the behaviour. Valid values are any time string, eg: `1h`, `12min`, `1d`, `1h25`, `10sec`, ... | -| `hours_to_show` | number | `24` | v1.0.0 | The span of the graph in hours (Use `0.25` for 15min for eg.) | +| `graph_span` | string | `24h` | NEXT_VERSION | The span of the graph as a time interval. Valid values are any time string, eg: `1h`, `12min`, `1d`, `1h25`, `10sec`, ... | | `show` | object | | v1.0.0 | See [show](#show-options) | | `cache` | boolean | `true` | v1.0.0 | Use in-browser data caching to reduce the load on Home Assistant's server | | `stacked` | boolean | `false` | v1.0.0 | Enable if you want the data to be stacked on the graph | @@ -216,7 +216,7 @@ series: ```yaml type: custom:apexcharts-card -hours_to_show: 6 +graph_span: 6h header: show: false series: @@ -240,7 +240,7 @@ series: ```yaml type: custom:apexcharts-card -hours_to_show: 1 +graph_span: 1h header: show: false series: diff --git a/src/apex-layouts.ts b/src/apex-layouts.ts index a3f3bc8..ab038cc 100644 --- a/src/apex-layouts.ts +++ b/src/apex-layouts.ts @@ -1,5 +1,6 @@ import { HomeAssistant } from 'custom-card-helpers'; -import { moment } from './const'; +import parse from 'parse-duration'; +import { HOUR_24, moment } from './const'; import { ChartCardConfig } from './types'; import { computeName, computeUom, mergeDeep } from './utils'; @@ -45,7 +46,8 @@ export function getLayoutConfig(config: ChartCardConfig, hass: HomeAssistant | u tooltip: { x: { formatter: - config.hours_to_show < 24 + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + parse(config.graph_span!)! < HOUR_24 ? function (val) { return moment(new Date(val)).format('HH:mm:ss'); } diff --git a/src/apexcharts-card.ts b/src/apexcharts-card.ts index 14ebf88..30906a5 100644 --- a/src/apexcharts-card.ts +++ b/src/apexcharts-card.ts @@ -18,8 +18,9 @@ import { DEFAULT_DURATION, DEFAULT_FUNC, DEFAULT_GROUP_BY_FILL, - DEFAULT_HOURS_TO_SHOW, + DEFAULT_GRAPH_SPAN, DEFAULT_SERIE_TYPE, + HOUR_24, } from './const'; import parse from 'parse-duration'; @@ -41,9 +42,13 @@ localForage .iterate((data, key) => { const value: EntityEntryCache = key.endsWith('-raw') ? data : decompress(data); const start = new Date(); - start.setHours(start.getHours() - value.hours_to_show); - if (new Date(value.last_fetched) < start) { + if (value.span === undefined) { localForage.removeItem(key); + } else { + start.setTime(start.getTime() - value.span); + if (new Date(value.last_fetched) < start) { + localForage.removeItem(key); + } } }) .catch((err) => { @@ -73,6 +78,8 @@ class ChartsCard extends LitElement { private _colors?: string[]; + private _graphSpan: number | null = HOUR_24; + @property({ attribute: false }) private _lastState: (number | string | null)[] = []; public connectedCallback() { @@ -148,10 +155,16 @@ class ChartsCard extends LitElement { throw new Error(`'update_interval: ${config.update_interval}' is not a valid interval of time`); } } + if (config.graph_span) { + this._graphSpan = parse(config.graph_span); + if (this._graphSpan === null) { + throw new Error(`'graph_span: ${config.update_interval}' is not a valid range of time`); + } + } this._config = mergeDeep( { - hours_to_show: DEFAULT_HOURS_TO_SHOW, + graph_span: DEFAULT_GRAPH_SPAN, cache: true, useCompress: false, show: { loading: true }, @@ -183,7 +196,7 @@ class ChartsCard extends LitElement { serie.entity, index, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this._config!.hours_to_show, + this._graphSpan!, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this._config!.cache, serie, @@ -301,7 +314,9 @@ class ChartsCard extends LitElement { // const end = this.getEndDate(); const end = new Date(); const start = new Date(end); - start.setTime(start.getTime() - getMilli(config.hours_to_show)); + // validated during Init + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + start.setTime(start.getTime() - this._graphSpan!); try { const promise = this._graphs.map((graph) => graph?._updateHistory(start, end)); diff --git a/src/const.ts b/src/const.ts index d660039..15754d1 100644 --- a/src/const.ts +++ b/src/const.ts @@ -3,8 +3,9 @@ import { extendMoment } from 'moment-range'; export const moment = extendMoment(Moment); export const ONE_HOUR = 1000 * 3600; +export const HOUR_24 = ONE_HOUR * 24; -export const DEFAULT_HOURS_TO_SHOW = 24; +export const DEFAULT_GRAPH_SPAN = '24h'; export const DEFAULT_SERIE_TYPE = 'line'; export const DEFAULT_DURATION = '1h'; export const DEFAULT_FUNC = 'raw'; diff --git a/src/graphEntry.ts b/src/graphEntry.ts index be1ee1d..78e54c6 100644 --- a/src/graphEntry.ts +++ b/src/graphEntry.ts @@ -4,7 +4,7 @@ import { compress, decompress, log } from './utils'; import localForage from 'localforage'; import { HassEntity } from 'home-assistant-js-websocket'; import { DateRange } from 'moment-range'; -import { DEFAULT_HOURS_TO_SHOW, moment } from './const'; +import { HOUR_24, moment } from './const'; import parse from 'parse-duration'; import SparkMD5 from 'spark-md5'; @@ -23,7 +23,9 @@ export default class GraphEntry { private _cache = true; - private _hoursToShow: number; + // private _hoursToShow: number; + + private _graphSpan: number; private _useCompress = false; @@ -43,7 +45,7 @@ export default class GraphEntry { private _md5Config: string; - constructor(entity: string, index: number, hoursToShow: number, cache: boolean, config: ChartCardSeriesConfig) { + constructor(entity: string, index: number, graphSpan: number, cache: boolean, config: ChartCardSeriesConfig) { const aggregateFuncMap = { avg: this._average, max: this._maximum, @@ -58,19 +60,19 @@ export default class GraphEntry { this._cache = cache; this._entityID = entity; this._history = undefined; - this._hoursToShow = hoursToShow; + this._graphSpan = graphSpan; this._config = config; const now = new Date(); const now2 = new Date(now); this._func = aggregateFuncMap[config.group_by.func]; - now2.setHours(now2.getHours() - DEFAULT_HOURS_TO_SHOW); + now2.setTime(now2.getTime() - HOUR_24); this._timeRange = moment.range(now, now2); this._realEnd = new Date(); this._realStart = new Date(); // Valid because tested during init; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this._groupByDurationMs = parse(this._config.group_by.duration)!; - this._md5Config = SparkMD5.hash(`${this._hoursToShow}${JSON.stringify(this._config)}`); + this._md5Config = SparkMD5.hash(`${this._graphSpan}${JSON.stringify(this._config)}`); } set hass(hass: HomeAssistant) { @@ -129,7 +131,7 @@ export default class GraphEntry { let history = this._cache ? await this._getCache(this._entityID, this._useCompress) : undefined; - if (history && history.hours_to_show === this._hoursToShow) { + if (history && history.span === this._graphSpan) { const currDataIndex = history.data.findIndex((item) => item && new Date(item[0]).getTime() > start.getTime()); if (currDataIndex !== -1) { // skip initial state when fetching recent/not-cached data @@ -160,14 +162,14 @@ export default class GraphEntry { return [new Date(item.last_changed).getTime(), !Number.isNaN(stateParsed) ? stateParsed : null]; }); if (history?.data.length) { - history.hours_to_show = this._hoursToShow; + history.span = this._graphSpan; history.last_fetched = new Date(); if (history.data.length !== 0) { history.data.push(...newStateHistory); } } else { history = { - hours_to_show: this._hoursToShow, + span: this._graphSpan, last_fetched: new Date(), data: newStateHistory, }; diff --git a/src/types-config-ti.ts b/src/types-config-ti.ts index c9e57a9..acb7239 100644 --- a/src/types-config-ti.ts +++ b/src/types-config-ti.ts @@ -8,7 +8,7 @@ export const ChartCardExternalConfig = t.iface([], { "type": t.lit('custom:apexcharts-card'), "update_interval": t.opt("string"), "series": t.array("ChartCardSeriesExternalConfig"), - "hours_to_show": t.opt("number"), + "graph_span": t.opt("string"), "show": t.opt(t.iface([], { "loading": t.opt("boolean"), })), diff --git a/src/types-config.ts b/src/types-config.ts index 474d133..8ab57a1 100644 --- a/src/types-config.ts +++ b/src/types-config.ts @@ -2,7 +2,7 @@ export interface ChartCardExternalConfig { type: 'custom:apexcharts-card'; update_interval?: string; series: ChartCardSeriesExternalConfig[]; - hours_to_show?: number; + graph_span?: string; show?: { loading?: boolean; }; diff --git a/src/types.ts b/src/types.ts index e0d6b5d..bbb1f59 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,7 +3,7 @@ import { ChartCardExternalConfig, ChartCardSeriesExternalConfig, GroupByFill, Gr export interface ChartCardConfig extends ChartCardExternalConfig { series: ChartCardSeriesConfig[]; - hours_to_show: number; + graph_span: string; cache: boolean; useCompress: boolean; apex_config?: ApexOptions; @@ -19,7 +19,7 @@ export interface ChartCardSeriesConfig extends ChartCardSeriesExternalConfig { } export interface EntityEntryCache { - hours_to_show: number; + span: number; last_fetched: Date; data: EntityCachePoints; }