This commit is contained in:
jayjojayson 2026-03-02 00:31:47 +01:00
parent e2f070941d
commit 7ecf0d53b6
4 changed files with 200 additions and 48 deletions

View file

@ -20,7 +20,7 @@ export default {
"editor.battery_soc_label": "Ladestand (%)",
"editor.house_total_title": "🏠 Gesamtverbrauch (optional)",
"editor.house_sensor_label": "Sensor für Hausverbrauch (optional)",
"editor.house_sensor_hint": "Wird benötigt, damit das Haus-Icon anklickbar ist (compact view).",
"editor.house_sensor_hint": "Wird benötigt, damit das Haus-Icon anklickbar ist (more-details). Ansonsten wird der Hausverbrauch berechnet.",
"editor.consumer_1_title": "🚗 Links (Lila)",
"editor.consumer_2_title": "♨️ Mitte (Orange)",
"editor.consumer_3_title": "🏊 Rechts (Türkis)",
@ -37,8 +37,12 @@ export default {
"editor.grid_to_battery_sensor": "Netz-zu-Batterie Sensor (Watt)",
"editor.grid_to_battery_hint": "Optional: separater Sensor für den Netz-zu-Batterie Fluss. Wenn leer, wird der Wert automatisch berechnet.",
"editor.grid_combined_sensor": "Kombinierter Netz-Sensor (W, Optional)",
"editor.grid_combined_hint": "Ein Sensor für Import UND Export: positiv = Import, negativ = Export. Überschreibt die getrennten Import/Export Sensoren.",
"editor.color_picker": "Farbe anpassen",
"editor.grid_combined_hint": "Ein Sensor für Import UND Export: positiv = Import, negativ = Export. Überschreibt den kombinierten Import/Export Sensor.",
"editor.color_picker": "Kreis Farbe",
"editor.pipe_color": "Rohr Farbe",
"editor.export_color": "Export Farbe",
"editor.consumer_unit_kw": "Sensor meldet in kW",
"editor.show_consumer_always": "Verbraucher bei null Watt anzeigen",
},
card: {
"card.label_solar": "Solar",

View file

@ -20,7 +20,7 @@ export default {
"editor.battery_soc_label": "State of Charge (%)",
"editor.house_total_title": "🏠 Total Consumption (optional)",
"editor.house_sensor_label": "Sensor for House Consumption (optional)",
"editor.house_sensor_hint": "Required to make the house icon clickable (compact view).",
"editor.house_sensor_hint": "Required to make the house icon clickable (more-details). Otherwise, the house consumption is calculated.",
"editor.consumer_1_title": "🚗 Left (Purple)",
"editor.consumer_2_title": "♨️ Center (Orange)",
"editor.consumer_3_title": "🏊 Right (Cyan)",
@ -37,8 +37,12 @@ export default {
"editor.grid_to_battery_sensor": "Grid to Battery Sensor (Watt)",
"editor.grid_to_battery_hint": "Optional: separate sensor for grid-to-battery flow. If empty, the value is calculated automatically.",
"editor.grid_combined_sensor": "Combined Grid Sensor (W, Optional)",
"editor.grid_combined_hint": "Single sensor for import AND export: positive = import, negative = export. Overrides separate import/export sensors.",
"editor.color_picker": "Custom Color",
"editor.grid_combined_hint": "Single sensor for import AND export: positive = import, negative = export. Overrides combined import/export sensor.",
"editor.color_picker": "Bubble Color",
"editor.pipe_color": "Pipe Color",
"editor.export_color": "Export Color",
"editor.consumer_unit_kw": "Sensor reports in kW",
"editor.show_consumer_always": "Show Consumers at zero watts",
},
card: {
"card.label_solar": "Solar",

View file

@ -162,6 +162,35 @@ class PowerFluxCardEditor extends LitElement {
`;
}
_renderColorPickerDual(bubbleKey, pipeKey, defaultColor) {
const bubbleColor = this._config[bubbleKey] || defaultColor;
const pipeColor = this._config[pipeKey] || defaultColor;
const hasBubbleCustom = !!this._config[bubbleKey];
const hasPipeCustom = !!this._config[pipeKey];
return html`
<div class="color-picker-dual">
<div class="color-picker-row">
<input type="color"
.value=${bubbleColor}
@input=${(e) => this._colorChanged(bubbleKey, e)}>
<span class="color-label">${this._localize('editor.color_picker')}</span>
${hasBubbleCustom ? html`<ha-icon class="color-reset-btn"
icon="mdi:refresh"
@click=${() => this._resetColor(bubbleKey)}></ha-icon>` : ''}
</div>
<div class="color-picker-row">
<input type="color"
.value=${pipeColor}
@input=${(e) => this._colorChanged(pipeKey, e)}>
<span class="color-label">${this._localize('editor.pipe_color')}</span>
${hasPipeCustom ? html`<ha-icon class="color-reset-btn"
icon="mdi:refresh"
@click=${() => this._resetColor(pipeKey)}></ha-icon>` : ''}
</div>
</div>
`;
}
static get styles() {
return css`
.card-config {
@ -294,6 +323,13 @@ class PowerFluxCardEditor extends LitElement {
.color-reset-btn:hover {
color: var(--primary-color);
}
.color-picker-dual {
display: flex;
gap: 8px;
}
.color-picker-dual .color-picker-row {
flex: 1;
}
`;
}
@ -332,7 +368,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_solar || "", 'secondary_solar', this._localize('editor.secondary_sensor'))}
${this._renderColorPicker('color_solar', this._localize('editor.color_picker'), '#ffdd00')}
${this._renderColorPickerDual('color_solar', 'color_pipe_solar', '#ffdd00')}
<div class="separator"></div>
@ -366,11 +402,13 @@ class PowerFluxCardEditor extends LitElement {
</div>
${this._renderEntitySelector(entitySelectorSchema, entities.grid_combined || "", 'grid_combined', this._localize('editor.grid_combined_sensor'))}
<div class="separator"></div>
<div style="font-size: 0.8em; color: var(--secondary-text-color); margin-top: 4px;">
${this._localize('editor.grid_combined_hint')}
</div>
<div class="separator"></div>
${this._renderEntitySelector(entitySelectorSchema, entities.grid, 'grid', this._localize('card.label_import') + " (W)")}
@ -398,7 +436,9 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_grid || "", 'secondary_grid', this._localize('editor.secondary_sensor'))}
${this._renderColorPicker('color_grid', this._localize('editor.color_picker'), '#3b82f6')}
${this._renderColorPickerDual('color_grid', 'color_pipe_grid', '#3b82f6')}
${this._renderColorPicker('color_export', this._localize('editor.export_color'), '#ff3333')}
<div class="separator"></div>
@ -462,7 +502,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_battery || "", 'secondary_battery', this._localize('editor.secondary_sensor'))}
${this._renderColorPicker('color_battery', this._localize('editor.color_picker'), '#00ff88')}
${this._renderColorPickerDual('color_battery', 'color_pipe_battery', '#00ff88')}
<div class="separator"></div>
@ -543,9 +583,18 @@ class PowerFluxCardEditor extends LitElement {
></ha-switch>
</div>
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 8px; margin-bottom: 8px;">
<span>${this._localize('editor.consumer_unit_kw')}</span>
<ha-switch
.checked=${this._config.consumer_1_unit_kw === true}
.configValue=${'consumer_1_unit_kw'}
@change=${this._valueChanged}
></ha-switch>
</div>
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_1 || "", 'secondary_consumer_1', this._localize('editor.secondary_sensor'))}
${this._renderColorPicker('color_consumer_1', this._localize('editor.color_picker'), '#a855f7')}
${this._renderColorPickerDual('color_consumer_1', 'color_pipe_consumer_1', '#a855f7')}
</div>
<div class="consumer-group">
@ -570,9 +619,18 @@ class PowerFluxCardEditor extends LitElement {
@value-changed=${this._valueChanged}
></ha-selector>
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 8px; margin-bottom: 8px;">
<span>${this._localize('editor.consumer_unit_kw')}</span>
<ha-switch
.checked=${this._config.consumer_2_unit_kw === true}
.configValue=${'consumer_2_unit_kw'}
@change=${this._valueChanged}
></ha-switch>
</div>
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_2 || "", 'secondary_consumer_2', this._localize('editor.secondary_sensor'))}
${this._renderColorPicker('color_consumer_2', this._localize('editor.color_picker'), '#f97316')}
${this._renderColorPickerDual('color_consumer_2', 'color_pipe_consumer_2', '#f97316')}
</div>
<div class="consumer-group">
@ -597,9 +655,18 @@ class PowerFluxCardEditor extends LitElement {
@value-changed=${this._valueChanged}
></ha-selector>
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 8px; margin-bottom: 8px;">
<span>${this._localize('editor.consumer_unit_kw')}</span>
<ha-switch
.checked=${this._config.consumer_3_unit_kw === true}
.configValue=${'consumer_3_unit_kw'}
@change=${this._valueChanged}
></ha-switch>
</div>
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_3 || "", 'secondary_consumer_3', this._localize('editor.secondary_sensor'))}
${this._renderColorPicker('color_consumer_3', this._localize('editor.color_picker'), '#06b6d4')}
${this._renderColorPickerDual('color_consumer_3', 'color_pipe_consumer_3', '#06b6d4')}
</div>
`;
}
@ -733,6 +800,15 @@ class PowerFluxCardEditor extends LitElement {
<div class="switch-label">${this._localize('editor.hide_inactive')}</div>
</div>
<div class="switch-row">
<ha-switch
.checked=${this._config.show_consumer_always === true}
.configValue=${'show_consumer_always'}
@change=${this._valueChanged}
></ha-switch>
<div class="switch-label">${this._localize('editor.show_consumer_always')}</div>
</div>
<div class="switch-row">
<ha-switch
.checked=${this._config.compact_view === true}

View file

@ -40,6 +40,10 @@ console.log(
return {
zoom: 0.9,
compact_view: false,
consumer_1_unit_kw: false,
consumer_2_unit_kw: false,
consumer_3_unit_kw: false,
show_consumer_always: false,
show_donut_border: false,
show_neon_glow: true,
show_comet_tail: false,
@ -114,9 +118,16 @@ console.log(
'color_solar': '--neon-yellow',
'color_grid': '--neon-blue',
'color_battery': '--neon-green',
'color_export': '--export-color',
'color_consumer_1': '--consumer-1-color',
'color_consumer_2': '--consumer-2-color',
'color_consumer_3': '--consumer-3-color',
'color_pipe_solar': '--pipe-solar-color',
'color_pipe_grid': '--pipe-grid-color',
'color_pipe_battery': '--pipe-battery-color',
'color_pipe_consumer_1': '--pipe-consumer-1-color',
'color_pipe_consumer_2': '--pipe-consumer-2-color',
'color_pipe_consumer_3': '--pipe-consumer-3-color',
};
for (const [configKey, cssVar] of Object.entries(colorMap)) {
if (this.config[configKey]) {
@ -146,9 +157,16 @@ console.log(
--neon-red: #ff3333;
--grid-grey: #9e9e9e;
--export-purple: #a855f7;
--export-color: #ff3333;
--consumer-1-color: #a855f7;
--consumer-2-color: #f97316;
--consumer-3-color: #06b6d4;
--pipe-solar-color: var(--neon-yellow);
--pipe-grid-color: var(--neon-blue);
--pipe-battery-color: var(--neon-green);
--pipe-consumer-1-color: var(--consumer-1-color);
--pipe-consumer-2-color: var(--consumer-2-color);
--pipe-consumer-3-color: var(--consumer-3-color);
--flow-dasharray: 0 380;
}
:host([data-theme-light]) {
@ -159,6 +177,7 @@ console.log(
--neon-red: #dc2626;
--grid-grey: #6b7280;
--export-purple: #7c3aed;
--export-color: #dc2626;
--consumer-1-color: #7c3aed;
--consumer-2-color: #ea580c;
--consumer-3-color: #0891b2;
@ -272,8 +291,8 @@ console.log(
.bubble.tinted { background: rgba(255, 255, 255, 0.05); }
.bubble.tinted.solar { background: color-mix(in srgb, var(--neon-yellow), transparent 85%); }
.bubble.tinted.grid { background: color-mix(in srgb, var(--neon-blue), transparent 85%); }
.bubble.tinted.grid.exporting { background: color-mix(in srgb, var(--neon-red), transparent 85%); }
.bubble.grid.exporting { border-color: var(--neon-red); }
.bubble.tinted.grid.exporting { background: color-mix(in srgb, var(--export-color), transparent 85%); }
.bubble.grid.exporting { border-color: var(--export-color); }
.bubble.tinted.battery { background: color-mix(in srgb, var(--neon-green), transparent 85%); }
.bubble.tinted.c1 { background: color-mix(in srgb, var(--consumer-1-color), transparent 85%); }
.bubble.tinted.c2 { background: color-mix(in srgb, var(--consumer-2-color), transparent 85%); }
@ -290,6 +309,16 @@ console.log(
-webkit-mask-composite: xor; mask-composite: exclude; z-index: -1; pointer-events: none;
}
.bubble.grid.donut { border: none !important; background: transparent; }
.bubble.grid.donut.tinted { background: color-mix(in srgb, var(--neon-blue), transparent 85%); }
.bubble.grid.donut.tinted.exporting { background: color-mix(in srgb, var(--export-color), transparent 85%); }
.bubble.grid.donut::before {
content: ""; position: absolute; inset: 0; border-radius: 50%; padding: 4px;
background: var(--grid-gradient, var(--neon-blue));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor; mask-composite: exclude; z-index: -1; pointer-events: none;
}
.icon-svg, .icon-custom {
width: 33px; height: 33px; position: absolute; top: 10px; left: 50%; margin-left: -17px; z-index: 2; display: block;
}
@ -329,6 +358,7 @@ console.log(
.glow.solar { box-shadow: 0 0 15px color-mix(in srgb, var(--neon-yellow), transparent 60%); }
.glow.battery { box-shadow: 0 0 15px color-mix(in srgb, var(--neon-green), transparent 60%); }
.glow.grid { box-shadow: 0 0 15px color-mix(in srgb, var(--neon-blue), transparent 60%); }
.glow.grid.exporting { box-shadow: 0 0 15px color-mix(in srgb, var(--export-color), transparent 60%); }
.glow.c1 { box-shadow: 0 0 15px color-mix(in srgb, var(--consumer-1-color), transparent 60%); }
.glow.c2 { box-shadow: 0 0 15px color-mix(in srgb, var(--consumer-2-color), transparent 60%); }
.glow.c3 { box-shadow: 0 0 15px color-mix(in srgb, var(--consumer-3-color), transparent 60%); }
@ -344,22 +374,22 @@ console.log(
svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; }
.bg-path { fill: none; stroke-width: 6; transition: opacity 0.3s ease; }
.bg-solar { stroke: var(--neon-yellow); }
.bg-grid { stroke: var(--neon-blue); }
.bg-battery { stroke: var(--neon-green); }
.bg-export { stroke: var(--neon-red); }
.bg-c1 { stroke: var(--consumer-1-color); }
.bg-c2 { stroke: var(--consumer-2-color); }
.bg-c3 { stroke: var(--consumer-3-color); }
.bg-solar { stroke: var(--pipe-solar-color); }
.bg-grid { stroke: var(--pipe-grid-color); }
.bg-battery { stroke: var(--pipe-battery-color); }
.bg-export { stroke: var(--export-color); }
.bg-c1 { stroke: var(--pipe-consumer-1-color); }
.bg-c2 { stroke: var(--pipe-consumer-2-color); }
.bg-c3 { stroke: var(--pipe-consumer-3-color); }
.flow-line {
fill: none; stroke-width: var(--flow-stroke-width, 8px); stroke-linecap: round; stroke-dasharray: var(--flow-dasharray);
animation: dash linear infinite; opacity: 0; transition: opacity 0.5s;
}
.flow-solar { stroke: var(--neon-yellow); }
.flow-grid { stroke: var(--neon-blue); }
.flow-battery { stroke: var(--neon-green); }
.flow-export { stroke: var(--neon-red); }
.flow-solar { stroke: var(--pipe-solar-color); }
.flow-grid { stroke: var(--pipe-grid-color); }
.flow-battery { stroke: var(--pipe-battery-color); }
.flow-export { stroke: var(--export-color); }
@keyframes dash { to { stroke-dashoffset: -1500; } }
@ -367,10 +397,10 @@ console.log(
font-size: 10px; font-weight: bold; text-anchor: middle; fill: #fff; filter: transition: opacity 0.3s ease;
}
.flow-text.no-shadow { filter: none; }
.text-solar { fill: var(--neon-yellow); }
.text-grid { fill: var(--neon-blue); }
.text-export { fill: var(--neon-red); }
.text-battery { fill: var(--neon-green); }
.text-solar { fill: var(--pipe-solar-color); }
.text-grid { fill: var(--pipe-grid-color); }
.text-export { fill: var(--export-color); }
.text-battery { fill: var(--pipe-battery-color); }
`;
}
@ -425,6 +455,11 @@ console.log(
return style.getPropertyValue(`--consumer-${index}-color`).trim() || ['#a855f7', '#f97316', '#06b6d4'][index - 1];
}
_getConsumerPipeColor(index) {
const style = getComputedStyle(this);
return style.getPropertyValue(`--pipe-consumer-${index}-color`).trim() || this._getConsumerColor(index);
}
// --- DOM NODE SVG GENERATOR ---
_renderSVGPath(d, color) {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
@ -478,6 +513,9 @@ console.log(
const state = this.hass.states[entity];
return state ? parseFloat(state.state) || 0 : 0;
};
const getValKw = (entity, isKw) => {
return getVal(entity) * (isKw ? 1000 : 1);
};
const solar = entities.solar ? Math.max(0, getVal(entities.solar)) : 0;
const hasGridCombined = !!(entities.grid_combined && entities.grid_combined !== "");
@ -488,7 +526,7 @@ console.log(
if (this.config.invert_battery) {
battery *= -1;
}
let c1Val = entities.consumer_1 ? getVal(entities.consumer_1) : 0; // EV Value
let c1Val = entities.consumer_1 ? getValKw(entities.consumer_1, this.config.consumer_1_unit_kw === true) : 0; // EV Value
if (this.config.invert_consumer_1) { c1Val *= -1; }
c1Val = Math.abs(c1Val);
@ -778,16 +816,20 @@ console.log(
const state = this.hass.states[entity];
return state ? parseFloat(state.state) || 0 : 0;
};
const getValKw = (entity, isKw) => {
return getVal(entity) * (isKw ? 1000 : 1);
};
let c1Val = entities.consumer_1 ? getVal(entities.consumer_1) : 0;
let c1Val = entities.consumer_1 ? getValKw(entities.consumer_1, this.config.consumer_1_unit_kw === true) : 0;
if (this.config.invert_consumer_1) { c1Val *= -1; }
c1Val = Math.abs(c1Val);
const c2Val = entities.consumer_2 ? getVal(entities.consumer_2) : 0;
const c3Val = entities.consumer_3 ? getVal(entities.consumer_3) : 0;
const c2Val = entities.consumer_2 ? getValKw(entities.consumer_2, this.config.consumer_2_unit_kw === true) : 0;
const c3Val = entities.consumer_3 ? getValKw(entities.consumer_3, this.config.consumer_3_unit_kw === true) : 0;
const showC1 = (entities.consumer_1 && Math.round(c1Val) > 0);
const showC2 = (entities.consumer_2 && Math.round(c2Val) > 0);
const showC3 = (entities.consumer_3 && Math.round(c3Val) > 0);
const alwaysShowConsumer = this.config.show_consumer_always === true;
const showC1 = (entities.consumer_1 && (alwaysShowConsumer || Math.round(c1Val) > 0));
const showC2 = (entities.consumer_2 && (alwaysShowConsumer || Math.round(c2Val) > 0));
const showC3 = (entities.consumer_3 && (alwaysShowConsumer || Math.round(c3Val) > 0));
const anyBottomVisible = showC1 || showC2 || showC3;
const solar = hasSolar ? getVal(entities.solar) : 0;
@ -928,8 +970,28 @@ console.log(
const isGridActive = Math.round(gridImport) > 0 || Math.round(gridExport) > 0;
const isGridExporting = Math.round(gridExport) > 0 && Math.round(gridImport) === 0;
// --- Grid Donut Gradient ---
let gridGradientVal = '';
if (showDonut && hasGrid && isGridActive) {
const gridTotal = gridToHouse + gridToBatt + gridExport;
if (gridTotal > 0) {
const gPctToHouse = (gridToHouse / gridTotal) * 100;
const gPctToBatt = (gridToBatt / gridTotal) * 100;
const gPctExport = (gridExport / gridTotal) * 100;
let gStops = [];
let gCurrent = 0;
if (gPctToHouse > 0) { gStops.push(`var(--neon-blue) ${gCurrent}% ${gCurrent + gPctToHouse}%`); gCurrent += gPctToHouse; }
if (gPctToBatt > 0) { gStops.push(`var(--neon-green) ${gCurrent}% ${gCurrent + gPctToBatt}%`); gCurrent += gPctToBatt; }
if (gPctExport > 0) { gStops.push(`var(--export-color) ${gCurrent}% ${gCurrent + gPctExport}%`); gCurrent += gPctExport; }
if (gCurrent < 99.9) { gStops.push(`var(--neon-blue) ${gCurrent}% 100%`); }
gridGradientVal = `conic-gradient(from 330deg, ${gStops.join(', ')})`;
} else {
gridGradientVal = isGridExporting ? 'var(--export-color)' : 'var(--neon-blue)';
}
}
const solarColor = isSolarActive ? 'var(--neon-yellow)' : 'var(--secondary-text-color)';
const gridColor = isGridExporting ? 'var(--neon-red)' : (isGridActive ? 'var(--neon-blue)' : 'var(--secondary-text-color)');
const gridColor = isGridExporting ? 'var(--export-color)' : (isGridActive ? 'var(--neon-blue)' : 'var(--secondary-text-color)');
const getAnimStyle = (val) => {
if (val <= 1) return "opacity: 0;";
@ -1061,6 +1123,11 @@ console.log(
const pathSolarBatt = "M 50 70 Q 210 -20 370 70";
const pathGridImport = "M 210 160 L 210 220";
const pathGridExport = "M 95 115 Q 130 145 165 115";
const pathHouseExport = "M 210 220 L 210 160";
const exportFromSolar = solarVal > 1;
const activeExportPath = exportFromSolar ? pathGridExport : pathHouseExport;
const exportTextX = exportFromSolar ? '130' : '185';
const exportTextY = exportFromSolar ? '145' : '195';
const pathGridToBatt = "M 255 115 Q 290 145 325 115";
const pathBattHouse = "M 370 160 Q 370 265 255 265";
const pathHouseC1 = "M 165 265 Q 50 265 50 370";
@ -1083,33 +1150,33 @@ console.log(
<path class="bg-path bg-solar" d="${pathSolarBatt}" style="${getPipeStyle(solarToBatt)} ${styleSolarBatt}" />
<path class="bg-path bg-grid" d="${pathGridImport}" style="${getPipeStyle(gridToHouse)} ${styleGrid}" />
<path class="bg-path bg-export" d="${pathGridExport}" style="${getPipeStyle(gridExport)} ${styleGrid}" />
<path class="bg-path bg-export" d="${activeExportPath}" style="${getPipeStyle(gridExport)} ${styleGrid}" />
<path class="bg-path bg-grid" d="${pathGridToBatt}" style="${getPipeStyle(gridToBatt)} ${styleGridBatt}" />
<path class="bg-path bg-battery" d="${pathBattHouse}" style="${getPipeStyle(batteryDischarge)} ${styleBattery}" />
<path d="${pathHouseC1}" fill="none" stroke="${this._getConsumerColor(1)}" stroke-width="6" style="${getConsumerPipeStyle(showC1, c1Val)}" />
<path d="${pathHouseC2}" fill="none" stroke="${this._getConsumerColor(2)}" stroke-width="6" style="${getConsumerPipeStyle(showC2, c2Val)}" />
<path d="${pathHouseC3}" fill="none" stroke="${this._getConsumerColor(3)}" stroke-width="6" style="${getConsumerPipeStyle(showC3, c3Val)}" />
<path d="${pathHouseC1}" fill="none" stroke="${this._getConsumerPipeColor(1)}" stroke-width="6" style="${getConsumerPipeStyle(showC1, c1Val)}" />
<path d="${pathHouseC2}" fill="none" stroke="${this._getConsumerPipeColor(2)}" stroke-width="6" style="${getConsumerPipeStyle(showC2, c2Val)}" />
<path d="${pathHouseC3}" fill="none" stroke="${this._getConsumerPipeColor(3)}" stroke-width="6" style="${getConsumerPipeStyle(showC3, c3Val)}" />
<path class="flow-line flow-solar" d="${pathSolarHouse}" style="${getAnimStyle(solarToHouse)} ${styleSolar}" />
<path class="flow-line flow-solar" d="${pathSolarBatt}" style="${getAnimStyle(solarToBatt)} ${styleSolarBatt}" />
<path class="flow-line flow-grid" d="${pathGridImport}" style="${getAnimStyle(gridToHouse)} ${styleGrid}" />
<path class="flow-line flow-export" d="${pathGridExport}" style="${getAnimStyle(gridExport)} ${styleGrid}" />
<path class="flow-line flow-export" d="${activeExportPath}" style="${getAnimStyle(gridExport)} ${styleGrid}" />
<path class="flow-line flow-grid" d="${pathGridToBatt}" style="${getAnimStyle(gridToBatt)} ${styleGridBatt}" />
<path class="flow-line flow-battery" d="${pathBattHouse}" style="${getAnimStyle(batteryDischarge)} ${styleBattery}" />
<path class="flow-line" d="${pathHouseC1}" stroke="${this._getConsumerColor(1)}" style="${getConsumerAnimStyle(showC1, c1Val)}" />
<path class="flow-line" d="${pathHouseC2}" stroke="${this._getConsumerColor(2)}" style="${getConsumerAnimStyle(showC2, c2Val)}" />
<path class="flow-line" d="${pathHouseC3}" stroke="${this._getConsumerColor(3)}" style="${getConsumerAnimStyle(showC3, c3Val)}" />
<path class="flow-line" d="${pathHouseC1}" stroke="${this._getConsumerPipeColor(1)}" style="${getConsumerAnimStyle(showC1, c1Val)}" />
<path class="flow-line" d="${pathHouseC2}" stroke="${this._getConsumerPipeColor(2)}" style="${getConsumerAnimStyle(showC2, c2Val)}" />
<path class="flow-line" d="${pathHouseC3}" stroke="${this._getConsumerPipeColor(3)}" style="${getConsumerAnimStyle(showC3, c3Val)}" />
<text x="100" y="235" class="${textClass} text-solar" style="${getTextStyle(solarToHouse, 'solar')} ${styleSolar}">${this._formatPower(solarToHouse)}</text>
<text x="210" y="45" class="${textClass} text-solar" style="${getTextStyle(solarToBatt, 'solar')} ${styleSolarBatt}">${this._formatPower(solarToBatt)}</text>
<text x="235" y="195" class="${textClass} text-grid" style="${getTextStyle(gridToHouse, 'grid')} ${styleGrid}">${this._formatPower(gridToHouse)}</text>
<text x="130" y="145" class="${textClass} text-export" style="${getTextStyle(gridExport, 'grid')} ${styleGrid}">${this._formatPower(gridExport)}</text>
<text x="${exportTextX}" y="${exportTextY}" class="${textClass} text-export" style="${getTextStyle(gridExport, 'grid')} ${styleGrid}">${this._formatPower(gridExport)}</text>
<text x="290" y="145" class="${textClass} text-grid" style="${getTextStyle(gridToBatt, 'grid')} ${styleGridBatt}">${this._formatPower(gridToBatt)}</text>
<text x="320" y="235" class="${textClass} text-battery" style="${getTextStyle(batteryDischarge, 'battery')} ${styleBattery}">${this._formatPower(batteryDischarge)}</text>
@ -1125,7 +1192,8 @@ console.log(
</div>` : ''}
${hasGrid ? html`
<div class="bubble ${isGridActive ? (isGridExporting ? 'grid exporting' : 'grid') : 'inactive'} node node-grid ${tintClass} ${isGridActive ? glowClass : ''} ${getCustomClass(iconGrid)}"
<div class="bubble ${isGridActive ? (isGridExporting ? 'grid exporting' : 'grid') : 'inactive'} node node-grid ${showDonut && isGridActive ? 'donut' : ''} ${tintClass} ${isGridActive ? glowClass : ''} ${getCustomClass(iconGrid)}"
style="${showDonut && isGridActive ? `--grid-gradient: ${gridGradientVal};` : ''}"
@click=${() => this._handleClick(entities.grid_combined || entities.grid)}>
${renderMainIcon('grid', isGridExporting ? gridExport : gridImport, iconGrid, gridColor)}
${renderSecondaryOrLabel(labelGridText, showLabelGrid, entities.secondary_grid, hasSecondaryGrid)}