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 configuration |
+Preset 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;}>"
}
]
},