Skip to content

Commit 8f1bc10

Browse files
author
David Buezas
committed
Attributes and disabling layout defaults and default trace style
1 parent c59956a commit 8f1bc10

File tree

6 files changed

+127
-38
lines changed

6 files changed

+127
-38
lines changed

readme.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ entities:
122122
- entity: sensor.humidity
123123
```
124124

125+
### Attribute values
126+
127+
Plot the attributes of an entity by adding `::atribute_name` to the entity name
128+
129+
```yaml
130+
entities: climate.living::temperature
131+
climate.kitchen::temperature
132+
```
133+
125134
## Extra entity attributes:
126135

127136
```yaml
@@ -212,11 +221,52 @@ note: `ys[0]` represents the first "known" value, which is the value furthest to
212221
}
213222
```
214223

224+
#### Access all entity attributes inside lambda
225+
226+
```yaml
227+
- entity: climate.wintergarten_floor::valve
228+
unit_of_measurement: °C
229+
lambda: |-
230+
(ys, xs, entity) =>
231+
entity.map(({attributes}) =>
232+
return +attributes.temperature - (+attributes.valve / 100) * 2
233+
)
234+
```
235+
236+
## Default trace styling
237+
238+
```yaml
239+
entities: sensor.temperature1
240+
sensor.temperature2
241+
default_trace:
242+
fill: tozeroy
243+
line:
244+
width: 2
245+
```
246+
215247
## layout:
216248

217249
To define layout aspects, like margins, title, axes names, ...
218250
Anything from https://plotly.com/javascript/reference/layout/.
219251

252+
### disable default layout:
253+
254+
Use this if you want to use plotly default layout instead. Very useful for heavy customization while following pure plotly examples.
255+
256+
```yaml
257+
entities:
258+
- entity: sensor.temperature_in_celsius
259+
no_default_layout: true
260+
```
261+
262+
### disable Home Assistant themes:
263+
264+
```yaml
265+
entities:
266+
- entity: sensor.temperature_in_celsius
267+
no_theme: true
268+
```
269+
220270
## config:
221271

222272
To define general configurations like enabling scroll to zoom, disabling the modebar, etc.

src/Cache.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { HomeAssistant } from "custom-card-helpers";
22
import { compactRanges, subtractRanges } from "./date-ranges";
33
import { isTruthy } from "./style-hack";
44
import { TimestampRange, History } from "./types";
5+
import { sleep } from "./utils";
56

67
type Histories = Record<string, History>;
78

@@ -13,28 +14,47 @@ export function mapValues<T, S>(
1314
}
1415
async function fetchSingleRange(
1516
hass: HomeAssistant,
16-
entityId: string,
17+
entityIdWithAttribute: string,
1718
[startT, endT]: number[]
1819
) {
1920
const start = new Date(startT);
2021
const end = new Date(endT);
22+
const [entityId2, attribute] = entityIdWithAttribute.split("::");
23+
const minimal_response = !!attribute ? "&minimal_response" : "";
2124
const uri =
2225
`history/period/${start.toISOString()}?` +
23-
`filter_entity_id=${entityId}&` +
24-
`significant_changes_only=1&` +
25-
`minimal_response&end_time=${end.toISOString()}`;
26-
let [list]: History[] = (await hass.callApi("GET", uri)) || [];
26+
`filter_entity_id=${entityId2}&` +
27+
`significant_changes_only=1` +
28+
minimal_response +
29+
`&end_time=${end.toISOString()}`;
30+
let list: History | undefined;
31+
let succeeded = false;
32+
let retries = 0;
33+
while (!succeeded) {
34+
try {
35+
const lists: History[] = (await hass.callApi("GET", uri)) || [];
36+
list = lists[0];
37+
succeeded = true;
38+
} catch (e) {
39+
console.error(e);
40+
retries++;
41+
if (retries > 10) return null;
42+
}
43+
}
2744
if (!list) return null;
2845
return {
29-
entityId,
46+
entityId: entityIdWithAttribute,
3047
range: [startT, Math.min(+new Date(), endT)], // cap range to now
3148
attributes: {
3249
unit_of_measurement: "",
3350
...list[0].attributes,
3451
},
3552
history: list.map((entry) => ({
3653
...entry,
37-
last_changed: +new Date(entry.last_changed),
54+
state: attribute ? entry.attributes[attribute] : entry.state,
55+
last_changed: +new Date(
56+
attribute ? entry.last_updated : entry.last_changed
57+
),
3858
})),
3959
};
4060
}

src/plotly-graph-card.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import { TimestampRange } from "./types";
1111
import Cache from "./Cache";
1212
import getThemedLayout from "./themed-layout";
1313
import isProduction from "./is-production";
14-
15-
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
14+
import { sleep } from "./utils";
1615

1716
const componentName = isProduction ? "plotly-graph" : "plotly-graph-dev";
1817

@@ -89,9 +88,9 @@ export class PlotlyGraph extends HTMLElement {
8988
);
9089
}
9190
this.setupListeners();
92-
this.fetch(this.getAutoFetchRange()).then(() => {
93-
this.contentEl.style.visibility = "";
94-
});
91+
this.fetch(this.getAutoFetchRange())
92+
.then(() => this.fetch(this.getAutoFetchRange())) // again so home assistant extends until end of time axis
93+
.then(() => (this.contentEl.style.visibility = ""));
9594
}
9695
async withoutRelayout(fn: Function) {
9796
this.isInternalRelayout++;
@@ -210,7 +209,7 @@ export class PlotlyGraph extends HTMLElement {
210209
if (is.hours_to_show !== was.hours_to_show) {
211210
this.exitBrowsingMode();
212211
}
213-
this.fetch(this.getAutoFetchRange());
212+
await this.fetch(this.getAutoFetchRange());
214213
}
215214
fetch = async (range: TimestampRange) => {
216215
let entityNames = this.config.entities.map(({ entity }) => entity) || [];
@@ -248,7 +247,11 @@ export class PlotlyGraph extends HTMLElement {
248247
"--secondary-text-color": "red",
249248
};
250249
haTheme = mapValues(haTheme, (_, key) => styles.getPropertyValue(key));
251-
return getThemedLayout(haTheme);
250+
return getThemedLayout(
251+
haTheme,
252+
this.config.no_theme,
253+
this.config.no_default_layout
254+
);
252255
}
253256

254257
getData(): Plotly.Data[] {
@@ -279,9 +282,10 @@ export class PlotlyGraph extends HTMLElement {
279282
shape: "hv",
280283
},
281284
x: xs,
282-
y: trace.lambda(ys, xs),
285+
y: trace.lambda(ys, xs, history),
283286
yaxis: "y" + (yaxis_idx == 0 ? "" : yaxis_idx + 1),
284287
},
288+
this.config.default_trace,
285289
trace
286290
);
287291
});

src/themed-layout.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,31 +46,41 @@ const defaultLayout: Partial<Plotly.Layout> = {
4646
},
4747
};
4848

49+
const themeAxisStyle = {
50+
tickcolor: "rgba(127,127,127,.3)",
51+
gridcolor: "rgba(127,127,127,.3)",
52+
linecolor: "rgba(127,127,127,.3)",
53+
zerolinecolor: "rgba(127,127,127,.3)",
54+
// automargin: true,
55+
};
56+
4957
export default function getThemedLayout(
50-
haTheme: HATheme
58+
haTheme: HATheme,
59+
no_theme?: boolean,
60+
no_default_layout?: boolean
5161
): Partial<Plotly.Layout> {
52-
const axisStyle = {
53-
tickcolor: "rgba(127,127,127,.3)",
54-
gridcolor: "rgba(127,127,127,.3)",
55-
linecolor: "rgba(127,127,127,.3)",
56-
zerolinecolor: "rgba(127,127,127,.3)",
57-
// automargin: true,
62+
const theme = {
63+
paper_bgcolor: haTheme["--card-background-color"],
64+
plot_bgcolor: haTheme["--card-background-color"],
65+
font: {
66+
color: haTheme["--secondary-text-color"],
67+
size: 11,
68+
},
69+
xaxis: { ...themeAxisStyle },
70+
yaxis1: { ...themeAxisStyle },
71+
yaxis2: { ...themeAxisStyle },
72+
yaxis3: { ...themeAxisStyle },
73+
yaxis4: { ...themeAxisStyle },
74+
yaxis5: { ...themeAxisStyle },
75+
yaxis6: { ...themeAxisStyle },
76+
yaxis7: { ...themeAxisStyle },
77+
yaxis8: { ...themeAxisStyle },
78+
yaxis9: { ...themeAxisStyle },
5879
};
80+
5981
return merge(
60-
{
61-
paper_bgcolor: haTheme["--card-background-color"],
62-
plot_bgcolor: haTheme["--card-background-color"],
63-
xaxis: { ...axisStyle },
64-
yaxis: { ...axisStyle },
65-
yaxis2: { ...axisStyle },
66-
yaxis3: { ...axisStyle },
67-
yaxis4: { ...axisStyle },
68-
yaxis5: { ...axisStyle },
69-
font: {
70-
color: haTheme["--secondary-text-color"],
71-
size: 11,
72-
},
73-
},
74-
defaultLayout
82+
{},
83+
no_theme ? {} : theme,
84+
no_default_layout ? {} : defaultLayout
7585
);
7686
}

src/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ export type Config = {
55
entities: (Partial<Plotly.PlotData> & {
66
entity: string;
77
unit_of_measurement?: string;
8-
lambda: (y: any[], x: Date[]) => number[];
8+
lambda: (y: any[], x: Date[], raw_entity: History) => number[];
99
})[];
10+
default_trace?: Partial<Plotly.PlotData>,
1011
layout?: Partial<Plotly.Layout>;
1112
config?: Partial<Plotly.Config>;
13+
no_theme?: boolean;
14+
no_default_layout?: boolean;
1215
};
1316
export type Timestamp = number;
1417
export type History = {
1518
entity_id: string;
1619
last_changed: Timestamp;
20+
last_updated: Timestamp;
1721
state: string;
1822
attributes: {
1923
friendly_name?: string;

src/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

0 commit comments

Comments
 (0)