From 903e095b669d85873c32829a8e6de7c5277edfad Mon Sep 17 00:00:00 2001 From: marwin Date: Tue, 6 May 2025 21:23:29 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20enhance=20mindmap=20functio?= =?UTF-8?q?nality=20and=20improve=20user=20interaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/js/mindmap-init.js | 1377 +++++++++--------------------- static/js/mindmap-interaction.js | 724 ++++++++++------ static/js/update_mindmap.js | 294 ++++++- templates/mindmap.html | 622 ++++---------- 4 files changed, 1297 insertions(+), 1720 deletions(-) diff --git a/static/js/mindmap-init.js b/static/js/mindmap-init.js index 4dcccdc..0d09df8 100644 --- a/static/js/mindmap-init.js +++ b/static/js/mindmap-init.js @@ -40,42 +40,51 @@ function loadScript(src, callback) { */ function initMindmap() { const cyContainer = document.getElementById('cy'); - const fitBtn = document.getElementById('fit-btn'); - const resetBtn = document.getElementById('reset-btn'); - const toggleLabelsBtn = document.getElementById('toggle-labels-btn'); - const nodeInfoPanel = document.getElementById('node-info-panel'); - const nodeDescription = document.getElementById('node-description'); - const connectedNodes = document.getElementById('connected-nodes'); - - let labelsVisible = true; - let selectedNode = null; // Erstelle Cytoscape-Instanz const cy = cytoscape({ container: cyContainer, - style: getDefaultStyles(), + style: getNeuralNetworkStyles(), layout: { name: 'cose', animate: true, - animationDuration: 800, + animationDuration: 1500, nodeDimensionsIncludeLabels: true, - padding: 50, - spacingFactor: 1.2, + padding: 100, + spacingFactor: 1.5, randomize: true, componentSpacing: 100, nodeRepulsion: 8000, edgeElasticity: 100, nestingFactor: 1.2, - gravity: 80 + gravity: 80, + idealEdgeLength: 150 }, - wheelSensitivity: 0.3, + wheelSensitivity: 0.1, // Sanfterer Zoom + minZoom: 0.3, + maxZoom: 2.5, }); // Daten vom Server laden loadMindmapData(cy); // Event-Handler zuweisen - setupEventListeners(cy, fitBtn, resetBtn, toggleLabelsBtn, nodeInfoPanel, nodeDescription, connectedNodes); + setupEventListeners(cy); + + // Globale Referenz für Externe Zugriffe + window.cy = cy; + window.mindmapInstance = { + cy: cy, + selectedNode: null, + centerNodeInView: function(node) { + cy.animate({ + center: { eles: node }, + zoom: 1.2, + duration: 800, + easing: 'ease-in-out-cubic' + }); + } + }; } /** @@ -123,8 +132,12 @@ function loadMindmapData(cy) { data.nodes.unshift(rootNode); } - // Knoten hinzufügen + // Knoten hinzufügen mit zufälligen Werten für neuronales Netzwerk data.nodes.forEach(node => { + // Neuronenzell-Größe und Aktivität + const neuronSize = Math.floor(Math.random() * 8) + 3; + const neuronActivity = Math.random() * 0.7 + 0.3; // Aktivitätslevel zwischen 0.3 und 1.0 + elements.push({ group: 'nodes', data: { @@ -132,7 +145,9 @@ function loadMindmapData(cy) { name: node.name, description: node.description || '', color: node.color_code || '#8B5CF6', - isRoot: node.name === 'Wissen' + isRoot: node.name === 'Wissen', + neuronSize: neuronSize, + neuronActivity: neuronActivity } }); }); @@ -145,7 +160,8 @@ function loadMindmapData(cy) { data: { id: `${edge.source}-${edge.target}`, source: edge.source.toString(), - target: edge.target.toString() + target: edge.target.toString(), + strength: Math.random() * 0.6 + 0.2 // Zufällige Verbindungsstärke zwischen 0.2 und 0.8 } }); }); @@ -160,7 +176,8 @@ function loadMindmapData(cy) { data: { id: `${rootId}-${node.id}`, source: rootId, - target: node.id.toString() + target: node.id.toString(), + strength: Math.random() * 0.6 + 0.2 } }); } @@ -175,14 +192,22 @@ function loadMindmapData(cy) { cy.layout({ name: 'cose', animate: true, - animationDuration: 800, + animationDuration: 1800, nodeDimensionsIncludeLabels: true, - padding: 50, - spacingFactor: 1.5, + padding: 100, + spacingFactor: 1.8, randomize: false, - fit: true + fit: true, + componentSpacing: 100, + nodeRepulsion: 8000, + edgeElasticity: 100 }).run(); + // Neuronale Netzwerk-Effekte hinzufügen + setTimeout(() => { + addNeuralNetworkEffects(cy); + }, 2000); + // Nach dem Laden Event auslösen document.dispatchEvent(new CustomEvent('mindmap-loaded')); }) @@ -208,8 +233,11 @@ function loadMindmapData(cy) { const fallbackElements = []; - // Knoten hinzufügen + // Knoten hinzufügen mit Neuronen-Eigenschaften fallbackData.nodes.forEach(node => { + const neuronSize = Math.floor(Math.random() * 8) + 3; + const neuronActivity = Math.random() * 0.7 + 0.3; + fallbackElements.push({ group: 'nodes', data: { @@ -217,7 +245,9 @@ function loadMindmapData(cy) { name: node.name, description: node.description || '', color: node.color_code || '#8B5CF6', - isRoot: node.name === 'Wissen' + isRoot: node.name === 'Wissen', + neuronSize: neuronSize, + neuronActivity: neuronActivity } }); }); @@ -229,7 +259,8 @@ function loadMindmapData(cy) { data: { id: `${edge.source}-${edge.target}`, source: edge.source.toString(), - target: edge.target.toString() + target: edge.target.toString(), + strength: Math.random() * 0.6 + 0.2 } }); }); @@ -239,1011 +270,379 @@ function loadMindmapData(cy) { cy.add(fallbackElements); // Layout anwenden - cy.layout({ name: 'cose', animate: true }).run(); + cy.layout({ + name: 'cose', + animate: true, + animationDuration: 1800, + nodeDimensionsIncludeLabels: true, + fit: true + }).run(); + + // Neuronale Netzwerk-Effekte hinzufügen + setTimeout(() => { + addNeuralNetworkEffects(cy); + }, 2000); + + // Nach dem Laden Event auslösen + document.dispatchEvent(new CustomEvent('mindmap-loaded')); }); } +// Neuronales Netzwerk-Effekte hinzufügen +function addNeuralNetworkEffects(cy) { + cy.nodes().forEach(node => { + const originalPos = node.position(); + const activity = node.data('neuronActivity') || 0.5; + + // Subtile Pulsierende Bewegung für Neuronen + setInterval(() => { + const randomFactor = Math.random() * 0.5 + 0.5; // Zufälliger Faktor zwischen 0.5 und 1 + const offset = (Math.random() - 0.5) * activity; + const pulseIntensity = 0.7 + (Math.sin(Date.now() / 2000) * 0.3 * activity * randomFactor); + + // Leichtes Pulsieren und Bewegung basierend auf "Neuronenaktivität" + node.animate({ + position: { + x: originalPos.x + offset, + y: originalPos.y + offset + }, + style: { + 'background-opacity': Math.max(0.7, pulseIntensity), + 'shadow-opacity': Math.max(0.5, pulseIntensity * 0.8) + }, + duration: 3000 + (Math.random() * 2000), + easing: 'ease-in-out-cubic', + complete: function() { + node.animate({ + position: { x: originalPos.x, y: originalPos.y }, + style: { + 'background-opacity': 0.9, + 'shadow-opacity': 0.6 + }, + duration: 3000 + (Math.random() * 2000), + easing: 'ease-in-out-cubic' + }); + } + }); + }, 6000 + Math.random() * 4000); // Leicht zufällige Intervalle für organischeres Aussehen + + // Zusätzliche "synaptische" Effekte für Kanten + node.connectedEdges().forEach(edge => { + const strength = edge.data('strength') || 0.5; + + // Pulsierende Aktivität entlang der Kanten + setInterval(() => { + const pulseIntensity = 0.5 + (Math.sin(Date.now() / 1500) * 0.5 * strength); + + edge.animate({ + style: { + 'line-opacity': Math.max(0.3, pulseIntensity), + 'width': 1 + (pulseIntensity * 0.6) + }, + duration: 1200, + easing: 'ease-in-out' + }); + }, 3000 + (Math.random() * 2000)); + }); + }); +} + /** * Richtet Event-Listener für die Mindmap ein * @param {Object} cy - Cytoscape-Instanz - * @param {HTMLElement} fitBtn - Fit-Button - * @param {HTMLElement} resetBtn - Reset-Button - * @param {HTMLElement} toggleLabelsBtn - Toggle-Labels-Button - * @param {HTMLElement} nodeInfoPanel - Node-Info-Panel - * @param {HTMLElement} nodeDescription - Node-Description - * @param {HTMLElement} connectedNodes - Connected-Nodes-Container */ -function setupEventListeners(cy, fitBtn, resetBtn, toggleLabelsBtn, nodeInfoPanel, nodeDescription, connectedNodes) { - let labelsVisible = true; - - // Fit-Button - if (fitBtn) { - fitBtn.addEventListener('click', function() { - cy.fit(); - }); - } - - // Reset-Button - if (resetBtn) { - resetBtn.addEventListener('click', function() { - cy.layout({ name: 'cose', animate: true }).run(); - }); - } - - // Toggle-Labels-Button - if (toggleLabelsBtn) { - toggleLabelsBtn.addEventListener('click', function() { - labelsVisible = !labelsVisible; - cy.style() - .selector('node') - .style({ - 'text-opacity': labelsVisible ? 1 : 0 - }) - .update(); - }); - } - - // Knoten-Klick +function setupEventListeners(cy) { + // Klick auf Knoten cy.on('tap', 'node', function(evt) { const node = evt.target; - // Zuvor ausgewählten Knoten zurücksetzen - cy.nodes().removeClass('selected'); + // Alle vorherigen Hervorhebungen zurücksetzen + cy.nodes().forEach(n => { + n.removeStyle(); + n.connectedEdges().removeStyle(); + }); - // Neuen Knoten auswählen - node.addClass('selected'); + // Speichere ausgewählten Knoten + window.mindmapInstance.selectedNode = node; - if (nodeInfoPanel && nodeDescription && connectedNodes) { - // Info-Panel aktualisieren - nodeDescription.textContent = node.data('description') || 'Keine Beschreibung verfügbar.'; - - // Verbundene Knoten anzeigen - connectedNodes.innerHTML = ''; - - // Verbundene Knoten sammeln - const connectedNodesList = node.neighborhood('node'); - - if (connectedNodesList.length > 0) { - connectedNodesList.forEach(connectedNode => { - // Nicht den ausgewählten Knoten selbst anzeigen - if (connectedNode.id() !== node.id()) { - const nodeLink = document.createElement('span'); - nodeLink.className = 'node-link'; - nodeLink.textContent = connectedNode.data('name'); - nodeLink.style.backgroundColor = connectedNode.data('color'); - - // Klick-Ereignis, um zu diesem Knoten zu wechseln - nodeLink.addEventListener('click', function() { - connectedNode.select(); - cy.animate({ - center: { eles: connectedNode }, - duration: 500, - easing: 'ease-in-out-cubic' - }); - }); - - connectedNodes.appendChild(nodeLink); - } - }); - } else { - connectedNodes.innerHTML = 'Keine verbundenen Knoten'; - } - - // Panel anzeigen - nodeInfoPanel.classList.add('visible'); - } + // Aktiviere leuchtenden Effekt statt Umkreisung + node.style({ + 'background-opacity': 1, + 'background-color': node.data('color'), + 'shadow-color': node.data('color'), + 'shadow-opacity': 1, + 'shadow-blur': 15, + 'shadow-offset-x': 0, + 'shadow-offset-y': 0 + }); + + // Verbundene Kanten und Knoten hervorheben + const connectedEdges = node.connectedEdges(); + const connectedNodes = node.neighborhood('node'); + + connectedEdges.style({ + 'line-color': '#a78bfa', + 'target-arrow-color': '#a78bfa', + 'source-arrow-color': '#a78bfa', + 'line-opacity': 0.8, + 'width': 2 + }); + + connectedNodes.style({ + 'shadow-opacity': 0.7, + 'shadow-blur': 10, + 'shadow-color': '#a78bfa' + }); + + // Info-Panel aktualisieren + updateInfoPanel(node); + + // Seitenleiste aktualisieren + updateSidebar(node); }); - // Hintergrund-Klick + // Klick auf Hintergrund - Auswahl zurücksetzen cy.on('tap', function(evt) { if (evt.target === cy) { - // Klick auf den Hintergrund - cy.nodes().removeClass('selected'); - - // Info-Panel verstecken - if (nodeInfoPanel) { - nodeInfoPanel.classList.remove('visible'); - } + resetSelection(cy); } }); - // Dark Mode-Änderungen - document.addEventListener('darkModeToggled', function(event) { - const isDark = event.detail.isDark; - cy.style(getDefaultStyles(isDark)); + // Smooth Zoom mit Mouse Wheel + cy.on('mousewheel', function(evt) { + const delta = evt.originalEvent.deltaY; + const factor = delta > 0 ? 0.95 : 1.05; + + cy.animate({ + zoom: cy.zoom() * factor, + duration: 100 + }); + }); +} + +// Aktualisiert das Info-Panel mit Daten des ausgewählten Knotens +function updateInfoPanel(node) { + const nodeInfoPanel = document.getElementById('node-info-panel'); + const nodeDescription = document.getElementById('node-description'); + const connectedNodes = document.getElementById('connected-nodes'); + const panelTitle = nodeInfoPanel.querySelector('.info-panel-title'); + + if (!nodeInfoPanel || !nodeDescription || !connectedNodes) return; + + // Titel und Beschreibung aktualisieren + panelTitle.textContent = node.data('name'); + nodeDescription.textContent = node.data('description') || 'Keine Beschreibung verfügbar.'; + + // Verbundene Knoten anzeigen + connectedNodes.innerHTML = ''; + + // Verbundene Knoten sammeln (direktes Neighborhood) + const connectedNodesList = []; + node.neighborhood('node').forEach(n => { + if (!connectedNodesList.includes(n) && n.id() !== node.id()) { + connectedNodesList.push(n); + } + }); + + // 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'; + nodeLink.style.backgroundColor = connectedNode.data('color'); + nodeLink.textContent = connectedNode.data('name'); + + // Beim Klick auf den verbundenen Knoten zu diesem wechseln + nodeLink.addEventListener('click', function() { + resetSelection(cy); + + // Verzögerung vor der neuen Auswahl für besseren visuellen Übergang + setTimeout(() => { + connectedNode.trigger('tap'); + }, 50); + }); + + connectedNodes.appendChild(nodeLink); + }); + } else { + connectedNodes.innerHTML = 'Keine verbundenen Knoten'; + } + + // Panel anzeigen mit Animation + nodeInfoPanel.style.transition = 'all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1)'; + nodeInfoPanel.classList.add('visible'); +} + +// Aktualisiert die Seitenleiste +function updateSidebar(node) { + // Alle standard Panels ausblenden + document.querySelectorAll('[data-sidebar]').forEach(panel => { + if (panel.getAttribute('data-sidebar') === 'node-description') { + // Beschreibungs-Panel anzeigen + panel.classList.remove('hidden'); + + // Titel und Beschreibung aktualisieren + const titleElement = panel.querySelector('[data-node-title]'); + const descriptionElement = panel.querySelector('[data-node-description]'); + + if (titleElement) { + titleElement.textContent = node.data('name'); + } + + if (descriptionElement) { + descriptionElement.innerHTML = formatDescription(node.data('description') || 'Keine Beschreibung verfügbar.'); + } + } else { + // Andere Panels ausblenden + panel.classList.add('hidden'); + } + }); +} + +// Formatiert die Beschreibung mit etwas HTML-Markup +function formatDescription(text) { + return text + .replace(/\n/g, '
') + .replace(/\*\*(.*?)\*\*/g, '$1') + .replace(/\*(.*?)\*/g, '$1') + .replace(/`(.*?)`/g, '$1'); +} + +// Setzt die Auswahl zurück +function resetSelection(cy) { + // Alle Stile zurücksetzen + cy.nodes().forEach(n => { + n.removeStyle(); + }); + + cy.edges().forEach(e => { + e.removeStyle(); + }); + + // Kein Knoten ausgewählt + window.mindmapInstance.selectedNode = null; + + // Info-Panel ausblenden + const nodeInfoPanel = document.getElementById('node-info-panel'); + if (nodeInfoPanel) { + nodeInfoPanel.classList.remove('visible'); + } + + // Standard-Seitenleisten-Panels anzeigen + document.querySelectorAll('[data-sidebar]').forEach(panel => { + if (panel.getAttribute('data-sidebar') === 'node-description') { + panel.classList.add('hidden'); + } else { + panel.classList.remove('hidden'); + } }); } /** - * Liefert die Standard-Stile für die Mindmap - * @param {boolean} darkMode - Ob der Dark Mode aktiv ist - * @returns {Array} Array von Cytoscape-Stilen + * Gibt die Stile für das neuronale Netzwerk-Design zurück + * @returns {Array} Stildefinitionen für Cytoscape */ -function getDefaultStyles(darkMode = document.documentElement.classList.contains('dark')) { +function getNeuralNetworkStyles() { return [ + // Neuronen (Knoten) { selector: 'node', style: { - 'background-color': 'data(color)', 'label': 'data(name)', - 'width': 40, - 'height': 40, - 'font-size': 12, 'text-valign': 'bottom', 'text-halign': 'center', - 'text-margin-y': 8, - 'color': darkMode ? '#f1f5f9' : '#334155', - 'text-background-color': darkMode ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)', - 'text-background-opacity': 0.8, - 'text-background-padding': '2px', - 'text-background-shape': 'roundrectangle', - 'text-wrap': 'ellipsis', - 'text-max-width': '100px' - } - }, - { - selector: 'node[?isRoot]', - style: { - 'width': 60, - 'height': 60, - 'font-size': 14, - 'font-weight': 'bold', - 'text-background-opacity': 0.9, - 'text-background-color': '#4299E1' + 'color': '#ffffff', + 'text-outline-width': 2, + 'text-outline-color': '#0a0e19', + 'text-outline-opacity': 0.9, + 'font-size': 10, + 'text-margin-y': 6, + 'width': 'mapData(neuronSize, 3, 10, 15, 40)', + 'height': 'mapData(neuronSize, 3, 10, 15, 40)', + 'background-color': 'data(color)', + 'background-opacity': 0.9, + 'border-width': 0, + 'shape': 'ellipse', + 'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 5, 15)', + 'shadow-color': 'data(color)', + 'shadow-opacity': 0.6, + 'shadow-offset-x': 0, + 'shadow-offset-y': 0, + 'text-wrap': 'wrap', + 'text-max-width': 100, + 'transition-property': 'background-color, shadow-color, shadow-opacity, shadow-blur', + 'transition-duration': '0.3s' } }, + // Synapsen (Kanten) { selector: 'edge', style: { - 'width': 2, - 'line-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)', - 'target-arrow-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)', 'curve-style': 'bezier', - 'target-arrow-shape': 'triangle' + 'line-color': '#8a8aaa', + 'width': 'mapData(strength, 0.2, 0.8, 0.8, 2)', + 'line-opacity': 'mapData(strength, 0.2, 0.8, 0.4, 0.7)', + 'target-arrow-shape': 'none', // Keine Pfeilspitzen bei Neuronen + 'target-arrow-color': '#8a8aaa', + 'arrow-scale': 0.6, + 'transition-property': 'line-color, line-opacity, width', + 'transition-duration': '0.3s' } }, + // Schwache Verbindungen { - selector: 'node.selected', + selector: 'edge[strength <= 0.4]', style: { - 'background-color': 'data(color)', - 'border-width': 3, - 'border-color': '#8b5cf6', + 'line-style': 'dotted' + } + }, + // Mittlere Verbindungen + { + selector: 'edge[strength > 0.4][strength <= 0.6]', + style: { + 'line-style': 'dashed' + } + }, + // Starke Verbindungen + { + selector: 'edge[strength > 0.6]', + style: { + 'line-style': 'solid' + } + }, + // Wurzelknoten (speziell gestaltet) + { + selector: 'node[isRoot]', + style: { + 'font-size': 12, + 'font-weight': 'bold', 'width': 50, 'height': 50, - 'font-size': 14, - 'font-weight': 'bold', - 'text-background-color': '#8b5cf6', - 'text-background-opacity': 0.9 + 'background-color': '#6366f1', + 'shadow-blur': 20, + 'shadow-color': '#6366f1', + 'shadow-opacity': 0.8, + 'text-margin-y': 8 + } + }, + // Hover-Effekt für Knoten + { + selector: 'node:hover', + style: { + 'shadow-blur': 20, + 'shadow-opacity': 0.9, + 'transition-property': 'shadow-opacity, shadow-blur', + 'transition-duration': '0.2s' + } + }, + // Hover-Effekt für Kanten + { + selector: 'edge:hover', + style: { + 'line-color': '#a78bfa', + 'line-opacity': 0.8, + 'width': 2 } } ]; -} - -class MindMap { - constructor(selector, options = {}) { - // Standardoptionen festlegen - this.options = Object.assign({ - // Standard-Basisoptionen - editable: false, // Ist die Mindmap editierbar? - isUserLoggedIn: false, // Ist der Benutzer angemeldet? - isPublicMap: true, // Handelt es sich um die öffentliche Mindmap? - initialZoom: 1, // Anfängliche Zoom-Stufe - fitViewOnInit: true, // Passt die Ansicht automatisch an - minZoom: 0.2, // Minimale Zoom-Stufe - maxZoom: 3, // Maximale Zoom-Stufe - // Farbpalette für verschiedene Elemente - colorPalette: { - node: { - default: '#8b5cf6', // Standard-Knotenfarbe - selected: '#7c3aed', // Farbe für ausgewählte Knoten - hover: '#6d28d9' // Farbe für Hover-Effekt - }, - edge: { - default: '#8b5cf6', // Standard-Kantenfarbe - selected: '#7c3aed', // Farbe für ausgewählte Kanten - hover: '#6d28d9' // Farbe für Hover-Effekt - }, - text: { - default: '#f3f4f6', // Standard-Textfarbe - dark: '#1f2937', // Dunkle Textfarbe (für helle Hintergründe) - light: '#f9fafb' // Helle Textfarbe (für dunkle Hintergründe) - }, - background: { - dark: '#111827', // Dunkler Hintergrund - light: '#f9fafb' // Heller Hintergrund - } - }, - // Anpassbare Funktionen - callbacks: { - onNodeClick: null, // Callback für Knotenklick - onEdgeClick: null, // Callback für Kantenklick - onViewportChange: null, // Callback für Änderung der Ansicht - onSelectionChange: null, // Callback für Änderung der Auswahl - onLoad: null // Callback nach dem Laden der Mindmap - } - }, options); - - // Container-Element - this.container = document.querySelector(selector); - if (!this.container) { - console.error(`Container mit Selektor '${selector}' nicht gefunden`); - return; - } - - // Prüfen, ob der Benutzer angemeldet ist - this.isUserLoggedIn = document.body.classList.contains('user-logged-in') || this.options.isUserLoggedIn; - - // Wenn der Benutzer angemeldet ist und es sich um die öffentliche Mindmap handelt, - // wird die Editierbarkeit aktiviert - if (this.isUserLoggedIn && this.options.isPublicMap) { - this.options.editable = true; - } - - // Initialisiere Cytoscape - this.initCytoscape(); - - // Füge Bearbeiten-Funktionalität hinzu, wenn editierbar - if (this.options.editable) { - this.initEditableMode(); - } - - // Lade Daten basierend auf dem Typ der Mindmap - if (this.options.isPublicMap) { - this.loadPublicMindmap(); - } else if (this.options.userMindmapId) { - this.loadUserMindmap(this.options.userMindmapId); - } - - // Initialisiere UI-Elemente - this.initUI(); - - // Event-Listener-Initialisierung - this.initEventListeners(); - - // Flash-Message-System - this.flashContainer = null; - this.initFlashMessages(); - } - - // ... existing code ... - - // Initialisiert die Bearbeitungsmodi für die Mindmap - initEditableMode() { - // Toolbar für Bearbeitungsmodus hinzufügen - this.addEditToolbar(); - - // Kontextmenü-Funktionalität für Knoten - this.cy.on('cxttap', 'node', (evt) => { - const node = evt.target; - const position = evt.renderedPosition; - - // Zeige Kontextmenü - this.showNodeContextMenu(node, position); - }); - - // Doppelklick-Funktion für das Hinzufügen neuer Knoten - this.cy.on('dblclick', (evt) => { - // Nur auf Hintergrund reagieren, nicht auf Knoten - if (evt.target === this.cy) { - const position = evt.position; - this.showAddNodeDialog(position); - } - }); - - // Kontextmenü-Schließen bei Klick außerhalb - document.addEventListener('click', (e) => { - const contextMenu = document.getElementById('context-menu'); - if (contextMenu && !contextMenu.contains(e.target)) { - contextMenu.remove(); - } - }); - } - - // Zeigt ein Kontextmenü für einen Knoten an - showNodeContextMenu(node, position) { - // Entferne vorhandenes Kontextmenü - const existingMenu = document.getElementById('context-menu'); - if (existingMenu) existingMenu.remove(); - - // Erstelle neues Kontextmenü - const contextMenu = document.createElement('div'); - contextMenu.id = 'context-menu'; - contextMenu.style.position = 'absolute'; - contextMenu.style.left = `${position.x}px`; - contextMenu.style.top = `${position.y}px`; - contextMenu.style.zIndex = '1000'; - - // Menü-Elemente - const menuItems = [ - { label: 'Gedanken anzeigen', icon: 'fas fa-brain', action: () => this.showThoughtsForNode(node) }, - { label: 'Neuen Gedanken erstellen', icon: 'fas fa-plus-circle', action: () => this.createThoughtForNode(node) }, - { label: 'Notiz hinzufügen', icon: 'fas fa-sticky-note', action: () => this.addNoteToNode(node) }, - { label: 'Knoten bearbeiten', icon: 'fas fa-edit', action: () => this.editNode(node) }, - { label: 'Verbindung erstellen', icon: 'fas fa-link', action: () => this.startEdgeCreation(node) }, - { label: 'Aus Mindmap entfernen', icon: 'fas fa-trash-alt', action: () => this.removeNodeFromMap(node) } - ]; - - // Menü erstellen - menuItems.forEach(item => { - const menuItem = document.createElement('div'); - menuItem.className = 'menu-item'; - menuItem.innerHTML = ` ${item.label}`; - menuItem.addEventListener('click', () => { - item.action(); - contextMenu.remove(); - }); - contextMenu.appendChild(menuItem); - }); - - // Anhängen an Body (außerhalb des Cytoscape-Containers) - document.body.appendChild(contextMenu); - - // Stellen Sie sicher, dass das Kontextmenü vollständig sichtbar ist - const menuRect = contextMenu.getBoundingClientRect(); - const windowWidth = window.innerWidth; - const windowHeight = window.innerHeight; - - if (menuRect.right > windowWidth) { - contextMenu.style.left = `${position.x - menuRect.width}px`; - } - - if (menuRect.bottom > windowHeight) { - contextMenu.style.top = `${position.y - menuRect.height}px`; - } - } - - // Fügt die Edit-Toolbar zur Mindmap hinzu - addEditToolbar() { - const toolbar = document.querySelector('.mindmap-toolbar'); - if (!toolbar) return; - - // Trennlinie - const separator = document.createElement('div'); - separator.className = 'border-l h-6 mx-2 opacity-20'; - toolbar.appendChild(separator); - - // Bearbeiten-Buttons - const editButtons = [ - { id: 'add-node-btn', icon: 'fa-plus', text: 'Knoten hinzufügen', action: () => this.showAddNodeDialog() }, - { id: 'save-layout-btn', icon: 'fa-save', text: 'Layout speichern', action: () => this.saveCurrentLayout() } - ]; - - editButtons.forEach(btn => { - const button = document.createElement('button'); - button.id = btn.id; - button.className = 'mindmap-action-btn edit-btn'; - button.innerHTML = `${btn.text}`; - button.addEventListener('click', btn.action); - toolbar.appendChild(button); - }); - - // Animation für die Edit-Buttons - const editBtns = document.querySelectorAll('.edit-btn'); - editBtns.forEach(btn => { - btn.style.opacity = '0'; - btn.style.transform = 'translateY(10px)'; - - setTimeout(() => { - btn.style.transition = 'all 0.3s ease'; - btn.style.opacity = '1'; - btn.style.transform = 'translateY(0)'; - }, 100); - }); - } - - // Zeigt den Dialog zum Hinzufügen eines neuen Knotens - showAddNodeDialog(position = null) { - // Entferne bestehende Dialoge - const existingDialog = document.getElementById('add-node-dialog'); - if (existingDialog) existingDialog.remove(); - - // Erstelle Dialog - const dialog = document.createElement('div'); - dialog.id = 'add-node-dialog'; - dialog.className = 'fixed inset-0 flex items-center justify-center z-50'; - dialog.innerHTML = ` -
-
-

Neuen Knoten hinzufügen

-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- `; - - // Füge Dialog zum Body hinzu - document.body.appendChild(dialog); - - // Animation für den Dialog - const dialogContent = dialog.querySelector('.bg-slate-800'); - dialogContent.style.opacity = '0'; - dialogContent.style.transform = 'scale(0.9)'; - setTimeout(() => { - dialogContent.style.transition = 'all 0.3s ease'; - dialogContent.style.opacity = '1'; - dialogContent.style.transform = 'scale(1)'; - }, 10); - - // Event-Listener für Abbrechen - document.getElementById('cancel-add-node').addEventListener('click', () => { - // Animation für das Schließen - dialogContent.style.opacity = '0'; - dialogContent.style.transform = 'scale(0.9)'; - setTimeout(() => dialog.remove(), 300); - }); - - // Event-Listener für Overlay-Klick - dialog.querySelector('.fixed.inset-0.bg-black').addEventListener('click', () => { - // Animation für das Schließen - dialogContent.style.opacity = '0'; - dialogContent.style.transform = 'scale(0.9)'; - setTimeout(() => dialog.remove(), 300); - }); - - // Event-Listener für Formular - document.getElementById('add-node-form').addEventListener('submit', (e) => { - e.preventDefault(); - - const name = document.getElementById('node-name').value; - const description = document.getElementById('node-description').value; - const color = document.getElementById('node-color').value; - - if (!name.trim()) { - this.showFlash('Bitte geben Sie einen Namen für den Knoten ein', 'error'); - return; - } - - // Knoten hinzufügen - this.addNodeToMindmap({ - name, - description, - color_code: color, - position - }); - - // Dialog schließen - dialogContent.style.opacity = '0'; - dialogContent.style.transform = 'scale(0.9)'; - setTimeout(() => dialog.remove(), 300); - }); - - // Fokus auf das Name-Feld - setTimeout(() => document.getElementById('node-name').focus(), 100); - } - - // Fügt einen neuen Knoten zur Mindmap hinzu - addNodeToMindmap(nodeData) { - // API-Anfrage vorbereiten - const data = { - name: nodeData.name, - description: nodeData.description || '', - color_code: nodeData.color_code || '#8b5cf6' - }; - - // Position hinzufügen, falls vorhanden - if (nodeData.position) { - data.x_position = nodeData.position.x; - data.y_position = nodeData.position.y; - } - - // API-Anfrage zum Hinzufügen des Knotens - fetch('/api/mindmap/public/add_node', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }) - .then(response => { - if (!response.ok) { - throw new Error('Netzwerkantwort war nicht ok'); - } - return response.json(); - }) - .then(data => { - if (data.success) { - // Hinzufügen des neuen Knotens zum Graphen - this.cy.add({ - group: 'nodes', - data: { - id: `node_${data.node_id}`, - name: nodeData.name, - description: nodeData.description || '', - color: nodeData.color_code - }, - position: nodeData.position || { x: 0, y: 0 } - }); - - // Flash-Nachricht anzeigen - this.showFlash('Knoten erfolgreich hinzugefügt', 'success'); - - // Ansicht anpassen - this.cy.fit(); - } else { - this.showFlash('Fehler beim Hinzufügen des Knotens: ' + (data.error || 'Unbekannter Fehler'), 'error'); - } - }) - .catch(error => { - console.error('Fehler beim Hinzufügen des Knotens:', error); - this.showFlash('Fehler beim Hinzufügen des Knotens', 'error'); - }); - } - - // Entfernt einen Knoten aus der Mindmap - removeNodeFromMap(node) { - if (!confirm('Möchten Sie diesen Knoten wirklich aus der Mindmap entfernen?')) { - return; - } - - const nodeId = node.id().split('_')[1]; // "node_123" => "123" - - // API-Anfrage zum Entfernen des Knotens - fetch(`/api/mindmap/public/remove_node/${nodeId}`, { - method: 'DELETE' - }) - .then(response => { - if (!response.ok) { - throw new Error('Netzwerkantwort war nicht ok'); - } - return response.json(); - }) - .then(data => { - if (data.success) { - // Knoten aus dem Graphen entfernen - this.cy.remove(node); - - // Flash-Nachricht anzeigen - this.showFlash('Knoten erfolgreich entfernt', 'success'); - } else { - this.showFlash('Fehler beim Entfernen des Knotens: ' + (data.error || 'Unbekannter Fehler'), 'error'); - } - }) - .catch(error => { - console.error('Fehler beim Entfernen des Knotens:', error); - this.showFlash('Fehler beim Entfernen des Knotens', 'error'); - }); - } - - // Speichert das aktuelle Layout der Mindmap - saveCurrentLayout() { - // Sammle alle Knotenpositionen - const nodePositions = []; - this.cy.nodes().forEach(node => { - const idParts = node.id().split('_'); - if (idParts.length === 2 && idParts[0] === 'node') { - nodePositions.push({ - node_id: parseInt(idParts[1]), - x_position: node.position('x'), - y_position: node.position('y') - }); - } - }); - - // API-Anfrage zum Speichern des Layouts - fetch('/api/mindmap/public/update_layout', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ positions: nodePositions }) - }) - .then(response => { - if (!response.ok) { - throw new Error('Netzwerkantwort war nicht ok'); - } - return response.json(); - }) - .then(data => { - if (data.success) { - this.showFlash('Layout erfolgreich gespeichert', 'success'); - } else { - this.showFlash('Fehler beim Speichern des Layouts: ' + (data.error || 'Unbekannter Fehler'), 'error'); - } - }) - .catch(error => { - console.error('Fehler beim Speichern des Layouts:', error); - this.showFlash('Fehler beim Speichern des Layouts', 'error'); - }); - } - - // Zeigt den Dialog zum Bearbeiten eines Knotens - editNode(node) { - // Entferne bestehende Dialoge - const existingDialog = document.getElementById('edit-node-dialog'); - if (existingDialog) existingDialog.remove(); - - // Knotendaten holen - const nodeData = node.data(); - - // Erstelle Dialog - const dialog = document.createElement('div'); - dialog.id = 'edit-node-dialog'; - dialog.className = 'fixed inset-0 flex items-center justify-center z-50'; - dialog.innerHTML = ` -
-
-

Knoten bearbeiten

-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- `; - - // Füge Dialog zum Body hinzu - document.body.appendChild(dialog); - - // Animation für den Dialog - const dialogContent = dialog.querySelector('.bg-slate-800'); - dialogContent.style.opacity = '0'; - dialogContent.style.transform = 'scale(0.9)'; - setTimeout(() => { - dialogContent.style.transition = 'all 0.3s ease'; - dialogContent.style.opacity = '1'; - dialogContent.style.transform = 'scale(1)'; - }, 10); - - // Event-Listener für Abbrechen - document.getElementById('cancel-edit-node').addEventListener('click', () => { - // Animation für das Schließen - dialogContent.style.opacity = '0'; - dialogContent.style.transform = 'scale(0.9)'; - setTimeout(() => dialog.remove(), 300); - }); - - // Event-Listener für Overlay-Klick - dialog.querySelector('.fixed.inset-0.bg-black').addEventListener('click', () => { - // Animation für das Schließen - dialogContent.style.opacity = '0'; - dialogContent.style.transform = 'scale(0.9)'; - setTimeout(() => dialog.remove(), 300); - }); - - // Event-Listener für Formular - document.getElementById('edit-node-form').addEventListener('submit', (e) => { - e.preventDefault(); - - const name = document.getElementById('node-name').value; - const description = document.getElementById('node-description').value; - const color = document.getElementById('node-color').value; - - if (!name.trim()) { - this.showFlash('Bitte geben Sie einen Namen für den Knoten ein', 'error'); - return; - } - - // Knoten aktualisieren - this.updateNode(node, { - name, - description, - color_code: color - }); - - // Dialog schließen - dialogContent.style.opacity = '0'; - dialogContent.style.transform = 'scale(0.9)'; - setTimeout(() => dialog.remove(), 300); - }); - - // Fokus auf das Name-Feld - setTimeout(() => document.getElementById('node-name').focus(), 100); - } - - // Aktualisiert einen Knoten in der Mindmap - updateNode(node, nodeData) { - const nodeId = node.id().split('_')[1]; // "node_123" => "123" - - // API-Anfrage zum Aktualisieren des Knotens - fetch(`/api/mindmap/public/update_node/${nodeId}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - name: nodeData.name, - description: nodeData.description, - color_code: nodeData.color_code - }) - }) - .then(response => { - if (!response.ok) { - throw new Error('Netzwerkantwort war nicht ok'); - } - return response.json(); - }) - .then(data => { - if (data.success) { - // Aktualisiere Knotendaten im Graph - node.data('name', nodeData.name); - node.data('description', nodeData.description); - node.data('color', nodeData.color_code); - - // Aktualisiere Darstellung - this.cy.style() - .selector(`#${node.id()}`) - .style({ - 'background-color': nodeData.color_code - }) - .update(); - - // Flash-Nachricht anzeigen - this.showFlash('Knoten erfolgreich aktualisiert', 'success'); - } else { - this.showFlash('Fehler beim Aktualisieren des Knotens: ' + (data.error || 'Unbekannter Fehler'), 'error'); - } - }) - .catch(error => { - console.error('Fehler beim Aktualisieren des Knotens:', error); - this.showFlash('Fehler beim Aktualisieren des Knotens', 'error'); - }); - } - - // Fügt eine Notiz zu einem Knoten hinzu - addNoteToNode(node) { - // Implementierung für das Hinzufügen von Notizen - // Ähnlich wie bei editNode, aber mit anderen Feldern - } - - // Startet den Prozess zum Erstellen einer Verbindung - startEdgeCreation(node) { - // Implementierung für das Erstellen von Verbindungen - } - - // Zeigt Gedanken für einen Knoten an - showThoughtsForNode(node) { - const nodeId = node.id().split('_')[1]; // "node_123" => "123" - - // Wir verwenden die fetchThoughtsForNode-Methode, die wir bereits implementiert haben - this.fetchThoughtsForNode(nodeId) - .then(thoughts => { - // Nur fortfahren, wenn wir tatsächlich Gedanken haben - if (thoughts.length === 0) { - this.showFlash('Keine Gedanken für diesen Knoten gefunden', 'info'); - return; - } - - // Erstelle einen Gedanken-Viewer, wenn er nicht existiert - let thoughtsViewer = document.getElementById('thoughts-viewer'); - if (!thoughtsViewer) { - thoughtsViewer = document.createElement('div'); - thoughtsViewer.id = 'thoughts-viewer'; - thoughtsViewer.className = 'fixed inset-0 flex items-center justify-center z-50'; - thoughtsViewer.innerHTML = ` -
-
-
-

Gedanken zu:

- -
-
-
- `; - document.body.appendChild(thoughtsViewer); - - // Event-Listener für Schließen-Button - document.getElementById('close-thoughts').addEventListener('click', () => { - // Animation für das Schließen - const content = thoughtsViewer.querySelector('.thoughts-content'); - const backdrop = thoughtsViewer.querySelector('.thoughts-backdrop'); - - content.style.transform = 'scale(0.9)'; - content.style.opacity = '0'; - backdrop.style.opacity = '0'; - - setTimeout(() => thoughtsViewer.remove(), 300); - }); - - // Event-Listener für Backdrop-Klick - thoughtsViewer.querySelector('.thoughts-backdrop').addEventListener('click', () => { - document.getElementById('close-thoughts').click(); - }); - } - - // Aktualisiere den Titel mit dem Knotennamen - thoughtsViewer.querySelector('.node-name').textContent = node.data('name'); - - // Container für die Gedanken - const thoughtsContainer = thoughtsViewer.querySelector('.thoughts-container'); - thoughtsContainer.innerHTML = ''; - - // Gedanken rendern - this.renderThoughts(thoughts, thoughtsContainer); - - // Animation für das Öffnen - const content = thoughtsViewer.querySelector('.thoughts-content'); - const backdrop = thoughtsViewer.querySelector('.thoughts-backdrop'); - - content.style.transform = 'scale(0.9)'; - content.style.opacity = '0'; - backdrop.style.opacity = '0'; - - setTimeout(() => { - content.style.transition = 'all 0.3s ease'; - backdrop.style.transition = 'opacity 0.3s ease'; - content.style.transform = 'scale(1)'; - content.style.opacity = '1'; - backdrop.style.opacity = '1'; - }, 10); - }); - } - - // Rendert die Gedanken in der UI mit Animationen - renderThoughts(thoughts, container) { - if (!container) return; - - container.innerHTML = ''; - - // Animation delay counter - let delay = 0; - - thoughts.forEach(thought => { - const thoughtCard = document.createElement('div'); - thoughtCard.className = 'thought-card mb-4 bg-slate-700/50 rounded-lg overflow-hidden border border-slate-600/50 transition-all duration-300 hover:border-purple-500/30'; - thoughtCard.setAttribute('data-id', thought.id); - thoughtCard.style.opacity = '0'; - thoughtCard.style.transform = 'translateY(20px)'; - - const cardColor = thought.color_code || this.colorPalette.default; - - thoughtCard.innerHTML = ` -
-

${thought.title}

-
- ${new Date(thought.created_at).toLocaleDateString('de-DE')} - ${thought.author ? `${thought.author.username}` : ''} -
-
-
-

${thought.abstract || thought.content.substring(0, 150) + '...'}

-
- - `; - - // Animation-Effekt mit Verzögerung für jede Karte - setTimeout(() => { - thoughtCard.style.transition = 'all 0.5s ease'; - thoughtCard.style.opacity = '1'; - thoughtCard.style.transform = 'translateY(0)'; - }, delay); - delay += 100; // Jede Karte erscheint mit 100ms Verzögerung - - // Event-Listener für Klick auf Gedanken - thoughtCard.addEventListener('click', (e) => { - // Verhindern, dass der Link-Klick den Kartenklick auslöst - if (e.target.tagName === 'A' || e.target.closest('a')) return; - window.location.href = `/thoughts/${thought.id}`; - }); - - // Hover-Animation für Karten - thoughtCard.addEventListener('mouseenter', () => { - thoughtCard.style.transform = 'translateY(-5px)'; - thoughtCard.style.boxShadow = '0 10px 25px rgba(0, 0, 0, 0.2)'; - }); - - thoughtCard.addEventListener('mouseleave', () => { - thoughtCard.style.transform = 'translateY(0)'; - thoughtCard.style.boxShadow = 'none'; - }); - - container.appendChild(thoughtCard); - }); - } - - // Flash-Nachrichten mit Animationen - showFlash(message, type = 'info') { - if (!this.flashContainer) { - this.flashContainer = document.createElement('div'); - this.flashContainer.className = 'fixed top-4 right-4 z-50 flex flex-col gap-2'; - document.body.appendChild(this.flashContainer); - } - - const flash = document.createElement('div'); - flash.className = `flash-message p-3 rounded-lg shadow-lg flex items-center gap-3 max-w-xs text-white`; - - // Verschiedene Stile je nach Typ - let backgroundColor, icon; - switch (type) { - case 'success': - backgroundColor = 'bg-green-500'; - icon = 'fa-check-circle'; - break; - case 'error': - backgroundColor = 'bg-red-500'; - icon = 'fa-exclamation-circle'; - break; - case 'warning': - backgroundColor = 'bg-yellow-500'; - icon = 'fa-exclamation-triangle'; - break; - default: - backgroundColor = 'bg-blue-500'; - icon = 'fa-info-circle'; - } - - flash.classList.add(backgroundColor); - - flash.innerHTML = ` -
-
${message}
- - `; - - // Füge den Flash zum Container hinzu - this.flashContainer.appendChild(flash); - - // Einblend-Animation - flash.style.opacity = '0'; - flash.style.transform = 'translateX(20px)'; - - setTimeout(() => { - flash.style.transition = 'all 0.3s ease'; - flash.style.opacity = '1'; - flash.style.transform = 'translateX(0)'; - }, 10); - - // Schließen-Button - const closeBtn = flash.querySelector('.flash-close'); - closeBtn.addEventListener('click', () => { - // Ausblend-Animation - flash.style.opacity = '0'; - flash.style.transform = 'translateX(20px)'; - - setTimeout(() => { - flash.remove(); - }, 300); - }); - - // Automatisches Ausblenden nach 5 Sekunden - setTimeout(() => { - if (flash.parentNode) { - flash.style.opacity = '0'; - flash.style.transform = 'translateX(20px)'; - - setTimeout(() => { - if (flash.parentNode) flash.remove(); - }, 300); - } - }, 5000); - - return flash; - } -} \ No newline at end of file +} \ No newline at end of file diff --git a/static/js/mindmap-interaction.js b/static/js/mindmap-interaction.js index ef22ee0..27898b5 100644 --- a/static/js/mindmap-interaction.js +++ b/static/js/mindmap-interaction.js @@ -15,42 +15,269 @@ document.addEventListener('DOMContentLoaded', function() { // 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() { - // Vergrößert die Mindmap auf Vollbildmodus, wenn der entsprechende Button geklickt wird - const fullscreenBtn = document.getElementById('fullscreen-btn'); - if (fullscreenBtn) { - fullscreenBtn.addEventListener('click', toggleFullscreen); - } - // 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'); - // Zugriff auf die Mindmap-Instanz - const mindmap = window.mindmapInstance; - if (!mindmap) { - console.warn('Mindmap-Instanz nicht gefunden!'); + // Cytoscape-Instanz + const cy = window.cy; + if (!cy) { + console.warn('Cytoscape-Instanz nicht gefunden!'); return; } - // Verbesserte Zoom-Kontrollen - setupZoomControls(mindmap); + // 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(mindmap); - - // Verbesserte Touch-Gesten für mobile Geräte - setupTouchInteractions(mindmap); + 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 @@ -58,114 +285,33 @@ function preventScrollWhileZooming() { const cyContainer = document.getElementById('cy'); if (cyContainer) { cyContainer.addEventListener('wheel', function(e) { - if (e.ctrlKey || e.metaKey) { - e.preventDefault(); // Verhindert Browser-Zoom bei Ctrl+Wheel - } + // Verhindern des Standard-Scrollens während des Zooms + e.preventDefault(); }, { passive: false }); } } -// Richtet verbesserte Zoom-Kontrollen ein -function setupZoomControls(mindmap) { - const zoomInBtn = document.getElementById('zoom-in-btn'); - const zoomOutBtn = document.getElementById('zoom-out-btn'); - const resetZoomBtn = document.getElementById('reset-btn'); - - if (zoomInBtn) { - zoomInBtn.addEventListener('click', function() { - mindmap.svg.transition() - .duration(300) - .call(mindmap.svg.zoom().scaleBy, 1.4); - }); - } - - if (zoomOutBtn) { - zoomOutBtn.addEventListener('click', function() { - mindmap.svg.transition() - .duration(300) - .call(mindmap.svg.zoom().scaleBy, 0.7); - }); - } - - if (resetZoomBtn) { - resetZoomBtn.addEventListener('click', function() { - mindmap.svg.transition() - .duration(500) - .call(mindmap.svg.zoom().transform, d3.zoomIdentity); - }); - } -} - -// Vollbildmodus umschalten -function toggleFullscreen() { - const mindmapContainer = document.querySelector('.mindmap-container'); - - if (!mindmapContainer) return; - - if (!document.fullscreenElement) { - // Vollbildmodus aktivieren - if (mindmapContainer.requestFullscreen) { - mindmapContainer.requestFullscreen(); - } else if (mindmapContainer.mozRequestFullScreen) { - mindmapContainer.mozRequestFullScreen(); - } else if (mindmapContainer.webkitRequestFullscreen) { - mindmapContainer.webkitRequestFullscreen(); - } else if (mindmapContainer.msRequestFullscreen) { - mindmapContainer.msRequestFullscreen(); - } - - // Icon ändern - const fullscreenBtn = document.getElementById('fullscreen-btn'); - if (fullscreenBtn) { - const icon = fullscreenBtn.querySelector('i'); - if (icon) { - icon.className = 'fa-solid fa-compress'; - } - fullscreenBtn.setAttribute('title', 'Vollbildmodus beenden'); - } - } else { - // Vollbildmodus beenden - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else if (document.webkitExitFullscreen) { - document.webkitExitFullscreen(); - } else if (document.msExitFullscreen) { - document.msExitFullscreen(); - } - - // Icon zurücksetzen - const fullscreenBtn = document.getElementById('fullscreen-btn'); - if (fullscreenBtn) { - const icon = fullscreenBtn.querySelector('i'); - if (icon) { - icon.className = 'fa-solid fa-expand'; - } - fullscreenBtn.setAttribute('title', 'Vollbildmodus'); - } - } -} - // Initialisiert Effekte für Seitenleisten-Panels function initializePanelEffects() { // Selektiert alle Panel-Elemente - const panels = document.querySelectorAll('[data-sidebar]'); + const panels = document.querySelectorAll('.sidebar-panel'); panels.forEach(panel => { // Hover-Effekt für Panels panel.addEventListener('mouseenter', function() { - this.classList.add('hover'); + 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.classList.remove('hover'); + this.style.transform = ''; + this.style.boxShadow = ''; }); }); } // Richtet Tastaturkürzel für Mindmap-Interaktionen ein -function setupKeyboardShortcuts(mindmap) { +function setupKeyboardShortcuts(cy) { document.addEventListener('keydown', function(e) { // Nur fortfahren, wenn keine Texteingabe im Fokus ist if (document.activeElement.tagName === 'INPUT' || @@ -178,229 +324,249 @@ function setupKeyboardShortcuts(mindmap) { switch(e.key) { case '+': case '=': - // Einzoomen + // Einzoomen (sanfter) if (e.ctrlKey || e.metaKey) { e.preventDefault(); - mindmap.svg.transition() - .duration(300) - .call(mindmap.svg.zoom().scaleBy, 1.2); + smoothZoom(cy, 1.15, 400); } break; case '-': - // Auszoomen + // Auszoomen (sanfter) if (e.ctrlKey || e.metaKey) { e.preventDefault(); - mindmap.svg.transition() - .duration(300) - .call(mindmap.svg.zoom().scaleBy, 0.8); + smoothZoom(cy, 0.85, 400); } break; case '0': - // Zoom zurücksetzen + // Zoom auf Gesamtansicht if (e.ctrlKey || e.metaKey) { e.preventDefault(); - mindmap.svg.transition() - .duration(500) - .call(mindmap.svg.zoom().transform, d3.zoomIdentity); - } - break; - - case 'f': - // Vollbildmodus umschalten - if (e.ctrlKey || e.metaKey) { - e.preventDefault(); - toggleFullscreen(); + smoothFit(cy); } break; case 'Escape': // Ausgewählten Knoten abwählen - if (mindmap.selectedNode) { - mindmap.nodeClicked(null, mindmap.selectedNode); - } + cy.nodes().unselect(); + resetNodeSelection(); break; } }); } -// Richtet Touch-Gesten für mobile Geräte ein -function setupTouchInteractions(mindmap) { - const cyContainer = document.getElementById('cy'); - if (!cyContainer) return; +// Sanften Zoom mit Animation anwenden +function smoothZoom(cy, factor, duration = 400) { + const currentZoom = cy.zoom(); + const targetZoom = currentZoom * factor; - let touchStartX, touchStartY; - let touchStartTime; + // Mittelpunkt der Ansicht verwenden + const center = { + x: cy.width() / 2, + y: cy.height() / 2 + }; - // Touch-Start-Event - cyContainer.addEventListener('touchstart', function(e) { - if (e.touches.length === 1) { - touchStartX = e.touches[0].clientX; - touchStartY = e.touches[0].clientY; - touchStartTime = Date.now(); - } - }); - - // Touch-End-Event für Doppeltipp-Erkennung - cyContainer.addEventListener('touchend', function(e) { - if (Date.now() - touchStartTime < 300) { // Kurzer Tipp - const doubleTapDelay = 300; - const now = Date.now(); - - if (now - (window.lastTapTime || 0) < doubleTapDelay) { - // Doppeltipp erkannt - Zentriere Ansicht - mindmap.svg.transition() - .duration(500) - .call(mindmap.svg.zoom().transform, d3.zoomIdentity); - - e.preventDefault(); + // 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(); } - - window.lastTapTime = now; - } + }); }); } -// Richtet einen Event-Listener für die Knotenauswahl ein -function setupNodeSelectionListener() { - document.addEventListener('mindmap-node-selected', function(event) { - const node = event.detail; - if (node) { - console.log('Knoten ausgewählt:', node); - showNodeDescriptionSidebar(node); +// 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); } }); - document.addEventListener('mindmap-node-deselected', function() { - console.log('Knoten abgewählt'); - showDefaultSidebar(); - }); + // 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() { - // Finde die Seitenleistenelemente - const aboutMindmapPanel = document.querySelector('[data-sidebar="about-mindmap"]'); - const categoriesPanel = document.querySelector('[data-sidebar="categories"]'); - const nodeDescriptionPanel = document.querySelector('[data-sidebar="node-description"]'); - - if (aboutMindmapPanel && categoriesPanel && nodeDescriptionPanel) { - // Beschreibungspanel ausblenden - nodeDescriptionPanel.style.display = 'none'; - - // Standardpanels einblenden mit Animation - aboutMindmapPanel.style.display = 'block'; - categoriesPanel.style.display = 'block'; - - setTimeout(() => { - aboutMindmapPanel.style.opacity = '1'; - aboutMindmapPanel.style.transform = 'translateY(0)'; - - categoriesPanel.style.opacity = '1'; - categoriesPanel.style.transform = 'translateY(0)'; - }, 50); - } + // 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) { - // Finde die Seitenleistenelemente - const aboutMindmapPanel = document.querySelector('[data-sidebar="about-mindmap"]'); - const categoriesPanel = document.querySelector('[data-sidebar="categories"]'); - const nodeDescriptionPanel = document.querySelector('[data-sidebar="node-description"]'); + // Das Knotenbeschreibungs-Panel finden + const nodeDescPanel = document.querySelector('[data-sidebar="node-description"]'); - if (aboutMindmapPanel && categoriesPanel && nodeDescriptionPanel) { - // Standardpanels ausblenden - aboutMindmapPanel.style.transition = 'all 0.3s ease'; - categoriesPanel.style.transition = 'all 0.3s ease'; + if (nodeDescPanel) { + // Panel sichtbar machen + nodeDescPanel.classList.remove('hidden'); - aboutMindmapPanel.style.opacity = '0'; - aboutMindmapPanel.style.transform = 'translateY(10px)'; + // Titel und Beschreibung aktualisieren + const nodeTitleElement = nodeDescPanel.querySelector('[data-node-title]'); + const nodeDescElement = nodeDescPanel.querySelector('[data-node-description]'); - categoriesPanel.style.opacity = '0'; - categoriesPanel.style.transform = 'translateY(10px)'; + if (nodeTitleElement) { + nodeTitleElement.textContent = node.data('name'); + } - setTimeout(() => { - aboutMindmapPanel.style.display = 'none'; - categoriesPanel.style.display = 'none'; - - // Beschreibungspanel vorbereiten - const titleElement = nodeDescriptionPanel.querySelector('[data-node-title]'); - const descriptionElement = nodeDescriptionPanel.querySelector('[data-node-description]'); - - if (titleElement && descriptionElement) { - titleElement.textContent = node.name || 'Unbenannter Knoten'; - - // Beschreibung setzen oder Standardbeschreibung generieren - let description = node.description; - if (!description || description.trim() === '') { - description = generateNodeDescription(node); - } - - descriptionElement.textContent = description; - } - - // Beschreibungspanel einblenden mit Animation - nodeDescriptionPanel.style.display = 'block'; - nodeDescriptionPanel.style.opacity = '0'; - nodeDescriptionPanel.style.transform = 'translateY(10px)'; - - setTimeout(() => { - nodeDescriptionPanel.style.transition = 'all 0.4s ease'; - nodeDescriptionPanel.style.opacity = '1'; - nodeDescriptionPanel.style.transform = 'translateY(0)'; - }, 50); - }, 300); + if (nodeDescElement) { + // Beschreibung mit HTML-Formatierung anzeigen + nodeDescElement.innerHTML = generateNodeDescription(node); + } } } -// Generiert automatisch eine Beschreibung für einen Knoten ohne Beschreibung +// Generiert eine formatierte HTML-Beschreibung für einen Knoten function generateNodeDescription(node) { - const descriptions = { - "Wissen": "Der zentrale Knotenpunkt der Mindmap, der alle wissenschaftlichen Disziplinen und Wissensgebiete verbindet. Hier finden sich grundlegende Erkenntnisse und Verbindungen zu spezifischeren Fachgebieten.", - - "Quantenphysik": "Ein Zweig der Physik, der sich mit dem Verhalten und den Interaktionen von Materie und Energie auf der kleinsten Skala beschäftigt. Quantenmechanische Phänomene wie Superposition und Verschränkung bilden die Grundlage für moderne Technologien wie Quantencomputer und -kommunikation.", - - "Neurowissenschaften": "Interdisziplinäres Forschungsgebiet, das die Struktur, Funktion und Entwicklung des Nervensystems und des Gehirns untersucht. Die Erkenntnisse beeinflussen unser Verständnis von Bewusstsein, Kognition, Verhalten und neurologischen Erkrankungen.", - - "Künstliche Intelligenz": "Forschungsgebiet der Informatik, das sich mit der Entwicklung von Systemen befasst, die menschliche Intelligenzformen simulieren können. KI umfasst maschinelles Lernen, neuronale Netze und verschiedene Ansätze zur Problemlösung und Mustererkennung.", - - "Klimaforschung": "Wissenschaftliche Disziplin, die sich mit der Untersuchung des Erdklimas, seinen Veränderungen und den zugrundeliegenden physikalischen Prozessen beschäftigt. Sie liefert wichtige Erkenntnisse zu Klimawandel, Wettermuster und globalen Umweltveränderungen.", - - "Genetik": "Wissenschaft der Gene, Vererbung und der Variation von Organismen. Moderne genetische Forschung umfasst Genomik, Gentechnologie und das Verständnis der molekularen Grundlagen des Lebens sowie ihrer Anwendungen in Medizin und Biotechnologie.", - - "Astrophysik": "Zweig der Astronomie, der die physikalischen Eigenschaften und Prozesse von Himmelskörpern und des Universums untersucht. Sie erforscht Phänomene wie Schwarze Löcher, Galaxien, kosmische Strahlung und die Entstehung und Entwicklung des Universums.", - - "Philosophie": "Disziplin, die sich mit fundamentalen Fragen des Wissens, der Realität und der Existenz auseinandersetzt. Sie umfasst Bereiche wie Metaphysik, Erkenntnistheorie, Ethik und Logik und bildet die Grundlage für kritisches Denken und wissenschaftliche Methodik.", - - "Wissenschaft": "Systematische Erforschung der Natur und der materiellen Welt durch Beobachtung, Experimente und die Formulierung überprüfbarer Theorien. Sie umfasst Naturwissenschaften, Sozialwissenschaften und angewandte Wissenschaften.", - - "Technologie": "Anwendung wissenschaftlicher Erkenntnisse für praktische Zwecke. Sie umfasst die Entwicklung von Werkzeugen, Maschinen, Materialien und Prozessen zur Lösung von Problemen und zur Verbesserung der menschlichen Lebensbedingungen.", - - "Künste": "Ausdruck menschlicher Kreativität und Imagination in verschiedenen Formen wie Malerei, Musik, Literatur, Theater und Film. Die Künste erforschen ästhetische, emotionale und intellektuelle Dimensionen der menschlichen Erfahrung.", - - "Biologie": "Wissenschaft des Lebens und der lebenden Organismen. Sie umfasst Bereiche wie Molekularbiologie, Evolutionsbiologie, Ökologie und beschäftigt sich mit der Struktur, Funktion, Entwicklung und Evolution lebender Systeme.", - - "Mathematik": "Wissenschaft der Muster, Strukturen und Beziehungen. Sie ist die Sprache der Naturwissenschaften und bildet die Grundlage für quantitative Analysen, logisches Denken und Problemlösung in allen wissenschaftlichen Disziplinen.", - - "Psychologie": "Wissenschaftliche Untersuchung des menschlichen Verhaltens und der mentalen Prozesse. Sie erforscht Bereiche wie Kognition, Emotion, Persönlichkeit, soziale Interaktionen und die Behandlung psychischer Störungen.", - - "Ethik": "Teilgebiet der Philosophie, das sich mit moralischen Prinzipien, Werten und der Frage nach richtigem und falschem Handeln beschäftigt. Sie bildet die Grundlage für moralische Entscheidungsfindung in allen Lebensbereichen." - }; + let description = node.data('description') || 'Keine Beschreibung verfügbar.'; - // Verwende vordefinierte Beschreibung, wenn verfügbar - if (node.name && descriptions[node.name]) { - return descriptions[node.name]; - } + // Einfache HTML-Formatierung (kann erweitert werden) + description = description + .replace(/\n/g, '
') + .replace(/\*\*(.*?)\*\*/g, '$1') + .replace(/\*(.*?)\*/g, '$1') + .replace(/`(.*?)`/g, '$1'); - // Generische Beschreibung basierend auf dem Knotentyp - switch (node.type) { - case 'category': - return `Dieser Knoten repräsentiert die Kategorie "${node.name}", die verschiedene verwandte Konzepte und Ideen zusammenfasst. Wählen Sie einen der verbundenen Unterthemen, um mehr Details zu erfahren.`; - case 'subcategory': - return `"${node.name}" ist eine Unterkategorie, die spezifische Aspekte eines größeren Themenbereichs beleuchtet. Die verbundenen Knoten zeigen wichtige Konzepte und Ideen innerhalb dieses Bereichs.`; - default: - return `Dieser Knoten repräsentiert das Konzept "${node.name}". Erforschen Sie die verbundenen Knoten, um Zusammenhänge und verwandte Ideen zu entdecken.`; - } + return description; } \ No newline at end of file diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js index be81ba7..f9a531b 100644 --- a/static/js/update_mindmap.js +++ b/static/js/update_mindmap.js @@ -1,7 +1,7 @@ /** * Update Mindmap - * Dieses Skript fügt die neuen wissenschaftlichen Knoten zur Mindmap hinzu - * und stellt sicher, dass sie korrekt angezeigt werden. + * Dieses Skript fügt Knoten zur Mindmap hinzu und stellt sicher, + * dass sie im neuronalen Netzwerk-Design angezeigt werden. */ // Warte bis DOM geladen ist @@ -16,13 +16,13 @@ document.addEventListener('DOMContentLoaded', function() { // Auf das Laden der Mindmap warten document.addEventListener('mindmap-loaded', function() { - console.log('Mindmap geladen, füge wissenschaftliche Knoten hinzu...'); + console.log('Mindmap geladen, wende neuronales Netzwerk-Design an...'); enhanceMindmap(); }); }); /** - * Erweitert die Mindmap mit den neu hinzugefügten wissenschaftlichen Knoten + * Erweitert die Mindmap mit dem neuronalen Netzwerk-Design */ function enhanceMindmap() { // Auf die bestehende Cytoscape-Instanz zugreifen @@ -33,25 +33,295 @@ function enhanceMindmap() { return; } - // Aktualisiere das Layout mit zusätzlichem Platz für die neuen Knoten + // Aktualisiere das Layout für eine bessere Verteilung cy.layout({ name: 'cose', animate: true, - animationDuration: 800, + animationDuration: 1800, nodeDimensionsIncludeLabels: true, padding: 100, spacingFactor: 1.8, randomize: false, - fit: true + fit: true, + componentSpacing: 100, + nodeRepulsion: 8000, + edgeElasticity: 100, + nestingFactor: 1.2, + gravity: 80 }).run(); - console.log('Mindmap wurde erfolgreich aktualisiert!'); + // Neuronen-Namen mit besserer Lesbarkeit umgestalten + cy.style() + .selector('node') + .style({ + 'text-background-color': 'rgba(10, 14, 25, 0.7)', + 'text-background-opacity': 0.7, + 'text-background-padding': '2px', + 'text-border-opacity': 0.2, + 'text-border-width': 1, + 'text-border-color': '#8b5cf6' + }) + .update(); - // Wenn ein Wissen-Knoten existiert, sicherstellen, dass er im Zentrum ist - const rootNode = cy.getElementById('1'); - if (rootNode.length > 0) { - cy.center(rootNode); + // Sicherstellen, dass alle Knoten Neuronen-Eigenschaften haben + cy.nodes().forEach(node => { + if (!node.data('neuronSize')) { + const neuronSize = Math.floor(Math.random() * 8) + 3; + node.data('neuronSize', neuronSize); + } + + if (!node.data('neuronActivity')) { + const neuronActivity = Math.random() * 0.7 + 0.3; + node.data('neuronActivity', neuronActivity); + } + + // Zusätzliche Neuronale Eigenschaften + node.data('pulseFrequency', Math.random() * 4 + 2); // Pulsfrequenz (2-6 Hz) + node.data('refractionPeriod', Math.random() * 300 + 700); // Refraktionszeit (700-1000ms) + node.data('threshold', Math.random() * 0.3 + 0.6); // Aktivierungsschwelle (0.6-0.9) + }); + + // Sicherstellen, dass alle Kanten Synapse-Eigenschaften haben + cy.edges().forEach(edge => { + if (!edge.data('strength')) { + const strength = Math.random() * 0.6 + 0.2; + edge.data('strength', strength); + } + + // Zusätzliche synaptische Eigenschaften + edge.data('conductionVelocity', Math.random() * 0.5 + 0.3); // Leitungsgeschwindigkeit (0.3-0.8) + edge.data('latency', Math.random() * 100 + 50); // Signalverzögerung (50-150ms) + }); + + // Neuronales Netzwerk-Stil anwenden + applyNeuralNetworkStyle(cy); + + console.log('Mindmap wurde erfolgreich im neuronalen Netzwerk-Stil aktualisiert'); + + // Spezielle Effekte für das neuronale Netzwerk hinzufügen + startNeuralActivitySimulation(cy); +} + +/** + * Wendet detaillierte neuronale Netzwerkstile auf die Mindmap an + * @param {Object} cy - Cytoscape-Instanz + */ +function applyNeuralNetworkStyle(cy) { + // Wende erweiterte Stile für Neuronen und Synapsen an + cy.style() + .selector('node') + .style({ + 'label': 'data(name)', + 'text-valign': 'bottom', + 'text-halign': 'center', + 'color': '#ffffff', + 'text-outline-width': 1.5, + 'text-outline-color': '#0a0e19', + 'text-outline-opacity': 0.9, + 'font-size': 10, + 'text-margin-y': 7, + 'width': 'mapData(neuronSize, 3, 10, 15, 40)', + 'height': 'mapData(neuronSize, 3, 10, 15, 40)', + 'background-color': 'data(color)', + 'background-opacity': 0.85, + 'border-width': 0, + 'shape': 'ellipse', + 'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 5, 15)', + 'shadow-color': 'data(color)', + 'shadow-opacity': 0.6, + 'shadow-offset-x': 0, + 'shadow-offset-y': 0 + }) + .selector('edge') + .style({ + 'width': 'mapData(strength, 0.2, 0.8, 0.7, 2)', + 'curve-style': 'bezier', + 'line-color': '#8a8aaa', + 'line-opacity': 'mapData(strength, 0.2, 0.8, 0.4, 0.7)', + 'line-style': function(ele) { + const strength = ele.data('strength'); + 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%' + }) + .selector('node[isRoot]') + .style({ + 'font-size': 12, + 'font-weight': 'bold', + 'width': 50, + 'height': 50, + 'background-color': '#6366f1', + 'shadow-blur': 20, + 'shadow-color': '#6366f1', + 'shadow-opacity': 0.8, + 'text-margin-y': 8 + }) + .update(); +} + +/** + * Simuliert neuronale Aktivität in der Mindmap + * @param {Object} cy - Cytoscape-Instanz + */ +function startNeuralActivitySimulation(cy) { + // Neuronen-Zustand für die Simulation + const neuronStates = new Map(); + + // Initialisieren aller Neuronen-Zustände + cy.nodes().forEach(node => { + neuronStates.set(node.id(), { + potential: Math.random() * 0.3, // Startpotential + lastFired: 0, // Zeitpunkt der letzten Aktivierung + isRefractory: false, // Refraktärphase + refractoryUntil: 0 // Ende der Refraktärphase + }); + }); + + // Neuronale Aktivität simulieren + function simulateNeuralActivity() { + const currentTime = Date.now(); + const nodes = cy.nodes().toArray(); + + // Zufällige Stimulation eines Neurons + if (Math.random() > 0.7) { + const randomNodeIndex = Math.floor(Math.random() * nodes.length); + const randomNode = nodes[randomNodeIndex]; + + const state = neuronStates.get(randomNode.id()); + if (state && !state.isRefractory) { + state.potential += 0.5; // Erhöhe das Potential durch externe Stimulation + } + } + + // Neuronen aktualisieren + nodes.forEach(node => { + const nodeId = node.id(); + const state = neuronStates.get(nodeId); + const threshold = node.data('threshold') || 0.7; + const refractoryPeriod = node.data('refractionPeriod') || 1000; + + // Überprüfen, ob die Refraktärphase beendet ist + if (state.isRefractory && currentTime >= state.refractoryUntil) { + state.isRefractory = false; + state.potential = 0.1; // Ruhepotential nach Refraktärphase + } + + // Wenn nicht in Refraktärphase und Potential über Schwelle + if (!state.isRefractory && state.potential >= threshold) { + // Neuron feuert + fireNeuron(node, state, currentTime); + } else if (!state.isRefractory) { + // Potential langsam verlieren, wenn nicht gefeuert wird + state.potential *= 0.95; + } + }); + + // Simulation fortsetzen + requestAnimationFrame(simulateNeuralActivity); } + + // Neuron "feuern" lassen + function fireNeuron(node, state, currentTime) { + // Neuron aktivieren + node.animate({ + style: { + 'background-opacity': 1, + 'shadow-opacity': 1, + 'shadow-blur': 25 + }, + duration: 300, + easing: 'ease-in-cubic', + complete: function() { + // Zurück zum normalen Zustand + node.animate({ + style: { + 'background-opacity': 0.85, + 'shadow-opacity': 0.6, + 'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 5, 15)' + }, + duration: 600, + easing: 'ease-out-cubic' + }); + } + }); + + // Refraktärphase setzen + state.isRefractory = true; + state.lastFired = currentTime; + state.refractoryPeriod = node.data('refractionPeriod') || 1000; + state.refractoryUntil = currentTime + state.refractoryPeriod; + state.potential = 0; // Potential zurücksetzen + + // Signal über verbundene Synapsen weiterleiten + propagateSignal(node, currentTime); + } + + // Signal über Synapsen propagieren + function propagateSignal(sourceNode, currentTime) { + // Verbundene Kanten auswählen + const edges = sourceNode.connectedEdges().filter(edge => + edge.source().id() === sourceNode.id() // Nur ausgehende Kanten + ); + + // Durch alle Kanten iterieren + edges.forEach(edge => { + // Signalverzögerung basierend auf synaptischen Eigenschaften + const latency = edge.data('latency') || 100; + const strength = edge.data('strength') || 0.5; + + // Signal entlang der Kante senden + setTimeout(() => { + edge.animate({ + style: { + 'line-color': '#a78bfa', + 'line-opacity': 0.9, + 'width': 2.5 + }, + duration: 200, + easing: 'ease-in', + complete: function() { + // Kante zurücksetzen + edge.animate({ + style: { + 'line-color': '#8a8aaa', + 'line-opacity': 'mapData(strength, 0.2, 0.8, 0.4, 0.7)', + 'width': 'mapData(strength, 0.2, 0.8, 0.7, 2)' + }, + duration: 400, + easing: 'ease-out' + }); + + // Zielknoten potenzial erhöhen + const targetNode = edge.target(); + const targetState = neuronStates.get(targetNode.id()); + + if (targetState && !targetState.isRefractory) { + // Potentialzunahme basierend auf synaptischer Stärke + targetState.potential += strength * 0.4; + + // Subtile Anzeige der Potenzialänderung + targetNode.animate({ + style: { + 'background-opacity': Math.min(1, 0.85 + (strength * 0.2)), + 'shadow-opacity': Math.min(1, 0.6 + (strength * 0.3)), + 'shadow-blur': Math.min(25, 10 + (strength * 15)) + }, + duration: 300, + easing: 'ease-in-out' + }); + } + } + }); + }, latency); + }); + } + + // Starte die Simulation + simulateNeuralActivity(); } // Hilfe-Funktion zum Hinzufügen eines Flash-Hinweises diff --git a/templates/mindmap.html b/templates/mindmap.html index 3bfc794..c465e8f 100644 --- a/templates/mindmap.html +++ b/templates/mindmap.html @@ -1,486 +1,228 @@ {% extends "base.html" %} -{% block title %}Mindmap{% endblock %} +{% block title %}Interaktive Mindmap{% endblock %} {% block extra_css %} {% endblock %} {% block content %} - -
- -
-

- Wissenslandkarte -

-

- Visualisiere die Verbindungen zwischen Gedanken und Konzepten -

+
+ +
+

Interaktive Wissenslandkarte

- -
- -
- - - - - - -
+ +
- -
+ +
+ + + + +
- -
-

Knoteninfo

-

Wählen Sie einen Knoten aus...

- -
-
Verknüpfte Knoten
- -
+ +
+

Knoteninformationen

+
+

Wählen Sie einen Knoten aus, um Details anzuzeigen.

- -