/** * Mindmap.js - Modul für die Mindmap-Visualisierung mit Cytoscape.js * Version: 1.0.0 * Datum: 01.05.2025 */ // Stellen Sie sicher, dass das globale MindMap-Objekt existiert if (!window.MindMap) { window.MindMap = {}; } /** * Mindmap-Visualisierungsklasse */ class MindmapVisualization { /** * Konstruktor für die Mindmap-Visualisierung * @param {string} containerId - ID des Container-Elements * @param {Object} options - Konfigurationsoptionen */ constructor(containerId, options = {}) { this.containerId = containerId; this.container = document.getElementById(containerId); this.cy = null; this.data = null; this.options = Object.assign({ defaultLayout: 'cose', darkMode: document.documentElement.classList.contains('dark'), apiEndpoint: '/api/mindmap/public', onNodeClick: null }, options); // Event-Listener für Dark Mode-Änderungen document.addEventListener('darkModeToggled', (event) => { this.options.darkMode = event.detail.isDark; if (this.cy) { this.updateStyles(); } }); } /** * Initialisiert die Mindmap */ async initialize() { console.log("Initialisiere Mindmap..."); try { // Versuche, Daten vom Server zu laden this.data = await this.loadDataFromServer(); console.log("Daten vom Server geladen:", this.data); } catch (error) { console.warn("Fehler beim Laden der Daten vom Server:", error); console.log("Verwende Standarddaten als Fallback"); this.data = this.getDefaultData(); } // Cytoscape initialisieren this.initializeCytoscape(); return this; } /** * Lädt Daten vom Server * @returns {Promise} - Promise mit den geladenen Daten */ async loadDataFromServer() { try { const response = await fetch(this.options.apiEndpoint); if (!response.ok) { throw new Error(`HTTP Fehler: ${response.status}`); } return await response.json(); } catch (error) { console.error("Fehler beim Laden der Daten:", error); throw error; } } /** * Liefert Standarddaten als Fallback * @returns {Object} - Standarddaten für die Mindmap */ getDefaultData() { return { nodes: [ { id: "root", name: "Wissen", description: "Zentrale Wissensbasis", color_code: "#4299E1" }, { id: "philosophy", name: "Philosophie", description: "Philosophisches Denken", color_code: "#9F7AEA" }, { id: "science", name: "Wissenschaft", description: "Wissenschaftliche Erkenntnisse", color_code: "#48BB78" }, { id: "technology", name: "Technologie", description: "Technologische Entwicklungen", color_code: "#ED8936" }, { id: "arts", name: "Künste", description: "Künstlerische Ausdrucksformen", color_code: "#ED64A6" } ], edges: [ { source: "root", target: "philosophy" }, { source: "root", target: "science" }, { source: "root", target: "technology" }, { source: "root", target: "arts" } ] }; } /** * Initialisiert Cytoscape.js */ initializeCytoscape() { if (!this.container) { console.error(`Container mit ID '${this.containerId}' nicht gefunden.`); return; } // Konvertiert die API-Daten ins Cytoscape-Format const elements = this.convertToCytoscapeFormat(this.data); // Erstellt die Cytoscape-Instanz this.cy = cytoscape({ container: this.container, elements: elements, style: this.getStyles(), layout: { name: this.options.defaultLayout, animate: true, animationDuration: 800, nodeDimensionsIncludeLabels: true, padding: 30, spacingFactor: 1.2, randomize: true, componentSpacing: 100, nodeRepulsion: 8000, edgeElasticity: 100, nestingFactor: 1.2, gravity: 80 } }); // Event-Listener für Knotenklicks this.cy.on('tap', 'node', (event) => { const node = event.target; this.handleNodeClick(node); }); console.log("Cytoscape initialisiert"); } /** * Konvertiert API-Daten ins Cytoscape-Format * @param {Object} data - Daten von der API * @returns {Array} - Elemente im Cytoscape-Format */ convertToCytoscapeFormat(data) { const elements = []; // Knoten hinzufügen if (data.nodes && Array.isArray(data.nodes)) { data.nodes.forEach(node => { elements.push({ data: { id: node.id, name: node.name, description: node.description, color: node.color_code || '#8B5CF6', category: node.category_id } }); }); } // Kanten hinzufügen if (data.edges && Array.isArray(data.edges)) { data.edges.forEach(edge => { elements.push({ data: { id: `${edge.source}-${edge.target}`, source: edge.source, target: edge.target } }); }); } return elements; } /** * Liefert die Styles für Cytoscape * @returns {Array} - Cytoscape-Stylesheets */ getStyles() { const darkMode = this.options.darkMode; return [ { 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: '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' } }, { selector: 'node:selected', style: { 'background-color': 'data(color)', 'border-width': 3, 'border-color': '#8b5cf6', 'width': 50, 'height': 50, 'font-size': 14, 'font-weight': 'bold', 'text-background-color': '#8b5cf6', 'text-background-opacity': 0.9 } } ]; } /** * Aktualisiert die Styles basierend auf dem Dark Mode */ updateStyles() { this.cy.style(this.getStyles()); } /** * Behandelt den Klick auf einen Knoten * @param {Object} node - Cytoscape-Knoten */ handleNodeClick(node) { console.log("Knoten angeklickt:", node.id(), node.data()); // Callback aufrufen, falls definiert if (typeof this.options.onNodeClick === 'function') { this.options.onNodeClick(node.data()); } } /** * Passt die Ansicht an alle Elemente an */ fitToElements() { if (this.cy) { this.cy.fit(); this.cy.center(); } } /** * Setzt das Layout zurück * @param {string} layoutName - Name des Layouts (optional) */ resetLayout(layoutName) { if (this.cy) { this.cy.layout({ name: layoutName || this.options.defaultLayout, animate: true, randomize: true, fit: true }).run(); } } } // Globale Export window.MindMap.Visualization = MindmapVisualization; // Automatische Initialisierung, wenn das DOM geladen ist document.addEventListener('DOMContentLoaded', function() { const cyContainer = document.getElementById('cy'); if (cyContainer) { console.log("Mindmap-Container gefunden, initialisiere..."); const mindmap = new MindmapVisualization('cy', { onNodeClick: function(nodeData) { console.log("Knoten ausgewählt:", nodeData); // Hier könnte man weitere Aktionen durchführen } }); mindmap.initialize().then(() => { console.log("Mindmap erfolgreich initialisiert"); // Speichere die Instanz global für den Zugriff von außen window.mindmap = mindmap; }).catch(error => { console.error("Fehler bei der Initialisierung der Mindmap:", error); }); } });