diff --git a/haos-addon/config.yaml b/haos-addon/config.yaml index af26c6c..3b57775 100644 --- a/haos-addon/config.yaml +++ b/haos-addon/config.yaml @@ -1,5 +1,5 @@ name: ShineBridge -version: "1.9.1" +version: "1.9.2" slug: shinebridge description: Energiemanagement für Growatt/Goodwe-Wechselrichter — PV-Überschussladen, Wallbox-EMS, Tarifvergleich, Zigbee2MQTT-Überschussgeräte url: https://gitea.bitfire.work/retr0/shinebridge diff --git a/haos-addon/src/web/index.html b/haos-addon/src/web/index.html index ceb99b7..1971683 100644 --- a/haos-addon/src/web/index.html +++ b/haos-addon/src/web/index.html @@ -797,19 +797,37 @@ async function loadFinance() { `; // ── SVG-Balkendiagramm ──────────────────────────────────────── - // 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 + // Bei langen Abrechnungsjahren nicht jeden einzelnen Tag zeigen — statt 112 + // Tagesbalken lieber ~16 Wochenbalken, deutlich lesbarer und trotzdem + // aussagekräftig für "würde sich Spot lohnen". + const rawN = days.length; + const bucket = rawN > 180 ? 14 : rawN > 21 ? 7 : 1; + const periods = bucket === 1 ? days.map(d => ({ + fixed_eur: d.fixed_eur, spot_eur: d.spot_eur, label: d.date.slice(5), + })) : (() => { + const out = []; + for (let i = 0; i < days.length; i += bucket) { + const chunk = days.slice(i, i + bucket); + const fixed = chunk.reduce((s, d) => s + (d.fixed_eur || 0), 0); + const spotVals = chunk.map(d => d.spot_eur).filter(v => v != null); + const spot = spotVals.length ? spotVals.reduce((a, b) => a + b, 0) : null; + const from = chunk[0].date.slice(5), to = chunk[chunk.length - 1].date.slice(5); + out.push({ fixed_eur: fixed, spot_eur: spot, label: from === to ? from : `${from}–${to}` }); + } + return out; + })(); + + const n = periods.length; + const H = 190, PL = 44, PR = 12, PT = 14, PB = 30; + const MIN_SLOT = 34; // px pro Balkenpaar — bewusst großzügig, jetzt wo wenige Balken übrig sind const W = Math.max(600, PL + PR + n * MIN_SLOT); const cW = W - PL - PR; const cH = H - PT - PB; - const barW = Math.max(3, Math.floor(cW / n) - 2); - const gap = Math.max(1, Math.floor(cW / n) - barW); + const slot = cW / n; + const barW = Math.min(22, Math.max(4, slot * 0.34)); + const barGap = Math.max(2, slot * 0.06); - const allVals = days.flatMap(d => [d.fixed_eur, d.spot_eur].filter(v => v != null)); + const allVals = periods.flatMap(d => [d.fixed_eur, d.spot_eur].filter(v => v != null)); const maxVal = Math.max(...allVals, 0.01); const yTicks = 4; @@ -817,40 +835,52 @@ async function loadFinance() { for (let i = 0; i <= yTicks; i++) { const v = maxVal * i / yTicks; const y = PT + cH - (cH * i / yTicks); - gridLines += ``; - yLabels += `${fEur(v)}`; + gridLines += ``; + yLabels += `${fEur(v)}`; } - // 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))); + const labelStep = Math.max(1, Math.round(50 / slot)); + const rBar = Math.min(3, barW / 2); let bars = '', xLabels = ''; - days.forEach((d, i) => { - const x0 = PL + i * (barW + gap); - const hFixed = cH * d.fixed_eur / maxVal; - bars += ``; + periods.forEach((d, i) => { + const cx = PL + i * slot + slot / 2; + const hFixed = Math.max(0, cH * d.fixed_eur / maxVal); + bars += ``; if (d.spot_eur != null) { - const hSpot = cH * d.spot_eur / maxVal; - bars += ``; + const hSpot = Math.max(0, cH * d.spot_eur / maxVal); + bars += ``; } if (i % labelStep === 0) { - const label = d.date.slice(5); - xLabels += `${label}`; + xLabels += `${d.label}`; } }); - const legend = `■ Festpreis - ■ Spot (hypothetisch)`; + const defs = ` + + + + + + + + + `; + + const bucketLabel = bucket === 1 ? 'Tag' : bucket === 7 ? 'Woche' : '2 Wochen'; + const legend = `
+ Festpreis + Spot (hypothetisch) +
`; const chartSection = `
-
Kosten je Tag
+
Kosten je ${bucketLabel}
- - ${gridLines}${yLabels}${bars}${xLabels}${legend} + + ${defs}${gridLines}${yLabels}${bars}${xLabels}
- ${n > 60 ? `
← Horizontal scrollbar, ${n} Tage
` : ''} + ${legend}
`; el.innerHTML = `
${empfehlung}${cards}${chartSection}
`;