Skip to content

Commit c53da6c

Browse files
committed
modularization and ha_theme unaffected by raw plotly config
1 parent 09ab46f commit c53da6c

File tree

5 files changed

+122
-98
lines changed

5 files changed

+122
-98
lines changed

readme.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -791,20 +791,19 @@ Anything from https://plotly.com/javascript/reference/layout/.
791791

792792
### Home Assistant theming:
793793

794-
Disables Home Assistant theme colors
794+
Toggle Home Assistant theme colors
795795

796796
```yaml
797797
type: custom:plotly-graph
798798
entities:
799799
- entity: sensor.temperature_in_celsius
800-
ha_theme: false #defaults to true unless raw_plotly_config is true
800+
ha_theme: false #defaults to true
801801
```
802802

803803
### Raw plotly config:
804804

805-
Disables all in-built defaults for layout and entitites. Useful when using histograms, 3d plots, etc.
806-
The `x` and `y` properties of the traces won't be automatically filled with entity data, you need to use $fn for that.
807-
It will also change the default of `ha_theme` to false (you can set it to true manually)
805+
Toggle all in-built defaults for layout and entitites. Useful when using histograms, 3d plots, etc.
806+
When true, the `x` and `y` properties of the traces won't be automatically filled with entity data, you need to use $fn for that.
808807

