Files
Symcon_Belevo_Energiemanage…/Energy_Pie/module.html
2025-12-17 07:05:42 +01:00

172 lines
5.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div style="padding:12px;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;">
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
<label style="display:flex;gap:6px;align-items:center;">
<span>Zeitraum</span>
<select id="range">
<option value="day">Tag</option>
<option value="week">Woche (MoSo)</option>
<option value="month">Monat</option>
<option value="total">Gesamt</option>
</select>
</label>
<label style="display:flex;gap:6px;align-items:center;">
<span>Datum</span>
<input id="date" type="date" />
</label>
<button id="prev" type="button"></button>
<button id="today" type="button">Heute</button>
<button id="next" type="button"></button>
</div>
<div style="margin-top:12px;display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap;">
<div style="display:flex;flex-direction:column;gap:6px;">
<canvas id="pie" width="280" height="280" style="border-radius:14px;"></canvas>
<div id="period" style="font-size:12px;opacity:0.7;"></div>
</div>
<div style="min-width:260px;flex:1;">
<div style="font-weight:600;margin-bottom:8px;">Aufteilung</div>
<div id="legend" style="display:flex;flex-direction:column;gap:6px;"></div>
</div>
</div>
</div>
<script>
const elRange = document.getElementById('range');
const elDate = document.getElementById('date');
const elPrev = document.getElementById('prev');
const elToday = document.getElementById('today');
const elNext = document.getElementById('next');
const elLegend = document.getElementById('legend');
const elPeriod = document.getElementById('period');
const canvas = document.getElementById('pie');
const ctx = canvas.getContext('2d');
elRange.addEventListener('change', () => requestAction('SetRange', elRange.value));
// Fire immediately when user picks a date (input + change), with a tiny debounce
let dateTimer = null;
function pushDateNow() {
if (dateTimer) clearTimeout(dateTimer);
dateTimer = setTimeout(() => {
requestAction('SetDate', elDate.value);
}, 80);
}
elDate.addEventListener('input', pushDateNow);
elDate.addEventListener('change', pushDateNow);
elPrev.addEventListener('click', () => requestAction('Prev', true));
elToday.addEventListener('click', () => requestAction('Today', true));
elNext.addEventListener('click', () => requestAction('Next', true));
// Called by Symcon when PHP sends UpdateVisualizationValue($payload)
function handleMessage(data) {
if (!data) return;
if (data.range) elRange.value = data.range;
if (data.date) elDate.value = data.date;
const isTotal = (data.range === 'total');
elDate.disabled = isTotal;
if (data.tStart && data.tEnd) {
const s = new Date(data.tStart * 1000);
const e = new Date(data.tEnd * 1000);
elPeriod.textContent = isTotal
? 'Zeitraum: Gesamt'
: `Zeitraum: ${fmtDateTime(s)} ${fmtDateTime(e)}`;
} else {
elPeriod.textContent = '';
}
if (data.values) {
drawPie(data.values);
drawLegend(data.values);
}
}
function fmtDateTime(d) {
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2,'0');
const dd = String(d.getDate()).padStart(2,'0');
const hh = String(d.getHours()).padStart(2,'0');
const mi = String(d.getMinutes()).padStart(2,'0');
return `${dd}.${mm}.${yyyy} ${hh}:${mi}`;
}
function drawLegend(values) {
const entries = Object.entries(values);
const sum = entries.reduce((a,[,v]) => a + (+v || 0), 0);
elLegend.innerHTML = entries.map(([k,v]) => {
const val = (+v || 0);
const pct = sum > 0 ? (val / sum * 100) : 0;
return `
<div style="display:flex;justify-content:space-between;gap:12px;">
<div>${escapeHtml(k)}</div>
<div style="white-space:nowrap;">${val.toFixed(2)} kWh (${pct.toFixed(1)}%)</div>
</div>`;
}).join('');
}
function drawPie(values) {
const entries = Object.entries(values).map(([k,v]) => [k, (+v || 0)]);
const sum = entries.reduce((a,[,v]) => a + v, 0);
ctx.clearRect(0,0,canvas.width,canvas.height);
// Background circle
ctx.beginPath();
ctx.arc(140,140,125,0,Math.PI*2);
ctx.fillStyle = '#f2f2f2';
ctx.fill();
if (sum <= 0) {
ctx.fillStyle = '#666';
ctx.font = '14px sans-serif';
ctx.fillText('Keine Daten', 92, 145);
return;
}
let start = -Math.PI / 2;
const colors = ['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc948'];
entries.forEach(([label,val], i) => {
if (val <= 0) return;
const ang = (val / sum) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(140,140);
ctx.arc(140,140,125,start,start+ang);
ctx.closePath();
ctx.fillStyle = colors[i % colors.length];
ctx.fill();
start += ang;
});
// Donut hole
ctx.beginPath();
ctx.arc(140,140,65,0,Math.PI*2);
ctx.fillStyle = '#fff';
ctx.fill();
// Sum in center
ctx.fillStyle = '#111';
ctx.font = 'bold 16px sans-serif';
const text = sum.toFixed(2) + ' kWh';
const w = ctx.measureText(text).width;
ctx.fillText(text, 140 - w/2, 146);
}
function escapeHtml(s) {
return String(s)
.replaceAll('&','&amp;')
.replaceAll('<','&lt;')
.replaceAll('>','&gt;')
.replaceAll('"','&quot;')
.replaceAll("'",'&#039;');
}
</script>