diff --git a/static/js/mindmap-init.js b/static/js/mindmap-init.js
index 4dcccdc..0d09df8 100644
--- a/static/js/mindmap-init.js
+++ b/static/js/mindmap-init.js
@@ -40,42 +40,51 @@ function loadScript(src, callback) {
*/
function initMindmap() {
const cyContainer = document.getElementById('cy');
- const fitBtn = document.getElementById('fit-btn');
- const resetBtn = document.getElementById('reset-btn');
- const toggleLabelsBtn = document.getElementById('toggle-labels-btn');
- const nodeInfoPanel = document.getElementById('node-info-panel');
- const nodeDescription = document.getElementById('node-description');
- const connectedNodes = document.getElementById('connected-nodes');
-
- let labelsVisible = true;
- let selectedNode = null;
// Erstelle Cytoscape-Instanz
const cy = cytoscape({
container: cyContainer,
- style: getDefaultStyles(),
+ style: getNeuralNetworkStyles(),
layout: {
name: 'cose',
animate: true,
- animationDuration: 800,
+ animationDuration: 1500,
nodeDimensionsIncludeLabels: true,
- padding: 50,
- spacingFactor: 1.2,
+ padding: 100,
+ spacingFactor: 1.5,
randomize: true,
componentSpacing: 100,
nodeRepulsion: 8000,
edgeElasticity: 100,
nestingFactor: 1.2,
- gravity: 80
+ gravity: 80,
+ idealEdgeLength: 150
},
- wheelSensitivity: 0.3,
+ wheelSensitivity: 0.1, // Sanfterer Zoom
+ minZoom: 0.3,
+ maxZoom: 2.5,
});
// Daten vom Server laden
loadMindmapData(cy);
// Event-Handler zuweisen
- setupEventListeners(cy, fitBtn, resetBtn, toggleLabelsBtn, nodeInfoPanel, nodeDescription, connectedNodes);
+ setupEventListeners(cy);
+
+ // Globale Referenz für Externe Zugriffe
+ window.cy = cy;
+ window.mindmapInstance = {
+ cy: cy,
+ selectedNode: null,
+ centerNodeInView: function(node) {
+ cy.animate({
+ center: { eles: node },
+ zoom: 1.2,
+ duration: 800,
+ easing: 'ease-in-out-cubic'
+ });
+ }
+ };
}
/**
@@ -123,8 +132,12 @@ function loadMindmapData(cy) {
data.nodes.unshift(rootNode);
}
- // Knoten hinzufügen
+ // Knoten hinzufügen mit zufälligen Werten für neuronales Netzwerk
data.nodes.forEach(node => {
+ // Neuronenzell-Größe und Aktivität
+ const neuronSize = Math.floor(Math.random() * 8) + 3;
+ const neuronActivity = Math.random() * 0.7 + 0.3; // Aktivitätslevel zwischen 0.3 und 1.0
+
elements.push({
group: 'nodes',
data: {
@@ -132,7 +145,9 @@ function loadMindmapData(cy) {
name: node.name,
description: node.description || '',
color: node.color_code || '#8B5CF6',
- isRoot: node.name === 'Wissen'
+ isRoot: node.name === 'Wissen',
+ neuronSize: neuronSize,
+ neuronActivity: neuronActivity
}
});
});
@@ -145,7 +160,8 @@ function loadMindmapData(cy) {
data: {
id: `${edge.source}-${edge.target}`,
source: edge.source.toString(),
- target: edge.target.toString()
+ target: edge.target.toString(),
+ strength: Math.random() * 0.6 + 0.2 // Zufällige Verbindungsstärke zwischen 0.2 und 0.8
}
});
});
@@ -160,7 +176,8 @@ function loadMindmapData(cy) {
data: {
id: `${rootId}-${node.id}`,
source: rootId,
- target: node.id.toString()
+ target: node.id.toString(),
+ strength: Math.random() * 0.6 + 0.2
}
});
}
@@ -175,14 +192,22 @@ function loadMindmapData(cy) {
cy.layout({
name: 'cose',
animate: true,
- animationDuration: 800,
+ animationDuration: 1800,
nodeDimensionsIncludeLabels: true,
- padding: 50,
- spacingFactor: 1.5,
+ padding: 100,
+ spacingFactor: 1.8,
randomize: false,
- fit: true
+ fit: true,
+ componentSpacing: 100,
+ nodeRepulsion: 8000,
+ edgeElasticity: 100
}).run();
+ // Neuronale Netzwerk-Effekte hinzufügen
+ setTimeout(() => {
+ addNeuralNetworkEffects(cy);
+ }, 2000);
+
// Nach dem Laden Event auslösen
document.dispatchEvent(new CustomEvent('mindmap-loaded'));
})
@@ -208,8 +233,11 @@ function loadMindmapData(cy) {
const fallbackElements = [];
- // Knoten hinzufügen
+ // Knoten hinzufügen mit Neuronen-Eigenschaften
fallbackData.nodes.forEach(node => {
+ const neuronSize = Math.floor(Math.random() * 8) + 3;
+ const neuronActivity = Math.random() * 0.7 + 0.3;
+
fallbackElements.push({
group: 'nodes',
data: {
@@ -217,7 +245,9 @@ function loadMindmapData(cy) {
name: node.name,
description: node.description || '',
color: node.color_code || '#8B5CF6',
- isRoot: node.name === 'Wissen'
+ isRoot: node.name === 'Wissen',
+ neuronSize: neuronSize,
+ neuronActivity: neuronActivity
}
});
});
@@ -229,7 +259,8 @@ function loadMindmapData(cy) {
data: {
id: `${edge.source}-${edge.target}`,
source: edge.source.toString(),
- target: edge.target.toString()
+ target: edge.target.toString(),
+ strength: Math.random() * 0.6 + 0.2
}
});
});
@@ -239,1011 +270,379 @@ function loadMindmapData(cy) {
cy.add(fallbackElements);
// Layout anwenden
- cy.layout({ name: 'cose', animate: true }).run();
+ cy.layout({
+ name: 'cose',
+ animate: true,
+ animationDuration: 1800,
+ nodeDimensionsIncludeLabels: true,
+ fit: true
+ }).run();
+
+ // Neuronale Netzwerk-Effekte hinzufügen
+ setTimeout(() => {
+ addNeuralNetworkEffects(cy);
+ }, 2000);
+
+ // Nach dem Laden Event auslösen
+ document.dispatchEvent(new CustomEvent('mindmap-loaded'));
});
}
+// Neuronales Netzwerk-Effekte hinzufügen
+function addNeuralNetworkEffects(cy) {
+ cy.nodes().forEach(node => {
+ const originalPos = node.position();
+ const activity = node.data('neuronActivity') || 0.5;
+
+ // Subtile Pulsierende Bewegung für Neuronen
+ setInterval(() => {
+ const randomFactor = Math.random() * 0.5 + 0.5; // Zufälliger Faktor zwischen 0.5 und 1
+ const offset = (Math.random() - 0.5) * activity;
+ const pulseIntensity = 0.7 + (Math.sin(Date.now() / 2000) * 0.3 * activity * randomFactor);
+
+ // Leichtes Pulsieren und Bewegung basierend auf "Neuronenaktivität"
+ node.animate({
+ position: {
+ x: originalPos.x + offset,
+ y: originalPos.y + offset
+ },
+ style: {
+ 'background-opacity': Math.max(0.7, pulseIntensity),
+ 'shadow-opacity': Math.max(0.5, pulseIntensity * 0.8)
+ },
+ duration: 3000 + (Math.random() * 2000),
+ easing: 'ease-in-out-cubic',
+ complete: function() {
+ node.animate({
+ position: { x: originalPos.x, y: originalPos.y },
+ style: {
+ 'background-opacity': 0.9,
+ 'shadow-opacity': 0.6
+ },
+ duration: 3000 + (Math.random() * 2000),
+ easing: 'ease-in-out-cubic'
+ });
+ }
+ });
+ }, 6000 + Math.random() * 4000); // Leicht zufällige Intervalle für organischeres Aussehen
+
+ // Zusätzliche "synaptische" Effekte für Kanten
+ node.connectedEdges().forEach(edge => {
+ const strength = edge.data('strength') || 0.5;
+
+ // Pulsierende Aktivität entlang der Kanten
+ setInterval(() => {
+ const pulseIntensity = 0.5 + (Math.sin(Date.now() / 1500) * 0.5 * strength);
+
+ edge.animate({
+ style: {
+ 'line-opacity': Math.max(0.3, pulseIntensity),
+ 'width': 1 + (pulseIntensity * 0.6)
+ },
+ duration: 1200,
+ easing: 'ease-in-out'
+ });
+ }, 3000 + (Math.random() * 2000));
+ });
+ });
+}
+
/**
* Richtet Event-Listener für die Mindmap ein
* @param {Object} cy - Cytoscape-Instanz
- * @param {HTMLElement} fitBtn - Fit-Button
- * @param {HTMLElement} resetBtn - Reset-Button
- * @param {HTMLElement} toggleLabelsBtn - Toggle-Labels-Button
- * @param {HTMLElement} nodeInfoPanel - Node-Info-Panel
- * @param {HTMLElement} nodeDescription - Node-Description
- * @param {HTMLElement} connectedNodes - Connected-Nodes-Container
*/
-function setupEventListeners(cy, fitBtn, resetBtn, toggleLabelsBtn, nodeInfoPanel, nodeDescription, connectedNodes) {
- let labelsVisible = true;
-
- // Fit-Button
- if (fitBtn) {
- fitBtn.addEventListener('click', function() {
- cy.fit();
- });
- }
-
- // Reset-Button
- if (resetBtn) {
- resetBtn.addEventListener('click', function() {
- cy.layout({ name: 'cose', animate: true }).run();
- });
- }
-
- // Toggle-Labels-Button
- if (toggleLabelsBtn) {
- toggleLabelsBtn.addEventListener('click', function() {
- labelsVisible = !labelsVisible;
- cy.style()
- .selector('node')
- .style({
- 'text-opacity': labelsVisible ? 1 : 0
- })
- .update();
- });
- }
-
- // Knoten-Klick
+function setupEventListeners(cy) {
+ // Klick auf Knoten
cy.on('tap', 'node', function(evt) {
const node = evt.target;
- // Zuvor ausgewählten Knoten zurücksetzen
- cy.nodes().removeClass('selected');
+ // Alle vorherigen Hervorhebungen zurücksetzen
+ cy.nodes().forEach(n => {
+ n.removeStyle();
+ n.connectedEdges().removeStyle();
+ });
- // Neuen Knoten auswählen
- node.addClass('selected');
+ // Speichere ausgewählten Knoten
+ window.mindmapInstance.selectedNode = node;
- if (nodeInfoPanel && nodeDescription && connectedNodes) {
- // Info-Panel aktualisieren
- nodeDescription.textContent = node.data('description') || 'Keine Beschreibung verfügbar.';
-
- // Verbundene Knoten anzeigen
- connectedNodes.innerHTML = '';
-
- // Verbundene Knoten sammeln
- const connectedNodesList = node.neighborhood('node');
-
- if (connectedNodesList.length > 0) {
- connectedNodesList.forEach(connectedNode => {
- // Nicht den ausgewählten Knoten selbst anzeigen
- if (connectedNode.id() !== node.id()) {
- const nodeLink = document.createElement('span');
- nodeLink.className = 'node-link';
- nodeLink.textContent = connectedNode.data('name');
- nodeLink.style.backgroundColor = connectedNode.data('color');
-
- // Klick-Ereignis, um zu diesem Knoten zu wechseln
- nodeLink.addEventListener('click', function() {
- connectedNode.select();
- cy.animate({
- center: { eles: connectedNode },
- duration: 500,
- easing: 'ease-in-out-cubic'
- });
- });
-
- connectedNodes.appendChild(nodeLink);
- }
- });
- } else {
- connectedNodes.innerHTML = 'Keine verbundenen Knoten';
- }
-
- // Panel anzeigen
- nodeInfoPanel.classList.add('visible');
- }
+ // Aktiviere leuchtenden Effekt statt Umkreisung
+ node.style({
+ 'background-opacity': 1,
+ 'background-color': node.data('color'),
+ 'shadow-color': node.data('color'),
+ 'shadow-opacity': 1,
+ 'shadow-blur': 15,
+ 'shadow-offset-x': 0,
+ 'shadow-offset-y': 0
+ });
+
+ // Verbundene Kanten und Knoten hervorheben
+ const connectedEdges = node.connectedEdges();
+ const connectedNodes = node.neighborhood('node');
+
+ connectedEdges.style({
+ 'line-color': '#a78bfa',
+ 'target-arrow-color': '#a78bfa',
+ 'source-arrow-color': '#a78bfa',
+ 'line-opacity': 0.8,
+ 'width': 2
+ });
+
+ connectedNodes.style({
+ 'shadow-opacity': 0.7,
+ 'shadow-blur': 10,
+ 'shadow-color': '#a78bfa'
+ });
+
+ // Info-Panel aktualisieren
+ updateInfoPanel(node);
+
+ // Seitenleiste aktualisieren
+ updateSidebar(node);
});
- // Hintergrund-Klick
+ // Klick auf Hintergrund - Auswahl zurücksetzen
cy.on('tap', function(evt) {
if (evt.target === cy) {
- // Klick auf den Hintergrund
- cy.nodes().removeClass('selected');
-
- // Info-Panel verstecken
- if (nodeInfoPanel) {
- nodeInfoPanel.classList.remove('visible');
- }
+ resetSelection(cy);
}
});
- // Dark Mode-Änderungen
- document.addEventListener('darkModeToggled', function(event) {
- const isDark = event.detail.isDark;
- cy.style(getDefaultStyles(isDark));
+ // Smooth Zoom mit Mouse Wheel
+ cy.on('mousewheel', function(evt) {
+ const delta = evt.originalEvent.deltaY;
+ const factor = delta > 0 ? 0.95 : 1.05;
+
+ cy.animate({
+ zoom: cy.zoom() * factor,
+ duration: 100
+ });
+ });
+}
+
+// Aktualisiert das Info-Panel mit Daten des ausgewählten Knotens
+function updateInfoPanel(node) {
+ const nodeInfoPanel = document.getElementById('node-info-panel');
+ const nodeDescription = document.getElementById('node-description');
+ const connectedNodes = document.getElementById('connected-nodes');
+ const panelTitle = nodeInfoPanel.querySelector('.info-panel-title');
+
+ if (!nodeInfoPanel || !nodeDescription || !connectedNodes) return;
+
+ // Titel und Beschreibung aktualisieren
+ panelTitle.textContent = node.data('name');
+ nodeDescription.textContent = node.data('description') || 'Keine Beschreibung verfügbar.';
+
+ // Verbundene Knoten anzeigen
+ connectedNodes.innerHTML = '';
+
+ // Verbundene Knoten sammeln (direktes Neighborhood)
+ const connectedNodesList = [];
+ node.neighborhood('node').forEach(n => {
+ if (!connectedNodesList.includes(n) && n.id() !== node.id()) {
+ connectedNodesList.push(n);
+ }
+ });
+
+ // Verbundene Knoten im Panel anzeigen
+ if (connectedNodesList.length > 0) {
+ connectedNodesList.forEach(connectedNode => {
+ const nodeLink = document.createElement('span');
+ nodeLink.className = 'inline-block px-2 py-1 text-xs rounded-md m-1 cursor-pointer';
+ nodeLink.style.backgroundColor = connectedNode.data('color');
+ nodeLink.textContent = connectedNode.data('name');
+
+ // Beim Klick auf den verbundenen Knoten zu diesem wechseln
+ nodeLink.addEventListener('click', function() {
+ resetSelection(cy);
+
+ // Verzögerung vor der neuen Auswahl für besseren visuellen Übergang
+ setTimeout(() => {
+ connectedNode.trigger('tap');
+ }, 50);
+ });
+
+ connectedNodes.appendChild(nodeLink);
+ });
+ } else {
+ connectedNodes.innerHTML = 'Keine verbundenen Knoten';
+ }
+
+ // Panel anzeigen mit Animation
+ nodeInfoPanel.style.transition = 'all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1)';
+ nodeInfoPanel.classList.add('visible');
+}
+
+// Aktualisiert die Seitenleiste
+function updateSidebar(node) {
+ // Alle standard Panels ausblenden
+ document.querySelectorAll('[data-sidebar]').forEach(panel => {
+ if (panel.getAttribute('data-sidebar') === 'node-description') {
+ // Beschreibungs-Panel anzeigen
+ panel.classList.remove('hidden');
+
+ // Titel und Beschreibung aktualisieren
+ const titleElement = panel.querySelector('[data-node-title]');
+ const descriptionElement = panel.querySelector('[data-node-description]');
+
+ if (titleElement) {
+ titleElement.textContent = node.data('name');
+ }
+
+ if (descriptionElement) {
+ descriptionElement.innerHTML = formatDescription(node.data('description') || 'Keine Beschreibung verfügbar.');
+ }
+ } else {
+ // Andere Panels ausblenden
+ panel.classList.add('hidden');
+ }
+ });
+}
+
+// Formatiert die Beschreibung mit etwas HTML-Markup
+function formatDescription(text) {
+ return text
+ .replace(/\n/g, '
')
+ .replace(/\*\*(.*?)\*\*/g, '$1')
+ .replace(/\*(.*?)\*/g, '$1')
+ .replace(/`(.*?)`/g, '$1');
+}
+
+// Setzt die Auswahl zurück
+function resetSelection(cy) {
+ // Alle Stile zurücksetzen
+ cy.nodes().forEach(n => {
+ n.removeStyle();
+ });
+
+ cy.edges().forEach(e => {
+ e.removeStyle();
+ });
+
+ // Kein Knoten ausgewählt
+ window.mindmapInstance.selectedNode = null;
+
+ // Info-Panel ausblenden
+ const nodeInfoPanel = document.getElementById('node-info-panel');
+ if (nodeInfoPanel) {
+ nodeInfoPanel.classList.remove('visible');
+ }
+
+ // Standard-Seitenleisten-Panels anzeigen
+ document.querySelectorAll('[data-sidebar]').forEach(panel => {
+ if (panel.getAttribute('data-sidebar') === 'node-description') {
+ panel.classList.add('hidden');
+ } else {
+ panel.classList.remove('hidden');
+ }
});
}
/**
- * Liefert die Standard-Stile für die Mindmap
- * @param {boolean} darkMode - Ob der Dark Mode aktiv ist
- * @returns {Array} Array von Cytoscape-Stilen
+ * Gibt die Stile für das neuronale Netzwerk-Design zurück
+ * @returns {Array} Stildefinitionen für Cytoscape
*/
-function getDefaultStyles(darkMode = document.documentElement.classList.contains('dark')) {
+function getNeuralNetworkStyles() {
return [
+ // Neuronen (Knoten)
{
selector: 'node',
style: {
- 'background-color': 'data(color)',
'label': 'data(name)',
- 'width': 40,
- 'height': 40,
- 'font-size': 12,
'text-valign': 'bottom',
'text-halign': 'center',
- 'text-margin-y': 8,
- 'color': darkMode ? '#f1f5f9' : '#334155',
- 'text-background-color': darkMode ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)',
- 'text-background-opacity': 0.8,
- 'text-background-padding': '2px',
- 'text-background-shape': 'roundrectangle',
- 'text-wrap': 'ellipsis',
- 'text-max-width': '100px'
- }
- },
- {
- selector: 'node[?isRoot]',
- style: {
- 'width': 60,
- 'height': 60,
- 'font-size': 14,
- 'font-weight': 'bold',
- 'text-background-opacity': 0.9,
- 'text-background-color': '#4299E1'
+ 'color': '#ffffff',
+ 'text-outline-width': 2,
+ 'text-outline-color': '#0a0e19',
+ 'text-outline-opacity': 0.9,
+ 'font-size': 10,
+ 'text-margin-y': 6,
+ 'width': 'mapData(neuronSize, 3, 10, 15, 40)',
+ 'height': 'mapData(neuronSize, 3, 10, 15, 40)',
+ 'background-color': 'data(color)',
+ 'background-opacity': 0.9,
+ 'border-width': 0,
+ 'shape': 'ellipse',
+ 'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 5, 15)',
+ 'shadow-color': 'data(color)',
+ 'shadow-opacity': 0.6,
+ 'shadow-offset-x': 0,
+ 'shadow-offset-y': 0,
+ 'text-wrap': 'wrap',
+ 'text-max-width': 100,
+ 'transition-property': 'background-color, shadow-color, shadow-opacity, shadow-blur',
+ 'transition-duration': '0.3s'
}
},
+ // Synapsen (Kanten)
{
selector: 'edge',
style: {
- 'width': 2,
- 'line-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
- 'target-arrow-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'curve-style': 'bezier',
- 'target-arrow-shape': 'triangle'
+ 'line-color': '#8a8aaa',
+ 'width': 'mapData(strength, 0.2, 0.8, 0.8, 2)',
+ 'line-opacity': 'mapData(strength, 0.2, 0.8, 0.4, 0.7)',
+ 'target-arrow-shape': 'none', // Keine Pfeilspitzen bei Neuronen
+ 'target-arrow-color': '#8a8aaa',
+ 'arrow-scale': 0.6,
+ 'transition-property': 'line-color, line-opacity, width',
+ 'transition-duration': '0.3s'
}
},
+ // Schwache Verbindungen
{
- selector: 'node.selected',
+ selector: 'edge[strength <= 0.4]',
style: {
- 'background-color': 'data(color)',
- 'border-width': 3,
- 'border-color': '#8b5cf6',
+ 'line-style': 'dotted'
+ }
+ },
+ // Mittlere Verbindungen
+ {
+ selector: 'edge[strength > 0.4][strength <= 0.6]',
+ style: {
+ 'line-style': 'dashed'
+ }
+ },
+ // Starke Verbindungen
+ {
+ selector: 'edge[strength > 0.6]',
+ style: {
+ 'line-style': 'solid'
+ }
+ },
+ // Wurzelknoten (speziell gestaltet)
+ {
+ selector: 'node[isRoot]',
+ style: {
+ 'font-size': 12,
+ 'font-weight': 'bold',
'width': 50,
'height': 50,
- 'font-size': 14,
- 'font-weight': 'bold',
- 'text-background-color': '#8b5cf6',
- 'text-background-opacity': 0.9
+ 'background-color': '#6366f1',
+ 'shadow-blur': 20,
+ 'shadow-color': '#6366f1',
+ 'shadow-opacity': 0.8,
+ 'text-margin-y': 8
+ }
+ },
+ // Hover-Effekt für Knoten
+ {
+ selector: 'node:hover',
+ style: {
+ 'shadow-blur': 20,
+ 'shadow-opacity': 0.9,
+ 'transition-property': 'shadow-opacity, shadow-blur',
+ 'transition-duration': '0.2s'
+ }
+ },
+ // Hover-Effekt für Kanten
+ {
+ selector: 'edge:hover',
+ style: {
+ 'line-color': '#a78bfa',
+ 'line-opacity': 0.8,
+ 'width': 2
}
}
];
-}
-
-class MindMap {
- constructor(selector, options = {}) {
- // Standardoptionen festlegen
- this.options = Object.assign({
- // Standard-Basisoptionen
- editable: false, // Ist die Mindmap editierbar?
- isUserLoggedIn: false, // Ist der Benutzer angemeldet?
- isPublicMap: true, // Handelt es sich um die öffentliche Mindmap?
- initialZoom: 1, // Anfängliche Zoom-Stufe
- fitViewOnInit: true, // Passt die Ansicht automatisch an
- minZoom: 0.2, // Minimale Zoom-Stufe
- maxZoom: 3, // Maximale Zoom-Stufe
- // Farbpalette für verschiedene Elemente
- colorPalette: {
- node: {
- default: '#8b5cf6', // Standard-Knotenfarbe
- selected: '#7c3aed', // Farbe für ausgewählte Knoten
- hover: '#6d28d9' // Farbe für Hover-Effekt
- },
- edge: {
- default: '#8b5cf6', // Standard-Kantenfarbe
- selected: '#7c3aed', // Farbe für ausgewählte Kanten
- hover: '#6d28d9' // Farbe für Hover-Effekt
- },
- text: {
- default: '#f3f4f6', // Standard-Textfarbe
- dark: '#1f2937', // Dunkle Textfarbe (für helle Hintergründe)
- light: '#f9fafb' // Helle Textfarbe (für dunkle Hintergründe)
- },
- background: {
- dark: '#111827', // Dunkler Hintergrund
- light: '#f9fafb' // Heller Hintergrund
- }
- },
- // Anpassbare Funktionen
- callbacks: {
- onNodeClick: null, // Callback für Knotenklick
- onEdgeClick: null, // Callback für Kantenklick
- onViewportChange: null, // Callback für Änderung der Ansicht
- onSelectionChange: null, // Callback für Änderung der Auswahl
- onLoad: null // Callback nach dem Laden der Mindmap
- }
- }, options);
-
- // Container-Element
- this.container = document.querySelector(selector);
- if (!this.container) {
- console.error(`Container mit Selektor '${selector}' nicht gefunden`);
- return;
- }
-
- // Prüfen, ob der Benutzer angemeldet ist
- this.isUserLoggedIn = document.body.classList.contains('user-logged-in') || this.options.isUserLoggedIn;
-
- // Wenn der Benutzer angemeldet ist und es sich um die öffentliche Mindmap handelt,
- // wird die Editierbarkeit aktiviert
- if (this.isUserLoggedIn && this.options.isPublicMap) {
- this.options.editable = true;
- }
-
- // Initialisiere Cytoscape
- this.initCytoscape();
-
- // Füge Bearbeiten-Funktionalität hinzu, wenn editierbar
- if (this.options.editable) {
- this.initEditableMode();
- }
-
- // Lade Daten basierend auf dem Typ der Mindmap
- if (this.options.isPublicMap) {
- this.loadPublicMindmap();
- } else if (this.options.userMindmapId) {
- this.loadUserMindmap(this.options.userMindmapId);
- }
-
- // Initialisiere UI-Elemente
- this.initUI();
-
- // Event-Listener-Initialisierung
- this.initEventListeners();
-
- // Flash-Message-System
- this.flashContainer = null;
- this.initFlashMessages();
- }
-
- // ... existing code ...
-
- // Initialisiert die Bearbeitungsmodi für die Mindmap
- initEditableMode() {
- // Toolbar für Bearbeitungsmodus hinzufügen
- this.addEditToolbar();
-
- // Kontextmenü-Funktionalität für Knoten
- this.cy.on('cxttap', 'node', (evt) => {
- const node = evt.target;
- const position = evt.renderedPosition;
-
- // Zeige Kontextmenü
- this.showNodeContextMenu(node, position);
- });
-
- // Doppelklick-Funktion für das Hinzufügen neuer Knoten
- this.cy.on('dblclick', (evt) => {
- // Nur auf Hintergrund reagieren, nicht auf Knoten
- if (evt.target === this.cy) {
- const position = evt.position;
- this.showAddNodeDialog(position);
- }
- });
-
- // Kontextmenü-Schließen bei Klick außerhalb
- document.addEventListener('click', (e) => {
- const contextMenu = document.getElementById('context-menu');
- if (contextMenu && !contextMenu.contains(e.target)) {
- contextMenu.remove();
- }
- });
- }
-
- // Zeigt ein Kontextmenü für einen Knoten an
- showNodeContextMenu(node, position) {
- // Entferne vorhandenes Kontextmenü
- const existingMenu = document.getElementById('context-menu');
- if (existingMenu) existingMenu.remove();
-
- // Erstelle neues Kontextmenü
- const contextMenu = document.createElement('div');
- contextMenu.id = 'context-menu';
- contextMenu.style.position = 'absolute';
- contextMenu.style.left = `${position.x}px`;
- contextMenu.style.top = `${position.y}px`;
- contextMenu.style.zIndex = '1000';
-
- // Menü-Elemente
- const menuItems = [
- { label: 'Gedanken anzeigen', icon: 'fas fa-brain', action: () => this.showThoughtsForNode(node) },
- { label: 'Neuen Gedanken erstellen', icon: 'fas fa-plus-circle', action: () => this.createThoughtForNode(node) },
- { label: 'Notiz hinzufügen', icon: 'fas fa-sticky-note', action: () => this.addNoteToNode(node) },
- { label: 'Knoten bearbeiten', icon: 'fas fa-edit', action: () => this.editNode(node) },
- { label: 'Verbindung erstellen', icon: 'fas fa-link', action: () => this.startEdgeCreation(node) },
- { label: 'Aus Mindmap entfernen', icon: 'fas fa-trash-alt', action: () => this.removeNodeFromMap(node) }
- ];
-
- // Menü erstellen
- menuItems.forEach(item => {
- const menuItem = document.createElement('div');
- menuItem.className = 'menu-item';
- menuItem.innerHTML = ` ${item.label}`;
- menuItem.addEventListener('click', () => {
- item.action();
- contextMenu.remove();
- });
- contextMenu.appendChild(menuItem);
- });
-
- // Anhängen an Body (außerhalb des Cytoscape-Containers)
- document.body.appendChild(contextMenu);
-
- // Stellen Sie sicher, dass das Kontextmenü vollständig sichtbar ist
- const menuRect = contextMenu.getBoundingClientRect();
- const windowWidth = window.innerWidth;
- const windowHeight = window.innerHeight;
-
- if (menuRect.right > windowWidth) {
- contextMenu.style.left = `${position.x - menuRect.width}px`;
- }
-
- if (menuRect.bottom > windowHeight) {
- contextMenu.style.top = `${position.y - menuRect.height}px`;
- }
- }
-
- // Fügt die Edit-Toolbar zur Mindmap hinzu
- addEditToolbar() {
- const toolbar = document.querySelector('.mindmap-toolbar');
- if (!toolbar) return;
-
- // Trennlinie
- const separator = document.createElement('div');
- separator.className = 'border-l h-6 mx-2 opacity-20';
- toolbar.appendChild(separator);
-
- // Bearbeiten-Buttons
- const editButtons = [
- { id: 'add-node-btn', icon: 'fa-plus', text: 'Knoten hinzufügen', action: () => this.showAddNodeDialog() },
- { id: 'save-layout-btn', icon: 'fa-save', text: 'Layout speichern', action: () => this.saveCurrentLayout() }
- ];
-
- editButtons.forEach(btn => {
- const button = document.createElement('button');
- button.id = btn.id;
- button.className = 'mindmap-action-btn edit-btn';
- button.innerHTML = `${btn.text}`;
- button.addEventListener('click', btn.action);
- toolbar.appendChild(button);
- });
-
- // Animation für die Edit-Buttons
- const editBtns = document.querySelectorAll('.edit-btn');
- editBtns.forEach(btn => {
- btn.style.opacity = '0';
- btn.style.transform = 'translateY(10px)';
-
- setTimeout(() => {
- btn.style.transition = 'all 0.3s ease';
- btn.style.opacity = '1';
- btn.style.transform = 'translateY(0)';
- }, 100);
- });
- }
-
- // Zeigt den Dialog zum Hinzufügen eines neuen Knotens
- showAddNodeDialog(position = null) {
- // Entferne bestehende Dialoge
- const existingDialog = document.getElementById('add-node-dialog');
- if (existingDialog) existingDialog.remove();
-
- // Erstelle Dialog
- const dialog = document.createElement('div');
- dialog.id = 'add-node-dialog';
- dialog.className = 'fixed inset-0 flex items-center justify-center z-50';
- dialog.innerHTML = `
-
${thought.abstract || thought.content.substring(0, 150) + '...'}
-$1');
- // Generische Beschreibung basierend auf dem Knotentyp
- switch (node.type) {
- case 'category':
- return `Dieser Knoten repräsentiert die Kategorie "${node.name}", die verschiedene verwandte Konzepte und Ideen zusammenfasst. Wählen Sie einen der verbundenen Unterthemen, um mehr Details zu erfahren.`;
- case 'subcategory':
- return `"${node.name}" ist eine Unterkategorie, die spezifische Aspekte eines größeren Themenbereichs beleuchtet. Die verbundenen Knoten zeigen wichtige Konzepte und Ideen innerhalb dieses Bereichs.`;
- default:
- return `Dieser Knoten repräsentiert das Konzept "${node.name}". Erforschen Sie die verbundenen Knoten, um Zusammenhänge und verwandte Ideen zu entdecken.`;
- }
+ return description;
}
\ No newline at end of file
diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js
index be81ba7..f9a531b 100644
--- a/static/js/update_mindmap.js
+++ b/static/js/update_mindmap.js
@@ -1,7 +1,7 @@
/**
* Update Mindmap
- * Dieses Skript fügt die neuen wissenschaftlichen Knoten zur Mindmap hinzu
- * und stellt sicher, dass sie korrekt angezeigt werden.
+ * Dieses Skript fügt Knoten zur Mindmap hinzu und stellt sicher,
+ * dass sie im neuronalen Netzwerk-Design angezeigt werden.
*/
// Warte bis DOM geladen ist
@@ -16,13 +16,13 @@ document.addEventListener('DOMContentLoaded', function() {
// Auf das Laden der Mindmap warten
document.addEventListener('mindmap-loaded', function() {
- console.log('Mindmap geladen, füge wissenschaftliche Knoten hinzu...');
+ console.log('Mindmap geladen, wende neuronales Netzwerk-Design an...');
enhanceMindmap();
});
});
/**
- * Erweitert die Mindmap mit den neu hinzugefügten wissenschaftlichen Knoten
+ * Erweitert die Mindmap mit dem neuronalen Netzwerk-Design
*/
function enhanceMindmap() {
// Auf die bestehende Cytoscape-Instanz zugreifen
@@ -33,25 +33,295 @@ function enhanceMindmap() {
return;
}
- // Aktualisiere das Layout mit zusätzlichem Platz für die neuen Knoten
+ // Aktualisiere das Layout für eine bessere Verteilung
cy.layout({
name: 'cose',
animate: true,
- animationDuration: 800,
+ animationDuration: 1800,
nodeDimensionsIncludeLabels: true,
padding: 100,
spacingFactor: 1.8,
randomize: false,
- fit: true
+ fit: true,
+ componentSpacing: 100,
+ nodeRepulsion: 8000,
+ edgeElasticity: 100,
+ nestingFactor: 1.2,
+ gravity: 80
}).run();
- console.log('Mindmap wurde erfolgreich aktualisiert!');
+ // Neuronen-Namen mit besserer Lesbarkeit umgestalten
+ cy.style()
+ .selector('node')
+ .style({
+ 'text-background-color': 'rgba(10, 14, 25, 0.7)',
+ 'text-background-opacity': 0.7,
+ 'text-background-padding': '2px',
+ 'text-border-opacity': 0.2,
+ 'text-border-width': 1,
+ 'text-border-color': '#8b5cf6'
+ })
+ .update();
- // Wenn ein Wissen-Knoten existiert, sicherstellen, dass er im Zentrum ist
- const rootNode = cy.getElementById('1');
- if (rootNode.length > 0) {
- cy.center(rootNode);
+ // Sicherstellen, dass alle Knoten Neuronen-Eigenschaften haben
+ cy.nodes().forEach(node => {
+ if (!node.data('neuronSize')) {
+ const neuronSize = Math.floor(Math.random() * 8) + 3;
+ node.data('neuronSize', neuronSize);
+ }
+
+ if (!node.data('neuronActivity')) {
+ const neuronActivity = Math.random() * 0.7 + 0.3;
+ node.data('neuronActivity', neuronActivity);
+ }
+
+ // Zusätzliche Neuronale Eigenschaften
+ node.data('pulseFrequency', Math.random() * 4 + 2); // Pulsfrequenz (2-6 Hz)
+ node.data('refractionPeriod', Math.random() * 300 + 700); // Refraktionszeit (700-1000ms)
+ node.data('threshold', Math.random() * 0.3 + 0.6); // Aktivierungsschwelle (0.6-0.9)
+ });
+
+ // Sicherstellen, dass alle Kanten Synapse-Eigenschaften haben
+ cy.edges().forEach(edge => {
+ if (!edge.data('strength')) {
+ const strength = Math.random() * 0.6 + 0.2;
+ edge.data('strength', strength);
+ }
+
+ // Zusätzliche synaptische Eigenschaften
+ edge.data('conductionVelocity', Math.random() * 0.5 + 0.3); // Leitungsgeschwindigkeit (0.3-0.8)
+ edge.data('latency', Math.random() * 100 + 50); // Signalverzögerung (50-150ms)
+ });
+
+ // Neuronales Netzwerk-Stil anwenden
+ applyNeuralNetworkStyle(cy);
+
+ console.log('Mindmap wurde erfolgreich im neuronalen Netzwerk-Stil aktualisiert');
+
+ // Spezielle Effekte für das neuronale Netzwerk hinzufügen
+ startNeuralActivitySimulation(cy);
+}
+
+/**
+ * Wendet detaillierte neuronale Netzwerkstile auf die Mindmap an
+ * @param {Object} cy - Cytoscape-Instanz
+ */
+function applyNeuralNetworkStyle(cy) {
+ // Wende erweiterte Stile für Neuronen und Synapsen an
+ cy.style()
+ .selector('node')
+ .style({
+ 'label': 'data(name)',
+ 'text-valign': 'bottom',
+ 'text-halign': 'center',
+ 'color': '#ffffff',
+ 'text-outline-width': 1.5,
+ 'text-outline-color': '#0a0e19',
+ 'text-outline-opacity': 0.9,
+ 'font-size': 10,
+ 'text-margin-y': 7,
+ 'width': 'mapData(neuronSize, 3, 10, 15, 40)',
+ 'height': 'mapData(neuronSize, 3, 10, 15, 40)',
+ 'background-color': 'data(color)',
+ 'background-opacity': 0.85,
+ 'border-width': 0,
+ 'shape': 'ellipse',
+ 'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 5, 15)',
+ 'shadow-color': 'data(color)',
+ 'shadow-opacity': 0.6,
+ 'shadow-offset-x': 0,
+ 'shadow-offset-y': 0
+ })
+ .selector('edge')
+ .style({
+ 'width': 'mapData(strength, 0.2, 0.8, 0.7, 2)',
+ 'curve-style': 'bezier',
+ 'line-color': '#8a8aaa',
+ 'line-opacity': 'mapData(strength, 0.2, 0.8, 0.4, 0.7)',
+ 'line-style': function(ele) {
+ const strength = ele.data('strength');
+ if (strength <= 0.4) return 'dotted';
+ if (strength <= 0.6) return 'dashed';
+ return 'solid';
+ },
+ 'target-arrow-shape': 'none',
+ 'source-endpoint': '0% 50%',
+ 'target-endpoint': '100% 50%'
+ })
+ .selector('node[isRoot]')
+ .style({
+ 'font-size': 12,
+ 'font-weight': 'bold',
+ 'width': 50,
+ 'height': 50,
+ 'background-color': '#6366f1',
+ 'shadow-blur': 20,
+ 'shadow-color': '#6366f1',
+ 'shadow-opacity': 0.8,
+ 'text-margin-y': 8
+ })
+ .update();
+}
+
+/**
+ * Simuliert neuronale Aktivität in der Mindmap
+ * @param {Object} cy - Cytoscape-Instanz
+ */
+function startNeuralActivitySimulation(cy) {
+ // Neuronen-Zustand für die Simulation
+ const neuronStates = new Map();
+
+ // Initialisieren aller Neuronen-Zustände
+ cy.nodes().forEach(node => {
+ neuronStates.set(node.id(), {
+ potential: Math.random() * 0.3, // Startpotential
+ lastFired: 0, // Zeitpunkt der letzten Aktivierung
+ isRefractory: false, // Refraktärphase
+ refractoryUntil: 0 // Ende der Refraktärphase
+ });
+ });
+
+ // Neuronale Aktivität simulieren
+ function simulateNeuralActivity() {
+ const currentTime = Date.now();
+ const nodes = cy.nodes().toArray();
+
+ // Zufällige Stimulation eines Neurons
+ if (Math.random() > 0.7) {
+ const randomNodeIndex = Math.floor(Math.random() * nodes.length);
+ const randomNode = nodes[randomNodeIndex];
+
+ const state = neuronStates.get(randomNode.id());
+ if (state && !state.isRefractory) {
+ state.potential += 0.5; // Erhöhe das Potential durch externe Stimulation
+ }
+ }
+
+ // Neuronen aktualisieren
+ nodes.forEach(node => {
+ const nodeId = node.id();
+ const state = neuronStates.get(nodeId);
+ const threshold = node.data('threshold') || 0.7;
+ const refractoryPeriod = node.data('refractionPeriod') || 1000;
+
+ // Überprüfen, ob die Refraktärphase beendet ist
+ if (state.isRefractory && currentTime >= state.refractoryUntil) {
+ state.isRefractory = false;
+ state.potential = 0.1; // Ruhepotential nach Refraktärphase
+ }
+
+ // Wenn nicht in Refraktärphase und Potential über Schwelle
+ if (!state.isRefractory && state.potential >= threshold) {
+ // Neuron feuert
+ fireNeuron(node, state, currentTime);
+ } else if (!state.isRefractory) {
+ // Potential langsam verlieren, wenn nicht gefeuert wird
+ state.potential *= 0.95;
+ }
+ });
+
+ // Simulation fortsetzen
+ requestAnimationFrame(simulateNeuralActivity);
}
+
+ // Neuron "feuern" lassen
+ function fireNeuron(node, state, currentTime) {
+ // Neuron aktivieren
+ node.animate({
+ style: {
+ 'background-opacity': 1,
+ 'shadow-opacity': 1,
+ 'shadow-blur': 25
+ },
+ duration: 300,
+ easing: 'ease-in-cubic',
+ complete: function() {
+ // Zurück zum normalen Zustand
+ node.animate({
+ style: {
+ 'background-opacity': 0.85,
+ 'shadow-opacity': 0.6,
+ 'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 5, 15)'
+ },
+ duration: 600,
+ easing: 'ease-out-cubic'
+ });
+ }
+ });
+
+ // Refraktärphase setzen
+ state.isRefractory = true;
+ state.lastFired = currentTime;
+ state.refractoryPeriod = node.data('refractionPeriod') || 1000;
+ state.refractoryUntil = currentTime + state.refractoryPeriod;
+ state.potential = 0; // Potential zurücksetzen
+
+ // Signal über verbundene Synapsen weiterleiten
+ propagateSignal(node, currentTime);
+ }
+
+ // Signal über Synapsen propagieren
+ function propagateSignal(sourceNode, currentTime) {
+ // Verbundene Kanten auswählen
+ const edges = sourceNode.connectedEdges().filter(edge =>
+ edge.source().id() === sourceNode.id() // Nur ausgehende Kanten
+ );
+
+ // Durch alle Kanten iterieren
+ edges.forEach(edge => {
+ // Signalverzögerung basierend auf synaptischen Eigenschaften
+ const latency = edge.data('latency') || 100;
+ const strength = edge.data('strength') || 0.5;
+
+ // Signal entlang der Kante senden
+ setTimeout(() => {
+ edge.animate({
+ style: {
+ 'line-color': '#a78bfa',
+ 'line-opacity': 0.9,
+ 'width': 2.5
+ },
+ duration: 200,
+ easing: 'ease-in',
+ complete: function() {
+ // Kante zurücksetzen
+ edge.animate({
+ style: {
+ 'line-color': '#8a8aaa',
+ 'line-opacity': 'mapData(strength, 0.2, 0.8, 0.4, 0.7)',
+ 'width': 'mapData(strength, 0.2, 0.8, 0.7, 2)'
+ },
+ duration: 400,
+ easing: 'ease-out'
+ });
+
+ // Zielknoten potenzial erhöhen
+ const targetNode = edge.target();
+ const targetState = neuronStates.get(targetNode.id());
+
+ if (targetState && !targetState.isRefractory) {
+ // Potentialzunahme basierend auf synaptischer Stärke
+ targetState.potential += strength * 0.4;
+
+ // Subtile Anzeige der Potenzialänderung
+ targetNode.animate({
+ style: {
+ 'background-opacity': Math.min(1, 0.85 + (strength * 0.2)),
+ 'shadow-opacity': Math.min(1, 0.6 + (strength * 0.3)),
+ 'shadow-blur': Math.min(25, 10 + (strength * 15))
+ },
+ duration: 300,
+ easing: 'ease-in-out'
+ });
+ }
+ }
+ });
+ }, latency);
+ });
+ }
+
+ // Starte die Simulation
+ simulateNeuralActivity();
}
// Hilfe-Funktion zum Hinzufügen eines Flash-Hinweises
diff --git a/templates/mindmap.html b/templates/mindmap.html
index 3bfc794..c465e8f 100644
--- a/templates/mindmap.html
+++ b/templates/mindmap.html
@@ -1,486 +1,228 @@
{% extends "base.html" %}
-{% block title %}Mindmap{% endblock %}
+{% block title %}Interaktive Mindmap{% endblock %}
{% block extra_css %}
{% endblock %}
{% block content %}
-
-