809808
```yaml
810809
type: custom:plotly-graph

src/parse-config/defaults.ts

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
import { merge } from "lodash";
2+
import { Config, InputConfig } from "../types";
13
import { parseColorScheme } from "./parse-color-scheme";
24
import { getEntityIndex } from "./parse-config";
5+
import getThemedLayout, { defaultLayout, HATheme } from "./themed-layout";
36

4-
export const defaultEntityRequired = {
7+
const defaultEntityRequired = {
58
entity: "",
69
show_value: false,
710
internal: false,
811
time_offset: "0s",
912
};
10-
export const defaultEntityOptional = {
13+
const defaultEntityOptional = {
1114
mode: "lines",
1215
line: {
1316
width: 1,
@@ -41,22 +44,22 @@ export const defaultEntityOptional = {
4144
},
4245
};
4346

44-
export const defaultYamlRequired = {
47+
const defaultYamlRequired = {
4548
title: "",
4649
hours_to_show: 1,
4750
refresh_interval: "auto",
4851
color_scheme: "category10",
4952
time_offset: "0s",
5053
raw_plotly_config: false,
51-
ha_theme: ({ getFromConfig }) => !getFromConfig(".raw_plotly_config"),
54+
ha_theme: true,
5255
disable_pinch_to_zoom: false,
5356
raw_plotly: false,
5457
defaults: {
5558
entity: {},
5659
yaxes: {},
5760
},
5861
};
59-
export const defaultYamlOptional = {
62+
const defaultYamlOptional = {
6063
config: {
6164
displaylogo: false,
6265
scrollZoom: true,
@@ -74,3 +77,84 @@ export const defaultYamlOptional = {
7477
},
7578
},
7679
};
80+
81+
export function addPreParsingDefaults(yaml: InputConfig): InputConfig {
82+
const out = merge(
83+
{},
84+
yaml,
85+
{ layout: {} },
86+
defaultYamlRequired,
87+
yaml.raw_plotly_config ? {} : defaultYamlOptional,
88+
yaml
89+
);
90+
for (let i = 1; i < 31; i++) {
91+
const yaxis = "yaxis" + (i == 1 ? "" : i);
92+
out.layout[yaxis] = merge(
93+
{},
94+
out.layout[yaxis],
95+
out.defaults?.yaxes,
96+
out.layout[yaxis]
97+
);
98+
}
99+
out.entities = out.entities.map((entity) => {
100+
if (typeof entity === "string") entity = { entity };
101+
entity.entity ??= "";
102+
const [oldAPI_entity, oldAPI_attribute] = entity.entity.split("::");
103+
if (oldAPI_attribute) {
104+
entity.entity = oldAPI_entity;
105+
entity.attribute = oldAPI_attribute;
106+
}
107+
entity = merge(
108+
{},
109+
entity,
110+
defaultEntityRequired,
111+
out.raw_plotly_config ? {} : defaultEntityOptional,
112+
out.defaults?.entity,
113+
entity
114+
);
115+
return entity;
116+
});
117+
return out;
118+
}
119+
120+
export function addPostParsingDefaults({
121+
yaml,
122+
isBrowsing,
123+
old_uirevision,
124+
css_vars,
125+
}: {
126+
yaml: Config;
127+
isBrowsing: boolean;
128+
old_uirevision: number;
129+
css_vars: HATheme;
130+
}): Config {
131+
// 3rd pass: decorate
132+
/**
133+
* These cannot be done via defaults because they are functions and
134+
* functions would be overwritten if the user sets a configuration on a parent
135+
* */
136+
const yAxisTitles = Object.fromEntries(
137+
yaml.entities.map(({ unit_of_measurement, yaxis }) => [
138+
"yaxis" + yaxis?.slice(1),
139+
{ title: unit_of_measurement },
140+
])
141+
);
142+
const layout = merge(
143+
{},
144+
yaml.layout,
145+
yaml.raw_plotly_config ? {} : defaultLayout,
146+
yaml.ha_theme ? getThemedLayout(css_vars) : {},
147+
yaml.raw_plotly_config
148+
? {}
149+
: {
150+
xaxis: {
151+
range: yaml.visible_range,
152+
},
153+
//changing the uirevision triggers a reset to the axes
154+
uirevision: isBrowsing ? old_uirevision : Math.random(),
155+
},
156+
yaml.raw_plotly_config ? {} : yAxisTitles,
157+
yaml.layout
158+
);
159+
return merge({}, yaml, { layout }, yaml);
160+
}

src/parse-config/parse-config.ts

Lines changed: 28 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
11
import Cache from "../cache/Cache";
2-
import getThemedLayout, { defaultLayout, HATheme } from "./themed-layout";
2+
import { HATheme } from "./themed-layout";
33

44
import propose from "propose";
55

66
import merge from "lodash/merge";
77
import get from "lodash/get";
8-
import {
9-
defaultEntityRequired,
10-
defaultEntityOptional,
11-
defaultYamlRequired,
12-
defaultYamlOptional,
13-
} from "./defaults";
8+
import { addPreParsingDefaults, addPostParsingDefaults } from "./defaults";
149
import { parseTimeDuration } from "../duration/duration";
1510
import { parseStatistics } from "./parse-statistics";
1611
import { HomeAssistant } from "custom-card-helpers";
1712
import filters from "../filters/filters";
1813
import bounds from "binary-search-bounds";
1914
import { has } from "lodash";
2015
import { StatisticValue } from "../recorder-types";
21-
import { Config, HassEntity, YValue } from "../types";
16+
import { Config, HassEntity, InputConfig, YValue } from "../types";
2217

2318
class ConfigParser {
2419
private yaml?: any;
@@ -29,7 +24,7 @@ class ConfigParser {
2924
private busy = false;
3025
private fnParam!: FnParam;
3126

32-
async update(input: { yaml: any; hass: HomeAssistant }) {
27+
async update(input: { yaml: any; hass: HomeAssistant; css_vars: HATheme }) {
3328
if (this.busy) throw new Error("ParseConfig was updated while busy");
3429
this.busy = true;
3530
try {
@@ -38,62 +33,30 @@ class ConfigParser {
3833
this.busy = false;
3934
}
4035
}
41-
private async _update(input: {
36+
private async _update({
37+
yaml: input_yaml,
38+
hass,
39+
css_vars,
40+
}: {
4241
yaml: any;
4342
hass: HomeAssistant;
43+
css_vars: HATheme;
4444
}): Promise<{ errors: Error[]; parsed: Config }> {
45+
const old_uirevision = this.yaml?.layout?.uirevision;
4546
this.yaml = {};
4647
this.errors = [];
47-
48-
this.hass = input.hass;
49-
const old_uirevision = this.yaml?.layout?.uirevision;
50-
this.yaml_with_defaults = JSON.parse(JSON.stringify(input.yaml));
51-
52-
// 1st pass: add defaults
53-
this.yaml_with_defaults = merge(
54-
{},
55-
this.yaml_with_defaults,
56-
defaultYamlRequired,
57-
this.yaml_with_defaults.raw_plotly_config ? {} : defaultYamlOptional,
58-
this.yaml_with_defaults
59-
);
60-
for (let i = 1; i < 31; i++) {
61-
const yaxis = "yaxis" + (i == 1 ? "" : i);
62-
this.yaml_with_defaults.layout[yaxis] = merge(
63-
{},
64-
this.yaml_with_defaults.layout[yaxis],
65-
this.yaml_with_defaults.defaults?.yaxes,
66-
this.yaml_with_defaults.layout[yaxis]
67-
);
68-
}
69-
this.yaml_with_defaults.entities = this.yaml_with_defaults.entities.map(
70-
(entity) => {
71-
if (typeof entity === "string") entity = { entity };
72-
entity.entity ??= "";
73-
const [oldAPI_entity, oldAPI_attribute] = entity.entity.split("::");
74-
if (oldAPI_attribute) {
75-
entity.entity = oldAPI_entity;
76-
entity.attribute = oldAPI_attribute;
77-
}
78-
entity = merge(
79-
{},
80-
entity,
81-
defaultEntityRequired,
82-
this.yaml_with_defaults.raw_plotly_config
83-
? {}
84-
: defaultEntityOptional,
85-
this.yaml_with_defaults.defaults?.entity,
86-
entity
87-
);
88-
return entity;
89-
}
90-
);
48+
this.hass = hass;
49+
this.yaml_with_defaults = addPreParsingDefaults(input_yaml);
50+
const isBrowsing = !!input_yaml.visible_range;
51+
console.log("isBrowsing", isBrowsing);
9152

9253
// 2nd pass: evaluate functions
54+
9355
this.fnParam = {
9456
vars: {},
9557
path: "",
96-
hass: input.hass,
58+
hass,
59+
css_vars,
9760
getFromConfig: () => "",
9861
};
9962
for (const [key, value] of Object.entries(this.yaml_with_defaults)) {
@@ -109,35 +72,13 @@ class ConfigParser {
10972
this.errors?.push(e as Error);
11073
}
11174
}
112-
113-
// 3rd pass: decorate
114-
/**
115-
* These cannot be done via defaults because they are functions and
116-
* functions would be overwritten if the user sets a configuration on a parent
117-
* */
118-
const isBrowsing = !!input.yaml.visible_range;
119-
const yAxisTitles = Object.fromEntries(
120-
this.yaml.entities.map(({ unit_of_measurement, yaxis }) => [
121-
"yaxis" + yaxis?.slice(1),
122-
{ title: unit_of_measurement },
123-
])
124-
);
125-
merge(
126-
this.yaml.layout,
127-
this.yaml.raw_plotly_config ? {} : defaultLayout,
128-
this.yaml.ha_theme ? getThemedLayout(this.yaml.css_vars) : {},
129-
this.yaml.raw_plotly_config
130-
? {}
131-
: {
132-
xaxis: {
133-
range: this.yaml.visible_range,
134-
},
135-
//changing the uirevision triggers a reset to the axes
136-
uirevision: isBrowsing ? old_uirevision : Math.random(),
137-
},
138-
this.yaml.raw_plotly_config ? {} : yAxisTitles,
139-
this.yaml.layout
140-
);
75+
//TODO: mutates
76+
this.yaml = addPostParsingDefaults({
77+
yaml: this.yaml,
78+
isBrowsing,
79+
old_uirevision,
80+
css_vars,
81+
});
14182

14283
return { errors: this.errors, parsed: this.yaml };
14384
}
@@ -158,11 +99,11 @@ class ConfigParser {
15899
this.getEvaledPath(pathQuery, path /* caller */);
159100

160101
if (
161-
path.match(/^entities\.\d+\./) && //isInsideEntity
102+
!this.fnParam.xs && // hasn't fetched yet
103+
path.match(/^entities\.\d+\./) &&
162104
!path.match(
163105
/^entities\.\d+\.(entity|attribute|time_offset|statistic|period)/
164106
) && //isInsideFetchParamNode
165-
!this.fnParam.xs && // alreadyFetchedData
166107
(is$fn(value) || path.match(/^entities\.\d+\.filters\.\d+$/)) // if function of filter
167108
)
168109
await this.fetchDataForEntity(path);
@@ -430,6 +371,7 @@ type FnParam = {
430371
hass: HomeAssistant;
431372
vars: Record<string, any>;
432373
path: string;
374+
css_vars: HATheme;
433375
xs?: Date[];
434376
ys?: YValue[];
435377
statistics?: StatisticValue[];

src/parse-config/themed-layout.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import merge from "lodash/merge";
21
export type HATheme = {
32
"--card-background-color": string;
43
"--primary-background-color": string;

src/plotly-graph-card.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,6 @@ export class PlotlyGraph extends HTMLElement {
319319
const yaml = merge(
320320
{},
321321
this.config,
322-
{ css_vars: this.getCSSVars() },
323322
{ layout: this.size },
324323
{ fetch_mask },
325324
this.isBrowsing ? { visible_range: this.getVisibleRange() } : {},
@@ -328,6 +327,7 @@ export class PlotlyGraph extends HTMLElement {
328327
const { errors, parsed } = await this.configParser.update({
329328
yaml,
330329
hass: this.hass,
330+
css_vars: this.getCSSVars(),
331331
});
332332
this.errorMsgEl.style.display = errors.length ? "block" : "none";
333333
this.errorMsgEl.innerHTML = errors

0 commit comments

Comments
 (0)