Spaces:
Paused
Paused
Download src/dashboard/static/visualizations/dynamics.js from ThomasHeisig/Brain-5D-Space: direct link, hf CLI and curl.
- Browser
- Download file 9.05 kB
-
https://huggingface.co/spaces/ThomasHeisig/Brain-5D-Space/resolve/main/src/dashboard/static/visualizations/dynamics.js
- Command line
-
hf download hf://spaces/ThomasHeisig/Brain-5D-Space/src/dashboard/static/visualizations/dynamics.js
-
curl -L -o dynamics.js https://huggingface.co/spaces/ThomasHeisig/Brain-5D-Space/resolve/main/src/dashboard/static/visualizations/dynamics.js
9.05 kB
| /** | |
| * Brain-5D Dashboard — Dynamics Visualizations (Raster, Histogram, Layer Explorer) | |
| * | |
| * @version 1.0.0 | |
| * @license MIT | |
| */ | |
| ; | |
| const $ = (id) => document.getElementById(id); | |
| function formatNumber(value) { | |
| if (value === null || value === undefined) return '—'; | |
| return Number(value).toLocaleString(); | |
| } | |
| function formatFloat(value, decimals = 3) { | |
| if (value === null || value === undefined) return '—'; | |
| return Number(value).toFixed(decimals); | |
| } | |
| function setText(id, value) { | |
| const el = $(id); | |
| if (el) el.textContent = String(value); | |
| } | |
| export async function refreshSpikeRaster() { | |
| try { | |
| const r = await fetch('/api/live/raster', { cache: 'no-store' }); | |
| if (!r.ok) throw new Error(`HTTP ${r.status}`); | |
| const data = await r.json(); | |
| drawSpikeRaster(data); | |
| const badge = $('raster-badge'); | |
| if (badge) { | |
| badge.textContent = `${data.sample_count}/${data.total_neurons} Neuronen`; | |
| badge.className = 'gate-badge passed'; | |
| } | |
| const meta = $('raster-meta'); | |
| if (meta) meta.textContent = `Tick ${data.tick} · Fenster: ${data.window_ticks} Ticks · ${data.sample_count} aktive Neuronen von ${data.total_neurons}`; | |
| } catch { | |
| const badge = $('raster-badge'); | |
| if (badge) { badge.textContent = 'offline'; badge.className = 'gate-badge failed'; } | |
| } | |
| } | |
| function drawSpikeRaster(data) { | |
| const canvas = $('spike-raster'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| const nids = data.neuron_ids || []; | |
| const ticks = data.spike_ticks || []; | |
| if (!nids.length || !ticks.length) { | |
| ctx.fillStyle = '#4a6a8a'; | |
| ctx.font = '14px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Keine Spike-Daten im aktuellen Fenster', canvas.width/2, canvas.height/2); | |
| return; | |
| } | |
| const windowTicks = data.window_ticks || 100; | |
| const tick = data.tick || 0; | |
| const tickMin = tick - windowTicks; | |
| const tickMax = tick; | |
| const neuronCount = nids.length; | |
| const idToRow = {}; | |
| nids.forEach((id, i) => { idToRow[id] = i; }); | |
| const margin = { top: 20, right: 20, bottom: 30, left: 50 }; | |
| const plotW = canvas.width - margin.left - margin.right; | |
| const plotH = canvas.height - margin.top - margin.bottom; | |
| ctx.fillStyle = '#050d14'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.strokeStyle = 'rgba(142, 178, 193, 0.06)'; | |
| ctx.lineWidth = 0.5; | |
| for (let i = 0; i < 10; i++) { | |
| const y = margin.top + (plotH / 10) * i; | |
| ctx.beginPath(); | |
| ctx.moveTo(margin.left, y); | |
| ctx.lineTo(margin.left + plotW, y); | |
| ctx.stroke(); | |
| } | |
| ctx.fillStyle = 'rgba(64, 224, 208, 0.6)'; | |
| for (let i = 0; i < ticks.length; i++) { | |
| const t = ticks[i]; | |
| const nid = nids[i % nids.length]; | |
| const row = idToRow[nid]; | |
| if (row === undefined) continue; | |
| const x = margin.left + ((t - tickMin) / Math.max(1, tickMax - tickMin)) * plotW; | |
| const y = margin.top + (row / Math.max(1, neuronCount)) * plotH; | |
| ctx.fillRect(x - 0.5, y - 1, 2, 2); | |
| } | |
| ctx.fillStyle = 'rgba(142, 178, 193, 0.5)'; | |
| ctx.font = '11px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Tick →', margin.left + plotW/2, canvas.height - 5); | |
| ctx.textAlign = 'right'; | |
| ctx.fillText('Neuron ↑', margin.left - 5, margin.top + 12); | |
| ctx.textAlign = 'left'; | |
| ctx.fillStyle = 'rgba(142, 178, 193, 0.3)'; | |
| ctx.font = '9px monospace'; | |
| ctx.fillText(tickMin, margin.left, canvas.height - 8); | |
| ctx.textAlign = 'right'; | |
| ctx.fillText(tickMax, margin.left + plotW, canvas.height - 8); | |
| } | |
| export async function refreshRateHistogram() { | |
| try { | |
| const r = await fetch('/api/live/histogram?bins=30', { cache: 'no-store' }); | |
| if (!r.ok) throw new Error(`HTTP ${r.status}`); | |
| const data = await r.json(); | |
| drawRateHistogram(data); | |
| const stats = $('histogram-stats'); | |
| if (stats) { | |
| stats.textContent = `μ=${data.mean_rate.toFixed(4)} σ=${data.std_rate.toFixed(4)}`; | |
| stats.className = 'gate-badge passed'; | |
| } | |
| const meta = $('histogram-meta'); | |
| if (meta) meta.textContent = `Tick ${data.tick} · ${data.active_count} aktiv / ${data.silent_count} stumm · Median ${data.median_rate.toFixed(4)}`; | |
| } catch { | |
| const stats = $('histogram-stats'); | |
| if (stats) { stats.textContent = 'offline'; stats.className = 'gate-badge failed'; } | |
| } | |
| } | |
| function drawRateHistogram(data) { | |
| const canvas = $('rate-histogram'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| const bins = data.bins || []; | |
| const counts = data.counts || []; | |
| if (!counts.length) { | |
| ctx.fillStyle = '#4a6a8a'; | |
| ctx.font = '14px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Keine Histogramm-Daten', canvas.width/2, canvas.height/2); | |
| return; | |
| } | |
| const margin = { top: 20, right: 20, bottom: 35, left: 60 }; | |
| const plotW = canvas.width - margin.left - margin.right; | |
| const plotH = canvas.height - margin.top - margin.bottom; | |
| ctx.fillStyle = '#050d14'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| const maxCount = Math.max(...counts, 1); | |
| const barW = plotW / counts.length; | |
| for (let i = 0; i < counts.length; i++) { | |
| const x = margin.left + i * barW; | |
| const h = (counts[i] / maxCount) * plotH; | |
| const y = margin.top + plotH - h; | |
| const normalized = counts[i] / maxCount; | |
| const hue = 210 - normalized * 195; | |
| ctx.fillStyle = `hsl(${hue}, 85%, ${30 + normalized * 40}%)`; | |
| ctx.fillRect(x + 1, y, barW - 2, h); | |
| } | |
| if (data.mean_rate > 0 && bins.length > 1) { | |
| const maxRate = bins[bins.length - 1]; | |
| const meanX = margin.left + (data.mean_rate / maxRate) * plotW; | |
| ctx.strokeStyle = 'rgba(255, 159, 28, 0.7)'; | |
| ctx.lineWidth = 1.5; | |
| ctx.setLineDash([4, 4]); | |
| ctx.beginPath(); | |
| ctx.moveTo(meanX, margin.top); | |
| ctx.lineTo(meanX, margin.top + plotH); | |
| ctx.stroke(); | |
| ctx.setLineDash([]); | |
| ctx.fillStyle = 'rgba(255, 159, 28, 0.7)'; | |
| ctx.font = '10px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('μ=' + data.mean_rate.toFixed(3), meanX, margin.top - 4); | |
| } | |
| ctx.fillStyle = 'rgba(142, 178, 193, 0.5)'; | |
| ctx.font = '11px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Feuerrate (spikes/tick) →', margin.left + plotW/2, canvas.height - 5); | |
| ctx.textAlign = 'right'; | |
| ctx.fillText('Anzahl ↑', margin.left - 5, margin.top + 12); | |
| } | |
| export async function refreshLayerExplorer() { | |
| const dimSelect = $('layer-dim'); | |
| const slider = $('layer-slider'); | |
| const kindSelect = $('layer-kind'); | |
| if (!dimSelect || !slider) return; | |
| const dim = dimSelect.value; | |
| const layerVal = parseInt(slider.value, 10); | |
| const kind = kindSelect ? kindSelect.value : 'activity'; | |
| try { | |
| const r = await fetch(`/api/live/projection?kind=${encodeURIComponent(kind)}&resolution=40`, { cache: 'no-store' }); | |
| if (!r.ok) throw new Error(`HTTP ${r.status}`); | |
| const data = await r.json(); | |
| drawLayerSlice(data, dim, layerVal); | |
| const badge = $('layer-badge'); | |
| if (badge) badge.textContent = dim.toUpperCase(); | |
| const meta = $('layer-meta'); | |
| if (meta) meta.textContent = `Tick ${data.tick} · ${dim.toUpperCase()}=${layerVal} · ${data.kind || kind} · ${data.source}`; | |
| } catch { | |
| const meta = $('layer-meta'); | |
| if (meta) meta.textContent = '⚠️ Layer-Daten nicht verfügbar'; | |
| } | |
| } | |
| function drawLayerSlice(payload, dim, layerValue) { | |
| const canvas = $('layer-canvas'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| const values = payload.values || []; | |
| if (!values.length) return; | |
| const rows = values.length; | |
| const cols = values[0]?.length || 1; | |
| const flat = values.flat().filter(v => v !== null && v !== undefined && Number.isFinite(v)); | |
| if (!flat.length) { | |
| ctx.fillStyle = '#4a6a8a'; | |
| ctx.font = '14px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Keine Daten', canvas.width/2, canvas.height/2); | |
| return; | |
| } | |
| const min = Math.min(...flat); | |
| const max = Math.max(...flat); | |
| const range = max - min || 1; | |
| const cellW = canvas.width / cols; | |
| const cellH = canvas.height / rows; | |
| for (let y = 0; y < rows; y++) { | |
| for (let x = 0; x < cols; x++) { | |
| const val = values[y]?.[x]; | |
| if (val === null || val === undefined) { | |
| ctx.fillStyle = 'rgba(40, 50, 65, 0.4)'; | |
| ctx.fillRect(x * cellW, y * cellH, Math.ceil(cellW), Math.ceil(cellH)); | |
| } else { | |
| const normalized = (val - min) / range; | |
| const hue = 210 - normalized * 195; | |
| const light = 16 + normalized * 50; | |
| ctx.fillStyle = `hsl(${hue}, 88%, ${light}%)`; | |
| ctx.fillRect(x * cellW, y * cellH, Math.ceil(cellW), Math.ceil(cellH)); | |
| } | |
| } | |
| } | |
| ctx.fillStyle = 'rgba(142, 178, 193, 0.4)'; | |
| ctx.font = '10px monospace'; | |
| ctx.textAlign = 'left'; | |
| ctx.fillText(`${dim.toUpperCase()}=${layerValue}`, 8, 16); | |
| ctx.textAlign = 'right'; | |
| ctx.fillText(`${min.toFixed(2)} … ${max.toFixed(2)}`, canvas.width - 8, 16); | |
| } | |