From 7dbe2b2d018f77e66b0135084c5ced83e2c2c621 Mon Sep 17 00:00:00 2001 From: David Buezas Date: Mon, 31 Oct 2022 10:16:52 +0100 Subject: [PATCH 1/9] Added offsets. Work In Progress --- src/cache/Cache.ts | 18 +++++++++++++++--- src/plotly-graph-card.ts | 20 ++++++++++++++------ src/types.ts | 5 +++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/cache/Cache.ts b/src/cache/Cache.ts index dc4414d..b5fe4f3 100644 --- a/src/cache/Cache.ts +++ b/src/cache/Cache.ts @@ -124,7 +124,11 @@ export default class Cache { } getHistory(entity: EntityConfig) { let key = getEntityKey(entity); - return this.histories[key] || []; + const history = this.histories[key] || []; + return history.map((datum) => ({ + ...datum, + timestamp: datum.timestamp + entity.offset, + })); } async update( range: TimestampRange, @@ -139,12 +143,20 @@ export default class Cache { .catch(() => {}) .then(async () => { if (removeOutsideRange) { + // @TODO: consider offsets this.removeOutsideRange(range); } - const promises = entities.flatMap(async (entity) => { + const promises = entities.map(async (entity) => { const entityKey = getEntityKey(entity); this.ranges[entityKey] ??= []; - const rangesToFetch = subtractRanges([range], this.ranges[entityKey]); + const offsetRange = [ + range[0] - entity.offset, + range[1] - entity.offset, + ]; + const rangesToFetch = subtractRanges( + [offsetRange], + this.ranges[entityKey] + ); for (const aRange of rangesToFetch) { const fetchedHistory = await fetchSingleRange( hass, diff --git a/src/plotly-graph-card.ts b/src/plotly-graph-card.ts index ed9e768..03d893f 100644 --- a/src/plotly-graph-card.ts +++ b/src/plotly-graph-card.ts @@ -49,10 +49,14 @@ function patchLonelyDatapoints(xs: Datum[], ys: Datum[]) { } } -function extendLastDatapointToPresent(xs: Datum[], ys: Datum[]) { +function extendLastDatapointToPresent( + xs: Datum[], + ys: Datum[], + offset: number +) { if (xs.length === 0) return; const last = JSON.parse(JSON.stringify(ys[ys.length - 1])); - xs.push(new Date()); + xs.push(new Date(Date.now() + offset)); ys.push(last); } @@ -252,7 +256,10 @@ export class PlotlyGraph extends HTMLElement { } getAutoFetchRange() { const ms = this.parsed_config.hours_to_show * 60 * 60 * 1000; - return [+new Date() - ms, +new Date()] as [number, number]; + return [ + +new Date() - ms + this.parsed_config.offset, + +new Date() + this.parsed_config.offset, + ] as [number, number]; } getAutoFetchRangeWithValueMargins() { const [start, end] = this.getAutoFetchRange(); @@ -368,6 +375,7 @@ export class PlotlyGraph extends HTMLElement { title: config.title, hours_to_show: config.hours_to_show ?? 1, refresh_interval: config.refresh_interval ?? "auto", + offset: parseTimeDuration(config.offset ?? "0s"), entities: config.entities.map((entityIn, entityIdx) => { if (typeof entityIn === "string") entityIn = { entity: entityIn }; @@ -386,6 +394,7 @@ export class PlotlyGraph extends HTMLElement { config.defaults?.entity, entityIn ); + entity.offset = parseTimeDuration(entityIn.offset ?? "0s"); if (entity.lambda) { entity.lambda = window.eval(entity.lambda); } @@ -487,8 +496,7 @@ export class PlotlyGraph extends HTMLElement { const was = this.parsed_config; this.parsed_config = newConfig; const is = this.parsed_config; - if (!this.contentEl) return; - if (is.hours_to_show !== was?.hours_to_show) { + if (is.hours_to_show !== was?.hours_to_show || is.offset !== was?.offset) { this.exitBrowsingMode(); } await this.fetch(); @@ -593,7 +601,7 @@ export class PlotlyGraph extends HTMLElement { let xs: Datum[] = xsIn; let ys = ysIn; - extendLastDatapointToPresent(xs, ys); + extendLastDatapointToPresent(xs, ys, trace.offset); if (trace.lambda) { try { const r = trace.lambda(ysIn, xsIn, history); diff --git a/src/types.ts b/src/types.ts index 49bb9ba..9d9bfd1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ import { Datum } from "plotly.js"; import { ColorSchemeArray, ColorSchemeNames } from "./color-schemes"; +import { TimeDurationStr } from "./duration/duration"; import { AutoPeriodConfig, StatisticPeriod, @@ -15,6 +16,7 @@ export type InputConfig = { refresh_interval?: number | "auto"; // in seconds color_scheme?: ColorSchemeNames | ColorSchemeArray | number; title?: string; + offset?: TimeDurationStr; entities: ({ entity: string; attribute?: string; @@ -27,6 +29,7 @@ export type InputConfig = { | { right_margin: number; }; + offset?: TimeDurationStr; } & Partial)[]; defaults?: { entity?: Partial; @@ -52,12 +55,14 @@ export type EntityConfig = EntityIdConfig & { | { right_margin: number; }; + offset: number; } & Partial; export type Config = { title?: string; hours_to_show: number; refresh_interval: number | "auto"; // in seconds + offset: number; entities: EntityConfig[]; layout: Partial; config: Partial; From 60c2e4a31ed450731e90f6e19a054cfd789e010a Mon Sep 17 00:00:00 2001 From: David Buezas Date: Mon, 31 Oct 2022 17:52:23 +0100 Subject: [PATCH 2/9] Added offset yaml to readme --- readme.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/readme.md b/readme.md index a230319..6afcab3 100644 --- a/readme.md +++ b/readme.md @@ -266,6 +266,17 @@ entities: ``` +### Offsets + +```yaml +type: custom:plotly-graph +offset: 6h +entities: + - entity: sensor.weather_24h_forecast + offset: -1d + - entity: sensor.actual_temperature +``` + ### `lambda:` transforms `lambda` takes a js function (as a string) to pre process the data before plotting it. Here you can do things like normalisation, integration. For example: From d8ac252738672619b84cc08aa4de72036197a6f4 Mon Sep 17 00:00:00 2001 From: David Buezas Date: Sat, 12 Nov 2022 19:23:55 +0100 Subject: [PATCH 3/9] Don't clear out-of-view data from the cache Clearing out-of-view data was used to ensure xaxis autoranging would show the expected time range in the screen, but xaxis autoranging is not used anymore, and this technique is incompatible with offsets. --- src/cache/Cache.ts | 31 ------------------------------- src/plotly-graph-card.ts | 3 --- 2 files changed, 34 deletions(-) diff --git a/src/cache/Cache.ts b/src/cache/Cache.ts index b5fe4f3..fc23971 100644 --- a/src/cache/Cache.ts +++ b/src/cache/Cache.ts @@ -132,7 +132,6 @@ export default class Cache { } async update( range: TimestampRange, - removeOutsideRange: boolean, entities: EntityConfig[], hass: HomeAssistant, significant_changes_only: boolean, @@ -142,10 +141,6 @@ export default class Cache { return (this.busy = this.busy .catch(() => {}) .then(async () => { - if (removeOutsideRange) { - // @TODO: consider offsets - this.removeOutsideRange(range); - } const promises = entities.map(async (entity) => { const entityKey = getEntityKey(entity); this.ranges[entityKey] ??= []; @@ -173,30 +168,4 @@ export default class Cache { await Promise.all(promises); })); } - - removeOutsideRange(range: TimestampRange) { - this.ranges = mapValues(this.ranges, (ranges) => - subtractRanges(ranges, [ - [Number.NEGATIVE_INFINITY, range[0] - 1], - [range[1] + 1, Number.POSITIVE_INFINITY], - ]) - ); - this.histories = mapValues(this.histories, (history) => { - let first: EntityState | undefined; - let last: EntityState | undefined; - const newHistory = history.filter((datum) => { - if (datum.timestamp <= range[0]) first = datum; - else if (!last && datum.timestamp >= range[1]) last = datum; - else return true; - return false; - }); - if (first) { - newHistory.unshift(first); - } - if (last) { - newHistory.push(last); - } - return newHistory; - }); - } } diff --git a/src/plotly-graph-card.ts b/src/plotly-graph-card.ts index 03d893f..3388f9a 100644 --- a/src/plotly-graph-card.ts +++ b/src/plotly-graph-card.ts @@ -203,8 +203,6 @@ export class PlotlyGraph extends HTMLElement { this.fetch(); } if (shouldPlot) { - if (!this.isBrowsing) - this.cache.removeOutsideRange(this.getAutoFetchRange()); this.plot(); } } @@ -538,7 +536,6 @@ export class PlotlyGraph extends HTMLElement { try { await this.cache.update( range, - !this.isBrowsing, visibleEntities, this.hass, this.parsed_config.minimal_response, From 89528b1b61a720076ff50b790c2fa9df12eb65c9 Mon Sep 17 00:00:00 2001 From: David Buezas Date: Sat, 12 Nov 2022 19:25:06 +0100 Subject: [PATCH 4/9] Add extend_to_present option to entities --- readme.md | 18 ++++++++++++++++++ src/plotly-graph-card.ts | 6 +++++- src/types.ts | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6afcab3..47b1125 100644 --- a/readme.md +++ b/readme.md @@ -277,6 +277,24 @@ entities: - entity: sensor.actual_temperature ``` +### Extend_to_present + +The boolean `extend_to_present` will take the last known datapoint and "expand" it to the present by creating a duplicate and setting its date to `now`. +This is useful to make the plot look fuller. +It's recommended to turn it off when using `offset`s, or when setting the mode of the trace to `markers`. +Defaults to `true` for state history, and `false` for statistics. + +```yaml +type: custom:plotly-graph +entities: + - entity: sensor.weather_24h_forecast + mode: "markers" + extend_to_present: false # true by default for state history + - entity: sensor.actual_temperature + statistics: mean + extend_to_present: true # false by default for statistics +``` + ### `lambda:` transforms `lambda` takes a js function (as a string) to pre process the data before plotting it. Here you can do things like normalisation, integration. For example: diff --git a/src/plotly-graph-card.ts b/src/plotly-graph-card.ts index 3388f9a..468e35d 100644 --- a/src/plotly-graph-card.ts +++ b/src/plotly-graph-card.ts @@ -439,6 +439,7 @@ export class PlotlyGraph extends HTMLElement { throw new Error( `period: "${entity.period}" is not valid. Use ${STATISTIC_PERIODS}` ); + entity.extend_to_present ??= !!entity.statistic; } const [oldAPI_entity, oldAPI_attribute] = entity.entity.split("::"); if (oldAPI_attribute) { @@ -598,7 +599,10 @@ export class PlotlyGraph extends HTMLElement { let xs: Datum[] = xsIn; let ys = ysIn; - extendLastDatapointToPresent(xs, ys, trace.offset); + if (trace.extend_to_present) { + extendLastDatapointToPresent(xs, ys, trace.offset); + } + if (trace.lambda) { try { const r = trace.lambda(ysIn, xsIn, history); diff --git a/src/types.ts b/src/types.ts index 9d9bfd1..003544d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -56,6 +56,7 @@ export type EntityConfig = EntityIdConfig & { right_margin: number; }; offset: number; + extend_to_present: boolean; } & Partial; export type Config = { From 923076f4ce949f6ad3b6761cacdb890b40005f44 Mon Sep 17 00:00:00 2001 From: David Buezas Date: Sat, 12 Nov 2022 20:48:05 +0100 Subject: [PATCH 5/9] Reintroduced range out-of-screen data cleaning, but out of the cache --- src/plotly-graph-card.ts | 36 +++++++++++++++++++++----- src/plotly-utils.ts | 55 ---------------------------------------- 2 files changed, 30 insertions(+), 61 deletions(-) delete mode 100644 src/plotly-utils.ts diff --git a/src/plotly-graph-card.ts b/src/plotly-graph-card.ts index 468e35d..3a34953 100644 --- a/src/plotly-graph-card.ts +++ b/src/plotly-graph-card.ts @@ -8,10 +8,12 @@ import Plotly from "./plotly"; import { Config, EntityConfig, + EntityState, InputConfig, isEntityIdAttrConfig, isEntityIdStateConfig, isEntityIdStatisticsConfig, + TimestampRange, } from "./types"; import Cache from "./cache/Cache"; import getThemedLayout from "./themed-layout"; @@ -59,6 +61,23 @@ function extendLastDatapointToPresent( xs.push(new Date(Date.now() + offset)); ys.push(last); } +function removeOutOfRange(xs: Datum[], ys: Datum[], range: TimestampRange) { + let first = -1; + let last = -1; + + for (let i = 0; i < xs.length; i++) { + if (xs[i]! < range[0]) first = i; + if (xs[i]! > range[1]) last = i; + } + if (last > -1) { + xs = xs.splice(last); + ys = ys.splice(last); + } + if (first > -1) { + xs = xs.splice(0, first); + ys = ys.splice(0, first); + } +} console.info( `%c ${componentName.toUpperCase()} %c ${version} ${process.env.NODE_ENV}`, @@ -303,12 +322,9 @@ export class PlotlyGraph extends HTMLElement { this.isBrowsing = false; this.resetButtonEl.classList.add("hidden"); this.withoutRelayout(async () => { - await Plotly.relayout(this.contentEl, { - uirevision: Math.random(), // to trigger the autoranges in all y-yaxes - xaxis: { range: this.getAutoFetchRangeWithValueMargins() }, // to reset xaxis to hours_to_show quickly, before refetching - }); + await this.plot(); // to reset xaxis to hours_to_show quickly, before refetching + await this.fetch(); }); - await this.fetch(); }; onRestyle = async () => { // trace visibility changed, fetch missing traces @@ -602,6 +618,10 @@ export class PlotlyGraph extends HTMLElement { if (trace.extend_to_present) { extendLastDatapointToPresent(xs, ys, trace.offset); } + if (!this.isBrowsing) { + // to ensure the y axis autorange containst the yaxis + removeOutOfRange(xs, ys, this.getAutoFetchRangeWithValueMargins()); + } if (trace.lambda) { try { @@ -671,7 +691,11 @@ export class PlotlyGraph extends HTMLElement { units.map((unit, i) => ["yaxis" + (i == 0 ? "" : i + 1), { title: unit }]) ); const layout = merge( - { uirevision: true }, + { + uirevision: this.isBrowsing + ? this.contentEl.layout.uirevision + : Math.random(), // to trigger the autoranges in all y-yaxes + }, { xaxis: { range: this.isBrowsing diff --git a/src/plotly-utils.ts b/src/plotly-utils.ts deleted file mode 100644 index df69492..0000000 --- a/src/plotly-utils.ts +++ /dev/null @@ -1,55 +0,0 @@ -import Plotly from "./plotly"; -export function autorange( - contentEl: HTMLElement, - range: number[], - data: Partial[], - layout: Partial, - config: Partial -) { - const boundedData = data.map((d) => { - const x: number[] = []; - const y: number[] = []; - for (let i = 0; i < d.x!.length; i++) { - const dx: number = (d as any).x[i]; - const dy: number = (d as any).y[i]; - if (range[0] <= dx && dx <= range[1]) { - x.push(dx); - y.push(dy); - } - } - return { - ...d, - x, - y, - }; - }); - Plotly.relayout(contentEl, { - "xaxis.autorange": true, - "yaxis.autorange": true, - }); - - Plotly.react(contentEl, boundedData, layout, { - displaylogo: false, - ...config, - }); - Plotly.relayout(contentEl, { - "xaxis.autorange": false, - "xaxis.range": range as [number, number], - "yaxis.autorange": false, - }); -} - -export const extractRanges = (layout: Partial) => { - const justRanges: Partial = {}; - Object.keys(layout).forEach((key) => { - if (layout[key]?.range) { - justRanges[key] ??= {}; - justRanges[key].range = layout[key].range; - } - if (layout[key]?.autorange) { - justRanges[key] ??= {}; - justRanges[key].autorange = layout[key].autorange; - } - }); - return justRanges; -}; From 7fd272ba49e31d5e18bfed3a6ce27b5d7fac0a18 Mon Sep 17 00:00:00 2001 From: David Buezas Date: Sun, 13 Nov 2022 17:03:36 +0100 Subject: [PATCH 6/9] Clear cache when exiting browsing mode (e.g scroll, zoom) This makes auto ranging after resetting behave as it used to (only show as much as zoomed out since the last reset) --- src/plotly-graph-card.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plotly-graph-card.ts b/src/plotly-graph-card.ts index 3a34953..49e31e6 100644 --- a/src/plotly-graph-card.ts +++ b/src/plotly-graph-card.ts @@ -314,15 +314,16 @@ export class PlotlyGraph extends HTMLElement { return +parseISO(date); }); } - async enterBrowsingMode() { + enterBrowsingMode = () => { this.isBrowsing = true; this.resetButtonEl.classList.remove("hidden"); - } + }; exitBrowsingMode = async () => { this.isBrowsing = false; this.resetButtonEl.classList.add("hidden"); this.withoutRelayout(async () => { await this.plot(); // to reset xaxis to hours_to_show quickly, before refetching + this.cache.clearCache(); // so that when the user zooms out and autoranges, not more that what's visible will be autoranged await this.fetch(); }); }; From 041797283b420aa8623afa7cf2c86e7df7c37f6d Mon Sep 17 00:00:00 2001 From: Zanna_37 Date: Mon, 14 Nov 2022 04:54:49 +0100 Subject: [PATCH 7/9] refactor: move images in a separate folder --- demo.gif => docs/resources/demo.gif | Bin demo2.gif => docs/resources/demo2.gif | Bin example1.png => docs/resources/example1.png | Bin .../resources/rangeselector.apng | Bin readme.md | 8 ++++---- 5 files changed, 4 insertions(+), 4 deletions(-) rename demo.gif => docs/resources/demo.gif (100%) rename demo2.gif => docs/resources/demo2.gif (100%) rename example1.png => docs/resources/example1.png (100%) rename rangeselector.apng => docs/resources/rangeselector.apng (100%) diff --git a/demo.gif b/docs/resources/demo.gif similarity index 100% rename from demo.gif rename to docs/resources/demo.gif diff --git a/demo2.gif b/docs/resources/demo2.gif similarity index 100% rename from demo2.gif rename to docs/resources/demo2.gif diff --git a/example1.png b/docs/resources/example1.png similarity index 100% rename from example1.png rename to docs/resources/example1.png diff --git a/rangeselector.apng b/docs/resources/rangeselector.apng similarity index 100% rename from rangeselector.apng rename to docs/resources/rangeselector.apng diff --git a/readme.md b/readme.md index 47b1125..846d586 100644 --- a/readme.md +++ b/readme.md @@ -3,8 +3,8 @@ # Plotly Graph Card - - + +

@@ -54,7 +54,7 @@ refresh_interval: 10 ### Filling, line width, color -![](example1.png) +![](docs/resources/example1.png) ```yaml type: custom:plotly-graph @@ -81,7 +81,7 @@ refresh_interval: 10 # in seconds ### Range Selector buttons -![](rangeselector.apng) +![](docs/resources/rangeselector.apng) ```yaml type: custom:plotly-graph From f021a6a2c1852f271aaaf914cad17cbf84e4624b Mon Sep 17 00:00:00 2001 From: Zanna_37 Date: Mon, 14 Nov 2022 07:04:55 +0100 Subject: [PATCH 8/9] docs: add offset to the README --- docs/resources/offset-nowline.png | Bin 0 -> 25863 bytes docs/resources/offset-temperature.png | Bin 0 -> 23899 bytes readme.md | 97 +++++++++++++++++++++++--- 3 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 docs/resources/offset-nowline.png create mode 100644 docs/resources/offset-temperature.png diff --git a/docs/resources/offset-nowline.png b/docs/resources/offset-nowline.png new file mode 100644 index 0000000000000000000000000000000000000000..4169f28ad2815c9723470a43916b299f8e01e0c5 GIT binary patch literal 25863 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 literal 0 HcmV?d00001 diff --git a/readme.md b/readme.md index 846d586..2a6df18 100644 --- a/readme.md +++ b/readme.md @@ -242,6 +242,92 @@ entities: Note that `5minute` period statistics are limited in time as normal recorder history is, contrary to other periods which keep data for years. +## Offsets +Offsets are useful to shift data in the temporal axis. For example, if you have a sensor that reports the forecasted temperature 3 hours from now, it means that the current value should be plotted in the future. With the `offset` attribute you can shift the data so it is placed in the correct position. +Another possible use is to compare past data with the current one. For example, you can plot yesterday's temperature and the current one on top of each other. + +The `offset` flag can be specified in two places. +**1)** When used at the top level of the configuration, it specifies how much "future" the graph shows by default. For example, if `hours_to_show` is 16 and `offset` is 3h, the graph shows the past 13 hours (16-3) plus the next 3 hours. +**2)** When used at the trace level, it offsets the trace by the specified amount. + + +```yaml +type: custom:plotly-graph +hours_to_show: 16 +offset: 3h +entities: + - entity: sensor.current_temperature + line: + width: 3 + color: orange + - entity: sensor.current_temperature + name: Temperature yesterday + offset: 1d + line: + width: 1 + dash: dot + color: orange + - entity: sensor.temperature_12h_forecast + offset: 12h + name: Forecast temperature + line: + width: 1 + dash: dot + color: grey +``` + +![Graph with offsets](docs/resources/offset-temperature.png) + +### Now line +When using offsets, it is useful to have a line that indicates the current time. This can be done by using a lambda function that returns a line with the current time as x value and 0 and 1 as y values. The line is then hidden from the legend. + +```yaml +type: custom:plotly-graph +hours_to_show: 6 +offset: 3h +entities: + - entity: sensor.forecast_temperature + yaxis: y1 + offset: 3h + - entity: sensor.nothing_now + name: Now + yaxis: y9 + showlegend: false + line: + width: 1 + dash: dot + color: deepskyblue + lambda: |- + () => { + return {x:[Date.now(),Date.now()], y:[0,1]} + } +layout: + yaxis9: + visible: false + fixedrange: true +``` + +![Graph with offsets and now-line](docs/resources/offset-nowline.png) + +## Duration +Whenever a time duration can be specified, this is the notation to use: + +| Unit | Suffix | Notes | +|--------------|--------|----------| +| Milliseconds | `ms` | | +| Seconds | `s` | | +| Minutes | `m` | | +| Hours | `h` | | +| Days | `d` | | +| Weeks | `w` | | +| Months | `M` | 30 days | +| Years | `y` | 365 days | + +Example: +```yaml +offset: 3h +``` + ## Extra entity attributes: ```yaml @@ -266,17 +352,6 @@ entities: ``` -### Offsets - -```yaml -type: custom:plotly-graph -offset: 6h -entities: - - entity: sensor.weather_24h_forecast - offset: -1d - - entity: sensor.actual_temperature -``` - ### Extend_to_present The boolean `extend_to_present` will take the last known datapoint and "expand" it to the present by creating a duplicate and setting its date to `now`. From 8a40c4fa2c83abf50bd4b1b802cc2a770e27091a Mon Sep 17 00:00:00 2001 From: David Buezas Date: Mon, 14 Nov 2022 18:16:56 +0100 Subject: [PATCH 9/9] Fix inverted `entity.extend_to_present`default --- src/plotly-graph-card.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plotly-graph-card.ts b/src/plotly-graph-card.ts index 49e31e6..c8b5e59 100644 --- a/src/plotly-graph-card.ts +++ b/src/plotly-graph-card.ts @@ -456,7 +456,7 @@ export class PlotlyGraph extends HTMLElement { throw new Error( `period: "${entity.period}" is not valid. Use ${STATISTIC_PERIODS}` ); - entity.extend_to_present ??= !!entity.statistic; + entity.extend_to_present ??= !entity.statistic; } const [oldAPI_entity, oldAPI_attribute] = entity.entity.split("::"); if (oldAPI_attribute) {