diff --git a/static/js/mindmap-init.js b/static/js/mindmap-init.js
index 4dcccdc..86cec23 100644
--- a/static/js/mindmap-init.js
+++ b/static/js/mindmap-init.js
@@ -1,1249 +1,214 @@
/**
- * Mindmap-Initialisierer
- * Lädt und initialisiert die Mindmap-Visualisierung
+ * Mindmap Initialisierung und Event-Handling
*/
-// Warte bis DOM geladen ist
-document.addEventListener('DOMContentLoaded', function() {
- // Prüfe, ob wir auf der Mindmap-Seite sind
- const cyContainer = document.getElementById('cy');
-
- if (!cyContainer) {
- console.log('Kein Mindmap-Container gefunden, überspringe Initialisierung.');
- return;
- }
-
- console.log('Initialisiere Mindmap-Visualisierung...');
-
- // Prüfe, ob Cytoscape.js verfügbar ist
- if (typeof cytoscape === 'undefined') {
- loadScript('/static/js/cytoscape.min.js', initMindmap);
- } else {
- initMindmap();
- }
-});
+// Warte auf die Cytoscape-Instanz
+document.addEventListener('mindmap-loaded', function() {
+ const cy = window.cy;
+ if (!cy) return;
-/**
- * Lädt ein Script dynamisch
- * @param {string} src - Quelldatei
- * @param {Function} callback - Callback nach dem Laden
- */
-function loadScript(src, callback) {
- const script = document.createElement('script');
- script.src = src;
- script.onload = callback;
- document.head.appendChild(script);
-}
-
-/**
- * Initialisiert die Mindmap-Visualisierung
- */
-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(),
- layout: {
- name: 'cose',
- animate: true,
- animationDuration: 800,
- nodeDimensionsIncludeLabels: true,
- padding: 50,
- spacingFactor: 1.2,
- randomize: true,
- componentSpacing: 100,
- nodeRepulsion: 8000,
- edgeElasticity: 100,
- nestingFactor: 1.2,
- gravity: 80
- },
- wheelSensitivity: 0.3,
- });
-
- // Daten vom Server laden
- loadMindmapData(cy);
-
- // Event-Handler zuweisen
- setupEventListeners(cy, fitBtn, resetBtn, toggleLabelsBtn, nodeInfoPanel, nodeDescription, connectedNodes);
-}
-
-/**
- * Lädt die Mindmap-Daten vom Server
- * @param {Object} cy - Cytoscape-Instanz
- */
-function loadMindmapData(cy) {
- fetch('/api/mindmap')
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP Fehler: ${response.status}`);
- }
- return response.json();
- })
- .then(data => {
- if (!data.nodes || data.nodes.length === 0) {
- console.log('Keine Daten gefunden, versuche Refresh-API...');
- return fetch('/api/refresh-mindmap')
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP Fehler beim Refresh: ${response.status}`);
- }
- return response.json();
- });
- }
- return data;
- })
- .then(data => {
- console.log('Mindmap-Daten geladen:', data);
-
- // Cytoscape-Elemente vorbereiten
- const elements = [];
-
- // Prüfen, ob "Wissen"-Knoten existiert
- let rootNode = data.nodes.find(node => node.name === "Wissen");
-
- // Wenn nicht, Root-Knoten hinzufügen
- if (!rootNode) {
- rootNode = {
- id: 'root',
- name: 'Wissen',
- description: 'Zentrale Wissensbasis',
- color_code: '#4299E1'
- };
- data.nodes.unshift(rootNode);
- }
-
- // Knoten hinzufügen
- data.nodes.forEach(node => {
- elements.push({
- group: 'nodes',
- data: {
- id: node.id.toString(),
- name: node.name,
- description: node.description || '',
- color: node.color_code || '#8B5CF6',
- isRoot: node.name === 'Wissen'
- }
- });
- });
-
- // Kanten hinzufügen, wenn vorhanden
- if (data.edges && data.edges.length > 0) {
- data.edges.forEach(edge => {
- elements.push({
- group: 'edges',
- data: {
- id: `${edge.source}-${edge.target}`,
- source: edge.source.toString(),
- target: edge.target.toString()
- }
- });
- });
- } else {
- // Wenn keine Kanten definiert sind, verbinde alle Knoten mit dem Root-Knoten
- const rootId = rootNode.id.toString();
-
- data.nodes.forEach(node => {
- if (node.id.toString() !== rootId) {
- elements.push({
- group: 'edges',
- data: {
- id: `${rootId}-${node.id}`,
- source: rootId,
- target: node.id.toString()
- }
- });
- }
- });
- }
-
- // Elemente zu Cytoscape hinzufügen
- cy.elements().remove();
- cy.add(elements);
-
- // Layout anwenden
- cy.layout({
- name: 'cose',
- animate: true,
- animationDuration: 800,
- nodeDimensionsIncludeLabels: true,
- padding: 50,
- spacingFactor: 1.5,
- randomize: false,
- fit: true
- }).run();
-
- // Nach dem Laden Event auslösen
- document.dispatchEvent(new CustomEvent('mindmap-loaded'));
- })
- .catch(error => {
- console.error('Fehler beim Laden der Mindmap-Daten:', error);
-
- // Fallback mit Standard-Daten
- const fallbackData = {
- nodes: [
- { id: 1, name: 'Wissen', description: 'Zentrale Wissensbasis', color_code: '#4299E1' },
- { id: 2, name: 'Philosophie', description: 'Philosophisches Denken', color_code: '#9F7AEA' },
- { id: 3, name: 'Wissenschaft', description: 'Wissenschaftliche Erkenntnisse', color_code: '#48BB78' },
- { id: 4, name: 'Technologie', description: 'Technologische Entwicklungen', color_code: '#ED8936' },
- { id: 5, name: 'Künste', description: 'Künstlerische Ausdrucksformen', color_code: '#ED64A6' }
- ],
- edges: [
- { source: 1, target: 2 },
- { source: 1, target: 3 },
- { source: 1, target: 4 },
- { source: 1, target: 5 }
- ]
- };
-
- const fallbackElements = [];
-
- // Knoten hinzufügen
- fallbackData.nodes.forEach(node => {
- fallbackElements.push({
- group: 'nodes',
- data: {
- id: node.id.toString(),
- name: node.name,
- description: node.description || '',
- color: node.color_code || '#8B5CF6',
- isRoot: node.name === 'Wissen'
- }
- });
- });
-
- // Kanten hinzufügen
- fallbackData.edges.forEach(edge => {
- fallbackElements.push({
- group: 'edges',
- data: {
- id: `${edge.source}-${edge.target}`,
- source: edge.source.toString(),
- target: edge.target.toString()
- }
- });
- });
-
- // Elemente zu Cytoscape hinzufügen
- cy.elements().remove();
- cy.add(fallbackElements);
-
- // Layout anwenden
- cy.layout({ name: 'cose', animate: true }).run();
- });
-}
-
-/**
- * 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
+ // Event-Listener für Knoten-Klicks
cy.on('tap', 'node', function(evt) {
const node = evt.target;
- // Zuvor ausgewählten Knoten zurücksetzen
- cy.nodes().removeClass('selected');
-
- // Neuen Knoten auswählen
- node.addClass('selected');
-
- 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');
- }
+ // Alle vorherigen Hervorhebungen zurücksetzen
+ cy.nodes().forEach(n => {
+ n.removeStyle();
+ n.connectedEdges().removeStyle();
});
- // Hintergrund-Klick
- 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');
- }
- }
+ // Speichere ausgewählten Knoten
+ window.mindmapInstance.selectedNode = node;
+
+ // 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
});
- // Dark Mode-Änderungen
- document.addEventListener('darkModeToggled', function(event) {
- const isDark = event.detail.isDark;
- cy.style(getDefaultStyles(isDark));
+ // 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);
+ });
+
+ // Klick auf Hintergrund - Auswahl zurücksetzen
+ cy.on('tap', function(evt) {
+ if (evt.target === cy) {
+ resetSelection(cy);
+ }
+ });
+
+ // Zoom-Controls
+ document.getElementById('zoomIn')?.addEventListener('click', () => {
+ cy.zoom({
+ level: cy.zoom() * 1.2,
+ renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
+ });
+ });
+
+ document.getElementById('zoomOut')?.addEventListener('click', () => {
+ cy.zoom({
+ level: cy.zoom() / 1.2,
+ renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
+ });
+ });
+
+ document.getElementById('resetView')?.addEventListener('click', () => {
+ cy.fit();
+ resetSelection(cy);
+ });
+
+ // Legend-Toggle
+ document.getElementById('toggleLegend')?.addEventListener('click', () => {
+ const legend = document.getElementById('categoryLegend');
+ if (legend) {
+ isLegendVisible = !isLegendVisible;
+ legend.style.display = isLegendVisible ? 'block' : 'none';
+ }
+ });
+
+ // Keyboard-Controls
+ document.addEventListener('keydown', (e) => {
+ if (e.key === '+' || e.key === '=') {
+ cy.zoom({
+ level: cy.zoom() * 1.2,
+ renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
+ });
+ } else if (e.key === '-' || e.key === '_') {
+ cy.zoom({
+ level: cy.zoom() / 1.2,
+ renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
+ });
+ } else if (e.key === 'Escape') {
+ resetSelection(cy);
+ }
+ });
+});
+
+/**
+ * Aktualisiert das Info-Panel mit Knoteninformationen
+ * @param {Object} node - Der ausgewählte Knoten
+ */
+function updateInfoPanel(node) {
+ const infoPanel = document.getElementById('infoPanel');
+ if (!infoPanel) return;
+
+ const data = node.data();
+ const connectedNodes = node.neighborhood('node');
+
+ let html = `
+
${data.label || data.name}
+ ${data.category || 'Keine Kategorie'}
+ ${data.description ? `${data.description}
` : ''}
+
+
Verbindungen (${connectedNodes.length})
+
+ `;
+
+ connectedNodes.forEach(connectedNode => {
+ const connectedData = connectedNode.data();
+ html += `
+ -
+ ${connectedData.label || connectedData.name}
+
+ `;
+ });
+
+ html += `
+
+
+ `;
+
+ infoPanel.innerHTML = html;
+ infoPanel.style.display = 'block';
}
/**
- * Liefert die Standard-Stile für die Mindmap
- * @param {boolean} darkMode - Ob der Dark Mode aktiv ist
- * @returns {Array} Array von Cytoscape-Stilen
+ * Aktualisiert die Seitenleiste mit Knoteninformationen
+ * @param {Object} node - Der ausgewählte Knoten
*/
-function getDefaultStyles(darkMode = document.documentElement.classList.contains('dark')) {
- 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: 'node[?isRoot]',
- style: {
- 'width': 60,
- 'height': 60,
- 'font-size': 14,
- 'font-weight': 'bold',
- 'text-background-opacity': 0.9,
- 'text-background-color': '#4299E1'
- }
- },
- {
- 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
- }
- }
- ];
-}
+function updateSidebar(node) {
+ const sidebar = document.getElementById('sidebar');
+ if (!sidebar) return;
-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();
- }
+ const data = node.data();
+ const connectedNodes = node.neighborhood('node');
- // ... existing code ...
+ let html = `
+
+
${data.label || data.name}
+
${data.category || 'Keine Kategorie'}
+ ${data.description ? `
${data.description}
` : ''}
+
+
Verbindungen (${connectedNodes.length})
+
+ `;
- // 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
-
-
+ connectedNodes.forEach(connectedNode => {
+ const connectedData = connectedNode.data();
+ html += `
+ -
+ ${connectedData.label || connectedData.name}
+
`;
-
- // 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:
-
+ html += `
+
-
`;
- 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);
- });
+
+ sidebar.innerHTML = html;
+}
+
+/**
+ * Setzt die Auswahl zurück
+ * @param {Object} cy - Cytoscape-Instanz
+ */
+function resetSelection(cy) {
+ window.mindmapInstance.selectedNode = null;
+
+ // Alle Hervorhebungen zurücksetzen
+ cy.nodes().forEach(node => {
+ node.removeStyle();
+ node.connectedEdges().removeStyle();
+ });
+
+ // Info-Panel ausblenden
+ const infoPanel = document.getElementById('infoPanel');
+ if (infoPanel) {
+ infoPanel.style.display = 'none';
}
- // 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.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;
+ // Seitenleiste leeren
+ const sidebar = document.getElementById('sidebar');
+ if (sidebar) {
+ sidebar.innerHTML = '';
}
}
\ No newline at end of file
diff --git a/static/js/mindmap-interaction.js b/static/js/mindmap-interaction.js
deleted file mode 100644
index ef22ee0..0000000
--- a/static/js/mindmap-interaction.js
+++ /dev/null
@@ -1,406 +0,0 @@
-/**
- * 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();
-});
-
-// 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();
-}
-
-// 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!');
- return;
- }
-
- // Verbesserte Zoom-Kontrollen
- setupZoomControls(mindmap);
-
- // 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);
-}
-
-// Verhindert Browser-Scrolling während Zoom in der Mindmap
-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
- }
- }, { 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]');
-
- panels.forEach(panel => {
- // Hover-Effekt für Panels
- panel.addEventListener('mouseenter', function() {
- this.classList.add('hover');
- });
-
- panel.addEventListener('mouseleave', function() {
- this.classList.remove('hover');
- });
- });
-}
-
-// Richtet Tastaturkürzel für Mindmap-Interaktionen ein
-function setupKeyboardShortcuts(mindmap) {
- 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
- if (e.ctrlKey || e.metaKey) {
- e.preventDefault();
- mindmap.svg.transition()
- .duration(300)
- .call(mindmap.svg.zoom().scaleBy, 1.2);
- }
- break;
-
- case '-':
- // Auszoomen
- if (e.ctrlKey || e.metaKey) {
- e.preventDefault();
- mindmap.svg.transition()
- .duration(300)
- .call(mindmap.svg.zoom().scaleBy, 0.8);
- }
- break;
-
- case '0':
- // Zoom zurücksetzen
- 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();
- }
- break;
-
- case 'Escape':
- // Ausgewählten Knoten abwählen
- if (mindmap.selectedNode) {
- mindmap.nodeClicked(null, mindmap.selectedNode);
- }
- break;
- }
- });
-}
-
-// Richtet Touch-Gesten für mobile Geräte ein
-function setupTouchInteractions(mindmap) {
- const cyContainer = document.getElementById('cy');
- if (!cyContainer) return;
-
- let touchStartX, touchStartY;
- let touchStartTime;
-
- // 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();
- }
-
- 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);
- }
- });
-
- document.addEventListener('mindmap-node-deselected', function() {
- console.log('Knoten abgewählt');
- showDefaultSidebar();
- });
-}
-
-// 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);
- }
-}
-
-// 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"]');
-
- if (aboutMindmapPanel && categoriesPanel && nodeDescriptionPanel) {
- // Standardpanels ausblenden
- aboutMindmapPanel.style.transition = 'all 0.3s ease';
- categoriesPanel.style.transition = 'all 0.3s ease';
-
- aboutMindmapPanel.style.opacity = '0';
- aboutMindmapPanel.style.transform = 'translateY(10px)';
-
- categoriesPanel.style.opacity = '0';
- categoriesPanel.style.transform = 'translateY(10px)';
-
- 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);
- }
-}
-
-// Generiert automatisch eine Beschreibung für einen Knoten ohne Beschreibung
-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."
- };
-
- // Verwende vordefinierte Beschreibung, wenn verfügbar
- if (node.name && descriptions[node.name]) {
- return descriptions[node.name];
- }
-
- // 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.`;
- }
-}
\ No newline at end of file
diff --git a/static/js/mindmap.html b/static/js/mindmap.html
deleted file mode 100644
index b17eb06..0000000
--- a/static/js/mindmap.html
+++ /dev/null
@@ -1,234 +0,0 @@
-
-
-
-
-
- Interaktive Mindmap
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/static/js/mindmap.js b/static/js/mindmap.js
deleted file mode 100644
index 110ac4b..0000000
--- a/static/js/mindmap.js
+++ /dev/null
@@ -1,749 +0,0 @@
-/**
- * Mindmap.js - Interaktive Mind-Map Implementierung
- * - Cytoscape.js für Graph-Rendering
- * - Fetch API für REST-Zugriffe
- * - Socket.IO für Echtzeit-Synchronisation
- */
-
-(async () => {
- /* 1. Initialisierung und Grundkonfiguration */
- const cy = cytoscape({
- container: document.getElementById('cy'),
- style: [
- {
- selector: 'node',
- style: {
- 'label': 'data(name)',
- 'text-valign': 'center',
- 'color': '#fff',
- 'background-color': 'data(color)',
- 'width': 45,
- 'height': 45,
- 'font-size': 11,
- 'text-outline-width': 1,
- 'text-outline-color': '#000',
- 'text-outline-opacity': 0.5,
- 'text-wrap': 'wrap',
- 'text-max-width': 80
- }
- },
- {
- selector: 'node[icon]',
- style: {
- 'background-image': function(ele) {
- return `static/img/icons/${ele.data('icon')}.svg`;
- },
- 'background-width': '60%',
- 'background-height': '60%',
- 'background-position-x': '50%',
- 'background-position-y': '40%',
- 'text-margin-y': 10
- }
- },
- {
- selector: 'edge',
- style: {
- 'width': 2,
- 'line-color': '#888',
- 'target-arrow-shape': 'triangle',
- 'curve-style': 'bezier',
- 'target-arrow-color': '#888'
- }
- },
- {
- selector: ':selected',
- style: {
- 'border-width': 3,
- 'border-color': '#f8f32b'
- }
- }
- ],
- layout: {
- name: 'breadthfirst',
- directed: true,
- padding: 30,
- spacingFactor: 1.2
- }
- });
-
- /* 2. Hilfs-Funktionen für API-Zugriffe */
- const get = async endpoint => {
- try {
- const response = await fetch(endpoint);
- if (!response.ok) {
- console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
- return []; // Leeres Array zurückgeben bei Fehlern
- }
- return await response.json();
- } catch (error) {
- console.error(`Fehler beim Abrufen von ${endpoint}:`, error);
- return []; // Leeres Array zurückgeben bei Netzwerkfehlern
- }
- };
-
- const post = async (endpoint, body) => {
- try {
- const response = await fetch(endpoint, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(body)
- });
- if (!response.ok) {
- console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
- return {}; // Leeres Objekt zurückgeben bei Fehlern
- }
- return await response.json();
- } catch (error) {
- console.error(`Fehler beim POST zu ${endpoint}:`, error);
- return {}; // Leeres Objekt zurückgeben bei Netzwerkfehlern
- }
- };
-
- const del = async endpoint => {
- try {
- const response = await fetch(endpoint, { method: 'DELETE' });
- if (!response.ok) {
- console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
- return {}; // Leeres Objekt zurückgeben bei Fehlern
- }
- return await response.json();
- } catch (error) {
- console.error(`Fehler beim DELETE zu ${endpoint}:`, error);
- return {}; // Leeres Objekt zurückgeben bei Netzwerkfehlern
- }
- };
-
- /* 3. Kategorien laden für Style-Informationen */
- let categories = await get('/api/categories');
-
- /* 4. Daten laden und Rendering */
- const loadMindmap = async () => {
- try {
- // Nodes und Beziehungen parallel laden
- const [nodes, relationships] = await Promise.all([
- get('/api/mind_map_nodes'),
- get('/api/node_relationships')
- ]);
-
- // Graph leeren (für Reload-Fälle)
- cy.elements().remove();
-
- // Überprüfen, ob nodes ein Array ist, wenn nicht, setze es auf ein leeres Array
- const nodesArray = Array.isArray(nodes) ? nodes : [];
-
- // Knoten zum Graph hinzufügen
- cy.add(
- nodesArray.map(node => {
- // Kategorie-Informationen für Styling abrufen
- const category = categories.find(c => c.id === node.category_id) || {};
-
- return {
- data: {
- id: node.id.toString(),
- name: node.name,
- description: node.description,
- color: node.color_code || category.color_code || '#6b7280',
- icon: node.icon || category.icon,
- category_id: node.category_id
- },
- position: node.x && node.y ? { x: node.x, y: node.y } : undefined
- };
- })
- );
-
- // Überprüfen, ob relationships ein Array ist, wenn nicht, setze es auf ein leeres Array
- const relationshipsArray = Array.isArray(relationships) ? relationships : [];
-
- // Kanten zum Graph hinzufügen
- cy.add(
- relationshipsArray.map(rel => ({
- data: {
- id: `${rel.parent_id}_${rel.child_id}`,
- source: rel.parent_id.toString(),
- target: rel.child_id.toString()
- }
- }))
- );
-
- // Wenn keine Knoten geladen wurden, Fallback-Knoten erstellen
- if (nodesArray.length === 0) {
- // Mindestens einen Standardknoten hinzufügen
- cy.add({
- data: {
- id: 'fallback-1',
- name: 'Mindmap',
- description: 'Erstellen Sie hier Ihre eigene Mindmap',
- color: '#3b82f6',
- icon: 'help-circle'
- },
- position: { x: 300, y: 200 }
- });
-
- // Erfolgsmeldung anzeigen
- console.log('Mindmap erfolgreich initialisiert mit Fallback-Knoten');
-
- // Info-Meldung für Benutzer anzeigen
- const infoBox = document.createElement('div');
- infoBox.classList.add('info-message');
- infoBox.style.position = 'absolute';
- infoBox.style.top = '50%';
- infoBox.style.left = '50%';
- infoBox.style.transform = 'translate(-50%, -50%)';
- infoBox.style.padding = '15px 20px';
- infoBox.style.backgroundColor = 'rgba(59, 130, 246, 0.9)';
- infoBox.style.color = 'white';
- infoBox.style.borderRadius = '8px';
- infoBox.style.zIndex = '5';
- infoBox.style.maxWidth = '80%';
- infoBox.style.textAlign = 'center';
- infoBox.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
- infoBox.innerHTML = 'Mindmap erfolgreich initialisiert.
Verwenden Sie die Werkzeugleiste, um Knoten hinzuzufügen.';
-
- document.getElementById('cy').appendChild(infoBox);
-
- // Meldung nach 5 Sekunden ausblenden
- setTimeout(() => {
- infoBox.style.opacity = '0';
- infoBox.style.transition = 'opacity 0.5s ease';
- setTimeout(() => {
- if (infoBox.parentNode) {
- infoBox.parentNode.removeChild(infoBox);
- }
- }, 500);
- }, 5000);
- }
-
- // Layout anwenden wenn keine Positionsdaten vorhanden
- const nodesWithoutPosition = cy.nodes().filter(node =>
- !node.position() || (node.position().x === 0 && node.position().y === 0)
- );
-
- if (nodesWithoutPosition.length > 0) {
- cy.layout({
- name: 'breadthfirst',
- directed: true,
- padding: 30,
- spacingFactor: 1.2
- }).run();
- }
-
- // Tooltip-Funktionalität
- cy.nodes().unbind('mouseover').bind('mouseover', (event) => {
- const node = event.target;
- const description = node.data('description');
-
- if (description) {
- const tooltip = document.getElementById('node-tooltip') ||
- document.createElement('div');
-
- if (!tooltip.id) {
- tooltip.id = 'node-tooltip';
- tooltip.style.position = 'absolute';
- tooltip.style.backgroundColor = '#333';
- tooltip.style.color = '#fff';
- tooltip.style.padding = '8px';
- tooltip.style.borderRadius = '4px';
- tooltip.style.maxWidth = '250px';
- tooltip.style.zIndex = 10;
- tooltip.style.pointerEvents = 'none';
- tooltip.style.transition = 'opacity 0.2s';
- tooltip.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
- document.body.appendChild(tooltip);
- }
-
- const renderedPosition = node.renderedPosition();
- const containerRect = cy.container().getBoundingClientRect();
-
- tooltip.innerHTML = description;
- tooltip.style.left = (containerRect.left + renderedPosition.x + 25) + 'px';
- tooltip.style.top = (containerRect.top + renderedPosition.y - 15) + 'px';
- tooltip.style.opacity = '1';
- }
- });
-
- cy.nodes().unbind('mouseout').bind('mouseout', () => {
- const tooltip = document.getElementById('node-tooltip');
- if (tooltip) {
- tooltip.style.opacity = '0';
- }
- });
-
- } catch (error) {
- console.error('Fehler beim Laden der Mindmap:', error);
- alert('Die Mindmap konnte nicht geladen werden. Bitte prüfen Sie die Konsole für Details.');
- }
- };
-
- // Initial laden
- await loadMindmap();
-
- /* 5. Socket.IO für Echtzeit-Synchronisation */
- const socket = io();
-
- socket.on('node_added', async (node) => {
- // Kategorie-Informationen für Styling abrufen
- const category = categories.find(c => c.id === node.category_id) || {};
-
- cy.add({
- data: {
- id: node.id.toString(),
- name: node.name,
- description: node.description,
- color: node.color_code || category.color_code || '#6b7280',
- icon: node.icon || category.icon,
- category_id: node.category_id
- }
- });
-
- // Layout neu anwenden, wenn nötig
- if (!node.x || !node.y) {
- cy.layout({ name: 'breadthfirst', directed: true, padding: 30 }).run();
- }
- });
-
- socket.on('node_updated', (node) => {
- const cyNode = cy.$id(node.id.toString());
- if (cyNode.length > 0) {
- // Kategorie-Informationen für Styling abrufen
- const category = categories.find(c => c.id === node.category_id) || {};
-
- cyNode.data({
- name: node.name,
- description: node.description,
- color: node.color_code || category.color_code || '#6b7280',
- icon: node.icon || category.icon,
- category_id: node.category_id
- });
-
- if (node.x && node.y) {
- cyNode.position({ x: node.x, y: node.y });
- }
- }
- });
-
- socket.on('node_deleted', (nodeId) => {
- const cyNode = cy.$id(nodeId.toString());
- if (cyNode.length > 0) {
- cy.remove(cyNode);
- }
- });
-
- socket.on('relationship_added', (rel) => {
- cy.add({
- data: {
- id: `${rel.parent_id}_${rel.child_id}`,
- source: rel.parent_id.toString(),
- target: rel.child_id.toString()
- }
- });
- });
-
- socket.on('relationship_deleted', (rel) => {
- const edgeId = `${rel.parent_id}_${rel.child_id}`;
- const cyEdge = cy.$id(edgeId);
- if (cyEdge.length > 0) {
- cy.remove(cyEdge);
- }
- });
-
- socket.on('category_updated', async () => {
- // Kategorien neu laden
- categories = await get('/api/categories');
- // Nodes aktualisieren, die diese Kategorie verwenden
- cy.nodes().forEach(node => {
- const categoryId = node.data('category_id');
- if (categoryId) {
- const category = categories.find(c => c.id === categoryId);
- if (category) {
- node.data('color', node.data('color_code') || category.color_code);
- node.data('icon', node.data('icon') || category.icon);
- }
- }
- });
- });
-
- /* 6. UI-Interaktionen */
- // Knoten hinzufügen
- const btnAddNode = document.getElementById('addNode');
- if (btnAddNode) {
- btnAddNode.addEventListener('click', async () => {
- const name = prompt('Knotenname eingeben:');
- if (!name) return;
-
- const description = prompt('Beschreibung (optional):');
-
- // Kategorie auswählen
- let categoryId = null;
- if (categories.length > 0) {
- const categoryOptions = categories.map((c, i) => `${i}: ${c.name}`).join('\n');
- const categoryChoice = prompt(
- `Kategorie auswählen (Nummer eingeben):\n${categoryOptions}`,
- '0'
- );
-
- if (categoryChoice !== null) {
- const index = parseInt(categoryChoice, 10);
- if (!isNaN(index) && index >= 0 && index < categories.length) {
- categoryId = categories[index].id;
- }
- }
- }
-
- // Knoten erstellen
- await post('/api/mind_map_node', {
- name,
- description,
- category_id: categoryId
- });
- // Darstellung wird durch Socket.IO Event übernommen
- });
- }
-
- // Verbindung hinzufügen
- const btnAddEdge = document.getElementById('addEdge');
- if (btnAddEdge) {
- btnAddEdge.addEventListener('click', async () => {
- const sel = cy.$('node:selected');
- if (sel.length !== 2) {
- alert('Bitte genau zwei Knoten auswählen (Parent → Child)');
- return;
- }
-
- const [parent, child] = sel.map(node => node.id());
- await post('/api/node_relationship', {
- parent_id: parent,
- child_id: child
- });
- // Darstellung wird durch Socket.IO Event übernommen
- });
- }
-
- // Knoten bearbeiten
- const btnEditNode = document.getElementById('editNode');
- if (btnEditNode) {
- btnEditNode.addEventListener('click', async () => {
- const sel = cy.$('node:selected');
- if (sel.length !== 1) {
- alert('Bitte genau einen Knoten auswählen');
- return;
- }
-
- const node = sel[0];
- const nodeData = node.data();
-
- const name = prompt('Knotenname:', nodeData.name);
- if (!name) return;
-
- const description = prompt('Beschreibung:', nodeData.description || '');
-
- // Kategorie auswählen
- let categoryId = nodeData.category_id;
- if (categories.length > 0) {
- const categoryOptions = categories.map((c, i) => `${i}: ${c.name}`).join('\n');
- const categoryChoice = prompt(
- `Kategorie auswählen (Nummer eingeben):\n${categoryOptions}`,
- categories.findIndex(c => c.id === categoryId).toString()
- );
-
- if (categoryChoice !== null) {
- const index = parseInt(categoryChoice, 10);
- if (!isNaN(index) && index >= 0 && index < categories.length) {
- categoryId = categories[index].id;
- }
- }
- }
-
- // Knoten aktualisieren
- await post(`/api/mind_map_node/${nodeData.id}`, {
- name,
- description,
- category_id: categoryId
- });
- // Darstellung wird durch Socket.IO Event übernommen
- });
- }
-
- // Knoten löschen
- const btnDeleteNode = document.getElementById('deleteNode');
- if (btnDeleteNode) {
- btnDeleteNode.addEventListener('click', async () => {
- const sel = cy.$('node:selected');
- if (sel.length !== 1) {
- alert('Bitte genau einen Knoten auswählen');
- return;
- }
-
- if (confirm('Sind Sie sicher, dass Sie diesen Knoten löschen möchten?')) {
- const nodeId = sel[0].id();
- await del(`/api/mind_map_node/${nodeId}`);
- // Darstellung wird durch Socket.IO Event übernommen
- }
- });
- }
-
- // Verbindung löschen
- const btnDeleteEdge = document.getElementById('deleteEdge');
- if (btnDeleteEdge) {
- btnDeleteEdge.addEventListener('click', async () => {
- const sel = cy.$('edge:selected');
- if (sel.length !== 1) {
- alert('Bitte genau eine Verbindung auswählen');
- return;
- }
-
- if (confirm('Sind Sie sicher, dass Sie diese Verbindung löschen möchten?')) {
- const edge = sel[0];
- const parentId = edge.source().id();
- const childId = edge.target().id();
-
- await del(`/api/node_relationship/${parentId}/${childId}`);
- // Darstellung wird durch Socket.IO Event übernommen
- }
- });
- }
-
- // Layout aktualisieren
- const btnReLayout = document.getElementById('reLayout');
- if (btnReLayout) {
- btnReLayout.addEventListener('click', () => {
- cy.layout({
- name: 'breadthfirst',
- directed: true,
- padding: 30,
- spacingFactor: 1.2
- }).run();
- });
- }
-
- /* 7. Position speichern bei Drag & Drop */
- cy.on('dragfree', 'node', async (e) => {
- const node = e.target;
- const position = node.position();
-
- await post(`/api/mind_map_node/${node.id()}/position`, {
- x: Math.round(position.x),
- y: Math.round(position.y)
- });
-
- // Andere Benutzer erhalten die Position über den node_updated Event
- });
-
- /* 8. Kontextmenü (optional) */
- const setupContextMenu = () => {
- cy.on('cxttap', 'node', function(e) {
- const node = e.target;
- const nodeData = node.data();
-
- // Position des Kontextmenüs berechnen
- const renderedPosition = node.renderedPosition();
- const containerRect = cy.container().getBoundingClientRect();
- const menuX = containerRect.left + renderedPosition.x;
- const menuY = containerRect.top + renderedPosition.y;
-
- // Kontextmenü erstellen oder aktualisieren
- let contextMenu = document.getElementById('context-menu');
- if (!contextMenu) {
- contextMenu = document.createElement('div');
- contextMenu.id = 'context-menu';
- contextMenu.style.position = 'absolute';
- contextMenu.style.backgroundColor = '#fff';
- contextMenu.style.border = '1px solid #ccc';
- contextMenu.style.borderRadius = '4px';
- contextMenu.style.padding = '5px 0';
- contextMenu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
- contextMenu.style.zIndex = 1000;
- document.body.appendChild(contextMenu);
- }
-
- // Menüinhalte
- contextMenu.innerHTML = `
-
-
-
- `;
-
- // Styling für Menüpunkte
- const menuItems = contextMenu.querySelectorAll('.menu-item');
- menuItems.forEach(item => {
- item.style.padding = '8px 20px';
- item.style.cursor = 'pointer';
- item.style.fontSize = '14px';
-
- item.addEventListener('mouseover', function() {
- this.style.backgroundColor = '#f0f0f0';
- });
-
- item.addEventListener('mouseout', function() {
- this.style.backgroundColor = 'transparent';
- });
-
- // Event-Handler
- item.addEventListener('click', async function() {
- const action = this.getAttribute('data-action');
-
- switch(action) {
- case 'edit':
- // Knoten bearbeiten (gleiche Logik wie beim Edit-Button)
- const name = prompt('Knotenname:', nodeData.name);
- if (name) {
- const description = prompt('Beschreibung:', nodeData.description || '');
- await post(`/api/mind_map_node/${nodeData.id}`, { name, description });
- }
- break;
-
- case 'connect':
- // Modus zum Verbinden aktivieren
- cy.nodes().unselect();
- node.select();
- alert('Wählen Sie nun einen zweiten Knoten aus, um eine Verbindung zu erstellen');
- break;
-
- case 'delete':
- if (confirm('Sind Sie sicher, dass Sie diesen Knoten löschen möchten?')) {
- await del(`/api/mind_map_node/${nodeData.id}`);
- }
- break;
- }
-
- // Menü schließen
- contextMenu.style.display = 'none';
- });
- });
-
- // Menü positionieren und anzeigen
- contextMenu.style.left = menuX + 'px';
- contextMenu.style.top = menuY + 'px';
- contextMenu.style.display = 'block';
-
- // Event-Listener zum Schließen des Menüs
- const closeMenu = function() {
- if (contextMenu) {
- contextMenu.style.display = 'none';
- }
- document.removeEventListener('click', closeMenu);
- };
-
- // Verzögerung, um den aktuellen Click nicht zu erfassen
- setTimeout(() => {
- document.addEventListener('click', closeMenu);
- }, 0);
-
- e.preventDefault();
- });
- };
-
- // Kontextmenü aktivieren (optional)
- // setupContextMenu();
-
- /* 9. Export-Funktion (optional) */
- const btnExport = document.getElementById('exportMindmap');
- if (btnExport) {
- btnExport.addEventListener('click', () => {
- const elements = cy.json().elements;
- const exportData = {
- nodes: elements.nodes.map(n => ({
- id: n.data.id,
- name: n.data.name,
- description: n.data.description,
- category_id: n.data.category_id,
- x: Math.round(n.position?.x || 0),
- y: Math.round(n.position?.y || 0)
- })),
- relationships: elements.edges.map(e => ({
- parent_id: e.data.source,
- child_id: e.data.target
- }))
- };
-
- const blob = new Blob([JSON.stringify(exportData, null, 2)], {type: 'application/json'});
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = 'mindmap_export.json';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- });
- }
-
- /* 10. Filter-Funktion nach Kategorien (optional) */
- const setupCategoryFilters = () => {
- const filterContainer = document.getElementById('category-filters');
- if (!filterContainer || !categories.length) return;
-
- filterContainer.innerHTML = '';
-
- // "Alle anzeigen" Option
- const allBtn = document.createElement('button');
- allBtn.innerText = 'Alle Kategorien';
- allBtn.className = 'category-filter active';
- allBtn.onclick = () => {
- document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active'));
- allBtn.classList.add('active');
- cy.nodes().removeClass('filtered').show();
- cy.edges().show();
- };
- filterContainer.appendChild(allBtn);
-
- // Filter-Button pro Kategorie
- categories.forEach(category => {
- const btn = document.createElement('button');
- btn.innerText = category.name;
- btn.className = 'category-filter';
- btn.style.backgroundColor = category.color_code;
- btn.style.color = '#fff';
- btn.onclick = () => {
- document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active'));
- btn.classList.add('active');
-
- const matchingNodes = cy.nodes().filter(node => node.data('category_id') === category.id);
- cy.nodes().addClass('filtered').hide();
- matchingNodes.removeClass('filtered').show();
-
- // Verbindungen zu/von diesen Knoten anzeigen
- cy.edges().hide();
- matchingNodes.connectedEdges().show();
- };
- filterContainer.appendChild(btn);
- });
- };
-
- // Filter-Funktionalität aktivieren (optional)
- // setupCategoryFilters();
-
- /* 11. Suchfunktion (optional) */
- const searchInput = document.getElementById('search-mindmap');
- if (searchInput) {
- searchInput.addEventListener('input', (e) => {
- const searchTerm = e.target.value.toLowerCase();
-
- if (!searchTerm) {
- cy.nodes().removeClass('search-hidden').show();
- cy.edges().show();
- return;
- }
-
- cy.nodes().forEach(node => {
- const name = node.data('name').toLowerCase();
- const description = (node.data('description') || '').toLowerCase();
-
- if (name.includes(searchTerm) || description.includes(searchTerm)) {
- node.removeClass('search-hidden').show();
- node.connectedEdges().show();
- } else {
- node.addClass('search-hidden').hide();
- // Kanten nur verstecken, wenn beide verbundenen Knoten versteckt sind
- node.connectedEdges().forEach(edge => {
- const otherNode = edge.source().id() === node.id() ? edge.target() : edge.source();
- if (otherNode.hasClass('search-hidden')) {
- edge.hide();
- }
- });
- }
- });
- });
- }
-
- console.log('Mindmap erfolgreich initialisiert');
-})();
\ No newline at end of file
diff --git a/static/js/modules/mindmap-page.js b/static/js/modules/mindmap-page.js
index 509c094..4879a58 100644
--- a/static/js/modules/mindmap-page.js
+++ b/static/js/modules/mindmap-page.js
@@ -26,105 +26,216 @@ document.addEventListener('DOMContentLoaded', function() {
function initMindmapPage() {
console.log('Mindmap-Seite wird initialisiert...');
- // Hauptcontainer für die Mindmap
- const cyContainer = document.getElementById('cy');
- if (!cyContainer) {
- console.error('Mindmap-Container #cy nicht gefunden!');
- return;
- }
-
- // Info-Panel für Knotendetails
- const nodeInfoPanel = document.getElementById('node-info-panel');
- const nodeDescription = document.getElementById('node-description');
- const connectedNodes = document.getElementById('connected-nodes');
-
- // Toolbar-Buttons
- const fitButton = document.getElementById('fit-btn');
- const resetButton = document.getElementById('reset-btn');
- const toggleLabelsButton = document.getElementById('toggle-labels-btn');
-
- // Mindmap-Instanz
- let mindmap = null;
-
- // Cytoscape.js für die Visualisierung initialisieren
- try {
- // Cytoscape.js-Bibliothek überprüfen
- if (typeof cytoscape === 'undefined') {
- loadExternalScript('https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js')
- .then(() => {
- initCytoscape();
- })
- .catch(error => {
- console.error('Fehler beim Laden von Cytoscape.js:', error);
- showErrorMessage(cyContainer, 'Cytoscape.js konnte nicht geladen werden.');
- });
- } else {
- initCytoscape();
- }
- } catch (error) {
- console.error('Fehler bei der Initialisierung der Mindmap:', error);
- showErrorMessage(cyContainer, 'Die Mindmap konnte nicht initialisiert werden: ' + error.message);
- }
-
- /**
- * Lädt ein externes Script asynchron
- */
- function loadExternalScript(url) {
- return new Promise((resolve, reject) => {
- const script = document.createElement('script');
- script.src = url;
- script.onload = resolve;
- script.onerror = reject;
- document.head.appendChild(script);
- });
- }
-
- /**
- * Zeigt eine Fehlermeldung im Container an
- */
- function showErrorMessage(container, message) {
- container.innerHTML = `
-
-
-
${message}
-
Bitte laden Sie die Seite neu oder kontaktieren Sie den Support.
-
- `;
- }
-
- /**
- * Initialisiert Cytoscape mit den Mindmap-Daten
- */
- function initCytoscape() {
- console.log('Cytoscape.js wird initialisiert...');
-
- // Zeige Ladeanimation
- cyContainer.innerHTML = `
-
- `;
-
- // Lade Daten vom Backend
- fetch('/api/mindmap')
- .then(response => {
- if (!response.ok) {
- throw new Error('Netzwerkfehler beim Laden der Mindmap-Daten');
- }
- return response.json();
- })
- .then(data => {
- console.log('Mindmap-Daten erfolgreich geladen');
- renderMindmap(data);
- })
- .catch(error => {
- console.error('Fehler beim Laden der Mindmap-Daten:', error);
-
- // Verwende Standarddaten als Fallback
- console.log('Verwende Standarddaten als Fallback...');
- const defaultData = generateDefaultData();
- renderMindmap(defaultData);
+ // Warte auf die Cytoscape-Instanz
+ document.addEventListener('mindmap-loaded', function() {
+ const cy = window.cy;
+ if (!cy) return;
+
+ // Event-Listener für Knoten-Klicks
+ cy.on('tap', 'node', function(evt) {
+ const node = evt.target;
+
+ // Alle vorherigen Hervorhebungen zurücksetzen
+ cy.nodes().forEach(n => {
+ n.removeStyle();
+ n.connectedEdges().removeStyle();
});
+
+ // Speichere ausgewählten Knoten
+ window.mindmapInstance.selectedNode = node;
+
+ // 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);
+ });
+
+ // Klick auf Hintergrund - Auswahl zurücksetzen
+ cy.on('tap', function(evt) {
+ if (evt.target === cy) {
+ resetSelection(cy);
+ }
+ });
+
+ // Zoom-Controls
+ document.getElementById('zoomIn')?.addEventListener('click', () => {
+ cy.zoom({
+ level: cy.zoom() * 1.2,
+ renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
+ });
+ });
+
+ document.getElementById('zoomOut')?.addEventListener('click', () => {
+ cy.zoom({
+ level: cy.zoom() / 1.2,
+ renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
+ });
+ });
+
+ document.getElementById('resetView')?.addEventListener('click', () => {
+ cy.fit();
+ resetSelection(cy);
+ });
+
+ // Legend-Toggle
+ document.getElementById('toggleLegend')?.addEventListener('click', () => {
+ const legend = document.getElementById('categoryLegend');
+ if (legend) {
+ isLegendVisible = !isLegendVisible;
+ legend.style.display = isLegendVisible ? 'block' : 'none';
+ }
+ });
+
+ // Keyboard-Controls
+ document.addEventListener('keydown', (e) => {
+ if (e.key === '+' || e.key === '=') {
+ cy.zoom({
+ level: cy.zoom() * 1.2,
+ renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
+ });
+ } else if (e.key === '-' || e.key === '_') {
+ cy.zoom({
+ level: cy.zoom() / 1.2,
+ renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
+ });
+ } else if (e.key === 'Escape') {
+ resetSelection(cy);
+ }
+ });
+ });
+}
+
+/**
+ * Aktualisiert das Info-Panel mit Knoteninformationen
+ * @param {Object} node - Der ausgewählte Knoten
+ */
+function updateInfoPanel(node) {
+ const infoPanel = document.getElementById('infoPanel');
+ if (!infoPanel) return;
+
+ const data = node.data();
+ const connectedNodes = node.neighborhood('node');
+
+ let html = `
+ ${data.label || data.name}
+ ${data.category || 'Keine Kategorie'}
+ ${data.description ? `${data.description}
` : ''}
+
+
Verbindungen (${connectedNodes.length})
+
+ `;
+
+ connectedNodes.forEach(connectedNode => {
+ const connectedData = connectedNode.data();
+ html += `
+ -
+ ${connectedData.label || connectedData.name}
+
+ `;
+ });
+
+ html += `
+
+
+ `;
+
+ infoPanel.innerHTML = html;
+ infoPanel.style.display = 'block';
+}
+
+/**
+ * Aktualisiert die Seitenleiste mit Knoteninformationen
+ * @param {Object} node - Der ausgewählte Knoten
+ */
+function updateSidebar(node) {
+ const sidebar = document.getElementById('sidebar');
+ if (!sidebar) return;
+
+ const data = node.data();
+ const connectedNodes = node.neighborhood('node');
+
+ let html = `
+
+
${data.label || data.name}
+
${data.category || 'Keine Kategorie'}
+ ${data.description ? `
${data.description}
` : ''}
+
+
Verbindungen (${connectedNodes.length})
+
+ `;
+
+ connectedNodes.forEach(connectedNode => {
+ const connectedData = connectedNode.data();
+ html += `
+ -
+ ${connectedData.label || connectedData.name}
+
+ `;
+ });
+
+ html += `
+
+
+
+ `;
+
+ sidebar.innerHTML = html;
+}
+
+/**
+ * Setzt die Auswahl zurück
+ * @param {Object} cy - Cytoscape-Instanz
+ */
+function resetSelection(cy) {
+ window.mindmapInstance.selectedNode = null;
+
+ // Alle Hervorhebungen zurücksetzen
+ cy.nodes().forEach(node => {
+ node.removeStyle();
+ node.connectedEdges().removeStyle();
+ });
+
+ // Info-Panel ausblenden
+ const infoPanel = document.getElementById('infoPanel');
+ if (infoPanel) {
+ infoPanel.style.display = 'none';
+ }
+
+ // Seitenleiste leeren
+ const sidebar = document.getElementById('sidebar');
+ if (sidebar) {
+ sidebar.innerHTML = '';
+ }
}
/**
@@ -435,4 +546,6 @@ function initMindmapPage() {
];
return colors[Math.floor(Math.random() * colors.length)];
}
-}
\ No newline at end of file
+
+// Initialisiere die Mindmap-Seite
+initMindmapPage();
\ No newline at end of file
diff --git a/static/js/modules/mindmap.js b/static/js/modules/mindmap.js
deleted file mode 100644
index e5eba3a..0000000
Binary files a/static/js/modules/mindmap.js and /dev/null differ
diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js
index be81ba7..08e6063 100644
--- a/static/js/update_mindmap.js
+++ b/static/js/update_mindmap.js
@@ -1,28 +1,538 @@
/**
* 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.
+ * Implementiert Lazy Loading & Progressive Disclosure
*/
+// Mock-Datenbank für Subthemen (später durch echte DB-Abfragen ersetzen)
+const subthemesDatabase = {
+ 'philosophy': [
+ {
+ id: 'epistemology',
+ label: 'Erkenntnistheorie',
+ category: 'Philosophie',
+ description: 'Untersuchung der Natur und Grenzen menschlicher Erkenntnis',
+ hasChildren: true
+ },
+ {
+ id: 'ethics',
+ label: 'Ethik',
+ category: 'Philosophie',
+ description: 'Lehre vom moralisch richtigen Handeln',
+ hasChildren: true
+ },
+ {
+ id: 'metaphysics',
+ label: 'Metaphysik',
+ category: 'Philosophie',
+ description: 'Grundfragen des Seins und der Wirklichkeit',
+ hasChildren: true
+ }
+ ],
+ 'science': [
+ {
+ id: 'physics',
+ label: 'Physik',
+ category: 'Wissenschaft',
+ description: 'Lehre von der Materie und ihren Wechselwirkungen',
+ hasChildren: true
+ },
+ {
+ id: 'biology',
+ label: 'Biologie',
+ category: 'Wissenschaft',
+ description: 'Lehre von den Lebewesen und ihren Lebensprozessen',
+ hasChildren: true
+ },
+ {
+ id: 'chemistry',
+ label: 'Chemie',
+ category: 'Wissenschaft',
+ description: 'Wissenschaft von den Stoffen und ihren Reaktionen',
+ hasChildren: true
+ }
+ ],
+ 'technology': [
+ {
+ id: 'ai',
+ label: 'Künstliche Intelligenz',
+ category: 'Technologie',
+ description: 'Maschinelles Lernen und intelligente Systeme',
+ hasChildren: true
+ },
+ {
+ id: 'robotics',
+ label: 'Robotik',
+ category: 'Technologie',
+ description: 'Entwicklung und Steuerung von Robotern',
+ hasChildren: true
+ },
+ {
+ id: 'quantum_computing',
+ label: 'Quantencomputing',
+ category: 'Technologie',
+ description: 'Computer basierend auf Quantenmechanik',
+ hasChildren: true
+ }
+ ],
+ 'arts': [
+ {
+ id: 'visual_arts',
+ label: 'Bildende Kunst',
+ category: 'Künste',
+ description: 'Malerei, Bildhauerei und andere visuelle Kunstformen',
+ hasChildren: true
+ },
+ {
+ id: 'music',
+ label: 'Musik',
+ category: 'Künste',
+ description: 'Tonkunst und musikalische Komposition',
+ hasChildren: true
+ },
+ {
+ id: 'literature',
+ label: 'Literatur',
+ category: 'Künste',
+ description: 'Schriftliche Kunstwerke und Poesie',
+ hasChildren: true
+ }
+ ]
+};
+
+// Icon-Definitionen für Kategorien (FontAwesome oder SVG-URL)
+const categoryIcons = {
+ 'Philosophie': 'fa-solid fa-lightbulb',
+ 'Wissenschaft': 'fa-solid fa-atom',
+ 'Technologie': 'fa-solid fa-microchip',
+ 'Künste': 'fa-solid fa-palette',
+ 'Psychologie': 'fa-solid fa-brain'
+};
+
+// Farben für Kategorien (wie im Bild)
+const categoryColors = {
+ 'Philosophie': '#b71c1c', // Rot
+ 'Wissenschaft': '#f4b400', // Gelb
+ 'Technologie': '#0d47a1', // Blau
+ 'Künste': '#c2185b', // Pink
+ 'Psychologie': '#009688' // Türkis
+};
+
+// Initiale Mindmap-Daten (nur oberste Ebene, mit Icon und Farbe)
+const mindmapData = {
+ nodes: [
+ {
+ id: 'center',
+ label: 'Wissenskarte',
+ isCenter: true,
+ color: '#f5f5f5',
+ icon: 'fa-solid fa-circle',
+ fontColor: '#222',
+ fontSize: 22
+ },
+ {
+ id: 'philosophy',
+ label: 'Philosophie',
+ category: 'Philosophie',
+ description: 'Die Lehre vom Denken und der Erkenntnis',
+ hasChildren: true,
+ expanded: false,
+ neuronSize: 8,
+ neuronActivity: 0.8,
+ color: categoryColors['Philosophie'],
+ icon: categoryIcons['Philosophie'],
+ fontColor: '#fff',
+ fontSize: 18
+ },
+ {
+ id: 'science',
+ label: 'Wissenschaft',
+ category: 'Wissenschaft',
+ description: 'Systematische Erforschung der Natur und Gesellschaft',
+ hasChildren: true,
+ expanded: false,
+ neuronSize: 8,
+ neuronActivity: 0.8,
+ color: categoryColors['Wissenschaft'],
+ icon: categoryIcons['Wissenschaft'],
+ fontColor: '#fff',
+ fontSize: 18
+ },
+ {
+ id: 'technology',
+ label: 'Technologie',
+ category: 'Technologie',
+ description: 'Anwendung wissenschaftlicher Erkenntnisse',
+ hasChildren: true,
+ expanded: false,
+ neuronSize: 8,
+ neuronActivity: 0.8,
+ color: categoryColors['Technologie'],
+ icon: categoryIcons['Technologie'],
+ fontColor: '#fff',
+ fontSize: 18
+ },
+ {
+ id: 'arts',
+ label: 'Künste',
+ category: 'Künste',
+ description: 'Kreativer Ausdruck und künstlerische Gestaltung',
+ hasChildren: true,
+ expanded: false,
+ neuronSize: 8,
+ neuronActivity: 0.8,
+ color: categoryColors['Künste'],
+ icon: categoryIcons['Künste'],
+ fontColor: '#fff',
+ fontSize: 18
+ }
+ ],
+ edges: [
+ { source: 'center', target: 'philosophy', strength: 0.9 },
+ { source: 'center', target: 'science', strength: 0.9 },
+ { source: 'center', target: 'technology', strength: 0.9 },
+ { source: 'center', target: 'arts', strength: 0.9 }
+ ]
+};
+
// Warte bis DOM geladen ist
document.addEventListener('DOMContentLoaded', function() {
- // Prüfe, ob wir auf der Mindmap-Seite sind
+ console.log('DOMContentLoaded Event ausgelöst');
+
+ // Prüfe, ob der Container existiert
const cyContainer = document.getElementById('cy');
+ console.log('Container gefunden:', cyContainer);
if (!cyContainer) {
- console.log('Kein Mindmap-Container gefunden, überspringe Initialisierung.');
+ console.error('Mindmap-Container #cy nicht gefunden!');
return;
}
- // Auf das Laden der Mindmap warten
- document.addEventListener('mindmap-loaded', function() {
- console.log('Mindmap geladen, füge wissenschaftliche Knoten hinzu...');
- enhanceMindmap();
+ // Prüfe, ob Cytoscape verfügbar ist
+ if (typeof cytoscape === 'undefined') {
+ console.error('Cytoscape ist nicht definiert!');
+ return;
+ }
+ console.log('Cytoscape ist verfügbar');
+
+ // Beispiel-Daten entfernt, stattdessen große Mindmap-Daten verwenden
+ const elements = [
+ // Knoten
+ ...mindmapData.nodes.map(node => ({
+ data: {
+ id: node.id,
+ label: node.label,
+ category: node.category,
+ description: node.description,
+ hasChildren: node.hasChildren,
+ expanded: node.expanded,
+ neuronSize: node.neuronSize,
+ neuronActivity: node.neuronActivity,
+ color: node.color,
+ icon: node.icon,
+ fontColor: node.fontColor,
+ fontSize: node.fontSize
+ }
+ })),
+ // Kanten
+ ...mindmapData.edges.map(edge => ({
+ data: {
+ source: edge.source,
+ target: edge.target,
+ label: edge.label,
+ strength: edge.strength
+ }
+ }))
+ ];
+
+ console.log('Initialisiere Cytoscape...');
+
+ // Initialisiere Cytoscape mit concentric/circle Layout und Icon-Overlay
+ window.cy = cytoscape({
+ container: cyContainer,
+ elements: elements,
+ style: [
+ {
+ selector: 'node',
+ style: {
+ 'background-color': 'data(color)',
+ 'label': 'data(label)',
+ 'color': 'data(fontColor)',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'font-size': 'data(fontSize)',
+ 'width': 'mapData(neuronSize, 3, 10, 60, 110)',
+ 'height': 'mapData(neuronSize, 3, 10, 60, 110)',
+ 'border-width': 4,
+ 'border-color': '#fff',
+ 'border-opacity': 0.9,
+ 'overlay-padding': 4,
+ 'z-index': 10,
+ 'shape': 'ellipse',
+ 'background-opacity': 0.95,
+ 'shadow-blur': 20,
+ 'shadow-color': 'data(color)',
+ 'shadow-opacity': 0.7,
+ 'shadow-offset-x': 0,
+ 'shadow-offset-y': 0,
+ 'text-wrap': 'wrap',
+ 'text-max-width': 90
+ }
+ },
+ {
+ selector: 'node[isCenter]','style': {
+ 'background-color': '#f5f5f5',
+ 'color': '#222',
+ 'font-size': 22,
+ 'border-width': 0,
+ 'width': 120,
+ 'height': 120
+ }
+ },
+ {
+ selector: 'node:selected',
+ style: {
+ 'border-color': '#f59e42',
+ 'border-width': 6,
+ 'shadow-blur': 30,
+ 'shadow-opacity': 0.9
+ }
+ },
+ {
+ selector: 'edge',
+ style: {
+ 'width': 'mapData(strength, 0.2, 1, 3, 7)',
+ 'line-color': '#bdbdbd',
+ 'line-opacity': 0.7,
+ 'curve-style': 'bezier',
+ 'target-arrow-shape': 'none',
+ 'control-point-distances': [40, -40],
+ 'control-point-weights': [0.5, 0.5],
+ 'edge-distances': 'intersection',
+ 'line-style': 'solid'
+ }
+ }
+ ],
+ layout: {
+ name: 'concentric',
+ fit: true,
+ padding: 60,
+ animate: true,
+ concentric: function(node) {
+ return node.data('isCenter') ? 2 : 1;
+ },
+ levelWidth: function() { return 1; }
+ }
});
+
+ console.log('Cytoscape initialisiert');
+
+ // Füge neuronale Eigenschaften zu allen Knoten hinzu
+ cy.nodes().forEach(node => {
+ const data = node.data();
+ node.data({
+ ...data,
+ neuronSize: data.neuronSize || 8,
+ neuronActivity: data.neuronActivity || 0.8,
+ refractionPeriod: Math.random() * 300 + 700,
+ threshold: Math.random() * 0.3 + 0.6,
+ lastFired: 0,
+ color: categoryColors[data.category] || '#60a5fa'
+ });
+ });
+
+ // Füge synaptische Eigenschaften zu allen Kanten hinzu
+ cy.edges().forEach(edge => {
+ const data = edge.data();
+ edge.data({
+ ...data,
+ strength: data.strength || 0.5,
+ conductionVelocity: Math.random() * 0.5 + 0.3,
+ latency: Math.random() * 100 + 50
+ });
+ });
+
+ // Starte neuronale Aktivitätssimulation
+ startNeuralActivitySimulation(cy);
+
+ // Mindmap mit echten Daten befüllen (Styles, Farben etc.)
+ updateMindmap();
+
+ // Event auslösen, damit andere Scripte reagieren können
+ document.dispatchEvent(new Event('mindmap-loaded'));
+ console.log('mindmap-loaded Event ausgelöst');
+
+ // Event-Listener für Knoten-Klicks
+ cy.on('tap', 'node', async function(evt) {
+ const node = evt.target;
+ console.log('Node clicked:', node.id(), 'hasChildren:', node.data('hasChildren'), 'expanded:', node.data('expanded'));
+
+ if (node.data('hasChildren') && !node.data('expanded')) {
+ await loadSubthemes(node);
+ }
+ });
+
+ // Nach dem Rendern: Icons als HTML-Overlay einfügen
+ setTimeout(() => {
+ cy.nodes().forEach(node => {
+ const icon = node.data('icon');
+ if (icon) {
+ const dom = cy.getElementById(node.id()).popperRef();
+ const iconDiv = document.createElement('div');
+ iconDiv.className = 'cy-node-icon';
+ iconDiv.innerHTML = ``;
+ document.body.appendChild(iconDiv);
+ // Positionierung mit Popper.js oder absolut über node.position()
+ }
+ });
+ }, 500);
});
+// Funktion zum Initialisieren des neuronalen Designs
+function initializeNeuralDesign(cy) {
+ // Füge neuronale Eigenschaften zu allen Knoten hinzu
+ cy.nodes().forEach(node => {
+ const data = node.data();
+ node.data({
+ ...data,
+ neuronSize: data.neuronSize || 8,
+ neuronActivity: data.neuronActivity || 0.8,
+ refractionPeriod: Math.random() * 300 + 700,
+ threshold: Math.random() * 0.3 + 0.6,
+ lastFired: 0,
+ color: categoryColors[data.category] || '#60a5fa'
+ });
+ });
+
+ // Füge synaptische Eigenschaften zu allen Kanten hinzu
+ cy.edges().forEach(edge => {
+ const data = edge.data();
+ edge.data({
+ ...data,
+ strength: data.strength || 0.5,
+ conductionVelocity: Math.random() * 0.5 + 0.3,
+ latency: Math.random() * 100 + 50
+ });
+ });
+
+ // Wende neuronales Styling an
+ cy.style()
+ .selector('node')
+ .style({
+ 'background-color': 'data(color)',
+ 'label': 'data(label)',
+ 'color': '#fff',
+ 'text-background-color': 'rgba(0, 0, 0, 0.7)',
+ 'text-background-opacity': 0.8,
+ 'text-background-padding': '4px',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'font-size': 16,
+ 'width': 'mapData(neuronSize, 3, 10, 30, 60)',
+ 'height': 'mapData(neuronSize, 3, 10, 30, 60)',
+ 'border-width': 2,
+ 'border-color': '#fff',
+ 'border-opacity': 0.8,
+ 'overlay-padding': 4,
+ 'z-index': 10,
+ 'shape': 'ellipse',
+ 'background-opacity': 0.85,
+ 'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 10, 20)',
+ 'shadow-color': 'data(color)',
+ 'shadow-opacity': 0.6,
+ 'shadow-offset-x': 0,
+ 'shadow-offset-y': 0
+ })
+ .selector('edge')
+ .style({
+ 'width': 'mapData(strength, 0.2, 1, 1, 3)',
+ 'line-color': '#a78bfa',
+ 'line-opacity': 'mapData(strength, 0.2, 1, 0.4, 0.8)',
+ 'target-arrow-color': '#a78bfa',
+ 'target-arrow-shape': 'none',
+ 'curve-style': 'bezier',
+ 'control-point-distances': [20, -20],
+ 'control-point-weights': [0.5, 0.5],
+ 'edge-distances': 'intersection',
+ 'loop-direction': '-45deg',
+ 'loop-sweep': '-90deg',
+ 'line-style': function(ele) {
+ const strength = ele.data('strength');
+ if (strength <= 0.4) return 'dotted';
+ if (strength <= 0.6) return 'dashed';
+ return 'solid';
+ }
+ })
+ .update();
+
+ // Starte neuronale Aktivitätssimulation
+ startNeuralActivitySimulation(cy);
+}
+
+// Modifiziere die updateMindmap Funktion
+function updateMindmap() {
+ if (!cy) return;
+
+ // Bestehende Elemente entfernen
+ cy.elements().remove();
+
+ // Neue Knoten hinzufügen
+ mindmapData.nodes.forEach(node => {
+ cy.add({
+ group: 'nodes',
+ data: {
+ id: node.id,
+ label: node.label,
+ category: node.category,
+ description: node.description,
+ hasChildren: node.hasChildren,
+ expanded: node.expanded,
+ neuronSize: node.neuronSize,
+ neuronActivity: node.neuronActivity,
+ color: node.color,
+ icon: node.icon,
+ fontColor: node.fontColor,
+ fontSize: node.fontSize
+ }
+ });
+ });
+
+ // Neue Kanten hinzufügen
+ mindmapData.edges.forEach(edge => {
+ cy.add({
+ group: 'edges',
+ data: {
+ source: edge.source,
+ target: edge.target,
+ strength: edge.strength
+ }
+ });
+ });
+
+ // Neuronales Design initialisieren
+ initializeNeuralDesign(cy);
+
+ // Layout anwenden
+ cy.layout({
+ name: 'cose',
+ animate: true,
+ animationDuration: 1000,
+ nodeDimensionsIncludeLabels: true,
+ padding: 100,
+ spacingFactor: 1.8,
+ randomize: false,
+ fit: true,
+ componentSpacing: 100,
+ nodeRepulsion: 8000,
+ edgeElasticity: 100,
+ nestingFactor: 1.2,
+ gravity: 80
+ }).run();
+}
+
/**
- * 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 +543,226 @@ 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) {
+ if (window.neuralInterval) clearInterval(window.neuralInterval);
+
+ const nodes = cy.nodes();
+ const edges = cy.edges();
+ let currentTime = Date.now();
+
+ // Neuronale Aktivität simulieren
+ function simulateNeuralActivity() {
+ currentTime = Date.now();
+
+ // Zufällige Neuronen "feuern" lassen
+ nodes.forEach(node => {
+ const data = node.data();
+ const lastFired = data.lastFired || 0;
+ const timeSinceLastFire = currentTime - lastFired;
+
+ // Prüfen ob Neuron feuern kann (Refraktionsperiode)
+ if (timeSinceLastFire > data.refractionPeriod) {
+ // Zufälliges Feuern basierend auf Aktivität
+ if (Math.random() < data.neuronActivity * 0.1) {
+ fireNeuron(node, true, currentTime);
+ }
+ }
+ });
+ }
+
+ // Neuron feuern lassen
+ function fireNeuron(node, state, currentTime) {
+ const data = node.data();
+ data.lastFired = currentTime;
+
+ // Visuelles Feedback
+ node.style({
+ 'background-opacity': 1,
+ 'shadow-blur': 25,
+ 'shadow-opacity': 0.9
+ });
+
+ // Nach kurzer Zeit zurück zum Normalzustand
+ setTimeout(() => {
+ node.style({
+ 'background-opacity': 0.85,
+ 'shadow-blur': 18,
+ 'shadow-opacity': 0.6
+ });
+ }, 200);
+
+ // Signal weiterleiten
+ if (state) {
+ propagateSignal(node, currentTime);
}
+ }
+
+ // Signal über Kanten weiterleiten
+ function propagateSignal(sourceNode, currentTime) {
+ const outgoingEdges = sourceNode.connectedEdges('out');
+
+ outgoingEdges.forEach(edge => {
+ const targetNode = edge.target();
+ const edgeData = edge.data();
+ const latency = edgeData.latency;
+
+ // Signal mit Verzögerung weiterleiten
+ setTimeout(() => {
+ const targetData = targetNode.data();
+ const timeSinceLastFire = currentTime - (targetData.lastFired || 0);
+
+ // Prüfen ob Zielneuron feuern kann
+ if (timeSinceLastFire > targetData.refractionPeriod) {
+ // Signalstärke berechnen
+ const signalStrength = edgeData.strength *
+ edgeData.conductionVelocity *
+ sourceNode.data('neuronActivity');
+
+ // Neuron feuern lassen wenn Signal stark genug
+ if (signalStrength > targetData.threshold) {
+ fireNeuron(targetNode, true, currentTime + latency);
+ }
+ }
+ }, latency);
+ });
+ }
+
+ // Simulation starten
+ window.neuralInterval = setInterval(simulateNeuralActivity, 100);
}
// Hilfe-Funktion zum Hinzufügen eines Flash-Hinweises
@@ -90,4 +801,204 @@ function createFlashContainer() {
container.className = 'fixed top-4 right-4 z-50 w-64';
document.body.appendChild(container);
return container;
-}
\ No newline at end of file
+}
+
+// Funktion zum Laden der Subthemen
+async function loadSubthemes(node) {
+ try {
+ console.log('Loading subthemes for node:', node.id());
+
+ // Simuliere Datenbankabfrage
+ const subthemes = subthemesDatabase[node.id()];
+
+ if (!subthemes) {
+ console.log('No subthemes found for node:', node.id());
+ return;
+ }
+
+ // Animation starten
+ showFlash('Lade Subthemen...', 'info');
+
+ // Neue Seite erstellen
+ const mindmapContainer = document.querySelector('.mindmap-container');
+ const newPage = document.createElement('div');
+ newPage.className = 'mindmap-page';
+ newPage.style.display = 'none';
+
+ // Kopfzeile erstellen
+ const header = document.createElement('div');
+ header.className = 'mindmap-header';
+ header.innerHTML = `
+
+ ${node.data('label')}
+ `;
+
+ // Container für die neue Mindmap
+ const newContainer = document.createElement('div');
+ newContainer.id = `cy-${node.id()}`;
+ newContainer.className = 'mindmap-view';
+
+ // Elemente zur Seite hinzufügen
+ newPage.appendChild(header);
+ newPage.appendChild(newContainer);
+ mindmapContainer.appendChild(newPage);
+
+ // Neue Cytoscape-Instanz erstellen
+ const newCy = cytoscape({
+ container: newContainer,
+ elements: [],
+ style: cy.style(),
+ layout: {
+ name: 'cose',
+ animate: true,
+ animationDuration: 500,
+ refresh: 20,
+ fit: true,
+ padding: 30,
+ nodeRepulsion: 4500,
+ idealEdgeLength: 50,
+ edgeElasticity: 0.45
+ }
+ });
+
+ // Neue Knoten hinzufügen
+ subthemes.forEach(subtheme => {
+ newCy.add({
+ group: 'nodes',
+ data: {
+ id: subtheme.id,
+ label: subtheme.label,
+ category: subtheme.category,
+ description: subtheme.description,
+ hasChildren: subtheme.hasChildren,
+ expanded: false,
+ neuronSize: 6,
+ neuronActivity: 0.7
+ }
+ });
+ });
+
+ // Neuronales Design für die neue Mindmap initialisieren
+ initializeNeuralDesign(newCy);
+
+ // Event-Listener für die neue Mindmap
+ newCy.on('tap', 'node', async function(evt) {
+ const clickedNode = evt.target;
+ if (clickedNode.data('hasChildren') && !clickedNode.data('expanded')) {
+ await loadSubthemes(clickedNode);
+ }
+ });
+
+ // Alte Seite ausblenden und neue anzeigen
+ cy.container().style.display = 'none';
+ newPage.style.display = 'block';
+
+ // Layout ausführen
+ newCy.layout().run();
+
+ // Erfolgsmeldung anzeigen
+ showFlash('Subthemen erfolgreich geladen', 'success');
+
+ // Icons für Subthemen wie oben einfügen
+ setTimeout(() => {
+ newCy.nodes().forEach(node => {
+ const icon = node.data('icon');
+ if (icon) {
+ const dom = newCy.getElementById(node.id()).popperRef();
+ const iconDiv = document.createElement('div');
+ iconDiv.className = 'cy-node-icon';
+ iconDiv.innerHTML = ``;
+ document.body.appendChild(iconDiv);
+ }
+ });
+ }, 500);
+
+ } catch (error) {
+ console.error('Fehler beim Laden der Subthemen:', error);
+ showFlash('Fehler beim Laden der Subthemen', 'error');
+ }
+}
+
+// Funktion zum Zurücknavigieren
+function goBack() {
+ const currentPage = document.querySelector('.mindmap-page:not([style*="display: none"])');
+ if (currentPage) {
+ currentPage.style.display = 'none';
+ cy.container().style.display = 'block';
+ }
+}
+
+// CSS-Styles für die neue Seite
+const style = document.createElement('style');
+style.textContent = `
+ .mindmap-page {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: var(--bg-color, #1a1a1a);
+ z-index: 1000;
+ }
+
+ .mindmap-header {
+ display: flex;
+ align-items: center;
+ padding: 1rem;
+ background: rgba(0, 0, 0, 0.2);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+ }
+
+ .back-button {
+ background: none;
+ border: none;
+ color: #fff;
+ cursor: pointer;
+ padding: 0.5rem;
+ margin-right: 1rem;
+ border-radius: 50%;
+ transition: background-color 0.3s;
+ }
+
+ .back-button:hover {
+ background: rgba(255, 255, 255, 0.1);
+ }
+
+ .mindmap-title {
+ color: #fff;
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin: 0;
+ }
+
+ .mindmap-view {
+ width: 100%;
+ height: calc(100% - 4rem);
+ }
+
+ /* Neuronale Effekte */
+ .cy-container {
+ background: linear-gradient(45deg, #1a1a1a, #2a2a2a);
+ }
+
+ .cy-container node {
+ transition: all 0.3s ease;
+ }
+
+ .cy-container node:hover {
+ filter: brightness(1.2);
+ }
+
+ .cy-node-icon {
+ position: absolute;
+ pointer-events: none;
+ z-index: 1001;
+ color: #fff;
+ text-shadow: 0 2px 8px rgba(0,0,0,0.25);
+ }
+`;
+document.head.appendChild(style);
\ No newline at end of file
diff --git a/static/mindmap.js b/static/mindmap.js
deleted file mode 100644
index f6a2257..0000000
--- a/static/mindmap.js
+++ /dev/null
@@ -1,2061 +0,0 @@
-/**
- * MindMap D3.js Modul
- * Visualisiert die Mindmap mit D3.js
- */
-
-class MindMapVisualization {
- constructor(containerSelector, options = {}) {
- this.containerSelector = containerSelector;
- this.container = d3.select(containerSelector);
- this.width = options.width || this.container.node().clientWidth || 800;
- this.height = options.height || 800; // Erhöhung der Standardhöhe für bessere Sichtbarkeit
- this.nodeRadius = options.nodeRadius || 22;
- this.selectedNodeRadius = options.selectedNodeRadius || 28;
- this.linkDistance = options.linkDistance || 150;
- this.chargeStrength = options.chargeStrength || -1000;
- this.centerForce = options.centerForce || 0.15;
- this.onNodeClick = options.onNodeClick || ((node) => console.log('Node clicked:', node));
-
- this.nodes = [];
- this.links = [];
- this.simulation = null;
- this.svg = null;
- this.linkElements = null;
- this.nodeElements = null;
- this.textElements = null;
- this.tooltipEnabled = options.tooltipEnabled !== undefined ? options.tooltipEnabled : true;
-
- this.mouseoverNode = null;
- this.selectedNode = null;
-
- this.zoomFactor = 1;
- this.tooltipDiv = null;
- this.isLoading = true;
-
- // Flash-Nachrichten-Container
- this.flashContainer = null;
-
- // Authentischere Farbpalette mit dezenten, professionellen Farben
- this.colorPalette = {
- 'default': '#6b7280', // Grau
- 'root': '#4f46e5', // Indigo
- 'philosophy': '#3b82f6', // Blau
- 'science': '#10b981', // Smaragdgrün
- 'technology': '#6366f1', // Indigo
- 'arts': '#8b5cf6', // Violett
- 'ai': '#7c3aed', // Violett
- 'ethics': '#475569', // Slate
- 'math': '#0369a1', // Dunkelblau
- 'psychology': '#0284c7', // Hellblau
- 'biology': '#059669', // Grün
- 'literature': '#4338ca', // Indigo
- 'history': '#0f766e', // Teal
- 'economics': '#374151', // Dunkelgrau
- 'sociology': '#4f46e5', // Indigo
- 'design': '#0891b2', // Cyan
- 'languages': '#2563eb' // Blau
- };
-
- // Sicherstellen, dass der Container bereit ist
- if (this.container.node()) {
- this.init();
- this.setupDefaultNodes();
- this.setupFlashMessages();
-
- // Sofortige Datenladung
- window.setTimeout(() => {
- this.loadData();
- }, 100);
- } else {
- console.error('Mindmap-Container nicht gefunden:', containerSelector);
- }
- }
-
- // Flash-Nachrichten-System einrichten
- setupFlashMessages() {
- // Flash-Container erstellen, falls er noch nicht existiert
- if (!document.getElementById('mindmap-flash-container')) {
- this.flashContainer = document.createElement('div');
- this.flashContainer.id = 'mindmap-flash-container';
- this.flashContainer.className = 'mindmap-flash-container';
- this.flashContainer.style.position = 'fixed';
- this.flashContainer.style.top = '20px';
- this.flashContainer.style.right = '20px';
- this.flashContainer.style.zIndex = '1000';
- this.flashContainer.style.maxWidth = '350px';
- this.flashContainer.style.display = 'flex';
- this.flashContainer.style.flexDirection = 'column';
- this.flashContainer.style.gap = '10px';
- document.body.appendChild(this.flashContainer);
- } else {
- this.flashContainer = document.getElementById('mindmap-flash-container');
- }
-
- // Prüfen, ob Server-seitige Flash-Nachrichten existieren und anzeigen
- this.checkForServerFlashMessages();
- }
-
- // Prüft auf Server-seitige Flash-Nachrichten
- async checkForServerFlashMessages() {
- try {
- const response = await fetch('/api/get_flash_messages');
- if (response.ok) {
- const messages = await response.json();
- messages.forEach(message => {
- this.showFlash(message.message, message.category);
- });
- }
- } catch (err) {
- console.error('Fehler beim Abrufen der Flash-Nachrichten:', err);
- }
- }
-
- // Zeigt eine Flash-Nachricht an
- showFlash(message, type = 'info', duration = 5000) {
- if (!this.flashContainer) return;
-
- const flashElement = document.createElement('div');
- flashElement.className = `mindmap-flash flash-${type}`;
- flashElement.style.padding = '12px 18px';
- flashElement.style.borderRadius = '8px';
- flashElement.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
- flashElement.style.display = 'flex';
- flashElement.style.alignItems = 'center';
- flashElement.style.justifyContent = 'space-between';
- flashElement.style.fontSize = '14px';
- flashElement.style.fontWeight = '500';
- flashElement.style.backdropFilter = 'blur(10px)';
- flashElement.style.opacity = '0';
- flashElement.style.transform = 'translateY(-20px)';
- flashElement.style.transition = 'all 0.3s ease';
-
- // Spezifische Stile je nach Nachrichtentyp
- switch(type) {
- case 'success':
- flashElement.style.backgroundColor = 'rgba(34, 197, 94, 0.9)';
- flashElement.style.borderLeft = '5px solid #16a34a';
- flashElement.style.color = 'white';
- break;
- case 'error':
- flashElement.style.backgroundColor = 'rgba(239, 68, 68, 0.9)';
- flashElement.style.borderLeft = '5px solid #dc2626';
- flashElement.style.color = 'white';
- break;
- case 'warning':
- flashElement.style.backgroundColor = 'rgba(245, 158, 11, 0.9)';
- flashElement.style.borderLeft = '5px solid #d97706';
- flashElement.style.color = 'white';
- break;
- default: // info
- flashElement.style.backgroundColor = 'rgba(59, 130, 246, 0.9)';
- flashElement.style.borderLeft = '5px solid #2563eb';
- flashElement.style.color = 'white';
- }
-
- // Icon je nach Nachrichtentyp
- let icon = '';
- switch(type) {
- case 'success':
- icon = '';
- break;
- case 'error':
- icon = '';
- break;
- case 'warning':
- icon = '';
- break;
- default:
- icon = '';
- }
-
- // Inhalt der Nachricht mit Icon
- const contentWrapper = document.createElement('div');
- contentWrapper.style.display = 'flex';
- contentWrapper.style.alignItems = 'center';
- contentWrapper.style.gap = '12px';
-
- const iconElement = document.createElement('div');
- iconElement.className = 'flash-icon';
- iconElement.innerHTML = icon;
-
- const textElement = document.createElement('div');
- textElement.className = 'flash-text';
- textElement.textContent = message;
-
- contentWrapper.appendChild(iconElement);
- contentWrapper.appendChild(textElement);
-
- // Schließen-Button
- const closeButton = document.createElement('button');
- closeButton.className = 'flash-close';
- closeButton.innerHTML = '';
- closeButton.style.background = 'none';
- closeButton.style.border = 'none';
- closeButton.style.color = 'currentColor';
- closeButton.style.cursor = 'pointer';
- closeButton.style.marginLeft = '15px';
- closeButton.style.padding = '3px';
- closeButton.style.fontSize = '14px';
- closeButton.style.opacity = '0.7';
- closeButton.style.transition = 'opacity 0.2s';
-
- closeButton.addEventListener('mouseover', () => {
- closeButton.style.opacity = '1';
- });
-
- closeButton.addEventListener('mouseout', () => {
- closeButton.style.opacity = '0.7';
- });
-
- closeButton.addEventListener('click', () => {
- this.removeFlash(flashElement);
- });
-
- // Zusammenfügen
- flashElement.appendChild(contentWrapper);
- flashElement.appendChild(closeButton);
-
- // Zum Container hinzufügen
- this.flashContainer.appendChild(flashElement);
-
- // Animation einblenden
- setTimeout(() => {
- flashElement.style.opacity = '1';
- flashElement.style.transform = 'translateY(0)';
- }, 10);
-
- // Automatisches Ausblenden nach der angegebenen Zeit
- if (duration > 0) {
- setTimeout(() => {
- this.removeFlash(flashElement);
- }, duration);
- }
-
- return flashElement;
- }
-
- // Entfernt eine Flash-Nachricht mit Animation
- removeFlash(flashElement) {
- if (!flashElement) return;
-
- flashElement.style.opacity = '0';
- flashElement.style.transform = 'translateY(-20px)';
-
- setTimeout(() => {
- if (flashElement.parentNode) {
- flashElement.parentNode.removeChild(flashElement);
- }
- }, 300);
- }
-
- // Standardknoten als Fallback einrichten, falls die API nicht reagiert
- setupDefaultNodes() {
- // Basis-Mindmap mit Hauptthemen
- const defaultNodes = [
- { id: "root", name: "Wissen", description: "Zentrale Wissensbasis", thought_count: 3 },
- { id: "philosophy", name: "Philosophie", description: "Philosophisches Denken", thought_count: 2 },
- { id: "science", name: "Wissenschaft", description: "Wissenschaftliche Erkenntnisse", thought_count: 5 },
- { id: "technology", name: "Technologie", description: "Technologische Entwicklungen", thought_count: 7 },
- { id: "arts", name: "Künste", description: "Künstlerische Ausdrucksformen", thought_count: 4 },
- { id: "ai", name: "Künstliche Intelligenz", description: "KI-Forschung und Anwendungen", thought_count: 6 },
- { id: "ethics", name: "Ethik", description: "Moralische Grundsätze", thought_count: 2 },
- { id: "math", name: "Mathematik", description: "Mathematische Konzepte", thought_count: 3 },
- { id: "psychology", name: "Psychologie", description: "Menschliches Verhalten und Kognition", thought_count: 4 },
- { id: "biology", name: "Biologie", description: "Lebenswissenschaften", thought_count: 3 },
- { id: "literature", name: "Literatur", description: "Literarische Werke und Analysen", thought_count: 2 }
- ];
-
- const defaultLinks = [
- { source: "root", target: "philosophy" },
- { source: "root", target: "science" },
- { source: "root", target: "technology" },
- { source: "root", target: "arts" },
- { source: "science", target: "math" },
- { source: "science", target: "biology" },
- { source: "technology", target: "ai" },
- { source: "philosophy", target: "ethics" },
- { source: "philosophy", target: "psychology" },
- { source: "arts", target: "literature" },
- { source: "ai", target: "ethics" },
- { source: "psychology", target: "biology" }
- ];
-
- // Als Fallback verwenden, falls die API fehlschlägt
- this.defaultNodes = defaultNodes;
- this.defaultLinks = defaultLinks;
- }
-
- init() {
- // SVG erstellen, wenn noch nicht vorhanden
- if (!this.svg) {
- // Container zuerst leeren und Loading-State ausblenden
- const loadingOverlay = this.container.select('.mindmap-loading');
- if (loadingOverlay) {
- loadingOverlay.style('opacity', 0.8);
- }
-
- this.svg = this.container
- .append('svg')
- .attr('width', '100%')
- .attr('height', this.height)
- .attr('viewBox', `0 0 ${this.width} ${this.height}`)
- .attr('class', 'mindmap-svg')
- .call(
- d3.zoom()
- .scaleExtent([0.1, 5])
- .on('zoom', (event) => {
- this.handleZoom(event.transform);
- })
- );
-
- // Hauptgruppe für alles, was zoom-transformierbar ist
- this.g = this.svg.append('g');
-
- // SVG-Definitionen für Filter und Effekte
- const defs = this.g.append('defs');
-
- // Verbesserte Glasmorphismus- und Glow-Effekte
-
- // Basis Glow-Effekt
- D3Extensions.createGlowFilter(defs, 'glow-effect', '#b38fff', 8);
-
- // Spezifische Effekte für verschiedene Zustände
- D3Extensions.createGlowFilter(defs, 'hover-glow', '#58a9ff', 6);
- D3Extensions.createGlowFilter(defs, 'selected-glow', '#b38fff', 10);
-
- // Schatten für alle Knoten
- D3Extensions.createShadowFilter(defs, 'shadow-effect');
-
- // Glasmorphismus-Effekt für Knoten
- D3Extensions.createGlassMorphismFilter(defs, 'glass-effect');
-
- // Erweiterte Effekte
- this.createAdvancedNodeEffects(defs);
-
- // Tooltip initialisieren mit verbessertem Glasmorphism-Stil
- if (!d3.select('body').select('.node-tooltip').size()) {
- this.tooltipDiv = d3.select('body')
- .append('div')
- .attr('class', 'node-tooltip')
- .style('opacity', 0)
- .style('position', 'absolute')
- .style('pointer-events', 'none')
- .style('background', 'rgba(24, 28, 45, 0.85)')
- .style('color', '#ffffff')
- .style('border', '1px solid rgba(179, 143, 255, 0.3)')
- .style('border-radius', '16px')
- .style('padding', '12px 16px')
- .style('font-size', '14px')
- .style('font-weight', '500')
- .style('line-height', '1.5')
- .style('max-width', '280px')
- .style('box-shadow', '0 12px 30px rgba(0, 0, 0, 0.5), 0 0 15px rgba(179, 143, 255, 0.25)')
- .style('backdrop-filter', 'blur(20px)')
- .style('-webkit-backdrop-filter', 'blur(20px)')
- .style('z-index', '1000');
- } else {
- this.tooltipDiv = d3.select('body').select('.node-tooltip');
- }
-
- // Initialisierung abgeschlossen - Loading-Overlay ausblenden
- setTimeout(() => {
- if (loadingOverlay) {
- loadingOverlay.transition()
- .duration(500)
- .style('opacity', 0)
- .on('end', function() {
- loadingOverlay.style('display', 'none');
- });
- }
- }, 1000);
- }
-
- // Force-Simulation initialisieren
- this.simulation = d3.forceSimulation()
- .force('link', d3.forceLink().id(d => d.id).distance(this.linkDistance))
- .force('charge', d3.forceManyBody().strength(this.chargeStrength))
- .force('center', d3.forceCenter(this.width / 2, this.height / 2).strength(this.centerForce))
- .force('collision', d3.forceCollide().radius(this.nodeRadius * 2.5));
-
- // Globale Mindmap-Instanz für externe Zugriffe setzen
- window.mindmapInstance = this;
- }
-
- // Erstellt erweiterte Effekte für die Knoten
- createAdvancedNodeEffects(defs) {
- // Verbesserte innere Leuchteffekte für Knoten
- const innerGlow = defs.append('filter')
- .attr('id', 'inner-glow')
- .attr('x', '-50%')
- .attr('y', '-50%')
- .attr('width', '200%')
- .attr('height', '200%');
-
- // Farbiger innerer Glühen
- innerGlow.append('feGaussianBlur')
- .attr('in', 'SourceAlpha')
- .attr('stdDeviation', 2)
- .attr('result', 'blur');
-
- innerGlow.append('feOffset')
- .attr('in', 'blur')
- .attr('dx', 0)
- .attr('dy', 0)
- .attr('result', 'offsetBlur');
-
- innerGlow.append('feFlood')
- .attr('flood-color', 'rgba(179, 143, 255, 0.8)')
- .attr('result', 'glowColor');
-
- innerGlow.append('feComposite')
- .attr('in', 'glowColor')
- .attr('in2', 'offsetBlur')
- .attr('operator', 'in')
- .attr('result', 'innerGlow');
-
- // Verbinden der Filter
- const innerGlowMerge = innerGlow.append('feMerge');
- innerGlowMerge.append('feMergeNode')
- .attr('in', 'innerGlow');
- innerGlowMerge.append('feMergeNode')
- .attr('in', 'SourceGraphic');
-
- // 3D-Glaseffekt mit verbesserter Tiefe
- D3Extensions.create3DGlassEffect(defs, '3d-glass');
-
- // Pulseffekt für Hervorhebung
- const pulseFilter = defs.append('filter')
- .attr('id', 'pulse-effect')
- .attr('x', '-50%')
- .attr('y', '-50%')
- .attr('width', '200%')
- .attr('height', '200%');
-
- // Animation definieren
- const pulseAnimation = pulseFilter.append('feComponentTransfer')
- .append('feFuncA')
- .attr('type', 'linear')
- .attr('slope', '1.5');
- }
-
- // Behandelt die Zoom-Transformation für die SVG mit verbesserter Anpassung
- handleZoom(transform) {
- this.g.attr('transform', transform);
- this.zoomFactor = transform.k;
-
- // Knotengröße dynamisch an Zoom anpassen für bessere Lesbarkeit
- if (this.nodeElements) {
- // Berechne relativen Radius basierend auf Zoom
- const nodeScaleFactor = 1 / Math.sqrt(transform.k);
- const minRadiusFactor = 0.6; // Minimale Größe beim Herauszoomen
- const maxRadiusFactor = 1.2; // Maximale Größe beim Hineinzoomen
-
- // Beschränke den Skalierungsfaktor in sinnvollen Grenzen
- const cappedScaleFactor = Math.max(minRadiusFactor, Math.min(nodeScaleFactor, maxRadiusFactor));
-
- this.nodeElements.selectAll('circle')
- .attr('r', d => {
- return d === this.selectedNode
- ? this.selectedNodeRadius * cappedScaleFactor
- : this.nodeRadius * cappedScaleFactor;
- })
- .attr('stroke-width', 2 * cappedScaleFactor); // Strichstärke anpassen
-
- // Schriftgröße dynamisch anpassen
- this.nodeElements.selectAll('text')
- .style('font-size', `${14 * cappedScaleFactor}px`)
- .attr('dy', 4 * cappedScaleFactor);
-
- // Verbindungslinien anpassen
- this.linkElements
- .attr('stroke-width', 1.5 * cappedScaleFactor)
- .attr('marker-end', transform.k < 0.6 ? 'none' : 'url(#arrowhead)'); // Pfeile bei starkem Zoom ausblenden
- }
- }
-
- // Lädt die Mindmap-Daten
- async loadData() {
- try {
- // Zeige Lade-Animation
- this.showLoading();
-
- // API-Aufruf durchführen, um die Kategorien und ihre Knoten zu laden
- const response = await fetch('/api/mindmap/public');
- if (!response.ok) {
- throw new Error('API-Fehler: ' + response.statusText);
- }
-
- const data = await response.json();
- console.log('Geladene Mindmap-Daten:', data);
-
- // Verarbeite die hierarchischen Daten in flache Knoten und Links
- const processed = this.processApiData(data);
- this.nodes = processed.nodes;
- this.links = processed.links;
-
- // Verbindungszählungen aktualisieren
- this.updateConnectionCounts();
-
- // Visualisierung aktualisieren
- this.updateVisualization();
-
- // Lade-Animation ausblenden
- this.hideLoading();
-
- // Erfolgreiche Ladung melden
- this.showFlash('Mindmap-Daten erfolgreich geladen', 'success');
- } catch (error) {
- console.error('Fehler beim Laden der Mindmap-Daten:', error);
-
- // Bei einem Fehler die Fallback-Daten verwenden
- this.nodes = this.defaultNodes;
- this.links = this.defaultLinks;
-
- // Verbindungszählungen auch für Fallback-Daten aktualisieren
- this.updateConnectionCounts();
-
- // Fehler anzeigen
- this.showError('Mindmap-Daten konnten nicht geladen werden. Verwende Standarddaten.');
- this.showFlash('Fehler beim Laden der Mindmap-Daten. Standarddaten werden angezeigt.', 'error');
-
- // Visualisierung auch im Fehlerfall aktualisieren
- this.updateVisualization();
- this.hideLoading();
- }
- }
-
- // Verarbeitet die API-Daten in das benötigte Format
- processApiData(apiData) {
- // Erstelle einen Root-Knoten, der alle Kategorien verbindet
- const rootNode = {
- id: "root",
- name: "Wissen",
- description: "Zentrale Wissensbasis",
- thought_count: 0
- };
-
- let nodes = [rootNode];
- let links = [];
-
- // Für jede Kategorie Knoten und Verbindungen erstellen
- apiData.forEach(category => {
- // Kategorie als Knoten hinzufügen
- const categoryNode = {
- id: `category_${category.id}`,
- name: category.name,
- description: category.description,
- color_code: category.color_code,
- icon: category.icon,
- thought_count: 0,
- type: 'category'
- };
-
- nodes.push(categoryNode);
-
- // Mit Root-Knoten verbinden
- links.push({
- source: "root",
- target: categoryNode.id
- });
-
- // Alle Knoten aus dieser Kategorie hinzufügen
- if (category.nodes && category.nodes.length > 0) {
- category.nodes.forEach(node => {
- // Zähle die Gedanken für die Kategorie
- categoryNode.thought_count += node.thought_count || 0;
-
- const mindmapNode = {
- id: `node_${node.id}`,
- name: node.name,
- description: node.description || '',
- color_code: node.color_code || category.color_code,
- thought_count: node.thought_count || 0,
- type: 'node',
- categoryId: category.id
- };
-
- nodes.push(mindmapNode);
-
- // Mit Kategorie-Knoten verbinden
- links.push({
- source: categoryNode.id,
- target: mindmapNode.id
- });
- });
- }
-
- // Rekursiv Unterkategorien verarbeiten
- if (category.children && category.children.length > 0) {
- this.processSubcategories(category.children, nodes, links, categoryNode.id);
- }
- });
-
- // Root-Knoten-Gedankenzähler aktualisieren
- rootNode.thought_count = nodes.reduce((sum, node) => sum + (node.thought_count || 0), 0);
-
- return { nodes, links };
- }
-
- // Verarbeitet Unterkategorien rekursiv
- processSubcategories(subcategories, nodes, links, parentId) {
- subcategories.forEach(category => {
- // Kategorie als Knoten hinzufügen
- const categoryNode = {
- id: `category_${category.id}`,
- name: category.name,
- description: category.description,
- color_code: category.color_code,
- icon: category.icon,
- thought_count: 0,
- type: 'subcategory'
- };
-
- nodes.push(categoryNode);
-
- // Mit Eltern-Kategorie verbinden
- links.push({
- source: parentId,
- target: categoryNode.id
- });
-
- // Alle Knoten aus dieser Kategorie hinzufügen
- if (category.nodes && category.nodes.length > 0) {
- category.nodes.forEach(node => {
- // Zähle die Gedanken für die Kategorie
- categoryNode.thought_count += node.thought_count || 0;
-
- const mindmapNode = {
- id: `node_${node.id}`,
- name: node.name,
- description: node.description || '',
- color_code: node.color_code || category.color_code,
- thought_count: node.thought_count || 0,
- type: 'node',
- categoryId: category.id
- };
-
- nodes.push(mindmapNode);
-
- // Mit Kategorie-Knoten verbinden
- links.push({
- source: categoryNode.id,
- target: mindmapNode.id
- });
- });
- }
-
- // Rekursiv Unterkategorien verarbeiten
- if (category.children && category.children.length > 0) {
- this.processSubcategories(category.children, nodes, links, categoryNode.id);
- }
- });
- }
-
- // Zeigt den Ladebildschirm an
- showLoading() {
- const loadingOverlay = this.container.select('.mindmap-loading');
-
- if (loadingOverlay && !loadingOverlay.empty()) {
- loadingOverlay
- .style('display', 'flex')
- .style('opacity', 1);
-
- // Ladebalken-Animation
- const progressBar = loadingOverlay.select('.loading-progress');
- if (!progressBar.empty()) {
- let progress = 0;
- const progressInterval = setInterval(() => {
- progress += Math.random() * 15;
- if (progress > 90) {
- progress = 90 + Math.random() * 5;
- clearInterval(progressInterval);
- }
- updateProgress(progress);
- }, 200);
-
- function updateProgress(progress) {
- progressBar.style('width', `${Math.min(progress, 95)}%`);
- }
- }
- }
- }
-
- // Blendet den Ladebildschirm aus
- hideLoading() {
- const loadingOverlay = this.container.select('.mindmap-loading');
-
- if (loadingOverlay && !loadingOverlay.empty()) {
- // Lade-Fortschritt auf 100% setzen
- const progressBar = loadingOverlay.select('.loading-progress');
- if (!progressBar.empty()) {
- progressBar.transition()
- .duration(300)
- .style('width', '100%');
- }
-
- // Overlay ausblenden
- setTimeout(() => {
- loadingOverlay.transition()
- .duration(500)
- .style('opacity', 0)
- .on('end', function() {
- loadingOverlay.style('display', 'none');
- });
- }, 400);
- }
- }
-
- // Verarbeitet hierarchische Daten in flache Knoten und Links
- processHierarchicalData(hierarchicalNodes, parentId = null) {
- let nodes = [];
- let links = [];
-
- for (const node of hierarchicalNodes) {
- nodes.push({
- id: node.id,
- name: node.name,
- description: node.description || '',
- thought_count: node.thought_count || 0
- });
-
- if (parentId) {
- links.push({
- source: parentId,
- target: node.id
- });
- }
-
- if (node.children && node.children.length > 0) {
- const { nodes: childNodes, links: childLinks } = this.processHierarchicalData(node.children, node.id);
- nodes = [...nodes, ...childNodes];
- links = [...links, ...childLinks];
- }
- }
-
- return { nodes, links };
- }
-
- // Generiert eine konsistente Farbe basierend auf dem Knotennamen
- generateColorFromString(str) {
- // Authentischere, dezente Farben für wissenschaftliche Darstellung
- const colors = [
- '#4f46e5', '#0369a1', '#0f766e', '#374151', '#4338ca',
- '#0284c7', '#059669', '#475569', '#6366f1', '#0891b2'
- ];
-
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- hash = str.charCodeAt(i) + ((hash << 5) - hash);
- }
-
- return colors[Math.abs(hash) % colors.length];
- }
-
- /**
- * Aktualisiert die Visualisierung basierend auf den aktuellen Daten
- */
- updateVisualization() {
- if (!this.g || !this.nodes.length) return;
-
- // Daten für Simulation vorbereiten
- // Kopieren der Knoten und Links, um Referenzen zu erhalten
- const nodes = this.nodes.map(d => Object.assign({}, d));
- const links = this.links.map(d => Object.assign({}, d));
-
- // Links erstellen oder aktualisieren
- this.linkElements = this.g.selectAll('.link')
- .data(links, d => `${d.source.id || d.source}-${d.target.id || d.target}`);
-
- // Links entfernen, die nicht mehr existieren
- this.linkElements.exit().remove();
-
- // Neue Links erstellen
- const linkEnter = this.linkElements
- .enter().append('path')
- .attr('class', 'link')
- .attr('stroke-width', 2)
- .attr('stroke', 'rgba(255, 255, 255, 0.3)')
- .attr('fill', 'none')
- .attr('marker-end', 'url(#arrowhead)');
-
- // Alle Links aktualisieren
- this.linkElements = linkEnter.merge(this.linkElements);
-
- // Pfeilspitzen für die Links definieren
- if (!this.g.select('#arrowhead').size()) {
- this.g.append('defs').append('marker')
- .attr('id', 'arrowhead')
- .attr('viewBox', '0 -5 10 10')
- .attr('refX', 28) // Abstand vom Ende des Links zum Knoten
- .attr('refY', 0)
- .attr('orient', 'auto')
- .attr('markerWidth', 6)
- .attr('markerHeight', 6)
- .attr('xoverflow', 'visible')
- .append('path')
- .attr('d', 'M 0,-3 L 8,0 L 0,3')
- .attr('fill', 'rgba(255, 255, 255, 0.6)');
- }
-
- // Knoten erstellen oder aktualisieren
- this.nodeElements = this.g.selectAll('.node')
- .data(nodes, d => d.id);
-
- // Knoten entfernen, die nicht mehr existieren
- this.nodeElements.exit().remove();
-
- // Container für neue Knoten erstellen
- const nodeEnter = this.nodeElements
- .enter().append('g')
- .attr('class', 'node')
- .call(d3.drag()
- .on('start', (event, d) => this.dragStarted(event, d))
- .on('drag', (event, d) => this.dragged(event, d))
- .on('end', (event, d) => this.dragEnded(event, d))
- )
- .on('mouseover', (event, d) => this.nodeMouseover(event, d))
- .on('mouseout', (event, d) => this.nodeMouseout(event, d))
- .on('click', (event, d) => this.nodeClicked(event, d));
-
- // Kreisformen für die Knoten hinzufügen
- nodeEnter.append('circle')
- .attr('r', d => this.nodeRadius)
- .attr('fill', d => this.getNodeColor(d))
- .attr('stroke', 'rgba(255, 255, 255, 0.12)')
- .attr('stroke-width', 2)
- .style('filter', 'url(#glass-effect)');
-
- // Label für die Knoten hinzufügen
- nodeEnter.append('text')
- .attr('class', 'node-label')
- .attr('dy', 4)
- .attr('text-anchor', 'middle')
- .text(d => this.truncateNodeLabel(d.name))
- .style('font-size', d => D3Extensions.getAdaptiveFontSize(d.name, 16, 10) + 'px');
-
- // Alle Knoten aktualisieren
- this.nodeElements = nodeEnter.merge(this.nodeElements);
-
- // Simulation mit den neuen Daten aktualisieren
- this.simulation
- .nodes(nodes)
- .force('link', d3.forceLink(links).id(d => d.id).distance(this.linkDistance));
-
- // Simulation-Tick-Funktion setzen
- this.simulation.on('tick', () => this.ticked());
-
- // Simulation neustarten
- this.simulation.alpha(1).restart();
-
- // Nach kurzer Verzögerung die Knoten mit zusätzlichen Effekten versehen
- setTimeout(() => {
- this.nodeElements.selectAll('circle')
- .transition()
- .duration(500)
- .attr('r', d => {
- // Größe abhängig von der Anzahl der Gedanken
- const baseRadius = this.nodeRadius;
- const bonus = d.thought_count ? Math.min(d.thought_count / 3, 6) : 0;
- return baseRadius + bonus;
- });
- }, 300);
- }
-
- // Lange Knotenbeschriftungen abkürzen
- truncateNodeLabel(label) {
- if (!label) return '';
-
- const maxLength = 18; // Maximale Zeichenlänge
-
- if (label.length <= maxLength) {
- return label;
- } else {
- return label.substring(0, maxLength - 3) + '...';
- }
- }
-
- // Bestimmt die Farbe eines Knotens basierend auf seinem Typ oder direkt angegebener Farbe
- getNodeColor(node) {
- // Direkt angegebene Farbe verwenden, wenn vorhanden
- if (node.color_code) {
- return node.color_code;
- }
-
- // Kategorietyp-basierte Färbung mit dezenten, wissenschaftlichen Farben
- if (node.type === 'category') {
- return this.colorPalette.root;
- } else if (node.type === 'subcategory') {
- return this.colorPalette.science;
- }
-
- // Fallback für verschiedene Knotentypen
- return this.colorPalette[node.id] || this.colorPalette.default;
- }
-
- // Aktualisiert die Positionen in jedem Simulationsschritt
- ticked() {
- if (!this.linkElements || !this.nodeElements) return;
-
- // Aktualisierung der Linkpositionen mit gebogenem Pfad
- this.linkElements
- .attr('d', d => {
- const dx = d.target.x - d.source.x;
- const dy = d.target.y - d.source.y;
- const dr = Math.sqrt(dx * dx + dy * dy) * 1.5; // Kurvenstärke
- return `M${d.source.x},${d.source.y}A${dr},${dr} 0 0,1 ${d.target.x},${d.target.y}`;
- });
-
- // Aktualisierung der Knotenpositionen
- this.nodeElements
- .attr('transform', d => `translate(${d.x},${d.y})`);
- }
-
- // D3.js Drag-Funktionen
- dragStarted(event, d) {
- if (!event.active) this.simulation.alphaTarget(0.3).restart();
- d.fx = d.x;
- d.fy = d.y;
- }
-
- dragged(event, d) {
- d.fx = event.x;
- d.fy = event.y;
- }
-
- dragEnded(event, d) {
- if (!event.active) this.simulation.alphaTarget(0);
- d.fx = null;
- d.fy = null;
- }
-
- // Hover-Effekte für Knoten
- nodeMouseover(event, d) {
- if (this.tooltipEnabled) {
- // Tooltip-Inhalt erstellen
- const tooltipContent = `
- ${d.name}
- ${d.description || 'Keine Beschreibung verfügbar'}
- ${d.thought_count > 0 ? `${d.thought_count} Gedanken verknüpft
` : ''}
- `;
-
- this.tooltipDiv.html(tooltipContent)
- .style('opacity', 0.95);
-
- // Tooltip positionieren (oberhalb des Nodes)
- const nodeRect = event.target.getBoundingClientRect();
- const tooltipWidth = 250;
- const tooltipHeight = 100; // Ungefähre Höhe des Tooltips
-
- const leftPos = nodeRect.left + (nodeRect.width / 2) - (tooltipWidth / 2);
- const topPos = nodeRect.top - tooltipHeight - 10; // 10px Abstand
-
- this.tooltipDiv
- .style('left', `${leftPos}px`)
- .style('top', `${topPos}px`)
- .style('width', `${tooltipWidth}px`);
- }
-
- // Speichern des aktuellen Hover-Nodes
- this.mouseoverNode = d;
-
- // Highlights für verbundene Nodes und Links hinzufügen
- if (this.g) {
- // Verbundene Nodes identifizieren
- const connectedNodes = this.getConnectedNodesById(d.id);
- const connectedNodeIds = connectedNodes.map(node => node.id);
-
- // Alle Nodes etwas transparenter machen
- this.g.selectAll('.node')
- .transition()
- .duration(200)
- .style('opacity', node => {
- if (node.id === d.id || connectedNodeIds.includes(node.id)) {
- return 1.0;
- } else {
- return 0.5;
- }
- });
-
- // Den Hover-Node hervorheben mit größerem Radius
- this.g.selectAll('.node')
- .filter(node => node.id === d.id)
- .select('circle')
- .transition()
- .duration(200)
- .attr('r', this.nodeRadius * 1.2)
- .style('filter', 'url(#hover-glow)')
- .style('stroke', 'rgba(255, 255, 255, 0.25)');
-
- // Verbundene Links hervorheben
- this.g.selectAll('.link')
- .transition()
- .duration(200)
- .style('opacity', link => {
- const sourceId = link.source.id || link.source;
- const targetId = link.target.id || link.target;
-
- if (sourceId === d.id || targetId === d.id) {
- return 0.9;
- } else {
- return 0.3;
- }
- })
- .style('stroke-width', link => {
- const sourceId = link.source.id || link.source;
- const targetId = link.target.id || link.target;
-
- if (sourceId === d.id || targetId === d.id) {
- return 3;
- } else {
- return 2;
- }
- })
- .style('stroke', link => {
- const sourceId = link.source.id || link.source;
- const targetId = link.target.id || link.target;
-
- if (sourceId === d.id || targetId === d.id) {
- return 'rgba(179, 143, 255, 0.7)';
- } else {
- return 'rgba(255, 255, 255, 0.3)';
- }
- });
- }
- }
-
- nodeMouseout(event, d) {
- if (this.tooltipEnabled) {
- this.tooltipDiv.transition()
- .duration(200)
- .style('opacity', 0);
- }
-
- this.mouseoverNode = null;
-
- // Highlights zurücksetzen, falls kein Node ausgewählt ist
- if (!this.selectedNode && this.g) {
- // Alle Nodes wieder auf volle Deckkraft setzen
- this.g.selectAll('.node')
- .transition()
- .duration(200)
- .style('opacity', 1.0);
-
- // Hover-Node-Radius zurücksetzen
- this.g.selectAll('.node')
- .filter(node => node.id === d.id)
- .select('circle')
- .transition()
- .duration(200)
- .attr('r', this.nodeRadius)
- .style('filter', 'none')
- .style('stroke', 'rgba(255, 255, 255, 0.12)');
-
- // Links zurücksetzen
- this.g.selectAll('.link')
- .transition()
- .duration(200)
- .style('opacity', 0.7)
- .style('stroke-width', 2)
- .style('stroke', 'rgba(255, 255, 255, 0.3)');
- }
- // Falls ein Node ausgewählt ist, den Highlight-Status für diesen beibehalten
- else if (this.selectedNode && this.g) {
- const connectedNodes = this.getConnectedNodesById(this.selectedNode.id);
- const connectedNodeIds = connectedNodes.map(node => node.id);
-
- // Alle Nodes auf den richtigen Highlight-Status setzen
- this.g.selectAll('.node')
- .transition()
- .duration(200)
- .style('opacity', node => {
- if (node.id === this.selectedNode.id || connectedNodeIds.includes(node.id)) {
- return 1.0;
- } else {
- return 0.5;
- }
- });
-
- // Hover-Node zurücksetzen, wenn er nicht der ausgewählte ist
- if (d.id !== this.selectedNode.id) {
- this.g.selectAll('.node')
- .filter(node => node.id === d.id)
- .select('circle')
- .transition()
- .duration(200)
- .attr('r', this.nodeRadius)
- .style('filter', 'none')
- .style('stroke', 'rgba(255, 255, 255, 0.12)');
- }
-
- // Links auf den richtigen Highlight-Status setzen
- this.g.selectAll('.link')
- .transition()
- .duration(200)
- .style('opacity', link => {
- const sourceId = link.source.id || link.source;
- const targetId = link.target.id || link.target;
-
- if (sourceId === this.selectedNode.id || targetId === this.selectedNode.id) {
- return 0.9;
- } else {
- return 0.3;
- }
- })
- .style('stroke-width', link => {
- const sourceId = link.source.id || link.source;
- const targetId = link.target.id || link.target;
-
- if (sourceId === this.selectedNode.id || targetId === this.selectedNode.id) {
- return 3;
- } else {
- return 2;
- }
- })
- .style('stroke', link => {
- const sourceId = link.source.id || link.source;
- const targetId = link.target.id || link.target;
-
- if (sourceId === this.selectedNode.id || targetId === this.selectedNode.id) {
- return 'rgba(179, 143, 255, 0.7)';
- } else {
- return 'rgba(255, 255, 255, 0.3)';
- }
- });
- }
- }
-
- // Findet alle verbundenen Knoten zu einem gegebenen Knoten
- getConnectedNodes(node) {
- if (!this.links || !this.nodes || !node) return [];
-
- // Sicherstellen, dass der Knoten eine ID hat
- const nodeId = node.id || node;
-
- return this.nodes.filter(n =>
- this.links.some(link => {
- const sourceId = link.source?.id || link.source;
- const targetId = link.target?.id || link.target;
- return (sourceId === nodeId && targetId === n.id) ||
- (targetId === nodeId && sourceId === n.id);
- })
- );
- }
-
- // Prüft, ob zwei Knoten verbunden sind
- isConnected(a, b) {
- if (!this.links || !a || !b) return false;
-
- // Sicherstellen, dass die Knoten IDs haben
- const aId = a.id || a;
- const bId = b.id || b;
-
- return this.links.some(link => {
- const sourceId = link.source?.id || link.source;
- const targetId = link.target?.id || link.target;
- return (sourceId === aId && targetId === bId) ||
- (targetId === aId && sourceId === bId);
- });
- }
-
- // Überprüft, ob ein Link zwischen zwei Knoten existiert
- hasLink(source, target) {
- if (!this.links || !source || !target) return false;
-
- // Sicherstellen, dass die Knoten IDs haben
- const sourceId = source.id || source;
- const targetId = target.id || target;
-
- return this.links.some(link => {
- const linkSourceId = link.source?.id || link.source;
- const linkTargetId = link.target?.id || link.target;
- return (linkSourceId === sourceId && linkTargetId === targetId) ||
- (linkTargetId === sourceId && linkSourceId === targetId);
- });
- }
-
- // Sicherere Methode zum Abrufen verbundener Knoten, die Prüfungen enthält
- getConnectedNodesById(nodeId) {
- if (!this.links || !this.nodes || !nodeId) return [];
-
- return this.nodes.filter(n =>
- this.links.some(link => {
- const sourceId = link.source.id || link.source;
- const targetId = link.target.id || link.target;
- return (sourceId === nodeId && targetId === n.id) ||
- (targetId === nodeId && sourceId === n.id);
- })
- );
- }
-
- // Aktualisiert die Verbindungszählungen für alle Knoten
- updateConnectionCounts() {
- if (!this.nodes || !this.links) return;
-
- // Für jeden Knoten die Anzahl der Verbindungen berechnen
- this.nodes.forEach(node => {
- // Sichere Methode, um verbundene Knoten zu zählen
- const connectedNodes = this.nodes.filter(n =>
- n.id !== node.id && this.links.some(link => {
- const sourceId = link.source.id || link.source;
- const targetId = link.target.id || link.target;
- return (sourceId === node.id && targetId === n.id) ||
- (targetId === node.id && sourceId === n.id);
- })
- );
-
- // Speichere die Anzahl als Eigenschaft des Knotens
- node.connectionCount = connectedNodes.length;
- });
- }
-
- // Klick-Handler für Knoten mit verbesserter Seitenleisten-Interaktion
- nodeClicked(event, d) {
- event.preventDefault();
- event.stopPropagation();
-
- // Selection-Handling: Knoten auswählen/abwählen
- if (this.selectedNode === d) {
- // Wenn der gleiche Knoten geklickt wird, Selektion aufheben
- this.selectedNode = null;
- this.nodeElements.classed('selected', false);
-
- this.nodeElements
- .select('circle:not(.node-background):not(.thought-indicator)')
- .transition()
- .duration(300)
- .attr('r', this.nodeRadius)
- .style('filter', 'url(#glass-with-reflection)')
- .attr('stroke-width', 2);
-
- // Event auslösen: Knoten abgewählt
- document.dispatchEvent(new CustomEvent('mindmap-node-deselected'));
-
- // Standardinformationspanels wieder anzeigen
- this.showDefaultSidebar();
-
- // Gedankenbereich ausblenden, wenn vorhanden
- const thoughtContainer = document.getElementById('thought-container');
- if (thoughtContainer) {
- // Sanfte Ausblendanimation
- thoughtContainer.style.transition = 'all 0.3s ease-out';
- thoughtContainer.style.opacity = '0';
- thoughtContainer.style.transform = 'translateY(10px)';
-
- setTimeout(() => {
- // Gedankenbereich komplett ausblenden
- thoughtContainer.style.display = 'none';
-
- // "Empty state" anzeigen oder andere UI-Anpassungen vornehmen
- const emptyStateEl = document.getElementById('mindmap-empty-state');
- if (emptyStateEl) {
- emptyStateEl.style.display = 'flex';
- emptyStateEl.style.opacity = '0';
- setTimeout(() => {
- emptyStateEl.style.transition = 'all 0.5s ease';
- emptyStateEl.style.opacity = '1';
- }, 50);
- }
- }, 300);
- }
-
- // Alle Kanten zurücksetzen
- this.linkElements
- .classed('highlighted', false)
- .transition()
- .duration(300)
- .style('stroke', 'rgba(75, 85, 99, 0.4)')
- .style('stroke-width', 2)
- .style('opacity', 0.7);
-
- // Interface-Callback für Knoten-Abwahl
- if (typeof window.onNodeDeselected === 'function') {
- window.onNodeDeselected();
- }
-
- // Flash-Nachricht für abgewählten Knoten
- this.showFlash('Knotenauswahl aufgehoben', 'info', 2000);
-
- return;
- }
-
- // Bisher ausgewählten Knoten zurücksetzen
- if (this.selectedNode) {
- this.nodeElements
- .filter(n => n === this.selectedNode)
- .classed('selected', false)
- .select('circle:not(.node-background):not(.thought-indicator)')
- .transition()
- .duration(300)
- .attr('r', this.nodeRadius)
- .style('filter', 'url(#glass-with-reflection)')
- .attr('stroke-width', 2);
- }
-
- // Neuen Knoten auswählen
- this.selectedNode = d;
-
- // Selected-Klasse für den Knoten setzen
- this.nodeElements
- .classed('selected', n => n === d);
-
- // Event auslösen: Knoten ausgewählt
- document.dispatchEvent(new CustomEvent('mindmap-node-selected', {
- detail: d
- }));
-
- // Visuelles Feedback für Auswahl
- this.nodeElements
- .filter(n => n === d)
- .select('circle:not(.node-background):not(.thought-indicator)')
- .transition()
- .duration(300)
- .attr('r', this.selectedNodeRadius)
- .style('filter', 'url(#selected-glow)')
- .attr('stroke-width', 3)
- .attr('stroke', 'rgba(79, 70, 229, 0.6)');
-
- // Verbundene Kanten hervorheben
- const connectedLinks = this.links.filter(link =>
- link.source === d || link.source.id === d.id ||
- link.target === d || link.target.id === d.id
- );
-
- // Alle Kanten zurücksetzen und dann verbundene hervorheben
- this.linkElements
- .classed('highlighted', false)
- .transition()
- .duration(300)
- .style('stroke', 'rgba(75, 85, 99, 0.4)')
- .style('stroke-width', 2)
- .style('opacity', 0.7);
-
- this.linkElements
- .filter(link =>
- connectedLinks.some(l =>
- (l.source === link.source || l.source.id === link.source.id) &&
- (l.target === link.target || l.target.id === link.target.id)
- )
- )
- .classed('highlighted', true)
- .transition()
- .duration(300)
- .style('stroke', 'rgba(79, 70, 229, 0.7)')
- .style('stroke-width', 3)
- .style('opacity', 0.9);
-
- // Knoten zentrieren
- this.centerNodeInView(d);
-
- // Gedanken laden
- this.loadThoughtsForNode(d);
-
- // Callback für UI-Integration
- if (typeof this.onNodeClick === 'function') {
- this.onNodeClick(d);
- }
-
- // Interface-Callback für externe Verwendung
- if (typeof window.onNodeSelected === 'function') {
- window.onNodeSelected(d);
- }
- }
-
- // Neue Methode: Zeigt die Standardseitenleiste an (Über die Mindmap und Kategorien)
- 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);
- }
- }
-
- // Neue Methode: Zeigt die Knotenbeschreibung in der Seitenleiste an
- 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"]');
-
- if (aboutMindmapPanel && categoriesPanel && nodeDescriptionPanel) {
- // Standardpanels ausblenden
- aboutMindmapPanel.style.transition = 'all 0.3s ease';
- categoriesPanel.style.transition = 'all 0.3s ease';
-
- aboutMindmapPanel.style.opacity = '0';
- aboutMindmapPanel.style.transform = 'translateY(10px)';
-
- categoriesPanel.style.opacity = '0';
- categoriesPanel.style.transform = 'translateY(10px)';
-
- 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 = this.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);
- }
- }
-
- // Neue Methode: Generiert automatisch eine Beschreibung für einen Knoten ohne Beschreibung
- 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."
- };
-
- // Verwende vordefinierte Beschreibung, wenn verfügbar
- if (node.name && descriptions[node.name]) {
- return descriptions[node.name];
- }
-
- // 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.`;
- }
- }
-
- // Lädt die Gedanken für einen Knoten und zeigt sie an
- loadThoughtsForNode(node) {
- // UI-Element für Gedanken finden
- const thoughtContainer = document.getElementById('thought-container');
- const loadingIndicator = document.getElementById('thoughts-loading');
- const thoughtsList = document.getElementById('thoughts-list');
- const thoughtsTitle = document.getElementById('thoughts-title');
- const emptyStateEl = document.getElementById('mindmap-empty-state');
-
- if (!thoughtContainer || !thoughtsList) {
- console.error('Gedanken-Container nicht gefunden');
- this.showFlash('Fehler: Gedanken-Container nicht gefunden', 'error');
- return;
- }
-
- // "Empty state" ausblenden
- if (emptyStateEl) {
- emptyStateEl.style.transition = 'all 0.3s ease';
- emptyStateEl.style.opacity = '0';
- setTimeout(() => {
- emptyStateEl.style.display = 'none';
- }, 300);
- }
-
- // Container anzeigen mit Animation
- thoughtContainer.style.display = 'block';
- setTimeout(() => {
- thoughtContainer.style.transition = 'all 0.4s ease';
- thoughtContainer.style.opacity = '1';
- thoughtContainer.style.transform = 'translateY(0)';
- }, 50);
-
- // Titel setzen
- if (thoughtsTitle) {
- thoughtsTitle.textContent = `Gedanken zu "${node.name}"`;
- }
-
- // Ladeanimation anzeigen
- if (loadingIndicator) {
- loadingIndicator.style.display = 'flex';
- }
-
- // Bisherige Gedanken leeren
- if (thoughtsList) {
- thoughtsList.innerHTML = '';
- }
-
- // Flash-Nachricht über ausgewählten Knoten
- this.showFlash(`Knoten "${node.name}" ausgewählt`, 'info');
-
- // Verzögerung für Animation
- setTimeout(() => {
- // API-Aufruf für echte Daten aus der Datenbank
- this.fetchThoughtsForNode(node.id)
- .then(thoughts => {
- // Ladeanimation ausblenden
- if (loadingIndicator) {
- loadingIndicator.style.display = 'none';
- }
-
- // Gedanken anzeigen oder "leer"-Zustand
- if (thoughts && thoughts.length > 0) {
- this.renderThoughts(thoughts, thoughtsList);
- } else {
- this.renderEmptyThoughts(thoughtsList, node);
- this.showFlash(`Keine Gedanken zu "${node.name}" gefunden`, 'warning');
- }
- })
- .catch(error => {
- console.error('Fehler beim Laden der Gedanken:', error);
- if (loadingIndicator) {
- loadingIndicator.style.display = 'none';
- }
- this.renderErrorState(thoughtsList);
- this.showFlash('Fehler beim Laden der Gedanken. Bitte versuche es später erneut.', 'error');
- });
- }, 600); // Verzögerung für bessere UX
- }
-
- // Holt Gedanken für einen Knoten aus der Datenbank
- async fetchThoughtsForNode(nodeId) {
- try {
- // Extrahiere die tatsächliche ID aus dem nodeId Format (z.B. "node_123" oder "category_456")
- const id = nodeId.toString().split('_')[1];
- if (!id) {
- console.warn('Ungültige Node-ID: ', nodeId);
- this.showFlash('Ungültige Knoten-ID: ' + nodeId, 'warning');
- return [];
- }
-
- // API-Aufruf an den entsprechenden Endpunkt
- const response = await fetch(`/api/nodes/${id}/thoughts`);
-
- if (!response.ok) {
- throw new Error(`API-Fehler: ${response.statusText}`);
- }
-
- const thoughts = await response.json();
- console.log('Geladene Gedanken für Knoten:', thoughts);
-
- if (thoughts.length > 0) {
- this.showFlash(`${thoughts.length} Gedanken zum Thema geladen`, 'info');
- } else {
- this.showFlash('Keine Gedanken für diesen Knoten gefunden', 'info');
- }
-
- return thoughts;
- } catch (error) {
- console.error('Fehler beim Laden der Gedanken für Knoten:', error);
- this.showFlash('Fehler beim Laden der Gedanken', 'error');
- return [];
- }
- }
-
- // Rendert die Gedanken in der UI
- renderThoughts(thoughts, container) {
- if (!container) return;
-
- container.innerHTML = '';
-
- thoughts.forEach(thought => {
- const thoughtCard = document.createElement('div');
- thoughtCard.className = 'thought-card';
- thoughtCard.setAttribute('data-id', thought.id);
-
- const cardColor = thought.color_code || this.colorPalette.default;
-
- thoughtCard.innerHTML = `
-
-
-
${thought.abstract || thought.content.substring(0, 150) + '...'}
-
-
- `;
-
- // 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') return;
- window.location.href = `/thoughts/${thought.id}`;
- });
-
- container.appendChild(thoughtCard);
- });
- }
-
- // Rendert eine Leermeldung, wenn keine Gedanken vorhanden sind
- renderEmptyThoughts(container, node) {
- if (!container) return;
-
- const emptyState = document.createElement('div');
- emptyState.className = 'empty-thoughts-state';
-
- emptyState.innerHTML = `
-
-
-
- Keine Gedanken verknüpft
- Zu "${node.name}" sind noch keine Gedanken verknüpft.
-
- `;
-
- container.appendChild(emptyState);
- }
-
- // Rendert einen Fehlerzustand
- renderErrorState(container) {
- if (!container) return;
-
- const errorState = document.createElement('div');
- errorState.className = 'error-thoughts-state';
-
- errorState.innerHTML = `
-
-
-
- Fehler beim Laden
- Die Gedanken konnten nicht geladen werden. Bitte versuche es später erneut.
-
- `;
-
- // Event-Listener für Retry-Button
- const retryButton = errorState.querySelector('.retry-button');
- if (retryButton && this.selectedNode) {
- retryButton.addEventListener('click', () => {
- this.loadThoughtsForNode(this.selectedNode);
- });
- }
-
- container.appendChild(errorState);
- }
-
- // Zentriert einen Knoten in der Ansicht
- centerNodeInView(node) {
- // Sanfter Übergang zur Knotenzentrierüng
- const transform = d3.zoomTransform(this.svg.node());
- const scale = transform.k;
-
- const x = -node.x * scale + this.width / 2;
- const y = -node.y * scale + this.height / 2;
-
- this.svg.transition()
- .duration(750)
- .call(
- d3.zoom().transform,
- d3.zoomIdentity.translate(x, y).scale(scale)
- );
-
- // Flash-Nachricht für Zentrierung
- if (node && node.name) {
- this.showFlash(`Ansicht auf "${node.name}" zentriert`, 'info', 2000);
- }
- }
-
- // Fehlermeldung anzeigen
- showError(message) {
- // Standard-Fehlermeldung als Banner
- const errorBanner = d3.select('body').selectAll('.error-banner').data([0]);
-
- const errorEnter = errorBanner.enter()
- .append('div')
- .attr('class', 'error-banner')
- .style('position', 'fixed')
- .style('bottom', '-100px')
- .style('left', '50%')
- .style('transform', 'translateX(-50%)')
- .style('background', 'rgba(220, 38, 38, 0.9)')
- .style('color', 'white')
- .style('padding', '12px 20px')
- .style('border-radius', '8px')
- .style('z-index', '1000')
- .style('box-shadow', '0 10px 25px rgba(0, 0, 0, 0.3)')
- .style('font-weight', '500')
- .style('max-width', '90%')
- .style('text-align', 'center');
-
- const banner = errorEnter.merge(errorBanner);
- banner.html(` ${message}`)
- .transition()
- .duration(500)
- .style('bottom', '20px')
- .transition()
- .delay(5000)
- .duration(500)
- .style('bottom', '-100px');
-
- // Auch als Flash-Nachricht anzeigen
- this.showFlash(message, 'error');
- }
-
- // Fokussieren auf einen bestimmten Knoten per ID
- focusNode(nodeId) {
- const targetNode = this.nodes.find(n => n.id === nodeId);
- if (!targetNode) {
- this.showFlash(`Knoten mit ID "${nodeId}" nicht gefunden`, 'error');
- return;
- }
-
- // Ausgewählten Zustand zurücksetzen
- this.selectedNode = null;
-
- // Node-Klick simulieren
- this.nodeClicked(null, targetNode);
-
- // Fokussieren mit einer Animation
- if (targetNode.x && targetNode.y) {
- const transform = d3.zoomIdentity
- .translate(this.width / 2 - targetNode.x * 1.2, this.height / 2 - targetNode.y * 1.2)
- .scale(1.2);
-
- this.svg.transition()
- .duration(750)
- .call(
- d3.zoom().transform,
- transform
- );
-
- this.showFlash(`Fokus auf Knoten "${targetNode.name}" gesetzt`, 'success');
- }
- }
-
- // Filtert Knoten nach Suchbegriff
- filterBySearchTerm(searchTerm) {
- if (!searchTerm || searchTerm.trim() === '') {
- // Alle Knoten anzeigen, wenn kein Suchbegriff
- this.nodeElements
- .style('display', 'block')
- .selectAll('circle')
- .style('opacity', 1);
-
- this.textElements
- .style('opacity', 1);
-
- this.linkElements
- .style('display', 'block')
- .style('stroke-opacity', 0.5);
-
- this.showFlash('Suchfilter zurückgesetzt', 'info', 2000);
-
- return;
- }
-
- searchTerm = searchTerm.toLowerCase().trim();
-
- // Knoten finden, die dem Suchbegriff entsprechen
- const matchingNodes = this.nodes.filter(node =>
- node.name.toLowerCase().includes(searchTerm) ||
- (node.description && node.description.toLowerCase().includes(searchTerm))
- );
-
- const matchingNodeIds = matchingNodes.map(n => n.id);
-
- // Nur passende Knoten und ihre Verbindungen anzeigen
- this.nodeElements
- .style('display', d => matchingNodeIds.includes(d.id) ? 'block' : 'none')
- .selectAll('circle')
- .style('opacity', 1);
-
- this.textElements
- .style('opacity', d => matchingNodeIds.includes(d.id) ? 1 : 0.2);
-
- this.linkElements
- .style('display', link =>
- matchingNodeIds.includes(link.source.id) && matchingNodeIds.includes(link.target.id) ? 'block' : 'none')
- .style('stroke-opacity', 0.7);
-
- // Wenn nur ein Knoten gefunden wurde, darauf fokussieren
- if (matchingNodes.length === 1) {
- this.focusNode(matchingNodes[0].id);
- }
-
- // Wenn mehr als ein Knoten gefunden wurde, Simulation mit reduzierter Stärke neu starten
- if (matchingNodes.length > 1) {
- this.simulation.alpha(0.3).restart();
- this.showFlash(`${matchingNodes.length} Knoten für "${searchTerm}" gefunden`, 'success');
- } else if (matchingNodes.length === 0) {
- this.showFlash(`Keine Knoten für "${searchTerm}" gefunden`, 'warning');
- }
- }
-}
-
-// D3-Erweiterungen für spezielle Effekte
-class D3Extensions {
- static createGlowFilter(defs, id, color = '#b38fff', strength = 5) {
- const filter = defs.append('filter')
- .attr('id', id)
- .attr('height', '300%')
- .attr('width', '300%')
- .attr('x', '-100%')
- .attr('y', '-100%');
-
- // Farbe und Sättigung
- const colorMatrix = filter.append('feColorMatrix')
- .attr('type', 'matrix')
- .attr('values', `
- 1 0 0 0 ${color === '#b38fff' ? 0.7 : 0.35}
- 0 1 0 0 ${color === '#58a9ff' ? 0.7 : 0.35}
- 0 0 1 0 ${color === '#58a9ff' ? 0.7 : 0.55}
- 0 0 0 1 0
- `)
- .attr('result', 'colored');
-
- // Weichzeichner für Glühen
- const blur = filter.append('feGaussianBlur')
- .attr('in', 'colored')
- .attr('stdDeviation', strength)
- .attr('result', 'blur');
-
- // Kombination von Original und Glühen
- const merge = filter.append('feMerge');
-
- merge.append('feMergeNode')
- .attr('in', 'blur');
-
- merge.append('feMergeNode')
- .attr('in', 'SourceGraphic');
-
- return filter;
- }
-
- static createShadowFilter(defs, id) {
- const filter = defs.append('filter')
- .attr('id', id)
- .attr('height', '200%')
- .attr('width', '200%')
- .attr('x', '-50%')
- .attr('y', '-50%');
-
- // Offset der Lichtquelle
- const offset = filter.append('feOffset')
- .attr('in', 'SourceAlpha')
- .attr('dx', 3)
- .attr('dy', 4)
- .attr('result', 'offset');
-
- // Weichzeichnung für Schatten
- const blur = filter.append('feGaussianBlur')
- .attr('in', 'offset')
- .attr('stdDeviation', 5)
- .attr('result', 'blur');
-
- // Schatten-Opazität
- const opacity = filter.append('feComponentTransfer');
-
- opacity.append('feFuncA')
- .attr('type', 'linear')
- .attr('slope', 0.3);
-
- // Zusammenführen
- const merge = filter.append('feMerge');
-
- merge.append('feMergeNode');
- merge.append('feMergeNode')
- .attr('in', 'SourceGraphic');
-
- return filter;
- }
-
- static createGlassMorphismFilter(defs, id) {
- const filter = defs.append('filter')
- .attr('id', id)
- .attr('width', '300%')
- .attr('height', '300%')
- .attr('x', '-100%')
- .attr('y', '-100%');
-
- // Basis-Hintergrundfarbe
- const bgColor = filter.append('feFlood')
- .attr('flood-color', 'rgba(24, 28, 45, 0.75)')
- .attr('result', 'bgColor');
-
- // Weichzeichnung des Originalelements
- const blur = filter.append('feGaussianBlur')
- .attr('in', 'SourceGraphic')
- .attr('stdDeviation', '3')
- .attr('result', 'blur');
-
- // Komposition des Glaseffekts mit Original
- const composite1 = filter.append('feComposite')
- .attr('in', 'bgColor')
- .attr('in2', 'blur')
- .attr('operator', 'in')
- .attr('result', 'glass');
-
- // Leichter Farbakzent
- const colorMatrix = filter.append('feColorMatrix')
- .attr('in', 'glass')
- .attr('type', 'matrix')
- .attr('values', '1 0 0 0 0.1 0 1 0 0 0.1 0 0 1 0 0.3 0 0 0 1 0')
- .attr('result', 'coloredGlass');
-
- // Leichte Transparenz an den Rändern
- const specLight = filter.append('feSpecularLighting')
- .attr('in', 'blur')
- .attr('surfaceScale', '3')
- .attr('specularConstant', '0.75')
- .attr('specularExponent', '20')
- .attr('lighting-color', '#ffffff')
- .attr('result', 'specLight');
-
- specLight.append('fePointLight')
- .attr('x', '-20')
- .attr('y', '-30')
- .attr('z', '120');
-
- // Lichtkombination
- const composite2 = filter.append('feComposite')
- .attr('in', 'specLight')
- .attr('in2', 'coloredGlass')
- .attr('operator', 'in')
- .attr('result', 'lightedGlass');
-
- // Alle Effekte kombinieren
- const merge = filter.append('feMerge')
- .attr('result', 'glassMerge');
-
- merge.append('feMergeNode')
- .attr('in', 'coloredGlass');
-
- merge.append('feMergeNode')
- .attr('in', 'lightedGlass');
-
- merge.append('feMergeNode')
- .attr('in', 'SourceGraphic');
-
- return filter;
- }
-
- // Erstellt einen erweiterten 3D-Glaseffekt mit Lichtreflexion
- static create3DGlassEffect(defs, id) {
- const filter = defs.append('filter')
- .attr('id', id)
- .attr('width', '300%')
- .attr('height', '300%')
- .attr('x', '-100%')
- .attr('y', '-100%');
-
- // Hintergrund-Färbung mit Transparenz
- const bgColor = filter.append('feFlood')
- .attr('flood-color', 'rgba(24, 28, 45, 0.7)')
- .attr('result', 'bgColor');
-
- // Alpha-Kanal modifizieren
- const composite1 = filter.append('feComposite')
- .attr('in', 'bgColor')
- .attr('in2', 'SourceAlpha')
- .attr('operator', 'in')
- .attr('result', 'shape');
-
- // Leichte Unschärfe hinzufügen
- const blur = filter.append('feGaussianBlur')
- .attr('in', 'shape')
- .attr('stdDeviation', '2')
- .attr('result', 'blurredShape');
-
- // Lichtquelle für 3D-Effekt
- const specLight = filter.append('feSpecularLighting')
- .attr('in', 'blurredShape')
- .attr('surfaceScale', '5')
- .attr('specularConstant', '1')
- .attr('specularExponent', '20')
- .attr('lighting-color', '#ffffff')
- .attr('result', 'specLight');
-
- specLight.append('fePointLight')
- .attr('x', '50')
- .attr('y', '-50')
- .attr('z', '200');
-
- // Farbmatrix für Lichttönung
- const colorMatrix = filter.append('feColorMatrix')
- .attr('in', 'specLight')
- .attr('type', 'matrix')
- .attr('values', '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0')
- .attr('result', 'coloredLight');
-
- // Alle Effekte kombinieren
- const merge = filter.append('feMerge');
-
- merge.append('feMergeNode')
- .attr('in', 'blurredShape');
-
- merge.append('feMergeNode')
- .attr('in', 'coloredLight');
-
- merge.append('feMergeNode')
- .attr('in', 'SourceGraphic');
-
- return filter;
- }
-}
-
-// Globales Objekt für Zugriff außerhalb des Moduls
-window.MindMapVisualization = MindMapVisualization;
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index 362eb77..5e1efb9 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -111,12 +111,6 @@
-
-
-
-
-
-
@@ -740,205 +734,207 @@
{% block scripts %}{% endblock %}
+ {% block extra_js %}{% endblock %}
-
+
diff --git a/templates/mindmap.html b/templates/mindmap.html
index 99f2600..362608e 100644
--- a/templates/mindmap.html
+++ b/templates/mindmap.html
@@ -1,402 +1,232 @@
{% 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
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
+
+
+
+
+
-
-
-
Knoteninfo
-
Wählen Sie einen Knoten aus...
-
-
-
Verknüpfte Knoten
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
- Über die Mindmap
-
-
-
Die interaktive Wissenslandkarte zeigt Verbindungen zwischen verschiedenen Gedanken und Konzepten.
-
Sie können:
-
- - Knoten auswählen, um Details zu sehen
- - Zoomen (Mausrad oder Pinch-Geste)
- - Die Karte verschieben (Drag & Drop)
- - Die Toolbar nutzen für weitere Aktionen
-
-
-
-
-
-
-
- Kategorien
-
-
-
-
Philosophie
-
Wissenschaft
-
Technologie
-
Künste
-
Psychologie
-
-
-
-
-
-
Knotenbeschreibung
-
-
- Wählen Sie einen Knoten in der Mindmap aus, um dessen Beschreibung hier anzuzeigen.
-
-
-
-
-
-
-
-
- {% if current_user.is_authenticated %}
-
-
- Meine Mindmaps
-
-
-
-
-
- {% endif %}
+
+
+
+
{% endblock %}
{% block extra_js %}
-
-
-
-
-
+
+
+
+
+
-
-
{% endblock %}
\ No newline at end of file