resonanzband_untersuchung/heatmap_visualization/js/heatmap-renderer.js

112 lines
3.7 KiB
JavaScript

'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<SVGRectElement>} 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';
});
});
}