From a7db401b623ec9c925271d34963fc97ebbe54919 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Fri, 8 Aug 2025 16:37:44 +0300 Subject: [PATCH] Show sankey chart in vertical layout on mobile (#26439) * Show sankey chart in vertical layout on mobile * ts fix --- src/components/chart/ha-chart-base.ts | 64 ++++++++++--------- src/components/chart/ha-sankey-chart.ts | 6 +- src/mixins/mobile-aware-mixin.ts | 34 ++++++++++ .../cards/energy/hui-energy-sankey-card.ts | 15 ++++- src/panels/lovelace/cards/types.ts | 2 +- 5 files changed, 84 insertions(+), 37 deletions(-) create mode 100644 src/mixins/mobile-aware-mixin.ts diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 3d5ae876b5..db908c5a48 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -387,24 +387,25 @@ export class HaChartBase extends LitElement { lastTipX = e.x; lastTipY = e.y; this.chart?.setOption({ - xAxis: ensureArray(this.chart?.getOption().xAxis as any).map( - (axis: XAXisOption) => - axis.show - ? { - ...axis, - axisPointer: { - ...axis.axisPointer, - status: "show", - handle: { - color: style.getPropertyValue("primary-color"), - margin: 0, - size: 20, - ...axis.axisPointer?.handle, - show: true, - }, + xAxis: ensureArray( + (this.chart?.getOption().xAxis as any) ?? [] + ).map((axis: XAXisOption) => + axis.show + ? { + ...axis, + axisPointer: { + ...axis.axisPointer, + status: "show", + handle: { + color: style.getPropertyValue("primary-color"), + margin: 0, + size: 20, + ...axis.axisPointer?.handle, + show: true, }, - } - : axis + }, + } + : axis ), }); }); @@ -417,21 +418,22 @@ export class HaChartBase extends LitElement { return; } this.chart?.setOption({ - xAxis: ensureArray(this.chart?.getOption().xAxis as any).map( - (axis: XAXisOption) => - axis.show - ? { - ...axis, - axisPointer: { - ...axis.axisPointer, - handle: { - ...axis.axisPointer?.handle, - show: false, - }, - status: "hide", + xAxis: ensureArray( + (this.chart?.getOption().xAxis as any) ?? [] + ).map((axis: XAXisOption) => + axis.show + ? { + ...axis, + axisPointer: { + ...axis.axisPointer, + handle: { + ...axis.axisPointer?.handle, + show: false, }, - } - : axis + status: "hide", + }, + } + : axis ), }); this.chart?.dispatchAction({ diff --git a/src/components/chart/ha-sankey-chart.ts b/src/components/chart/ha-sankey-chart.ts index b10e3c19e2..4c2ebb78dc 100644 --- a/src/components/chart/ha-sankey-chart.ts +++ b/src/components/chart/ha-sankey-chart.ts @@ -186,14 +186,16 @@ export class HaSankeyChart extends LitElement { "" ); const wordWidth = measureTextWidth(longestWord, FONT_SIZE); + const availableWidth = params.rect.width + 6; const fontSize = Math.min( FONT_SIZE, - (params.rect.width / wordWidth) * FONT_SIZE + (availableWidth / wordWidth) * FONT_SIZE ); return { fontSize: fontSize > 1 ? fontSize : 0, - width: params.rect.width, + width: availableWidth, align: "center", + dy: -2, // shift up or the lowest row labels may be cut off }; } diff --git a/src/mixins/mobile-aware-mixin.ts b/src/mixins/mobile-aware-mixin.ts new file mode 100644 index 0000000000..d72fdfc56c --- /dev/null +++ b/src/mixins/mobile-aware-mixin.ts @@ -0,0 +1,34 @@ +import type { LitElement } from "lit"; +import { state } from "lit/decorators"; +import type { Constructor } from "../types"; +import { isMobileClient } from "../util/is_mobile"; +import { listenMediaQuery } from "../common/dom/media_query"; + +export const MobileAwareMixin = >( + superClass: T +) => { + class MobileAwareClass extends superClass { + @state() protected _isMobileSize = false; + + protected _isMobileClient = isMobileClient; + + private _unsubMql?: () => void; + + public connectedCallback() { + super.connectedCallback(); + this._unsubMql = listenMediaQuery( + "all and (max-width: 450px), all and (max-height: 500px)", + (matches) => { + this._isMobileSize = matches; + } + ); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._unsubMql?.(); + this._unsubMql = undefined; + } + } + return MobileAwareClass; +}; diff --git a/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts index 5f1a9cf146..11f8ae7a6b 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts @@ -23,6 +23,7 @@ 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"; +import { MobileAwareMixin } from "../../../../mixins/mobile-aware-mixin"; const DEFAULT_CONFIG: Partial = { group_by_floor: true, @@ -31,7 +32,7 @@ const DEFAULT_CONFIG: Partial = { @customElement("hui-energy-sankey-card") class HuiEnergySankeyCard - extends SubscribeMixin(LitElement) + extends SubscribeMixin(MobileAwareMixin(LitElement)) implements LovelaceCard { @property({ attribute: false }) public hass!: HomeAssistant; @@ -70,7 +71,11 @@ class HuiEnergySankeyCard } protected shouldUpdate(changedProps: PropertyValues): boolean { - return changedProps.has("_config") || changedProps.has("_data"); + return ( + changedProps.has("_config") || + changedProps.has("_data") || + changedProps.has("_isMobileSize") + ); } protected render() { @@ -373,13 +378,17 @@ class HuiEnergySankeyCard const hasData = nodes.some((node) => node.value > 0); + const vertical = + this._config.layout === "vertical" || + (this._config.layout !== "horizontal" && this._isMobileSize); + return html`
${hasData ? html`` : html`${this.hass.localize( diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 46433e3f05..5751ab0d47 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -213,7 +213,7 @@ export interface EnergyCarbonGaugeCardConfig extends EnergyCardBaseConfig { export interface EnergySankeyCardConfig extends EnergyCardBaseConfig { type: "energy-sankey"; title?: string; - layout?: "vertical" | "horizontal"; + layout?: "vertical" | "horizontal" | "auto"; group_by_floor?: boolean; group_by_area?: boolean; }