/** * Mindmap Interaction Enhancement * Verbessert die Interaktion mit der Mindmap und steuert die Seitenleisten-Anzeige */ // Stellt sicher, dass das Dokument geladen ist, bevor Aktionen ausgeführt werden document.addEventListener('DOMContentLoaded', function() { console.log('Mindmap-Interaktionsverbesserungen werden initialisiert...'); // Auf das Laden der Mindmap warten document.addEventListener('mindmap-loaded', setupInteractionEnhancements); // Sofortiges Setup für statische Interaktionen setupStaticInteractions(); // Direkten Event-Listener für Knotenauswahl einrichten setupNodeSelectionListener(); // Neuronales Netzwerk-Hintergrund-Effekt aktivieren setupNeuralNetworkEffect(); }); // Erzeugt subtile Hintergrundeffekte für neuronales Netzwerk function setupNeuralNetworkEffect() { const cyContainer = document.getElementById('cy'); if (!cyContainer) return; // Dendrite Animation CSS const dendriteStyle = document.createElement('style'); dendriteStyle.textContent = ` .neuron-pulse { position: absolute; border-radius: 50%; background: radial-gradient(circle, rgba(139, 92, 246, 0.1) 0%, transparent 70%); pointer-events: none; z-index: 0; opacity: 0; animation: neuronPulse 6s ease-in-out infinite; transform: translate(-50%, -50%); } @keyframes neuronPulse { 0%, 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.7); } 50% { opacity: 0.8; transform: translate(-50%, -50%) scale(1.2); } } .synapse-line { position: absolute; height: 1px; background: linear-gradient(90deg, transparent, rgba(139, 92, 246, 0.3), transparent); pointer-events: none; z-index: 0; opacity: 0; transform-origin: 0% 50%; animation: synapseFlow 8s ease-in-out infinite; } @keyframes synapseFlow { 0%, 100% { opacity: 0; } 50% { opacity: 0.5; } } `; document.head.appendChild(dendriteStyle); // Zufällige Pulsierende Dendrite-Effekte setInterval(() => { if (Math.random() > 0.7) { const pulse = document.createElement('div'); pulse.className = 'neuron-pulse'; // Zufällige Größe und Position const size = Math.random() * 200 + 100; pulse.style.width = `${size}px`; pulse.style.height = `${size}px`; pulse.style.left = `${Math.random() * 100}%`; pulse.style.top = `${Math.random() * 100}%`; // Animation-Eigenschaften variieren pulse.style.animationDuration = `${Math.random() * 4 + 3}s`; pulse.style.animationDelay = `${Math.random() * 2}s`; cyContainer.appendChild(pulse); // Element nach Animation entfernen setTimeout(() => pulse.remove(), 7000); } // Zufällige Synapse-Linien-Effekte if (Math.random() > 0.8) { const synapse = document.createElement('div'); synapse.className = 'synapse-line'; // Zufällige Position und Größe const startX = Math.random() * 100; const startY = Math.random() * 100; const length = Math.random() * 200 + 50; const angle = Math.random() * 360; synapse.style.width = `${length}px`; synapse.style.left = `${startX}%`; synapse.style.top = `${startY}%`; synapse.style.transform = `rotate(${angle}deg)`; // Animation-Eigenschaften synapse.style.animationDuration = `${Math.random() * 3 + 5}s`; synapse.style.animationDelay = `${Math.random() * 2}s`; cyContainer.appendChild(synapse); // Element nach Animation entfernen setTimeout(() => synapse.remove(), 9000); } }, 800); } // Richtet grundlegende statische Interaktionen ein function setupStaticInteractions() { // Initialisiert die Hover-Effekte für die Seitenleisten-Panels initializePanelEffects(); // Prevent default Zoom bei CTRL + Mausrad document.addEventListener('wheel', function(e) { if (e.ctrlKey) { e.preventDefault(); } }, { passive: false }); // Initialisiert verbesserten Zoom-Handler direkt nach dem Laden initializeZoomHandler(); } // Richtet erweiterte Interaktionen mit der geladenen Mindmap ein function setupInteractionEnhancements() { console.log('Mindmap geladen - verbesserte Interaktionen werden eingerichtet'); // Cytoscape-Instanz const cy = window.cy; if (!cy) { console.warn('Cytoscape-Instanz nicht gefunden!'); return; } // Hover-Effekte für Knoten cy.on('mouseover', 'node', function(evt) { const node = evt.target; // Nur anwenden, wenn der Knoten nicht ausgewählt ist if (!node.selected()) { node.style({ 'shadow-opacity': 0.8, 'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 10, 20)', 'background-opacity': 1 }); } // Verbundene Kanten hervorheben node.connectedEdges().style({ 'line-opacity': 0.7, 'width': 'mapData(strength, 0.2, 0.8, 1.5, 2.5)' }); }); cy.on('mouseout', 'node', function(evt) { const node = evt.target; // Nur zurücksetzen, wenn nicht ausgewählt if (!node.selected()) { node.removeStyle(); } // Verbundene Kanten zurücksetzen, wenn nicht mit ausgewähltem Knoten verbunden node.connectedEdges().forEach(edge => { const sourceSelected = edge.source().selected(); const targetSelected = edge.target().selected(); if (!sourceSelected && !targetSelected) { edge.removeStyle(); } }); }); // Verhindere, dass der Browser die Seite scrollt, wenn über der Mindmap gezoomt wird preventScrollWhileZooming(); // Tastaturkürzel für Mindmap-Interaktionen setupKeyboardShortcuts(cy); } // Initialisiert speziellen Zoom-Handler für sanften Zoom function initializeZoomHandler() { // Auf das Laden der Mindmap warten document.addEventListener('mindmap-loaded', function() { if (!window.cy) return; const cy = window.cy; // Laufenden AnimationsFrame-Request speichern let zoomAnimationFrame = null; let targetZoom = cy.zoom(); let currentZoom = targetZoom; let zoomCenter = { x: 0, y: 0 }; let zoomTime = 0; // Aktuellen Zoom überwachen und sanft anpassen function updateZoom() { // Sanfter Übergang zum Ziel-Zoom-Level zoomTime += 0.08; // Easing-Funktion für flüssigere Bewegung const easedProgress = 1 - Math.pow(1 - Math.min(zoomTime, 1), 3); if (currentZoom !== targetZoom) { currentZoom += (targetZoom - currentZoom) * easedProgress; // Zoom mit Position anwenden cy.zoom({ level: currentZoom, position: zoomCenter }); // Loop fortsetzen, bis wir sehr nahe am Ziel sind if (Math.abs(currentZoom - targetZoom) > 0.001 && zoomTime < 1) { zoomAnimationFrame = requestAnimationFrame(updateZoom); } else { // Endgültigen Zoom setzen, um sicherzustellen, dass wir genau das Ziel erreichen cy.zoom({ level: targetZoom, position: zoomCenter }); zoomAnimationFrame = null; } } else { zoomAnimationFrame = null; } } // Überschreibe den Standard-mousewheel-Handler von Cytoscape cy.removeAllListeners('mousewheel'); cy.on('mousewheel', function(e) { e.preventDefault(); const delta = e.originalEvent.deltaY; const mousePosition = e.position || e.cyPosition; // Glätten und Limitieren des Zoom-Faktors const factor = delta > 0 ? 0.97 : 1.03; // Neues Zoom-Level berechnen mit Begrenzung const maxZoom = cy.maxZoom() || 3; const minZoom = cy.minZoom() || 0.2; targetZoom = Math.min(maxZoom, Math.max(minZoom, cy.zoom() * factor)); // Position für Zoom setzen zoomCenter = mousePosition; // Zeit zurücksetzen zoomTime = 0; // Laufende Animation abbrechen und neue starten if (zoomAnimationFrame) { cancelAnimationFrame(zoomAnimationFrame); } zoomAnimationFrame = requestAnimationFrame(updateZoom); }); // Panning auch flüssiger gestalten cy.on('pan', function() { cy.style().selector('node').style({ 'transition-property': 'none', }).update(); }); cy.on('panend', function() { cy.style().selector('node').style({ 'transition-property': 'background-color, shadow-color, shadow-opacity, shadow-blur', 'transition-duration': '0.3s' }).update(); }); }); } // Verhindert Browser-Scrolling während Zoom in der Mindmap function preventScrollWhileZooming() { const cyContainer = document.getElementById('cy'); if (cyContainer) { cyContainer.addEventListener('wheel', function(e) { // Verhindern des Standard-Scrollens während des Zooms e.preventDefault(); }, { passive: false }); } } // Initialisiert Effekte für Seitenleisten-Panels function initializePanelEffects() { // Selektiert alle Panel-Elemente const panels = document.querySelectorAll('.sidebar-panel'); panels.forEach(panel => { // Hover-Effekt für Panels panel.addEventListener('mouseenter', function() { this.style.transform = 'translateY(-5px)'; this.style.boxShadow = '0 10px 25px rgba(0, 0, 0, 0.3), 0 0 15px rgba(139, 92, 246, 0.3)'; }); panel.addEventListener('mouseleave', function() { this.style.transform = ''; this.style.boxShadow = ''; }); }); } // Richtet Tastaturkürzel für Mindmap-Interaktionen ein function setupKeyboardShortcuts(cy) { document.addEventListener('keydown', function(e) { // Nur fortfahren, wenn keine Texteingabe im Fokus ist if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.isContentEditable) { return; } // Tastaturkürzel switch(e.key) { case '+': case '=': // Einzoomen (sanfter) if (e.ctrlKey || e.metaKey) { e.preventDefault(); smoothZoom(cy, 1.15, 400); } break; case '-': // Auszoomen (sanfter) if (e.ctrlKey || e.metaKey) { e.preventDefault(); smoothZoom(cy, 0.85, 400); } break; case '0': // Zoom auf Gesamtansicht if (e.ctrlKey || e.metaKey) { e.preventDefault(); smoothFit(cy); } break; case 'Escape': // Ausgewählten Knoten abwählen cy.nodes().unselect(); resetNodeSelection(); break; } }); } // Sanften Zoom mit Animation anwenden function smoothZoom(cy, factor, duration = 400) { const currentZoom = cy.zoom(); const targetZoom = currentZoom * factor; // Mittelpunkt der Ansicht verwenden const center = { x: cy.width() / 2, y: cy.height() / 2 }; // Sanftes Zoomen mit Animation cy.animation({ zoom: { level: targetZoom, position: center }, duration: duration, easing: 'cubic-bezier(0.19, 1, 0.22, 1)' }).play(); } // Sanftes Anpassen der Ansicht mit Animation function smoothFit(cy, padding = 50) { cy.animation({ fit: { eles: cy.elements(), padding: padding }, duration: 600, easing: 'cubic-bezier(0.19, 1, 0.22, 1)' }).play(); } // Richtet den Event-Listener für die Knotenauswahl ein function setupNodeSelectionListener() { document.addEventListener('mindmap-loaded', function() { if (!window.cy) return; window.cy.on('tap', 'node', function(evt) { handleNodeSelection(evt.target); }); window.cy.on('tap', function(evt) { if (evt.target === window.cy) { resetNodeSelection(); } }); }); } // Verarbeitet die Knotenauswahl function handleNodeSelection(node) { if (!node) return; // Stelle sicher, dass nur dieser Knoten ausgewählt ist window.cy.nodes().unselect(); node.select(); // Speichere ausgewählten Knoten global window.mindmapInstance.selectedNode = node; // Zeige Informationen in der Sidebar an showNodeDescriptionSidebar(node); // Informationspanel aktualisieren updateNodeInfoPanel(node); // Node zentrieren mit Animation window.cy.animation({ center: { eles: node }, zoom: 1.3, duration: 800, easing: 'cubic-bezier(0.19, 1, 0.22, 1)' }).play(); // Hervorhebung für den ausgewählten Knoten mit Leuchteffekt node.style({ 'background-opacity': 1, 'shadow-opacity': 1, 'shadow-blur': 20, 'shadow-color': node.data('color') }); // Verbindungen hervorheben node.connectedEdges().style({ 'line-color': '#a78bfa', 'line-opacity': 0.7, 'width': 2, 'line-style': 'solid' }); } // Setzt die Knotenauswahl zurück function resetNodeSelection() { const nodeInfoPanel = document.getElementById('node-info-panel'); if (nodeInfoPanel) { nodeInfoPanel.classList.remove('visible'); } showDefaultSidebar(); // Globale Referenz zurücksetzen if (window.mindmapInstance) { window.mindmapInstance.selectedNode = null; } } // Zeigt das Informationspanel für einen Knoten an function updateNodeInfoPanel(node) { const nodeInfoPanel = document.getElementById('node-info-panel'); const nodeDescription = document.getElementById('node-description'); const connectedNodes = document.getElementById('connected-nodes'); const panelTitle = nodeInfoPanel ? nodeInfoPanel.querySelector('.info-panel-title') : null; if (!nodeInfoPanel || !nodeDescription || !connectedNodes || !panelTitle) return; // Titel und Beschreibung aktualisieren panelTitle.textContent = node.data('name'); nodeDescription.textContent = node.data('description') || 'Keine Beschreibung verfügbar.'; // Verbundene Knoten auflisten connectedNodes.innerHTML = ''; // Verbundene Knoten ermitteln const connectedNodesList = []; node.connectedEdges().forEach(edge => { let connectedNode; if (edge.source().id() === node.id()) { connectedNode = edge.target(); } else { connectedNode = edge.source(); } if (!connectedNodesList.some(n => n.id() === connectedNode.id())) { connectedNodesList.push(connectedNode); } }); // Verbundene Knoten im Panel anzeigen if (connectedNodesList.length > 0) { connectedNodesList.forEach(connectedNode => { const nodeLink = document.createElement('span'); nodeLink.className = 'inline-block px-2 py-1 text-xs rounded-md m-1 cursor-pointer opacity-80 hover:opacity-100 transition-opacity'; nodeLink.style.backgroundColor = connectedNode.data('color'); nodeLink.textContent = connectedNode.data('name'); // Beim Klick auf den verbundenen Knoten zu diesem navigieren nodeLink.addEventListener('click', function() { handleNodeSelection(connectedNode); }); connectedNodes.appendChild(nodeLink); }); } else { connectedNodes.innerHTML = 'Keine verbundenen Knoten'; } // Panel anzeigen mit Animation nodeInfoPanel.classList.add('visible'); } // Zeigt die Standard-Seitenleiste an function showDefaultSidebar() { // Alle Seitenleisten-Panels anzeigen/ausblenden const allPanels = document.querySelectorAll('[data-sidebar]'); allPanels.forEach(panel => { if (panel.getAttribute('data-sidebar') === 'node-description') { panel.classList.add('hidden'); } else { panel.classList.remove('hidden'); } }); } // Zeigt die Knotenbeschreibung in der Seitenleiste an function showNodeDescriptionSidebar(node) { // Das Knotenbeschreibungs-Panel finden const nodeDescPanel = document.querySelector('[data-sidebar="node-description"]'); if (nodeDescPanel) { // Panel sichtbar machen nodeDescPanel.classList.remove('hidden'); // Titel und Beschreibung aktualisieren const nodeTitleElement = nodeDescPanel.querySelector('[data-node-title]'); const nodeDescElement = nodeDescPanel.querySelector('[data-node-description]'); if (nodeTitleElement) { nodeTitleElement.textContent = node.data('name'); } if (nodeDescElement) { // Beschreibung mit HTML-Formatierung anzeigen nodeDescElement.innerHTML = generateNodeDescription(node); } } } // Generiert eine formatierte HTML-Beschreibung für einen Knoten function generateNodeDescription(node) { let description = node.data('description') || 'Keine Beschreibung verfügbar.'; // Einfache HTML-Formatierung (kann erweitert werden) description = description .replace(/\n/g, '
') .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/`(.*?)`/g, '$1'); return description; }