From eafceb2b1f5b1377bc169e5f343384d3500191e8 Mon Sep 17 00:00:00 2001 From: dcbr <15089458+dcbr@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:12:37 +0100 Subject: [PATCH] Add presets and xaxes defaults (#526) --- readme.md | 108 ++++++++++++++++++++++++++++++++++- src/parse-config/defaults.ts | 56 +++++++++++++++--- src/types.ts | 2 + yaml-editor/src/schema.json | 15 ++++- 4 files changed, 170 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index 840f2a0..96eb01d 100644 --- a/readme.md +++ b/readme.md @@ -922,7 +922,7 @@ hours_to_show: current_day ## Default trace & axis styling -default configurations for all entities and all yaxes (e.g yaxis, yaxis2, yaxis3, etc). +default configurations for all entities and all xaxes (e.g xaxis, xaxis2, xaxis3, etc) and yaxes (e.g yaxis, yaxis2, yaxis3, etc). ```yaml type: custom:plotly-graph @@ -934,6 +934,8 @@ defaults: fill: tozeroy line: width: 2 + xaxes: + showgrid: false # Disables vertical gridlines yaxes: fixedrange: true # disables vertical zoom & scroll ``` @@ -1040,6 +1042,110 @@ config: When using `hours_to_show: current_week`, the "First day of the week" configured in Home Assistant is used +## Presets + +If you find yourself reusing the same card configuration frequently, you can save it as a preset. + +### Setup + +Presets are loaded from the global `PlotlyGraphCardPresets` JS object (such that they can be shared across different dashboards). +The recommended way to add or modify presets is to set up a `plotly_presets.js` script in the `www` subdirectory of your `config` folder. +```js +window.PlotlyGraphCardPresets = { + // Add your presets here with the following format (or check the examples below) + // PresetName: { PresetConfiguration } +}; +``` +To ensure this file is loaded on every dashboard, add the following lines to your `configuration.yaml`. +```yaml +frontend: + extra_module_url: + - /local/plotly_presets.js +``` +You might have to clear your browser cache or restart HA for changes to take effect. + +### Examples + +The preset configuration should be defined as a JS object instead of the YAML format used by the card. +Below is an example YAML configuration that is split into several corresponding presets. + + + + + + + + + + +
YAML configurationPreset configurations
+ +```yaml +hours_to_show: current_day +time_offset: -24h +defaults: + entity: + hovertemplate: "$fn ({ get }) => `%{y:,.1f} ${get('.unit_of_measurement')}${get('.name')}`" + xaxes: + showspikes: true + spikemode: across + spikethickness: -2 +``` + + + +```js +window.PlotlyGraphCardPresets = { + yesterday: { // Start of preset with name 'yesterday' + hours_to_show: "current_day", + time_offset: "-24h", + }, + simpleHover: { // Start of preset with name 'simpleHover' + defaults: { + entity: { + hovertemplate: ({get}) => `%{y:,.1f} ${get(".unit_of_measurement")}${get(".name")}`, + }, + }, + }, + verticalSpikes: { // Start of preset with name 'verticalSpikes' + defaults: { + xaxes: { + showspikes: true, + spikemode: "across", + spikethickness: -2, + }, + }, + }, +}; +``` + +
+ +### Usage + +To use your defined templates, simply specify the preset name under the `preset` key. +You can also specify a list of preset names to combine several of them. + +E.g. with the above preset definitions, we can show yesterday's temperatures. +```yaml +type: custom:plotly-graph +entities: + - sensor.temperature1 + - sensor.temperature2 +preset: yesterday +``` + +Or show a simplified hover tooltip together with vertical spikes. +```yaml +type: custom:plotly-graph +entities: + - sensor.temperature1 + - sensor.temperature2 +preset: + - simpleHover + - verticalSpikes +``` + # deprecations: ### `no_theme` diff --git a/src/parse-config/defaults.ts b/src/parse-config/defaults.ts index bb61c3d..2bd7a6d 100644 --- a/src/parse-config/defaults.ts +++ b/src/parse-config/defaults.ts @@ -3,6 +3,7 @@ import { Config, InputConfig } from "../types"; import { parseColorScheme } from "./parse-color-scheme"; import { getEntityIndex } from "./parse-config"; import getThemedLayout, { HATheme } from "./themed-layout"; +declare const window: Window & { PlotlyGraphCardPresets?: Record }; const noop$fn = () => () => {}; const defaultEntityRequired = { entity: "", @@ -59,6 +60,7 @@ const defaultYamlRequired = { raw_plotly: false, defaults: { entity: {}, + xaxes: {}, yaxes: {}, }, layout: {}, @@ -68,6 +70,15 @@ const defaultYamlRequired = { // +const defaultExtraXAxes: Partial = { + // automargin: true, // it makes zooming very jumpy + type: "date", + autorange: false, + overlaying: "x", + showgrid: false, + visible: false, +}; + const defaultExtraYAxes: Partial = { // automargin: true, // it makes zooming very jumpy side: "right", @@ -98,6 +109,12 @@ const defaultYamlOptional: { type: "date", // automargin: true, // it makes zooming very jumpy }, + ...Object.fromEntries( + Array.from({ length: 28 }).map((_, i) => [ + `xaxis${i + 2}`, + { ...defaultExtraXAxes }, + ]) + ), yaxis: { // automargin: true, // it makes zooming very jumpy }, @@ -144,22 +161,44 @@ const defaultYamlOptional: { }, }; +function getPresetYaml(presets: string | string[] | undefined, skips?: Set): Partial { + if (!window.PlotlyGraphCardPresets || presets === undefined) return {}; + if (!Array.isArray(presets)) presets = [presets]; + if (presets.length == 0) return {}; + if (skips === undefined) skips = new Set(); + const nestedPresets: string[] = []; + const presetYamls = presets.map((preset) => { + const yaml = window.PlotlyGraphCardPresets![preset] ?? {}; + if (yaml.preset !== undefined) { + if (!Array.isArray(yaml.preset)) yaml.preset = [yaml.preset]; + nestedPresets.push(...yaml.preset); + } + return yaml; + }); + const newPresets = nestedPresets.filter((preset) => !skips.has(preset)); + const nestedYaml = getPresetYaml(newPresets, new Set([...skips, ...presets])); + return merge({}, ...presetYamls, nestedYaml); +} + export function addPreParsingDefaults( yaml_in: InputConfig, css_vars: HATheme ): InputConfig { // merging in two steps to ensure ha_theme and raw_plotly_config took its default value let yaml = merge({}, yaml_in, defaultYamlRequired, yaml_in); + const preset = getPresetYaml(yaml.preset); for (let i = 1; i < 31; i++) { - const yaxis = "yaxis" + (i == 1 ? "" : i); - yaml.layout[yaxis] = merge( - {}, - yaml.layout[yaxis], - yaml.defaults.yaxes, - yaml.layout[yaxis] - ); + for (const d of ["x", "y"]) { + const axis = d + "axis" + (i == 1 ? "" : i); + yaml.layout[axis] = merge( + {}, + yaml.layout[axis], + yaml.defaults[d + "axes"], + preset.defaults?.[d+ "axes"] ?? {}, + yaml.layout[axis] + ); + } } - yaml = merge( {}, yaml, @@ -167,6 +206,7 @@ export function addPreParsingDefaults( layout: yaml.ha_theme ? getThemedLayout(css_vars) : {}, }, yaml.raw_plotly_config ? {} : defaultYamlOptional, + preset, yaml ); diff --git a/src/types.ts b/src/types.ts index d662cf7..6e4dd85 100644 --- a/src/types.ts +++ b/src/types.ts @@ -63,6 +63,7 @@ export type InputConfig = { } & Partial)[]; defaults?: { entity?: Partial; + xaxes?: Partial; yaxes?: Partial; }; on_dblclick?: Function; @@ -74,6 +75,7 @@ export type InputConfig = { minimal_response?: boolean; // defaults to true disable_pinch_to_zoom?: boolean; // defaults to false autorange_after_scroll?: boolean; // defaults to false + preset?: string | string[]; }; export type EntityConfig = EntityIdConfig & { diff --git a/yaml-editor/src/schema.json b/yaml-editor/src/schema.json index 98eeb7e..f1205a0 100644 --- a/yaml-editor/src/schema.json +++ b/yaml-editor/src/schema.json @@ -73274,7 +73274,7 @@ ], "type": "object" }, - "With$fn<{entity?:Partial|undefined;yaxes?:Partial>|undefined;}>": { + "With$fn<{entity?:Partial|undefined;xaxes?:Partial>|undefined;yaxes?:Partial>|undefined;}>": { "properties": { "entity": { "anyOf": [ @@ -73287,6 +73287,17 @@ } ] }, + "xaxes": { + "anyOf": [ + { + "pattern": "^[\\s]*\\$(ex|fn)\\s[\\s\\S]+$", + "type": "string" + }, + { + "$ref": "#/definitions/With$fn>>" + } + ] + }, "yaxes": { "anyOf": [ { @@ -100647,7 +100658,7 @@ "type": "string" }, { - "$ref": "#/definitions/With$fn<{entity?:Partial|undefined;yaxes?:Partial>|undefined;}>" + "$ref": "#/definitions/With$fn<{entity?:Partial|undefined;xaxes?:Partial>|undefined;yaxes?:Partial>|undefined;}>" } ] },