diff --git a/readme.md b/readme.md index f11b318..4aeef8f 100644 --- a/readme.md +++ b/readme.md @@ -696,7 +696,55 @@ entities: - map_y: y + vars.temp1.ys[i] ``` -### Universal functions +### Entity click handlers + +When the legend is clicked (or doubleclicked), the trace will be hidden (or showed alone) by default. This behaviour is controlled by [layout-legend-itemclick](https://plotly.com/javascript/reference/layout/#layout-legend-itemclick). +On top of that, a `$fn` function can be used to add custom behaviour. +If a handler returns false, the default behaviour trace toggle behaviour will be disabled, but this will also inhibit the `on_legend_dblclick ` handler. Disable the default behaviour via layout-legend-itemclick instead if you want to use both click and dblclick handlers. + +```yaml +type: custom:plotly-graph +entities: + - entity: sensor.temperature1 + on_legend_click: |- + $fn () => (event_data) => { + event = new Event( "hass-more-info") + event.detail = { entityId: 'sensor.temperature1' }; + document.querySelector('home-assistant').dispatchEvent(event); + return false; // disable trace toggling + } +``` + +Alternatively, clicking on points of the trace itself. + +```yaml +type: custom:plotly-graph +entities: + - entity: sensor.temperature1 + on_click: |- + $fn () => (event_data) => { + ... + // WARNING: this doesn't work and I don't understand why. Help welcome + } +``` + +There is also a double click plot handler, it works on the whole plotting area (not points of an entity). Beware that double click also autoscales the plot. + +```yaml +type: custom:plotly-graph +entities: + - entity: sensor.temperature1 +on_dblclick: |- + $fn ({ hass }) => () => { + hass.callService('light', 'turn_on', { + entity_id: 'light.portique_lumiere' + }) + } +``` + +See more in plotly's [official docs](https://plotly.com/javascript/plotlyjs-events) + +## Universal functions Javascript functions allowed everywhere in the yaml. Evaluation is top to bottom and shallow to deep (depth first traversal). diff --git a/src/filters/fft-regression.js b/src/filters/fft-regression.js index 009ac91..9d8c0e2 100644 --- a/src/filters/fft-regression.js +++ b/src/filters/fft-regression.js @@ -25,7 +25,6 @@ export default class FFTRegression extends BaseRegression { const sorted = Array.from(re.data) .map((x, i) => [x, i]) .sort((a, b) => b[0] - a[0]); - console.log(`sorted`, sorted); for (let i = degree; i < sorted.length; i++) { re.set(sorted[i][1], 0); diff --git a/src/parse-config/defaults.ts b/src/parse-config/defaults.ts index 607fe79..ab1c7d2 100644 --- a/src/parse-config/defaults.ts +++ b/src/parse-config/defaults.ts @@ -3,12 +3,15 @@ import { Config, InputConfig } from "../types"; import { parseColorScheme } from "./parse-color-scheme"; import { getEntityIndex } from "./parse-config"; import getThemedLayout, { HATheme } from "./themed-layout"; - +const noop$fn = () => () => {}; const defaultEntityRequired = { entity: "", show_value: false, internal: false, time_offset: "0s", + on_legend_click: noop$fn, + on_legend_dblclick: noop$fn, + on_click: noop$fn, }; const defaultEntityOptional = { mode: "lines", @@ -59,6 +62,7 @@ const defaultYamlRequired = { yaxes: {}, }, layout: {}, + on_dblclick: noop$fn, }; // diff --git a/src/plotly-graph-card.ts b/src/plotly-graph-card.ts index e11c7f7..5b49d9f 100644 --- a/src/plotly-graph-card.ts +++ b/src/plotly-graph-card.ts @@ -50,6 +50,10 @@ export class PlotlyGraph extends HTMLElement { relayoutListener?: EventEmitter; restyleListener?: EventEmitter; refreshTimeout?: number; + legendItemClick?: EventEmitter; + legendItemDoubleclick?: EventEmitter; + dataClick?: EventEmitter; + doubleclick?: EventEmitter; } = {}; constructor() { @@ -161,6 +165,18 @@ export class PlotlyGraph extends HTMLElement { "plotly_restyle", this.onRestyle )!; + this.handles.legendItemClick = this.contentEl.on( + "plotly_legendclick", + this.onLegendItemClick + )!; + this.handles.legendItemDoubleclick = this.contentEl.on( + "plotly_legenddoubleclick", + this.onLegendItemDoubleclick + )!; + this.handles.doubleclick = this.contentEl.on( + "plotly_doubleclick", + this.onDoubleclick + )!; this.resetButtonEl.addEventListener("click", this.exitBrowsingMode); this.touchController.connect(); this.plot({ should_fetch: true }); @@ -170,6 +186,16 @@ export class PlotlyGraph extends HTMLElement { this.handles.resizeObserver!.disconnect(); this.handles.relayoutListener!.off("plotly_relayout", this.onRelayout); this.handles.restyleListener!.off("plotly_restyle", this.onRestyle); + this.handles.legendItemClick!.off( + "plotly_legendclick", + this.onLegendItemClick + )!; + this.handles.legendItemDoubleclick!.off( + "plotly_legenddoubleclick", + this.onLegendItemDoubleclick + )!; + this.handles.dataClick!.off("plotly_click", this.onDataClick)!; + this.handles.doubleclick!.off("plotly_doubleclick", this.onDoubleclick)!; clearTimeout(this.handles.refreshTimeout!); this.resetButtonEl.removeEventListener("click", this.exitBrowsingMode); this.touchController.disconnect(); @@ -261,6 +287,27 @@ export class PlotlyGraph extends HTMLElement { await this.plot({ should_fetch: true }); }); }; + onLegendItemClick = ({ curveNumber, ...rest }) => { + return this.parsed_config.entities[curveNumber].on_legend_click({ + curveNumber, + ...rest, + }); + }; + onLegendItemDoubleclick = ({ curveNumber, ...rest }) => { + return this.parsed_config.entities[curveNumber].on_legend_dblclick({ + curveNumber, + ...rest, + }); + }; + onDataClick = ({ points, ...rest }) => { + return this.parsed_config.entities[points[0].curveNumber].on_click({ + points, + ...rest, + }); + }; + onDoubleclick = () => { + return this.parsed_config.on_dblclick(); + }; onRestyle = async () => { // trace visibility changed, fetch missing traces if (this.isInternalRelayout) return; @@ -346,7 +393,6 @@ export class PlotlyGraph extends HTMLElement { .map((e) => "" + (e || "See devtools console") + "") .join("\n
\n"); this.parsed_config = parsed; - console.log("fetched", this.parsed_config); const { entities, layout, config, refresh_interval } = this.parsed_config; clearTimeout(this.handles.refreshTimeout!); @@ -364,6 +410,11 @@ export class PlotlyGraph extends HTMLElement { await Plotly.react(this.contentEl, entities, layout, config); this.contentEl.style.visibility = ""; }); + // this.handles.dataClick?.off("plotly_click", this.onDataClick)!; + this.handles.dataClick = this.contentEl.on( + "plotly_click", + this.onDataClick + )!; }); // The height of your card. Home Assistant uses this to automatically // distribute all cards over the available columns. diff --git a/src/types.ts b/src/types.ts index 392ba7b..9fc5219 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,7 +28,6 @@ export type InputConfig = { statistic?: StatisticType; period?: StatisticPeriod | "auto" | AutoPeriodConfig; unit_of_measurement?: string; - lambda?: string; internal?: boolean; show_value?: | boolean @@ -38,11 +37,15 @@ export type InputConfig = { offset?: TimeDurationStr; extend_to_present?: boolean; filters?: (Record | string)[]; + on_legend_click?: Function; + on_legend_dblclick?: Function; + on_click?: Function; } & Partial)[]; defaults?: { entity?: Partial; yaxes?: Partial; }; + on_dblclick?: Function; layout?: Partial; config?: Partial; ha_theme?: boolean; @@ -54,14 +57,6 @@ export type InputConfig = { export type EntityConfig = EntityIdConfig & { unit_of_measurement?: string; - lambda?: ( - y: YValue[], - x: Date[], - raw_entity: ((StatisticValue | HassEntity) & { - timestamp: number; - value: any; - })[] - ) => YValue[] | { x?: Date[]; y?: YValue[] }; internal: boolean; show_value: | boolean @@ -71,6 +66,9 @@ export type EntityConfig = EntityIdConfig & { offset: number; extend_to_present: boolean; filters: FilterFn[]; + on_legend_click: Function; + on_legend_dblclick: Function; + on_click: Function; } & Partial; export type Config = { @@ -87,6 +85,7 @@ export type Config = { minimal_response: boolean; disable_pinch_to_zoom: boolean; visible_range: [number, number]; + on_dblclick: Function; }; export type EntityIdStateConfig = { entity: string;