Add heatmap_visualization/js/heatmap-renderer.js
This commit is contained in:
parent
7adc947593
commit
5be206f2c7
1 changed files with 112 additions and 0 deletions
112
heatmap_visualization/js/heatmap-renderer.js
Normal file
112
heatmap_visualization/js/heatmap-renderer.js
Normal file
|
|
@ -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<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';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue