Fix for charts with identically named entities (#26166)

* Fix for charts with identically named entities

* lint

* type fix
This commit is contained in:
Petar Petrov 2025-07-15 20:01:58 +03:00 committed by GitHub
parent aee9e4b0a5
commit 72a12a4ba4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 39 deletions

View File

@ -35,6 +35,15 @@ const LEGEND_OVERFLOW_LIMIT = 10;
const LEGEND_OVERFLOW_LIMIT_MOBILE = 6;
const DOUBLE_TAP_TIME = 300;
export type CustomLegendOption = ECOption["legend"] & {
type: "custom";
data?: {
id?: string;
name: string;
itemStyle?: Record<string, any>;
}[];
};
@customElement("ha-chart-base")
export class HaChartBase extends LitElement {
public chart?: EChartsType;
@ -219,16 +228,18 @@ export class HaChartBase extends LitElement {
if (!this.options?.legend || !this.data) {
return nothing;
}
const legend = ensureArray(this.options.legend)[0] as LegendComponentOption;
if (!legend.show || legend.type !== "custom") {
const legend = ensureArray(this.options.legend).find(
(l) => l.show && l.type === "custom"
) as CustomLegendOption | undefined;
if (!legend) {
return nothing;
}
const datasets = ensureArray(this.data);
const items: LegendComponentOption["data"] =
const items =
legend.data ||
((datasets
datasets
.filter((d) => (d.data as any[])?.length && (d.id || d.name))
.map((d) => d.name ?? d.id) || []) as string[]);
.map((d) => ({ id: d.id, name: d.name }));
const isMobile = window.matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)"
@ -249,25 +260,29 @@ export class HaChartBase extends LitElement {
}
let itemStyle: Record<string, any> = {};
let name = "";
let id = "";
if (typeof item === "string") {
name = item;
const dataset = datasets.find(
(d) => d.id === item || d.name === item
);
itemStyle = {
color: dataset?.color as string,
...(dataset?.itemStyle as { borderColor?: string }),
};
id = item;
} else {
name = item.name ?? "";
id = item.id ?? name;
itemStyle = item.itemStyle ?? {};
}
const dataset =
datasets.find((d) => d.id === id) ??
datasets.find((d) => d.name === id);
itemStyle = {
color: dataset?.color as string,
...(dataset?.itemStyle as { borderColor?: string }),
itemStyle,
};
const color = itemStyle?.color as string;
const borderColor = itemStyle?.borderColor as string;
return html`<li
.name=${name}
.id=${id}
@click=${this._legendClick}
class=${classMap({ hidden: this._hiddenDatasets.has(name) })}
class=${classMap({ hidden: this._hiddenDatasets.has(id) })}
.title=${name}
>
<div
@ -645,7 +660,7 @@ export class HaChartBase extends LitElement {
| YAXisOption
| undefined;
const series = ensureArray(this.data).map((s) => {
const data = this._hiddenDatasets.has(String(s.name ?? s.id))
const data = this._hiddenDatasets.has(String(s.id ?? s.name))
? undefined
: s.data;
if (data && s.type === "line") {
@ -740,13 +755,13 @@ export class HaChartBase extends LitElement {
if (!this.chart) {
return;
}
const name = ev.currentTarget?.name;
if (this._hiddenDatasets.has(name)) {
this._hiddenDatasets.delete(name);
fireEvent(this, "dataset-unhidden", { name });
const id = ev.currentTarget?.id;
if (this._hiddenDatasets.has(id)) {
this._hiddenDatasets.delete(id);
fireEvent(this, "dataset-unhidden", { id });
} else {
this._hiddenDatasets.add(name);
fireEvent(this, "dataset-hidden", { name });
this._hiddenDatasets.add(id);
fireEvent(this, "dataset-hidden", { id });
}
this.requestUpdate("_hiddenDatasets");
}
@ -881,8 +896,8 @@ declare global {
"ha-chart-base": HaChartBase;
}
interface HASSDomEvents {
"dataset-hidden": { name: string };
"dataset-unhidden": { name: string };
"dataset-hidden": { id: string };
"dataset-unhidden": { id: string };
"chart-click": ECElementEvent;
}
}

View File

@ -111,7 +111,7 @@ export class StateHistoryChartLine extends LitElement {
this._chartData.forEach((dataset, index) => {
if (
dataset.tooltip?.show === false ||
this._hiddenStats.has(dataset.name as string)
this._hiddenStats.has(dataset.id as string)
)
return;
const param = params.find(
@ -185,11 +185,11 @@ export class StateHistoryChartLine extends LitElement {
};
private _datasetHidden(ev: CustomEvent) {
this._hiddenStats.add(ev.detail.name);
this._hiddenStats.add(ev.detail.id);
}
private _datasetUnhidden(ev: CustomEvent) {
this._hiddenStats.delete(ev.detail.name);
this._hiddenStats.delete(ev.detail.id);
}
public willUpdate(changedProps: PropertyValues) {

View File

@ -31,6 +31,7 @@ import {
} from "../../data/recorder";
import type { ECOption } from "../../resources/echarts";
import type { HomeAssistant } from "../../types";
import type { CustomLegendOption } from "./ha-chart-base";
import "./ha-chart-base";
export const supportedStatTypeMap: Record<StatisticType, StatisticType> = {
@ -96,7 +97,7 @@ export class StatisticsChart extends LitElement {
@state() private _chartData: (LineSeriesOption | BarSeriesOption)[] = [];
@state() private _legendData: string[] = [];
@state() private _legendData: NonNullable<CustomLegendOption["data"]> = [];
@state() private _statisticIds: string[] = [];
@ -183,12 +184,12 @@ export class StatisticsChart extends LitElement {
}
private _datasetHidden(ev: CustomEvent) {
this._hiddenStats.add(ev.detail.name);
this._hiddenStats.add(ev.detail.id);
this.requestUpdate("_hiddenStats");
}
private _datasetUnhidden(ev: CustomEvent) {
this._hiddenStats.delete(ev.detail.name);
this._hiddenStats.delete(ev.detail.id);
this.requestUpdate("_hiddenStats");
}
@ -199,8 +200,8 @@ export class StatisticsChart extends LitElement {
: "";
return params
.map((param, index: number) => {
if (rendered[param.seriesName]) return "";
rendered[param.seriesName] = true;
if (rendered[param.seriesIndex]) return "";
rendered[param.seriesIndex] = true;
const statisticId = this._statisticIds[param.seriesIndex];
const stateObj = this.hass.states[statisticId];
@ -367,6 +368,7 @@ export class StatisticsChart extends LitElement {
const statisticsData = Object.entries(this.statisticsData);
const totalDataSets: typeof this._chartData = [];
const legendData: {
id: string;
name: string;
color?: ZRColor;
borderColor?: ZRColor;
@ -540,6 +542,7 @@ export class StatisticsChart extends LitElement {
: displayedLegend === false;
if (showLegend) {
statLegendData.push({
id: statistic_id,
name,
color: series.color as ZRColor,
borderColor: series.itemStyle?.borderColor,
@ -584,7 +587,7 @@ export class StatisticsChart extends LitElement {
}
dataValues.push(val);
});
if (!this._hiddenStats.has(name)) {
if (!this._hiddenStats.has(statistic_id)) {
pushData(startDate, new Date(stat.end), dataValues);
}
});
@ -598,10 +601,10 @@ export class StatisticsChart extends LitElement {
this.unit = unit;
}
legendData.forEach(({ name, color, borderColor }) => {
legendData.forEach(({ id, name, color, borderColor }) => {
// Add an empty series for the legend
totalDataSets.push({
id: name + "-legend",
id: id,
name: name,
color,
itemStyle: {
@ -616,7 +619,7 @@ export class StatisticsChart extends LitElement {
this._chartData = totalDataSets;
if (legendData.length !== this._legendData.length) {
// only update the legend if it has changed or it will trigger options update
this._legendData = legendData.map(({ name }) => name);
this._legendData = legendData.map(({ id, name }) => ({ id, name }));
}
this._statisticIds = statisticIds;
}

View File

@ -6,7 +6,6 @@ import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import type { BarSeriesOption } from "echarts/charts";
import type { LegendComponentOption } from "echarts/components";
import { getGraphColorByIndex } from "../../../../common/color/colors";
import { getEnergyColor } from "./common/color";
import "../../../../components/ha-card";
@ -39,6 +38,7 @@ import {
import { storage } from "../../../../common/decorators/storage";
import type { ECOption } from "../../../../resources/echarts";
import { formatNumber } from "../../../../common/number/format_number";
import type { CustomLegendOption } from "../../../../components/chart/ha-chart-base";
const UNIT = "kWh";
@ -55,7 +55,7 @@ export class HuiEnergyDevicesDetailGraphCard
@state() private _data?: EnergyData;
@state() private _legendData?: LegendComponentOption["data"];
@state() private _legendData?: CustomLegendOption["data"];
@state() private _start = startOfToday();
@ -149,12 +149,12 @@ export class HuiEnergyDevicesDetailGraphCard
);
private _datasetHidden(ev) {
this._hiddenStats = [...this._hiddenStats, ev.detail.name];
this._hiddenStats = [...this._hiddenStats, ev.detail.id];
}
private _datasetUnhidden(ev) {
this._hiddenStats = this._hiddenStats.filter(
(stat) => stat !== ev.detail.name
(stat) => stat !== ev.detail.id
);
}
@ -314,6 +314,7 @@ export class HuiEnergyDevicesDetailGraphCard
datasets.push(...processedData);
this._legendData = processedData.map((d) => ({
id: d.id as string,
name: d.name as string,
itemStyle: {
color: d.color as string,
@ -330,6 +331,7 @@ export class HuiEnergyDevicesDetailGraphCard
);
datasets.push(untrackedData);
this._legendData.push({
id: untrackedData.id as string,
name: untrackedData.name as string,
itemStyle: {
color: untrackedData.color as string,