From 5be206f2c7bf87da5b17cc7fded08580073cb238 Mon Sep 17 00:00:00 2001 From: Mika Date: Wed, 18 Mar 2026 13:17:48 +0000 Subject: [PATCH] Add heatmap_visualization/js/heatmap-renderer.js --- heatmap_visualization/js/heatmap-renderer.js | 112 +++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 heatmap_visualization/js/heatmap-renderer.js diff --git a/heatmap_visualization/js/heatmap-renderer.js b/heatmap_visualization/js/heatmap-renderer.js new file mode 100644 index 0000000..cef0c41 --- /dev/null +++ b/heatmap_visualization/js/heatmap-renderer.js @@ -0,0 +1,112 @@ +'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'; + }); + }); +}