diff --git a/static/css/src/mindmap.css b/static/css/src/mindmap.css new file mode 100644 index 0000000..99bb762 --- /dev/null +++ b/static/css/src/mindmap.css @@ -0,0 +1,165 @@ +/** + * Mindmap Hauptkomponenten Styling + */ + +/* Toast-Container und Benachrichtigungen */ +#toast-container { + position: fixed; + top: 1rem; + right: 1rem; + z-index: 9999; + display: flex; + flex-direction: column; + gap: 0.5rem; + max-width: 350px; +} + +.toast-message { + transition: all 0.3s ease; + overflow: hidden; +} + +.toast-message.translate-x-full { + transform: translateX(100%); +} + +/* Flash-Nachrichten */ +.flash-message { + position: fixed; + top: 1rem; + left: 50%; + transform: translateX(-50%) translateY(-100%); + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + background-color: rgba(30, 41, 59, 0.95); + color: white; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + z-index: 9999; + transition: all 0.3s ease; +} + +.flash-message.visible { + transform: translateX(-50%) translateY(0); +} + +.flash-message.info { + background-color: rgba(59, 130, 246, 0.95); +} + +.flash-message.success { + background-color: rgba(16, 185, 129, 0.95); +} + +.flash-message.warning { + background-color: rgba(245, 158, 11, 0.95); +} + +.flash-message.error { + background-color: rgba(239, 68, 68, 0.95); +} + +/* Zoom-Steuerungen */ +.control-panel { + position: absolute; + right: 2rem; + top: 50%; + transform: translateY(-50%); + background: rgba(15, 23, 42, 0.9); + border-radius: 1rem; + padding: 1.5rem; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + z-index: 10; + border: 1px solid rgba(255, 255, 255, 0.1); + backdrop-filter: blur(8px); +} + +.control-button { + display: flex; + align-items: center; + padding: 0.75rem 1rem; + margin: 0.5rem 0; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 0.5rem; + color: #fff; + cursor: pointer; + transition: all 0.3s ease; +} + +.control-button:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateX(-5px); +} + +.control-button i { + margin-right: 0.75rem; +} + +/* Info-Panel */ +.info-panel { + position: absolute; + left: 2rem; + top: 50%; + transform: translateY(-50%) translateX(-20px); + opacity: 0; + background: rgba(15, 23, 42, 0.9); + border-radius: 1rem; + padding: 1.5rem; + width: 300px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + z-index: 10; + border: 1px solid rgba(255, 255, 255, 0.1); + transition: all 0.3s ease; + backdrop-filter: blur(8px); +} + +.info-panel.visible { + opacity: 1; + transform: translateY(-50%) translateX(0); +} + +.info-title { + font-size: 1.25rem; + font-weight: 600; + color: #fff; + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.info-content { + color: rgba(255, 255, 255, 0.8); + font-size: 0.95rem; + line-height: 1.6; +} + +/* Kategorie-Legende */ +.category-legend { + position: absolute; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + background: rgba(15, 23, 42, 0.9); + border-radius: 1rem; + padding: 1rem 2rem; + display: flex; + gap: 1.5rem; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + z-index: 10; + border: 1px solid rgba(255, 255, 255, 0.1); + backdrop-filter: blur(8px); +} + +.category-item { + display: flex; + align-items: center; + color: rgba(255, 255, 255, 0.8); + font-size: 0.9rem; +} + +.category-color { + width: 12px; + height: 12px; + border-radius: 50%; + margin-right: 0.5rem; +} \ No newline at end of file diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js index ba36af9..4aafd1c 100644 --- a/static/js/update_mindmap.js +++ b/static/js/update_mindmap.js @@ -309,179 +309,138 @@ async function initializeMindmap() { label: node.name, category: node.category, description: node.description, - hasChildren: node.has_children, - expanded: false, - color: node.color_code, - fontColor: '#ffffff', - fontSize: node.is_center ? 20 : 16 + color: getCategoryColor(node.category) } })), // Kanten ...data.edges.map(edge => ({ data: { + id: edge.id, source: edge.source, target: edge.target, - strength: edge.strength || 0.5 + strength: edge.weight || 1.0 } })) ]; - // Bestehende Cytoscape-Instanz entfernen, falls vorhanden - if (window.cy && typeof window.cy.destroy === 'function') { - window.cy.destroy(); - } - + // Cytoscape-Container const cyContainer = document.getElementById('cy'); if (!cyContainer) { - throw new Error('Mindmap-Container #cy nicht gefunden!'); + throw new Error('Cytoscape-Container nicht gefunden'); } - window.cy = cytoscape({ + // Cytoscape initialisieren + const cy = cytoscape({ container: cyContainer, elements: elements, style: [ + // Knoten-Styling { selector: 'node', style: mindmapStyles.node.base }, - { - selector: 'node[isCenter]', - style: mindmapStyles.node.center - }, - { - selector: 'node:selected', - style: mindmapStyles.node.selected - }, + // Kanten-Styling { selector: 'edge', style: mindmapStyles.edge.base + }, + // Ausgewählte Knoten + { + selector: 'node:selected', + style: mindmapStyles.node.selected } ], layout: mindmapStyles.layout.base }); - // Füge neuronale Eigenschaften zu allen Knoten hinzu - cy.nodes().forEach(node => { - const data = node.data(); - // Verwende mindmapConfig für Kategorie-Farben oder einen Standardwert - const categoryColor = data.category && mindmapConfig.categories[data.category] - ? mindmapConfig.categories[data.category].color - : '#60a5fa'; - - node.data({ - ...data, - neuronSize: data.neuronSize || 8, - neuronActivity: data.neuronActivity || 0.8, - refractionPeriod: Math.random() * 300 + 700, - threshold: Math.random() * 0.3 + 0.6, - lastFired: 0, - color: data.color || categoryColor - }); - }); - - // Füge synaptische Eigenschaften zu allen Kanten hinzu - cy.edges().forEach(edge => { - const data = edge.data(); - edge.data({ - ...data, - strength: data.strength || 0.5, - conductionVelocity: Math.random() * 0.5 + 0.3, - latency: Math.random() * 100 + 50 - }); - }); - - // Event-Listener für Knoten-Klicks - cy.on('tap', 'node', async function(evt) { + // Event-Listener für Knoten-Interaktionen + cy.on('tap', 'node', function(evt) { const node = evt.target; - console.log('Node clicked:', node.id(), 'hasChildren:', node.data('hasChildren'), 'expanded:', node.data('expanded')); - - if (node.data('hasChildren') && !node.data('expanded')) { - await loadSubthemes(node); - } + showNodeInfo(node); }); - // Layout ausführen - cy.layout(mindmapStyles.layout.base).run(); - - // Starte neuronale Aktivitätssimulation - startNeuralActivitySimulation(cy); - - // Mindmap mit echten Daten befüllen (Styles, Farben etc.) - updateMindmap(); - - return true; + // UI aktualisieren + document.getElementById('loader').style.display = 'none'; + document.getElementById('statusMessage').style.display = 'none'; + + // Neurales Design anwenden + initializeNeuralDesign(cy); + + // Gebe die Cytoscape-Instanz zurück + return cy; } catch (error) { - console.error('Fehler bei der Mindmap-Initialisierung:', error); - showUINotification({ - error: 'Mindmap konnte nicht initialisiert werden', - details: error.message - }, 'error'); - return false; + console.error('Fehler beim Initialisieren der Mindmap:', error); + document.getElementById('statusMessage').textContent = 'Fehler: ' + error.message; + document.getElementById('statusMessage').style.display = 'block'; + document.getElementById('loader').style.display = 'none'; + + showUINotification('Fehler beim Initialisieren der Mindmap: ' + error.message, 'error'); + throw error; } } -// Warte bis DOM geladen ist -document.addEventListener('DOMContentLoaded', function() { - console.log('DOMContentLoaded Event ausgelöst'); +/** + * Gibt die Farbe für eine Kategorie zurück + * @param {string} category - Die Kategorie + * @returns {string} - Die Farbe als Hex-Code + */ +function getCategoryColor(category) { + const categoryMap = { + 'Philosophie': '#9F7AEA', // Violett + 'Wissenschaft': '#60A5FA', // Blau + 'Technologie': '#10B981', // Grün + 'Künste': '#F59E0B', // Orange + 'Psychologie': '#EF4444' // Rot + }; - // Prüfe, ob der Container existiert - const cyContainer = document.getElementById('cy'); - console.log('Container gefunden:', cyContainer); - - if (!cyContainer) { - console.error('Mindmap-Container #cy nicht gefunden!'); - return; - } - - // Prüfe, ob Cytoscape verfügbar ist - if (typeof cytoscape === 'undefined') { - console.error('Cytoscape ist nicht definiert!'); - return; - } - console.log('Cytoscape ist verfügbar'); + return categoryMap[category] || '#8B5CF6'; // Standardfarbe, wenn Kategorie nicht gefunden +} - // Initialisiere die Mindmap - initializeMindmap() - .then(success => { - if (success) { - console.log('Mindmap wurde erfolgreich initialisiert'); - // Event auslösen, damit andere Scripte reagieren können - document.dispatchEvent(new Event('mindmap-loaded')); - console.log('mindmap-loaded Event ausgelöst'); - } else { - console.error('Mindmap-Initialisierung fehlgeschlagen'); - } - }) - .catch(error => { - console.error('Fehler bei der Mindmap-Initialisierung:', error); - showUINotification({ - error: 'Mindmap konnte nicht initialisiert werden', - details: error.message - }, 'error'); - }); -}); +/** + * Zeigt Informationen zu einem Knoten an + * @param {Object} node - Der Knoten, dessen Informationen angezeigt werden sollen + */ +function showNodeInfo(node) { + if (!node || !node.isNode()) return; + + const infoPanel = document.getElementById('infoPanel'); + const infoTitle = infoPanel.querySelector('.info-title'); + const infoContent = infoPanel.querySelector('.info-content'); + + if (!infoPanel || !infoTitle || !infoContent) return; + + const data = node.data(); + + infoTitle.textContent = data.label || 'Knotendetails'; + + let contentHTML = ` +
Kategorie: ${data.category || 'Nicht kategorisiert'}
+Beschreibung: ${data.description || 'Keine Beschreibung verfügbar'}
+ `; + + infoContent.innerHTML = contentHTML; + infoPanel.classList.add('visible'); +} // Funktion zum Initialisieren des neuronalen Designs function initializeNeuralDesign(cy) { - // Füge neuronale Eigenschaften zu allen Knoten hinzu + if (!cy) return; + + console.log('Initialisiere neurales Design für Mindmap'); + + // Füge neurale Eigenschaften zu allen Knoten hinzu cy.nodes().forEach(node => { const data = node.data(); - // Verwende mindmapConfig für Kategorie-Farben oder einen Standardwert - const categoryColor = data.category && mindmapConfig.categories[data.category] - ? mindmapConfig.categories[data.category].color - : '#60a5fa'; - node.data({ ...data, neuronSize: data.neuronSize || 8, neuronActivity: data.neuronActivity || 0.8, refractionPeriod: Math.random() * 300 + 700, threshold: Math.random() * 0.3 + 0.6, - lastFired: 0, - color: data.color || categoryColor + lastFired: 0 }); }); - + // Füge synaptische Eigenschaften zu allen Kanten hinzu cy.edges().forEach(edge => { const data = edge.data(); @@ -492,66 +451,172 @@ function initializeNeuralDesign(cy) { latency: Math.random() * 100 + 50 }); }); - - // Wende neuronales Styling an - cy.style() - .selector('node') - .style({ - 'background-color': 'data(color)', - 'label': 'data(label)', - 'color': '#fff', - 'text-background-color': 'rgba(0, 0, 0, 0.7)', - 'text-background-opacity': 0.8, - 'text-background-padding': '4px', - 'text-valign': 'center', - 'text-halign': 'center', - 'font-size': 16, - 'width': 40, - 'height': 40, - 'border-width': 2, - 'border-color': '#fff', - 'border-opacity': 0.8, - 'overlay-padding': 4, - 'z-index': 10, - 'shape': 'ellipse', - 'background-opacity': 0.85, - 'shadow-blur': 15, - 'shadow-color': 'data(color)', - 'shadow-opacity': 0.6, - 'shadow-offset-x': 0, - 'shadow-offset-y': 0 - }) - .selector('edge') - .style({ - 'width': function(ele) { - return ele.data('strength') ? ele.data('strength') * 3 : 1; - }, - 'curve-style': 'bezier', - 'line-color': function(ele) { - const sourceColor = ele.source().data('color'); - return sourceColor || '#8a8aaa'; - }, - 'line-opacity': function(ele) { - return ele.data('strength') ? ele.data('strength') * 0.8 : 0.4; - }, - 'line-style': function(ele) { - const strength = ele.data('strength'); - if (!strength) return 'solid'; - if (strength <= 0.4) return 'dotted'; - if (strength <= 0.6) return 'dashed'; - return 'solid'; - }, - 'target-arrow-shape': 'none', - 'source-endpoint': '0% 50%', - 'target-endpoint': '100% 50%', - 'transition-property': 'line-opacity, width', - 'transition-duration': '0.3s', - 'transition-timing-function': 'ease-in-out' - }) - .update(); - - // Starte neuronale Aktivitätssimulation + + // Wende neurales Netzwerk-Styling an + applyNeuralNetworkStyle(cy); + + // Starte Simulation der neuronalen Aktivität startNeuralActivitySimulation(cy); + + return cy; +} + +// Funktionen für das neurale Netzwerk-Design +function applyNeuralNetworkStyle(cy) { + if (!cy) return; + + // Animation für pulsierende Knoten + const pulseKeyframes = [ + { scale: 1.0, opacity: 0.8, borderOpacity: 0.6 }, + { scale: 1.05, opacity: 1.0, borderOpacity: 0.9 }, + { scale: 1.0, opacity: 0.8, borderOpacity: 0.6 } + ]; + + // Anwenden auf alle Knoten + cy.nodes().forEach(node => { + const randomDelay = Math.random() * 3000; + + // CSS-Animationen auf die Knoten anwenden + node.style({ + 'border-width': 3, + 'border-opacity': 0.6, + 'background-opacity': 0.8, + 'transition-property': 'background-opacity, border-opacity, width, height', + 'transition-duration': '300ms' + }); + + // Pulseffekt mit zufälligem Delay starten + setTimeout(() => { + node.animate({ + style: pulseKeyframes, + duration: 2000 + Math.random() * 1000, + complete: function() { + // Animation wiederholen + this.loopAnimation = true; + if (this.loopAnimation) { + setTimeout(() => { + applyNeuralNetworkStyle(cy); + }, 100); + } + } + }); + }, randomDelay); + }); +} + +// Simulation der neuronalen Aktivität +function startNeuralActivitySimulation(cy) { + if (!cy) return; + + console.log('Starte Simulation der neuronalen Aktivität'); + let isSimulationRunning = true; + + // Funktion zur Simulation der neuronalen Aktivität + function simulateNeuralActivity() { + if (!isSimulationRunning) return; + + const currentTime = Date.now(); + + // Wähle zufällige Knoten zum Feuern aus + const randomNode = cy.nodes()[Math.floor(Math.random() * cy.nodes().length)]; + if (randomNode) { + fireNeuron(randomNode, { isPrimary: true }, currentTime); + } + + // Wiederholung der Simulation + setTimeout(simulateNeuralActivity, 2000 + Math.random() * 3000); + } + + // Funktion zum "Feuern" eines Neurons + function fireNeuron(node, state, currentTime) { + if (!node) return; + + const data = node.data(); + const lastFired = data.lastFired || 0; + const refractionPeriod = data.refractionPeriod || 1000; + + // Prüfe, ob das Neuron feuerbereit ist (Refraktärzeit vorbei) + if (currentTime - lastFired < refractionPeriod && !state.isPrimary) { + return; // Neuron ist noch in Refraktärphase + } + + // Aktualisiere "Letztes Feuern"-Zeitstempel + node.data('lastFired', currentTime); + + // Visuellen Effekt des Feuerns anzeigen + node.animate({ + style: { + 'background-opacity': 1, + 'border-opacity': 1, + 'border-color': '#f59e42', + 'border-width': 4 + }, + duration: 300, + complete: function() { + // Zurück zum Normalzustand + node.animate({ + style: { + 'background-opacity': 0.8, + 'border-opacity': 0.6, + 'border-color': '#ffffff', + 'border-width': 3 + }, + duration: 500 + }); + + // Signal an verbundene Knoten weitergeben + propagateSignal(node, currentTime); + } + }); + } + + // Funktion zur Signalausbreitung zu verbundenen Knoten + function propagateSignal(sourceNode, currentTime) { + // Verbundene Kanten finden + const connectedEdges = sourceNode.outgoers('edge'); + + connectedEdges.forEach(edge => { + const targetNode = edge.target(); + const data = edge.data(); + const strength = data.strength || 0.5; + const conductionVelocity = data.conductionVelocity || 0.5; + const latency = data.latency || 100; + + // Kante hervorheben (Signal fließt durch die Kante) + edge.animate({ + style: { + 'line-opacity': 1, + 'width': 3 + }, + duration: 200, + complete: function() { + // Nach einer Verzögerung (Latenz), das Ziel-Neuron feuern lassen + setTimeout(() => { + edge.animate({ + style: { + 'line-opacity': data.strength ? data.strength * 0.6 : 0.4, + 'width': data.strength ? data.strength * 2 : 1 + }, + duration: 300 + }); + + // Ziel-Neuron feuern lassen mit Wahrscheinlichkeit basierend auf Stärke + if (Math.random() < strength) { + fireNeuron(targetNode, { isPrimary: false }, currentTime + latency); + } + }, latency / conductionVelocity); + } + }); + }); + } + + // Starte die Simulation + simulateNeuralActivity(); + + // Funktion zum Stoppen der Simulation + cy.stopNeuralSimulation = function() { + isSimulationRunning = false; + }; } // Modifiziere die updateMindmap Funktion @@ -678,289 +743,122 @@ function enhanceMindmap() { startNeuralActivitySimulation(cy); } -/** - * Wendet detaillierte neuronale Netzwerkstile auf die Mindmap an - * @param {Object} cy - Cytoscape-Instanz - */ -function applyNeuralNetworkStyle(cy) { - cy.style() - .selector('node') - .style({ - 'label': 'data(label)', - 'text-valign': 'center', - 'text-halign': 'center', - 'color': 'data(fontColor)', - 'text-outline-width': 2, - 'text-outline-color': 'rgba(0,0,0,0.8)', - 'text-outline-opacity': 0.9, - 'font-size': 'data(fontSize)', - 'font-weight': '500', - 'text-margin-y': 8, - 'width': function(ele) { - if (ele.data('isCenter')) return 120; - return 80; - }, - 'height': function(ele) { - if (ele.data('isCenter')) return 120; - return 80; - }, - 'background-color': 'data(color)', - 'background-opacity': 0.9, - 'border-width': 2, - 'border-color': '#ffffff', - 'border-opacity': 0.8, - 'shape': 'ellipse', - 'transition-property': 'background-color, background-opacity, border-width', - 'transition-duration': '0.3s', - 'transition-timing-function': 'ease-in-out' - }) - .selector('edge') - .style({ - 'width': function(ele) { - return ele.data('strength') ? ele.data('strength') * 3 : 1; - }, - 'curve-style': 'bezier', - 'line-color': function(ele) { - const sourceColor = ele.source().data('color'); - return sourceColor || '#8a8aaa'; - }, - 'line-opacity': function(ele) { - return ele.data('strength') ? ele.data('strength') * 0.8 : 0.4; - }, - 'line-style': function(ele) { - const strength = ele.data('strength'); - if (!strength) return 'solid'; - if (strength <= 0.4) return 'dotted'; - if (strength <= 0.6) return 'dashed'; - return 'solid'; - }, - 'target-arrow-shape': 'none', - 'source-endpoint': '0% 50%', - 'target-endpoint': '100% 50%', - 'transition-property': 'line-opacity, width', - 'transition-duration': '0.3s', - 'transition-timing-function': 'ease-in-out' - }) - .update(); -} - -// Vereinfachte neuronale Aktivitätssimulation -function startNeuralActivitySimulation(cy) { - if (window.neuralInterval) clearInterval(window.neuralInterval); - - const nodes = cy.nodes(); - let currentTime = Date.now(); - - function simulateNeuralActivity() { - currentTime = Date.now(); - - nodes.forEach(node => { - const data = node.data(); - const lastFired = data.lastFired || 0; - const timeSinceLastFire = currentTime - lastFired; - - if (timeSinceLastFire > data.refractionPeriod) { - if (Math.random() < data.neuronActivity * 0.1) { - fireNeuron(node, true, currentTime); - } - } - }); - } - - function fireNeuron(node, state, currentTime) { - const data = node.data(); - data.lastFired = currentTime; - - node.style({ - 'background-opacity': 1, - 'border-width': 3 - }); - - setTimeout(() => { - node.style({ - 'background-opacity': 0.9, - 'border-width': 2 - }); - }, 200); - - if (state) { - propagateSignal(node, currentTime); - } - } - - function propagateSignal(sourceNode, currentTime) { - const outgoingEdges = sourceNode.connectedEdges(); - - outgoingEdges.forEach(edge => { - const targetNode = edge.target(); - const edgeData = edge.data(); - const latency = edgeData.latency; - - edge.style({ - 'line-opacity': 0.8, - 'width': edgeData.strength * 3 - }); - - setTimeout(() => { - edge.style({ - 'line-opacity': edgeData.strength * 0.6, - 'width': edgeData.strength * 2 - }); - }, 200); - - setTimeout(() => { - const targetData = targetNode.data(); - const timeSinceLastFire = currentTime - (targetData.lastFired || 0); - - if (timeSinceLastFire > targetData.refractionPeriod) { - const signalStrength = edgeData.strength * - edgeData.conductionVelocity * - sourceNode.data('neuronActivity'); - - if (signalStrength > targetData.threshold) { - fireNeuron(targetNode, true, currentTime + latency); - } - } - }, latency); - }); - } - - window.neuralInterval = setInterval(simulateNeuralActivity, 100); -} - // Hilfe-Funktion zum Hinzufügen eines Flash-Hinweises function showFlash(message, type = 'info') { - const flashContainer = createFlashContainer(); const flash = document.createElement('div'); flash.className = `flash-message ${type}`; - flash.textContent = message; - flashContainer.appendChild(flash); - document.body.appendChild(flashContainer); - + flash.innerHTML = ` +