'use strict'; /** * @module heatmap-renderer * @description Rendert die Heatmap basierend auf bereitgestellten Datenobjekten in SVG. */ /** * Erzeugt Heatmap-Zellen nach Intensitätswerten, integriert Farbskala. * @param {Object} data - Heatmap-Datenobjekt mit Werten und Metadaten. * @param {HTMLElement} container - DOM-Container, in den die Heatmap gerendert wird. */ export function renderHeatmap(data, container) { if (!data || !data.matrix || !Array.isArray(data.matrix)) { console.error('Ungültige Heatmap-Daten.'); return; } if (!container) { console.error('Container für Heatmap nicht gefunden.'); return; } container.innerHTML = ''; const rows = data.matrix.length; const cols = data.matrix[0].length; const maxVal = Math.max(...data.matrix.flat()); const minVal = Math.min(...data.matrix.flat()); const svgNS = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(svgNS, 'svg'); svg.setAttribute('viewBox', `0 0 ${cols} ${rows}`); svg.setAttribute('preserveAspectRatio', 'none'); svg.setAttribute('role', 'img'); svg.setAttribute('aria-label', 'Resonanzband-Heatmap-Darstellung'); svg.classList.add('heatmap-visualization'); for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { const value = data.matrix[y][x]; const normVal = (value - minVal) / (maxVal - minVal || 1); const color = valueToColor(normVal); const rect = document.createElementNS(svgNS, 'rect'); rect.setAttribute('x', x); rect.setAttribute('y', y); rect.setAttribute('width', 1); rect.setAttribute('height', 1); rect.setAttribute('fill', color); rect.classList.add('heatmap__cell'); rect.dataset.row = y; rect.dataset.col = x; rect.dataset.value = value; svg.appendChild(rect); } } container.appendChild(svg); attachTooltipEvents(svg.querySelectorAll('.heatmap__cell'), data); } /** * Konvertiert Intensitätswert in HEX-Farbwert auf Basis einer Blau-Rot Skala. * @param {number} value - Normalisierter Wert zwischen 0 und 1. * @returns {string} Farbwert in HEX. */ function valueToColor(value) { const r = Math.floor(255 * value); const g = Math.floor(64 * (1 - value)); const b = Math.floor(255 * (1 - value)); return `rgb(${r},${g},${b})`; } /** * Bindet Tooltip-Anzeige bei Hover über Heatmap-Zellen. * @param {NodeListOf} cells - Alle Zellen der Heatmap. * @param {Object} meta - Heatmap-Daten-Metadaten. */ export function attachTooltipEvents(cells, meta) { const tooltip = document.createElement('div'); tooltip.classList.add('heatmap__tooltip'); tooltip.setAttribute('role', 'tooltip'); tooltip.style.position = 'absolute'; tooltip.style.pointerEvents = 'none'; tooltip.style.display = 'none'; tooltip.style.padding = '0.4rem 0.6rem'; tooltip.style.background = 'rgba(0, 0, 0, 0.75)'; tooltip.style.color = '#fff'; tooltip.style.borderRadius = '4px'; tooltip.style.fontSize = '0.8rem'; tooltip.style.zIndex = '1000'; document.body.appendChild(tooltip); cells.forEach(cell => { cell.addEventListener('mouseenter', e => { const { row, col, value } = e.target.dataset; const detail = meta?.metadata?.[row]?.[col] ?? {}; const text = `Row: ${row}, Col: ${col}\nWert: ${value}\nBandzentrum: ${detail.band_center ?? '-'}\nBandbreite: ${detail.band_width ?? '-'}\nCluster Score: ${detail.cluster_score ?? '-'}`; tooltip.textContent = text; tooltip.style.display = 'block'; }); cell.addEventListener('mousemove', e => { tooltip.style.left = `${e.pageX + 10}px`; tooltip.style.top = `${e.pageY + 10}px`; }); cell.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; }); }); }