/** * chart.js - gemeinsames Hover/Tooltip-Modul für die SVG-Diagramme dieses * Dashboards. * * Grundidee wie beim Strompreis-Diagramm in WB-Smart/index.html (Maus- * Bewegung abfangen, passenden Datenpunkt bestimmen, Wert einblenden) - * aber bewusst NICHT mit denselben fest verdrahteten Pixel-Magic-Numbers * nachgebaut (dort z.B. "1.61458333333333" als hartcodierter Umrechnungs- * faktor für eine einzige feste Canvas-Größe). Stattdessen wird die * Mausposition über die SVG-viewBox in "SVG-Koordinaten" umgerechnet - * dadurch funktioniert dieselbe Funktion unabhängig von der tatsächlichen * Anzeigegröße (Desktop/Handy/Zoom) korrekt, ohne pro Bildschirmgröße * einen eigenen Korrekturfaktor zu brauchen. * * Zwei Modi: * - 'line' : cfg.points = [{x, label, values:[{cls,name,val}]}, ...] * (nächstgelegener Punkt zur Maus-X-Position wird angezeigt, * zusätzlich wird ein -Element als Fadenkreuz verschoben) * - 'bars' : cfg.zones = [{x0, x1, label, values:[{cls,name,val}]}, ...] * (die Zone, in der sich die Maus befindet, wird angezeigt - * Zonen können zusätzlich y0/y1 angeben, dann muss auch die * Maus-Y-Position in diesem Bereich liegen, z.B. für * liegende Rangfolge-Balken, bei denen jede Zeile eine * eigene Zone ist statt einer Spalte) */ function wetterChartInit(cfg) { var svg = document.getElementById(cfg.svgId); var tip = document.getElementById(cfg.tooltipId); var container = document.getElementById(cfg.containerId); var crosshair = cfg.crosshairId ? document.getElementById(cfg.crosshairId) : null; if (!svg || !tip || !container) { return; } var vb = svg.viewBox.baseVal; function zuSvgX(clientX) { var rect = svg.getBoundingClientRect(); if (rect.width === 0) { return 0; } return (clientX - rect.left) * (vb.width / rect.width); } function zuSvgY(clientY) { var rect = svg.getBoundingClientRect(); if (rect.height === 0) { return 0; } return (clientY - rect.top) * (vb.height / rect.height); } function findeNaechstenPunkt(x) { var pts = cfg.points; var beste = null; var bestAbstand = Infinity; for (var i = 0; i < pts.length; i++) { var d = Math.abs(pts[i].x - x); if (d < bestAbstand) { bestAbstand = d; beste = pts[i]; } } return beste; } function findeZone(x, y) { var zs = cfg.zones; for (var i = 0; i < zs.length; i++) { var z = zs[i]; var xTrifft = x >= z.x0 && x < z.x1; var yTrifft = (z.y0 === undefined) || (y >= z.y0 && y < z.y1); if (xTrifft && yTrifft) { return z; } } return null; } function verstecken() { tip.style.display = 'none'; if (crosshair) { crosshair.style.opacity = 0; } } function anzeigen(daten, clientX, clientY) { if (!daten) { verstecken(); return; } var html = '' + daten.label + '
'; for (var i = 0; i < daten.values.length; i++) { var v = daten.values[i]; html += '' + v.name + ': ' + v.val + '
'; } tip.innerHTML = html; tip.style.display = 'block'; var crect = container.getBoundingClientRect(); var left = clientX - crect.left + 14; var top = clientY - crect.top + 14; // Tooltip-Breite grob schaetzen, damit er nicht rechts abgeschnitten wird var geschaetzteBreite = 170; if (left + geschaetzteBreite > crect.width) { left = clientX - crect.left - geschaetzteBreite - 6; } if (left < 0) { left = 4; } tip.style.left = left + 'px'; tip.style.top = top + 'px'; if (crosshair && daten.x !== undefined) { crosshair.setAttribute('x1', daten.x); crosshair.setAttribute('x2', daten.x); crosshair.style.opacity = 0.35; } } function verarbeiten(clientX, clientY) { var x = zuSvgX(clientX); var daten; if (cfg.mode === 'line') { daten = findeNaechstenPunkt(x); } else { daten = findeZone(x, zuSvgY(clientY)); } anzeigen(daten, clientX, clientY); } svg.addEventListener('mousemove', function (e) { verarbeiten(e.clientX, e.clientY); }); svg.addEventListener('mouseleave', verstecken); svg.addEventListener('touchmove', function (e) { if (e.touches.length > 0) { verarbeiten(e.touches[0].clientX, e.touches[0].clientY); e.preventDefault(); } }, { passive: false }); svg.addEventListener('touchend', verstecken); }