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) => {
|
||||
const filteredNodes = data.nodes.filter((n) => n.value > 0);
|
||||
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 sectionWidth = width / indexes.length;
|
||||
const labelSpace = sectionWidth - NODE_SIZE - LABEL_DISTANCE;
|
||||
@ -119,7 +123,7 @@ export class HaSankeyChart extends LitElement {
|
||||
itemStyle: {
|
||||
color: node.color,
|
||||
},
|
||||
depth: node.index,
|
||||
depth: depthMap.get(node.index),
|
||||
})),
|
||||
links,
|
||||
draggable: false,
|
||||
|
@ -22,6 +22,12 @@ import "../../../../components/chart/ha-sankey-chart";
|
||||
import type { Link, Node } from "../../../../components/chart/ha-sankey-chart";
|
||||
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
||||
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")
|
||||
class HuiEnergySankeyCard
|
||||
@ -37,7 +43,7 @@ class HuiEnergySankeyCard
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public setConfig(config: EnergySankeyCardConfig): void {
|
||||
this._config = config;
|
||||
this._config = { ...DEFAULT_CONFIG, ...config };
|
||||
}
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
@ -219,22 +225,9 @@ class HuiEnergySankeyCard
|
||||
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;
|
||||
const deviceNodes: Node[] = [];
|
||||
prefs.device_consumption.forEach((device, idx) => {
|
||||
const entity = this.hass.entities[device.stat_consumption];
|
||||
const value =
|
||||
device.stat_consumption in this._data!.stats
|
||||
? calculateStatisticSumGrowth(
|
||||
@ -245,7 +238,7 @@ class HuiEnergySankeyCard
|
||||
return;
|
||||
}
|
||||
untrackedConsumption -= value;
|
||||
const deviceNode: Node = {
|
||||
deviceNodes.push({
|
||||
id: device.stat_consumption,
|
||||
label:
|
||||
device.name ||
|
||||
@ -258,103 +251,85 @@ class HuiEnergySankeyCard
|
||||
tooltip: `${formatNumber(value, this.hass.locale)} kWh`,
|
||||
color: getGraphColorByIndex(idx, computedStyle),
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(floors)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(this.hass.floors[b]?.level ?? -Infinity) -
|
||||
(this.hass.floors[a]?.level ?? -Infinity)
|
||||
)
|
||||
.forEach((floorId) => {
|
||||
let floorNodeId = `floor_${floorId}`;
|
||||
if (floorId === "no_floor") {
|
||||
// link "no_floor" areas to home
|
||||
floorNodeId = "home";
|
||||
} else {
|
||||
nodes.push({
|
||||
id: floorNodeId,
|
||||
label: this.hass.floors[floorId].name,
|
||||
value: floors[floorId].value,
|
||||
tooltip: `${formatNumber(floors[floorId].value, this.hass.locale)} kWh`,
|
||||
index: 2,
|
||||
color: computedStyle.getPropertyValue("--primary-color"),
|
||||
});
|
||||
links.push({
|
||||
source: "home",
|
||||
target: floorNodeId,
|
||||
});
|
||||
}
|
||||
floors[floorId].areas.forEach((areaId) => {
|
||||
let areaNodeId = `area_${areaId}`;
|
||||
if (areaId === "no_area") {
|
||||
// link "no_area" devices to home
|
||||
areaNodeId = "home";
|
||||
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)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(this.hass.floors[b]?.level ?? -Infinity) -
|
||||
(this.hass.floors[a]?.level ?? -Infinity)
|
||||
)
|
||||
.forEach((floorId) => {
|
||||
let floorNodeId = `floor_${floorId}`;
|
||||
if (floorId === "no_floor" || !group_by_floor) {
|
||||
// link "no_floor" areas to home
|
||||
floorNodeId = "home";
|
||||
} else {
|
||||
nodes.push({
|
||||
id: areaNodeId,
|
||||
label: this.hass.areas[areaId]!.name,
|
||||
value: areas[areaId].value,
|
||||
tooltip: `${formatNumber(areas[areaId].value, this.hass.locale)} kWh`,
|
||||
index: 3,
|
||||
id: floorNodeId,
|
||||
label: this.hass.floors[floorId].name,
|
||||
value: floors[floorId].value,
|
||||
tooltip: `${formatNumber(floors[floorId].value, this.hass.locale)} kWh`,
|
||||
index: 2,
|
||||
color: computedStyle.getPropertyValue("--primary-color"),
|
||||
});
|
||||
links.push({
|
||||
source: floorNodeId,
|
||||
target: areaNodeId,
|
||||
value: areas[areaId].value,
|
||||
source: "home",
|
||||
target: floorNodeId,
|
||||
});
|
||||
}
|
||||
areas[areaId].devices.forEach((device) => {
|
||||
nodes.push(device);
|
||||
links.push({
|
||||
source: areaNodeId,
|
||||
target: device.id,
|
||||
value: device.value,
|
||||
floors[floorId].areas.forEach((areaId) => {
|
||||
let targetNodeId: string;
|
||||
|
||||
if (areaId === "no_area" || !group_by_area) {
|
||||
// If group_by_area is false, link devices to floor or home
|
||||
targetNodeId = floorNodeId;
|
||||
} else {
|
||||
// Create area node and link it to floor
|
||||
const areaNodeId = `area_${areaId}`;
|
||||
nodes.push({
|
||||
id: areaNodeId,
|
||||
label: this.hass.areas[areaId]!.name,
|
||||
value: areas[areaId].value,
|
||||
tooltip: `${formatNumber(areas[areaId].value, this.hass.locale)} kWh`,
|
||||
index: 3,
|
||||
color: computedStyle.getPropertyValue("--primary-color"),
|
||||
});
|
||||
links.push({
|
||||
source: floorNodeId,
|
||||
target: areaNodeId,
|
||||
value: areas[areaId].value,
|
||||
});
|
||||
targetNodeId = areaNodeId;
|
||||
}
|
||||
|
||||
// Link devices to the appropriate target (area, floor, or home)
|
||||
areas[areaId].devices.forEach((device) => {
|
||||
nodes.push(device);
|
||||
links.push({
|
||||
source: targetNodeId,
|
||||
target: device.id,
|
||||
value: device.value,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
deviceNodes.forEach((deviceNode) => {
|
||||
nodes.push(deviceNode);
|
||||
links.push({
|
||||
source: "home",
|
||||
target: deviceNode.id,
|
||||
value: deviceNode.value,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// untracked consumption
|
||||
if (untrackedConsumption > 0) {
|
||||
nodes.push({
|
||||
@ -400,6 +375,59 @@ class HuiEnergySankeyCard
|
||||
private _valueFormatter = (value: number) =>
|
||||
`${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`
|
||||
:host {
|
||||
display: block;
|
||||
|
@ -201,6 +201,8 @@ export interface EnergySankeyCardConfig extends EnergyCardBaseConfig {
|
||||
type: "energy-sankey";
|
||||
title?: string;
|
||||
layout?: "vertical" | "horizontal";
|
||||
group_by_floor?: boolean;
|
||||
group_by_area?: boolean;
|
||||
}
|
||||
|
||||
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
||||
|
Loading…
x
Reference in New Issue
Block a user