ShineBridge v1.9.1 — Fix: Finanzen-Chart bei langen Abrechnungsjahren unlesbar

'Kosten je Tag' quetschte alle Tage in eine feste Breite von 600px — bei 112
Tagen wurden die Balken zu ~1px-Haarlinien. Breite skaliert jetzt mit der
Tagesanzahl (min. 10px Slot/Tag), Chart scrollt bei vielen Tagen horizontal
statt gestaucht zu werden. X-Achsen-Labels richten sich an der tatsächlichen
Balkenbreite aus statt an einer festen Label-Anzahl.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
retr0
2026-08-25 13:52:25 +02:00
co-authored by Claude Sonnet 5
parent e342fff539
commit e9168ee72b
2 changed files with 15 additions and 6 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
name: ShineBridge
version: "1.9.0"
version: "1.9.1"
slug: shinebridge
description: Energiemanagement für Growatt/Goodwe-Wechselrichter — PV-Überschussladen, Wallbox-EMS, Tarifvergleich, Zigbee2MQTT-Überschussgeräte
url: https://gitea.bitfire.work/retr0/shinebridge
+14 -5
View File
@@ -797,11 +797,16 @@ async function loadFinance() {
</div>`;
// ── SVG-Balkendiagramm ────────────────────────────────────────
const W = 600, H = 180, PL = 44, PR = 12, PT = 10, PB = 28;
// Breite skaliert mit der Anzahl Tage (Mindestplatz pro Tag), statt bei vielen
// Tagen alles in eine feste Breite zu quetschen — sonst werden Balken bei
// langen Abrechnungsjahren (>60 Tage) zu Haarlinien und unlesbar.
const H = 180, PL = 44, PR = 12, PT = 10, PB = 28;
const n = days.length;
const MIN_SLOT = 10; // px pro Tag (Balkenpaar + Abstand), unterhalb dessen nichts mehr lesbar ist
const W = Math.max(600, PL + PR + n * MIN_SLOT);
const cW = W - PL - PR;
const cH = H - PT - PB;
const n = days.length;
const barW = Math.max(2, Math.floor(cW / n) - 2);
const barW = Math.max(3, Math.floor(cW / n) - 2);
const gap = Math.max(1, Math.floor(cW / n) - barW);
const allVals = days.flatMap(d => [d.fixed_eur, d.spot_eur].filter(v => v != null));
@@ -816,6 +821,9 @@ async function loadFinance() {
yLabels += `<text x="${PL-4}" y="${(y+4).toFixed(1)}" text-anchor="end" font-size="9" fill="#8b949e">${fEur(v)}</text>`;
}
// Label-Abstand an der tatsächlichen Balkenbreite ausrichten (~60px pro Label),
// statt pauschal 8 Labels über die ganze — jetzt variable — Breite zu verteilen.
const labelStep = Math.max(1, Math.round(60 / (barW + gap)));
let bars = '', xLabels = '';
days.forEach((d, i) => {
const x0 = PL + i * (barW + gap);
@@ -825,7 +833,7 @@ async function loadFinance() {
const hSpot = cH * d.spot_eur / maxVal;
bars += `<rect x="${x0 + barW/2}" y="${(PT + cH - hSpot).toFixed(1)}" width="${barW/2}" height="${hSpot.toFixed(1)}" fill="${C.spot}" opacity="0.85"/>`;
}
if (i % Math.max(1, Math.floor(n / 8)) === 0) {
if (i % labelStep === 0) {
const label = d.date.slice(5);
xLabels += `<text x="${(x0 + barW/2).toFixed(1)}" y="${H - 4}" text-anchor="middle" font-size="9" fill="#8b949e">${label}</text>`;
}
@@ -838,10 +846,11 @@ async function loadFinance() {
<div style="border-top:1px solid var(--border);padding-top:24px;margin-top:4px">
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--text-dim);margin-bottom:14px">Kosten je Tag</div>
<div style="overflow-x:auto">
<svg viewBox="0 0 ${W} ${H+24}" style="width:100%;max-width:${W}px;display:block">
<svg viewBox="0 0 ${W} ${H+24}" width="${W}" height="${H+24}" style="width:${W}px;max-width:none;display:block">
${gridLines}${yLabels}${bars}${xLabels}${legend}
</svg>
</div>
${n > 60 ? `<div style="font-size:11px;color:var(--text-dim);margin-top:6px">← Horizontal scrollbar, ${n} Tage</div>` : ''}
</div>`;
el.innerHTML = `<div style="padding:4px 0">${empfehlung}${cards}${chartSection}</div>`;