mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 11:16:35 +00:00
Grouping options for hui-energy-sankey-card (#25207)
* Grouping options for hui-energy-sankey-card * use getEntityContext for area/floor * Update rspack monorepo to v1.3.7 (#25206) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update vaadinWebComponents monorepo to v24.7.4 (#25204) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Add typography styles (#25171) * Add typoghrapy styles * Split styles * Fix duplicated html * remove unused paper vars * Fix vars * Add vars autocompletion extension * Fix css syntax highlighting * History tooltip RTL fix (#24917) * History tooltip RTL fix * Fix background color * Render todo items with no state, change look of read only items (#24529) * Render todo items with no state, change look of read only items Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> Co-authored-by: Wendelin <w@pe8.at> * Themeable badge icon size and badge font size (#25185) Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> * Add context to config update entries (#25208) * Add context to config update entries * Add no area * import fix * import fix --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> Co-authored-by: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Co-authored-by: Bram Kragten <mail@bramkragten.nl> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Wendelin <w@pe8.at> Co-authored-by: emufan <emufan@users.noreply.github.com>
This commit is contained in:
parent
5d2d6dcd6c
commit
ded5ade0f2
@ -106,6 +106,10 @@ export class HaSankeyChart extends LitElement {
|
|||||||
private _createData = memoizeOne((data: SankeyChartData, width = 0) => {
|
private _createData = memoizeOne((data: SankeyChartData, width = 0) => {
|
||||||
const filteredNodes = data.nodes.filter((n) => n.value > 0);
|
const filteredNodes = data.nodes.filter((n) => n.value > 0);
|
||||||
const indexes = [...new Set(filteredNodes.map((n) => n.index))];
|
const indexes = [...new Set(filteredNodes.map((n) => n.index))];
|
||||||
|
const depthMap = new Map<number, number>();
|
||||||
|
indexes.sort().forEach((index, i) => {
|
||||||
|
depthMap.set(index, i);
|
||||||
|
});
|
||||||
const links = this._processLinks(filteredNodes, data.links);
|
const links = this._processLinks(filteredNodes, data.links);
|
||||||
const sectionWidth = width / indexes.length;
|
const sectionWidth = width / indexes.length;
|
||||||
const labelSpace = sectionWidth - NODE_SIZE - LABEL_DISTANCE;
|
const labelSpace = sectionWidth - NODE_SIZE - LABEL_DISTANCE;
|
||||||
@ -119,7 +123,7 @@ export class HaSankeyChart extends LitElement {
|
|||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: node.color,
|
color: node.color,
|
||||||
},
|
},
|
||||||
depth: node.index,
|
depth: depthMap.get(node.index),
|
||||||
})),
|
})),
|
||||||
links,
|
links,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
|
@ -22,6 +22,12 @@ import "../../../../components/chart/ha-sankey-chart";
|
|||||||
import type { Link, Node } from "../../../../components/chart/ha-sankey-chart";
|
import type { Link, Node } from "../../../../components/chart/ha-sankey-chart";
|
||||||
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
||||||
import { formatNumber } from "../../../../common/number/format_number";
|
import { formatNumber } from "../../../../common/number/format_number";
|
||||||
|
import { getEntityContext } from "../../../../common/entity/context/get_entity_context";
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: Partial<EnergySankeyCardConfig> = {
|
||||||
|
group_by_floor: true,
|
||||||
|
group_by_area: true,
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("hui-energy-sankey-card")
|
@customElement("hui-energy-sankey-card")
|
||||||
class HuiEnergySankeyCard
|
class HuiEnergySankeyCard
|
||||||
@ -37,7 +43,7 @@ class HuiEnergySankeyCard
|
|||||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||||
|
|
||||||
public setConfig(config: EnergySankeyCardConfig): void {
|
public setConfig(config: EnergySankeyCardConfig): void {
|
||||||
this._config = config;
|
this._config = { ...DEFAULT_CONFIG, ...config };
|
||||||
}
|
}
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
@ -219,22 +225,9 @@ class HuiEnergySankeyCard
|
|||||||
homeNode.value -= totalToGrid;
|
homeNode.value -= totalToGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group devices by areas and floors
|
|
||||||
const areas: Record<string, { value: number; devices: Node[] }> = {
|
|
||||||
no_area: {
|
|
||||||
value: 0,
|
|
||||||
devices: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const floors: Record<string, { value: number; areas: string[] }> = {
|
|
||||||
no_floor: {
|
|
||||||
value: 0,
|
|
||||||
areas: ["no_area"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let untrackedConsumption = homeNode.value;
|
let untrackedConsumption = homeNode.value;
|
||||||
|
const deviceNodes: Node[] = [];
|
||||||
prefs.device_consumption.forEach((device, idx) => {
|
prefs.device_consumption.forEach((device, idx) => {
|
||||||
const entity = this.hass.entities[device.stat_consumption];
|
|
||||||
const value =
|
const value =
|
||||||
device.stat_consumption in this._data!.stats
|
device.stat_consumption in this._data!.stats
|
||||||
? calculateStatisticSumGrowth(
|
? calculateStatisticSumGrowth(
|
||||||
@ -245,7 +238,7 @@ class HuiEnergySankeyCard
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
untrackedConsumption -= value;
|
untrackedConsumption -= value;
|
||||||
const deviceNode: Node = {
|
deviceNodes.push({
|
||||||
id: device.stat_consumption,
|
id: device.stat_consumption,
|
||||||
label:
|
label:
|
||||||
device.name ||
|
device.name ||
|
||||||
@ -258,47 +251,12 @@ class HuiEnergySankeyCard
|
|||||||
tooltip: `${formatNumber(value, this.hass.locale)} kWh`,
|
tooltip: `${formatNumber(value, this.hass.locale)} kWh`,
|
||||||
color: getGraphColorByIndex(idx, computedStyle),
|
color: getGraphColorByIndex(idx, computedStyle),
|
||||||
index: 4,
|
index: 4,
|
||||||
};
|
|
||||||
|
|
||||||
const entityAreaId =
|
|
||||||
entity?.area_id ??
|
|
||||||
(entity?.device_id && this.hass.devices[entity.device_id]?.area_id);
|
|
||||||
if (entityAreaId && entityAreaId in this.hass.areas) {
|
|
||||||
const area = this.hass.areas[entityAreaId];
|
|
||||||
|
|
||||||
if (area.area_id in areas) {
|
|
||||||
areas[area.area_id].value += deviceNode.value;
|
|
||||||
areas[area.area_id].devices.push(deviceNode);
|
|
||||||
} else {
|
|
||||||
areas[area.area_id] = {
|
|
||||||
value: deviceNode.value,
|
|
||||||
devices: [deviceNode],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// see if the area has a floor
|
|
||||||
if (area.floor_id && area.floor_id in this.hass.floors) {
|
|
||||||
if (area.floor_id in floors) {
|
|
||||||
floors[area.floor_id].value += deviceNode.value;
|
|
||||||
if (!floors[area.floor_id].areas.includes(area.area_id)) {
|
|
||||||
floors[area.floor_id].areas.push(area.area_id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
floors[area.floor_id] = {
|
|
||||||
value: deviceNode.value,
|
|
||||||
areas: [area.area_id],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
floors.no_floor.value += deviceNode.value;
|
|
||||||
if (!floors.no_floor.areas.includes(area.area_id)) {
|
|
||||||
floors.no_floor.areas.unshift(area.area_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
areas.no_area.value += deviceNode.value;
|
|
||||||
areas.no_area.devices.push(deviceNode);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const { group_by_area, group_by_floor } = this._config;
|
||||||
|
if (group_by_area || group_by_floor) {
|
||||||
|
const { areas, floors } = this._groupByFloorAndArea(deviceNodes);
|
||||||
|
|
||||||
Object.keys(floors)
|
Object.keys(floors)
|
||||||
.sort(
|
.sort(
|
||||||
@ -308,7 +266,7 @@ class HuiEnergySankeyCard
|
|||||||
)
|
)
|
||||||
.forEach((floorId) => {
|
.forEach((floorId) => {
|
||||||
let floorNodeId = `floor_${floorId}`;
|
let floorNodeId = `floor_${floorId}`;
|
||||||
if (floorId === "no_floor") {
|
if (floorId === "no_floor" || !group_by_floor) {
|
||||||
// link "no_floor" areas to home
|
// link "no_floor" areas to home
|
||||||
floorNodeId = "home";
|
floorNodeId = "home";
|
||||||
} else {
|
} else {
|
||||||
@ -326,11 +284,14 @@ class HuiEnergySankeyCard
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
floors[floorId].areas.forEach((areaId) => {
|
floors[floorId].areas.forEach((areaId) => {
|
||||||
let areaNodeId = `area_${areaId}`;
|
let targetNodeId: string;
|
||||||
if (areaId === "no_area") {
|
|
||||||
// link "no_area" devices to home
|
if (areaId === "no_area" || !group_by_area) {
|
||||||
areaNodeId = "home";
|
// If group_by_area is false, link devices to floor or home
|
||||||
|
targetNodeId = floorNodeId;
|
||||||
} else {
|
} else {
|
||||||
|
// Create area node and link it to floor
|
||||||
|
const areaNodeId = `area_${areaId}`;
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: areaNodeId,
|
id: areaNodeId,
|
||||||
label: this.hass.areas[areaId]!.name,
|
label: this.hass.areas[areaId]!.name,
|
||||||
@ -344,17 +305,31 @@ class HuiEnergySankeyCard
|
|||||||
target: areaNodeId,
|
target: areaNodeId,
|
||||||
value: areas[areaId].value,
|
value: areas[areaId].value,
|
||||||
});
|
});
|
||||||
|
targetNodeId = areaNodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link devices to the appropriate target (area, floor, or home)
|
||||||
areas[areaId].devices.forEach((device) => {
|
areas[areaId].devices.forEach((device) => {
|
||||||
nodes.push(device);
|
nodes.push(device);
|
||||||
links.push({
|
links.push({
|
||||||
source: areaNodeId,
|
source: targetNodeId,
|
||||||
target: device.id,
|
target: device.id,
|
||||||
value: device.value,
|
value: device.value,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
deviceNodes.forEach((deviceNode) => {
|
||||||
|
nodes.push(deviceNode);
|
||||||
|
links.push({
|
||||||
|
source: "home",
|
||||||
|
target: deviceNode.id,
|
||||||
|
value: deviceNode.value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// untracked consumption
|
// untracked consumption
|
||||||
if (untrackedConsumption > 0) {
|
if (untrackedConsumption > 0) {
|
||||||
nodes.push({
|
nodes.push({
|
||||||
@ -400,6 +375,59 @@ class HuiEnergySankeyCard
|
|||||||
private _valueFormatter = (value: number) =>
|
private _valueFormatter = (value: number) =>
|
||||||
`${formatNumber(value, this.hass.locale)} kWh`;
|
`${formatNumber(value, this.hass.locale)} kWh`;
|
||||||
|
|
||||||
|
protected _groupByFloorAndArea(deviceNodes: Node[]) {
|
||||||
|
const areas: Record<string, { value: number; devices: Node[] }> = {
|
||||||
|
no_area: {
|
||||||
|
value: 0,
|
||||||
|
devices: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const floors: Record<string, { value: number; areas: string[] }> = {
|
||||||
|
no_floor: {
|
||||||
|
value: 0,
|
||||||
|
areas: ["no_area"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
deviceNodes.forEach((deviceNode) => {
|
||||||
|
const entity = this.hass.states[deviceNode.id];
|
||||||
|
const { area, floor } = getEntityContext(entity, this.hass);
|
||||||
|
if (area) {
|
||||||
|
if (area.area_id in areas) {
|
||||||
|
areas[area.area_id].value += deviceNode.value;
|
||||||
|
areas[area.area_id].devices.push(deviceNode);
|
||||||
|
} else {
|
||||||
|
areas[area.area_id] = {
|
||||||
|
value: deviceNode.value,
|
||||||
|
devices: [deviceNode],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// see if the area has a floor
|
||||||
|
if (floor) {
|
||||||
|
if (floor.floor_id in floors) {
|
||||||
|
floors[floor.floor_id].value += deviceNode.value;
|
||||||
|
if (!floors[floor.floor_id].areas.includes(area.area_id)) {
|
||||||
|
floors[floor.floor_id].areas.push(area.area_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
floors[floor.floor_id] = {
|
||||||
|
value: deviceNode.value,
|
||||||
|
areas: [area.area_id],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
floors.no_floor.value += deviceNode.value;
|
||||||
|
if (!floors.no_floor.areas.includes(area.area_id)) {
|
||||||
|
floors.no_floor.areas.unshift(area.area_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
areas.no_area.value += deviceNode.value;
|
||||||
|
areas.no_area.devices.push(deviceNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { areas, floors };
|
||||||
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -201,6 +201,8 @@ export interface EnergySankeyCardConfig extends EnergyCardBaseConfig {
|
|||||||
type: "energy-sankey";
|
type: "energy-sankey";
|
||||||
title?: string;
|
title?: string;
|
||||||
layout?: "vertical" | "horizontal";
|
layout?: "vertical" | "horizontal";
|
||||||
|
group_by_floor?: boolean;
|
||||||
|
group_by_area?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user