feat(mindmap): enhance interaction and initialization logic in mindmap files

This commit is contained in:
2025-05-06 21:53:54 +01:00
parent 49e5e19b7c
commit aeb829e36a
6 changed files with 1806 additions and 2096 deletions

View File

@@ -1,648 +1,214 @@
/** /**
* Mindmap-Initialisierer * Mindmap Initialisierung und Event-Handling
* Lädt und initialisiert die Mindmap-Visualisierung
*/ */
// Warte bis DOM geladen ist // Warte auf die Cytoscape-Instanz
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('mindmap-loaded', function() {
// Prüfe, ob wir auf der Mindmap-Seite sind const cy = window.cy;
const cyContainer = document.getElementById('cy'); if (!cy) return;
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();
}
});
/** // Event-Listener für Knoten-Klicks
* 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');
// Erstelle Cytoscape-Instanz
const cy = cytoscape({
container: cyContainer,
style: getNeuralNetworkStyles(),
layout: {
name: 'cose',
animate: true,
animationDuration: 1500,
nodeDimensionsIncludeLabels: true,
padding: 100,
spacingFactor: 1.5,
randomize: true,
componentSpacing: 100,
nodeRepulsion: 8000,
edgeElasticity: 100,
nestingFactor: 1.2,
gravity: 80,
idealEdgeLength: 150
},
wheelSensitivity: 0.1, // Sanfterer Zoom
minZoom: 0.3,
maxZoom: 2.5,
});
// Daten vom Server laden
loadMindmapData(cy);
// Event-Handler zuweisen
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'
});
}
};
}
/**
* 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 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: {
id: node.id.toString(),
name: node.name,
description: node.description || '',
color: node.color_code || '#8B5CF6',
isRoot: node.name === 'Wissen',
neuronSize: neuronSize,
neuronActivity: neuronActivity
}
});
});
// 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(),
strength: Math.random() * 0.6 + 0.2 // Zufällige Verbindungsstärke zwischen 0.2 und 0.8
}
});
});
} 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(),
strength: Math.random() * 0.6 + 0.2
}
});
}
});
}
// Elemente zu Cytoscape hinzufügen
cy.elements().remove();
cy.add(elements);
// Layout anwenden
cy.layout({
name: 'cose',
animate: true,
animationDuration: 1800,
nodeDimensionsIncludeLabels: true,
padding: 100,
spacingFactor: 1.8,
randomize: false,
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'));
})
.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 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: {
id: node.id.toString(),
name: node.name,
description: node.description || '',
color: node.color_code || '#8B5CF6',
isRoot: node.name === 'Wissen',
neuronSize: neuronSize,
neuronActivity: neuronActivity
}
});
});
// 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(),
strength: Math.random() * 0.6 + 0.2
}
});
});
// Elemente zu Cytoscape hinzufügen
cy.elements().remove();
cy.add(fallbackElements);
// Layout anwenden
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
*/
function setupEventListeners(cy) {
// Klick auf Knoten
cy.on('tap', 'node', function(evt) { cy.on('tap', 'node', function(evt) {
const node = evt.target; const node = evt.target;
// Alle vorherigen Hervorhebungen zurücksetzen // 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);
}
});
// 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 = '<span class="text-sm italic">Keine verbundenen Knoten</span>';
}
// 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, '<br>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/`(.*?)`/g, '<code>$1</code>');
}
// Setzt die Auswahl zurück
function resetSelection(cy) {
// Alle Stile zurücksetzen
cy.nodes().forEach(n => { cy.nodes().forEach(n => {
n.removeStyle(); n.removeStyle();
n.connectedEdges().removeStyle();
}); });
cy.edges().forEach(e => { // Speichere ausgewählten Knoten
e.removeStyle(); 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
}); });
// Kein Knoten ausgewählt // Verbundene Kanten und Knoten hervorheben
window.mindmapInstance.selectedNode = null; const connectedEdges = node.connectedEdges();
const connectedNodes = node.neighborhood('node');
// Info-Panel ausblenden connectedEdges.style({
const nodeInfoPanel = document.getElementById('node-info-panel'); 'line-color': '#a78bfa',
if (nodeInfoPanel) { 'target-arrow-color': '#a78bfa',
nodeInfoPanel.classList.remove('visible'); '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);
} }
});
// Standard-Seitenleisten-Panels anzeigen
document.querySelectorAll('[data-sidebar]').forEach(panel => { // Zoom-Controls
if (panel.getAttribute('data-sidebar') === 'node-description') { document.getElementById('zoomIn')?.addEventListener('click', () => {
panel.classList.add('hidden'); cy.zoom({
} else { level: cy.zoom() * 1.2,
panel.classList.remove('hidden'); 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 = `
<h3>${data.label || data.name}</h3>
<p class="category">${data.category || 'Keine Kategorie'}</p>
${data.description ? `<p class="description">${data.description}</p>` : ''}
<div class="connections">
<h4>Verbindungen (${connectedNodes.length})</h4>
<ul>
`;
connectedNodes.forEach(connectedNode => {
const connectedData = connectedNode.data();
html += `
<li style="color: ${connectedData.color || '#60a5fa'}">
${connectedData.label || connectedData.name}
</li>
`;
});
html += `
</ul>
</div>
`;
infoPanel.innerHTML = html;
infoPanel.style.display = 'block';
} }
/** /**
* Gibt die Stile für das neuronale Netzwerk-Design zurück * Aktualisiert die Seitenleiste mit Knoteninformationen
* @returns {Array} Stildefinitionen für Cytoscape * @param {Object} node - Der ausgewählte Knoten
*/ */
function getNeuralNetworkStyles() { function updateSidebar(node) {
return [ const sidebar = document.getElementById('sidebar');
// Neuronen (Knoten) if (!sidebar) return;
{
selector: 'node', const data = node.data();
style: { const connectedNodes = node.neighborhood('node');
'label': 'data(name)',
'text-valign': 'bottom', let html = `
'text-halign': 'center', <div class="node-details">
'color': '#ffffff', <h3>${data.label || data.name}</h3>
'text-outline-width': 2, <p class="category">${data.category || 'Keine Kategorie'}</p>
'text-outline-color': '#0a0e19', ${data.description ? `<p class="description">${data.description}</p>` : ''}
'text-outline-opacity': 0.9, <div class="connections">
'font-size': 10, <h4>Verbindungen (${connectedNodes.length})</h4>
'text-margin-y': 6, <ul>
'width': 'mapData(neuronSize, 3, 10, 15, 40)', `;
'height': 'mapData(neuronSize, 3, 10, 15, 40)',
'background-color': 'data(color)', connectedNodes.forEach(connectedNode => {
'background-opacity': 0.9, const connectedData = connectedNode.data();
'border-width': 0, html += `
'shape': 'ellipse', <li style="color: ${connectedData.color || '#60a5fa'}">
'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 5, 15)', ${connectedData.label || connectedData.name}
'shadow-color': 'data(color)', </li>
'shadow-opacity': 0.6, `;
'shadow-offset-x': 0, });
'shadow-offset-y': 0,
'text-wrap': 'wrap', html += `
'text-max-width': 100, </ul>
'transition-property': 'background-color, shadow-color, shadow-opacity, shadow-blur', </div>
'transition-duration': '0.3s' </div>
} `;
},
// Synapsen (Kanten) sidebar.innerHTML = html;
{ }
selector: 'edge',
style: { /**
'curve-style': 'bezier', * Setzt die Auswahl zurück
'line-color': '#8a8aaa', * @param {Object} cy - Cytoscape-Instanz
'width': 'mapData(strength, 0.2, 0.8, 0.8, 2)', */
'line-opacity': 'mapData(strength, 0.2, 0.8, 0.4, 0.7)', function resetSelection(cy) {
'target-arrow-shape': 'none', // Keine Pfeilspitzen bei Neuronen window.mindmapInstance.selectedNode = null;
'target-arrow-color': '#8a8aaa',
'arrow-scale': 0.6, // Alle Hervorhebungen zurücksetzen
'transition-property': 'line-color, line-opacity, width', cy.nodes().forEach(node => {
'transition-duration': '0.3s' node.removeStyle();
} node.connectedEdges().removeStyle();
}, });
// Schwache Verbindungen
{ // Info-Panel ausblenden
selector: 'edge[strength <= 0.4]', const infoPanel = document.getElementById('infoPanel');
style: { if (infoPanel) {
'line-style': 'dotted' infoPanel.style.display = 'none';
} }
},
// Mittlere Verbindungen // Seitenleiste leeren
{ const sidebar = document.getElementById('sidebar');
selector: 'edge[strength > 0.4][strength <= 0.6]', if (sidebar) {
style: { sidebar.innerHTML = '';
'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,
'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
}
}
];
}

View File

@@ -4,238 +4,216 @@
*/ */
// Globale Variablen // Globale Variablen
let cy;
let selectedNode = null; let selectedNode = null;
let isLegendVisible = true; let isLegendVisible = true;
// Initialisierung der Mindmap // Initialisierung der Mindmap
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('mindmap-loaded', function() {
// Cytoscape-Container initialisieren const cy = window.cy;
cy = cytoscape({ if (!cy) return;
container: document.getElementById('cy'),
style: [
{
selector: 'node',
style: {
'background-color': '#60a5fa',
'label': 'data(label)',
'text-valign': 'center',
'text-halign': 'center',
'text-wrap': 'wrap',
'text-max-width': '100px',
'font-size': '12px',
'color': '#fff',
'text-outline-color': '#000',
'text-outline-width': '2px',
'width': '40px',
'height': '40px',
'border-width': '2px',
'border-color': '#fff',
'border-opacity': '0.5',
'padding': '10px',
'text-events': 'yes'
}
},
{
selector: 'edge',
style: {
'width': '2px',
'line-color': 'rgba(255, 255, 255, 0.3)',
'target-arrow-color': 'rgba(255, 255, 255, 0.3)',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier',
'label': 'data(label)',
'font-size': '10px',
'color': '#fff',
'text-outline-color': '#000',
'text-outline-width': '2px',
'text-rotation': 'autorotate'
}
},
{
selector: ':selected',
style: {
'background-color': '#8b5cf6',
'line-color': '#8b5cf6',
'target-arrow-color': '#8b5cf6',
'source-arrow-color': '#8b5cf6',
'text-outline-color': '#000',
'text-outline-width': '2px',
'border-width': '3px',
'border-color': '#fff',
'border-opacity': '1'
}
},
{
selector: '.highlighted',
style: {
'background-color': '#10b981',
'line-color': '#10b981',
'target-arrow-color': '#10b981',
'source-arrow-color': '#10b981',
'transition-property': 'background-color, line-color, target-arrow-color',
'transition-duration': '0.3s'
}
}
],
layout: {
name: 'cose',
idealEdgeLength: 100,
nodeOverlap: 20,
refresh: 20,
fit: true,
padding: 30,
randomize: false,
componentSpacing: 100,
nodeRepulsion: 400000,
edgeElasticity: 100,
nestingFactor: 5,
gravity: 80,
numIter: 1000,
initialTemp: 200,
coolingFactor: 0.95,
minTemp: 1.0
}
});
// Event-Listener für Knoten // Event-Listener für Knoten-Klicks
cy.on('tap', 'node', function(evt) { cy.on('tap', 'node', function(evt) {
const node = evt.target; const node = evt.target;
updateNodeInfo(node);
highlightConnectedNodes(node); // Alle vorherigen Hervorhebungen zurücksetzen
cy.nodes().forEach(n => {
n.removeStyle();
n.connectedEdges().removeStyle();
});
// Speichere ausgewählten Knoten
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);
}); });
// Event-Listener für Hintergrund-Klick // Klick auf Hintergrund - Auswahl zurücksetzen
cy.on('tap', function(evt) { cy.on('tap', function(evt) {
if (evt.target === cy) { if (evt.target === cy) {
resetHighlighting(); resetSelection(cy);
hideNodeInfo();
} }
}); });
// Zoom-Kontrollen // Zoom-Controls
document.getElementById('zoom-in').addEventListener('click', function() { document.getElementById('zoomIn')?.addEventListener('click', () => {
cy.zoom({ cy.zoom({
level: cy.zoom() * 1.2, level: cy.zoom() * 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
}); });
}); });
document.getElementById('zoom-out').addEventListener('click', function() { document.getElementById('zoomOut')?.addEventListener('click', () => {
cy.zoom({ cy.zoom({
level: cy.zoom() / 1.2, level: cy.zoom() / 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
}); });
}); });
document.getElementById('reset-view').addEventListener('click', function() { document.getElementById('resetView')?.addEventListener('click', () => {
cy.fit(); cy.fit();
resetSelection(cy);
}); });
// Legende ein-/ausblenden // Legend-Toggle
document.getElementById('toggle-legend').addEventListener('click', function() { document.getElementById('toggleLegend')?.addEventListener('click', () => {
const legend = document.querySelector('.category-legend'); const legend = document.getElementById('categoryLegend');
isLegendVisible = !isLegendVisible; if (legend) {
legend.style.display = isLegendVisible ? 'flex' : 'none'; isLegendVisible = !isLegendVisible;
legend.style.display = isLegendVisible ? 'block' : 'none';
}
}); });
// Tastatursteuerung // Keyboard-Controls
document.addEventListener('keydown', function(evt) { document.addEventListener('keydown', (e) => {
switch(evt.key) { if (e.key === '+' || e.key === '=') {
case '+': cy.zoom({
cy.zoom({ level: cy.zoom() * 1.2,
level: cy.zoom() * 1.2, renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } });
}); } else if (e.key === '-' || e.key === '_') {
break; cy.zoom({
case '-': level: cy.zoom() / 1.2,
cy.zoom({ renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
level: cy.zoom() / 1.2, });
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } } else if (e.key === 'Escape') {
}); resetSelection(cy);
break;
case 'Escape':
resetHighlighting();
hideNodeInfo();
break;
} }
}); });
}); });
// Knoteninformationen aktualisieren /**
function updateNodeInfo(node) { * Aktualisiert das Info-Panel mit Knoteninformationen
const infoPanel = document.getElementById('node-info'); * @param {Object} node - Der ausgewählte Knoten
const infoContent = infoPanel.querySelector('.info-content'); */
function updateInfoPanel(node) {
const infoPanel = document.getElementById('infoPanel');
if (!infoPanel) return;
const data = node.data();
const connectedNodes = node.neighborhood('node');
// Knotendaten abrufen let html = `
const nodeData = node.data(); <h3>${data.label || data.name}</h3>
<p class="category">${data.category || 'Keine Kategorie'}</p>
${data.description ? `<p class="description">${data.description}</p>` : ''}
<div class="connections">
<h4>Verbindungen (${connectedNodes.length})</h4>
<ul>
`;
// Info-Panel aktualisieren connectedNodes.forEach(connectedNode => {
infoContent.innerHTML = ` const connectedData = connectedNode.data();
<h4 class="text-lg font-semibold mb-2">${nodeData.label}</h4> html += `
<p class="mb-3">${nodeData.description || 'Keine Beschreibung verfügbar.'}</p> <li style="color: ${connectedData.color || '#60a5fa'}">
<div class="mt-4"> ${connectedData.label || connectedData.name}
<h5 class="text-sm font-semibold mb-2">Verknüpfte Konzepte:</h5> </li>
<ul class="space-y-1"> `;
${getConnectedNodesList(node)} });
html += `
</ul> </ul>
</div> </div>
`; `;
// Panel anzeigen infoPanel.innerHTML = html;
infoPanel.classList.add('visible'); infoPanel.style.display = 'block';
} }
// Verbundene Knoten hervorheben /**
function highlightConnectedNodes(node) { * Aktualisiert die Seitenleiste mit Knoteninformationen
// Vorherige Hervorhebungen zurücksetzen * @param {Object} node - Der ausgewählte Knoten
resetHighlighting(); */
function updateSidebar(node) {
// Ausgewählten Knoten hervorheben const sidebar = document.getElementById('sidebar');
node.addClass('highlighted'); if (!sidebar) return;
// Verbundene Knoten und Kanten hervorheben
const connectedElements = node.neighborhood();
connectedElements.addClass('highlighted');
}
// Hervorhebungen zurücksetzen const data = node.data();
function resetHighlighting() {
cy.elements().removeClass('highlighted');
}
// Info-Panel ausblenden
function hideNodeInfo() {
const infoPanel = document.getElementById('node-info');
infoPanel.classList.remove('visible');
}
// Liste der verbundenen Knoten generieren
function getConnectedNodesList(node) {
const connectedNodes = node.neighborhood('node'); const connectedNodes = node.neighborhood('node');
if (connectedNodes.length === 0) {
return '<li class="text-gray-400">Keine direkten Verbindungen</li>';
}
return connectedNodes.map(connectedNode => { let html = `
const data = connectedNode.data(); <div class="node-details">
return ` <h3>${data.label || data.name}</h3>
<li class="flex items-center space-x-2"> <p class="category">${data.category || 'Keine Kategorie'}</p>
<span class="w-2 h-2 rounded-full" style="background-color: ${getNodeColor(data.category)}"></span> ${data.description ? `<p class="description">${data.description}</p>` : ''}
<span>${data.label}</span> <div class="connections">
<h4>Verbindungen (${connectedNodes.length})</h4>
<ul>
`;
connectedNodes.forEach(connectedNode => {
const connectedData = connectedNode.data();
html += `
<li style="color: ${connectedData.color || '#60a5fa'}">
${connectedData.label || connectedData.name}
</li> </li>
`; `;
}).join(''); });
html += `
</ul>
</div>
</div>
`;
sidebar.innerHTML = html;
} }
// Farbe basierend auf Kategorie /**
function getNodeColor(category) { * Setzt die Auswahl zurück
const colors = { * @param {Object} cy - Cytoscape-Instanz
'Philosophie': '#60a5fa', */
'Wissenschaft': '#8b5cf6', function resetSelection(cy) {
'Technologie': '#10b981', selectedNode = null;
'Künste': '#f59e0b',
'Psychologie': '#ef4444' // Alle Hervorhebungen zurücksetzen
}; cy.nodes().forEach(node => {
return colors[category] || '#60a5fa'; 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 = '';
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -26,413 +26,526 @@ document.addEventListener('DOMContentLoaded', function() {
function initMindmapPage() { function initMindmapPage() {
console.log('Mindmap-Seite wird initialisiert...'); console.log('Mindmap-Seite wird initialisiert...');
// Hauptcontainer für die Mindmap // Warte auf die Cytoscape-Instanz
const cyContainer = document.getElementById('cy'); document.addEventListener('mindmap-loaded', function() {
if (!cyContainer) { const cy = window.cy;
console.error('Mindmap-Container #cy nicht gefunden!'); if (!cy) return;
return;
} // Event-Listener für Knoten-Klicks
cy.on('tap', 'node', function(evt) {
// Info-Panel für Knotendetails const node = evt.target;
const nodeInfoPanel = document.getElementById('node-info-panel');
const nodeDescription = document.getElementById('node-description'); // Alle vorherigen Hervorhebungen zurücksetzen
const connectedNodes = document.getElementById('connected-nodes'); cy.nodes().forEach(n => {
n.removeStyle();
// Toolbar-Buttons n.connectedEdges().removeStyle();
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 = `
<div class="p-8 text-center bg-red-50 dark:bg-red-900/20 rounded-lg">
<i class="fa-solid fa-triangle-exclamation text-4xl text-red-500 mb-4"></i>
<p class="text-lg text-red-600 dark:text-red-400">${message}</p>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Bitte laden Sie die Seite neu oder kontaktieren Sie den Support.</p>
</div>
`;
}
/**
* Initialisiert Cytoscape mit den Mindmap-Daten
*/
function initCytoscape() {
console.log('Cytoscape.js wird initialisiert...');
// Zeige Ladeanimation
cyContainer.innerHTML = `
<div class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-purple-500"></div>
</div>
`;
// 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);
}); });
}
// Speichere ausgewählten Knoten
/** window.mindmapInstance.selectedNode = node;
* Generiert Standarddaten für die Mindmap als Fallback
*/ // Aktiviere leuchtenden Effekt statt Umkreisung
function generateDefaultData() { node.style({
return { 'background-opacity': 1,
nodes: [ 'background-color': node.data('color'),
{ id: 'root', name: 'Wissen', description: 'Zentrale Wissensbasis', category: 'Zentral', color_code: '#4299E1' }, 'shadow-color': node.data('color'),
{ id: 'philosophy', name: 'Philosophie', description: 'Philosophisches Denken', category: 'Philosophie', color_code: '#9F7AEA', parent_id: 'root' }, 'shadow-opacity': 1,
{ id: 'science', name: 'Wissenschaft', description: 'Wissenschaftliche Erkenntnisse', category: 'Wissenschaft', color_code: '#48BB78', parent_id: 'root' }, 'shadow-blur': 15,
{ id: 'technology', name: 'Technologie', description: 'Technologische Entwicklungen', category: 'Technologie', color_code: '#ED8936', parent_id: 'root' }, 'shadow-offset-x': 0,
{ id: 'arts', name: 'Künste', description: 'Künstlerische Ausdrucksformen', category: 'Künste', color_code: '#ED64A6', parent_id: 'root' }, 'shadow-offset-y': 0
{ id: 'psychology', name: 'Psychologie', description: 'Menschliches Verhalten und Geist', category: 'Psychologie', color_code: '#4299E1', parent_id: 'root' } });
]
}; // Verbundene Kanten und Knoten hervorheben
} const connectedEdges = node.connectedEdges();
const connectedNodes = node.neighborhood('node');
/**
* Rendert die Mindmap mit Cytoscape.js connectedEdges.style({
*/ 'line-color': '#a78bfa',
function renderMindmap(data) { 'target-arrow-color': '#a78bfa',
console.log('Rendere Mindmap mit Daten:', data); '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);
});
// Konvertiere Backend-Daten in Cytoscape-Format // Klick auf Hintergrund - Auswahl zurücksetzen
const elements = convertToCytoscapeFormat(data); cy.on('tap', function(evt) {
if (evt.target === cy) {
// Leere den Container resetSelection(cy);
cyContainer.innerHTML = '';
// Erstelle Cytoscape-Instanz
mindmap = cytoscape({
container: cyContainer,
elements: elements,
style: [
{
selector: 'node',
style: {
'background-color': 'data(color)',
'label': 'data(name)',
'width': 30,
'height': 30,
'font-size': 12,
'text-valign': 'bottom',
'text-halign': 'center',
'text-margin-y': 8,
'color': document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#334155',
'text-background-color': document.documentElement.classList.contains('dark') ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)',
'text-background-opacity': 0.8,
'text-background-padding': '2px',
'text-background-shape': 'roundrectangle',
'text-wrap': 'ellipsis',
'text-max-width': '100px'
}
},
{
selector: 'edge',
style: {
'width': 2,
'line-color': document.documentElement.classList.contains('dark') ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'target-arrow-color': document.documentElement.classList.contains('dark') ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'curve-style': 'bezier'
}
},
{
selector: 'node:selected',
style: {
'background-color': 'data(color)',
'border-width': 3,
'border-color': '#8b5cf6',
'width': 40,
'height': 40,
'font-size': 14,
'font-weight': 'bold',
'text-background-color': '#8b5cf6',
'text-background-opacity': 0.9
}
}
],
layout: {
name: 'cose',
animate: true,
animationDuration: 800,
nodeDimensionsIncludeLabels: true,
refresh: 30,
randomize: true,
componentSpacing: 100,
nodeRepulsion: 8000,
nodeOverlap: 20,
idealEdgeLength: 200,
edgeElasticity: 100,
nestingFactor: 1.2,
gravity: 80,
fit: true,
padding: 30
} }
}); });
// Event-Listener für Knoteninteraktionen // Zoom-Controls
mindmap.on('tap', 'node', function(evt) { document.getElementById('zoomIn')?.addEventListener('click', () => {
const node = evt.target; cy.zoom({
const nodeData = node.data(); level: cy.zoom() * 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
// Update Info-Panel });
updateNodeInfoPanel(nodeData);
// Lade verbundene Knoten
updateConnectedNodes(node);
}); });
// Toolbar-Buttons aktivieren document.getElementById('zoomOut')?.addEventListener('click', () => {
if (fitButton) { cy.zoom({
fitButton.addEventListener('click', () => { level: cy.zoom() / 1.2,
mindmap.fit(); renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
mindmap.center();
}); });
} });
if (resetButton) { document.getElementById('resetView')?.addEventListener('click', () => {
resetButton.addEventListener('click', () => { cy.fit();
mindmap.layout({ resetSelection(cy);
name: 'cose', });
animate: true,
randomize: true, // Legend-Toggle
fit: true document.getElementById('toggleLegend')?.addEventListener('click', () => {
}).run(); const legend = document.getElementById('categoryLegend');
}); if (legend) {
} isLegendVisible = !isLegendVisible;
legend.style.display = isLegendVisible ? 'block' : 'none';
if (toggleLabelsButton) { }
let labelsVisible = true; });
toggleLabelsButton.addEventListener('click', () => {
labelsVisible = !labelsVisible; // Keyboard-Controls
document.addEventListener('keydown', (e) => {
if (labelsVisible) { if (e.key === '+' || e.key === '=') {
mindmap.style() cy.zoom({
.selector('node') level: cy.zoom() * 1.2,
.style('label', 'data(name)') renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
.update(); });
} else { } else if (e.key === '-' || e.key === '_') {
mindmap.style() cy.zoom({
.selector('node') level: cy.zoom() / 1.2,
.style('label', '') renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
.update(); });
} 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 = `
<h3>${data.label || data.name}</h3>
<p class="category">${data.category || 'Keine Kategorie'}</p>
${data.description ? `<p class="description">${data.description}</p>` : ''}
<div class="connections">
<h4>Verbindungen (${connectedNodes.length})</h4>
<ul>
`;
connectedNodes.forEach(connectedNode => {
const connectedData = connectedNode.data();
html += `
<li style="color: ${connectedData.color || '#60a5fa'}">
${connectedData.label || connectedData.name}
</li>
`;
});
html += `
</ul>
</div>
`;
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 = `
<div class="node-details">
<h3>${data.label || data.name}</h3>
<p class="category">${data.category || 'Keine Kategorie'}</p>
${data.description ? `<p class="description">${data.description}</p>` : ''}
<div class="connections">
<h4>Verbindungen (${connectedNodes.length})</h4>
<ul>
`;
connectedNodes.forEach(connectedNode => {
const connectedData = connectedNode.data();
html += `
<li style="color: ${connectedData.color || '#60a5fa'}">
${connectedData.label || connectedData.name}
</li>
`;
});
html += `
</ul>
</div>
</div>
`;
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 = '';
}
}
/**
* Generiert Standarddaten für die Mindmap als Fallback
*/
function generateDefaultData() {
return {
nodes: [
{ id: 'root', name: 'Wissen', description: 'Zentrale Wissensbasis', category: 'Zentral', color_code: '#4299E1' },
{ id: 'philosophy', name: 'Philosophie', description: 'Philosophisches Denken', category: 'Philosophie', color_code: '#9F7AEA', parent_id: 'root' },
{ id: 'science', name: 'Wissenschaft', description: 'Wissenschaftliche Erkenntnisse', category: 'Wissenschaft', color_code: '#48BB78', parent_id: 'root' },
{ id: 'technology', name: 'Technologie', description: 'Technologische Entwicklungen', category: 'Technologie', color_code: '#ED8936', parent_id: 'root' },
{ id: 'arts', name: 'Künste', description: 'Künstlerische Ausdrucksformen', category: 'Künste', color_code: '#ED64A6', parent_id: 'root' },
{ id: 'psychology', name: 'Psychologie', description: 'Menschliches Verhalten und Geist', category: 'Psychologie', color_code: '#4299E1', parent_id: 'root' }
]
};
}
/**
* Rendert die Mindmap mit Cytoscape.js
*/
function renderMindmap(data) {
console.log('Rendere Mindmap mit Daten:', data);
// Konvertiere Backend-Daten in Cytoscape-Format
const elements = convertToCytoscapeFormat(data);
// Leere den Container
cyContainer.innerHTML = '';
// Erstelle Cytoscape-Instanz
mindmap = cytoscape({
container: cyContainer,
elements: elements,
style: [
{
selector: 'node',
style: {
'background-color': 'data(color)',
'label': 'data(name)',
'width': 30,
'height': 30,
'font-size': 12,
'text-valign': 'bottom',
'text-halign': 'center',
'text-margin-y': 8,
'color': document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#334155',
'text-background-color': document.documentElement.classList.contains('dark') ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)',
'text-background-opacity': 0.8,
'text-background-padding': '2px',
'text-background-shape': 'roundrectangle',
'text-wrap': 'ellipsis',
'text-max-width': '100px'
} }
}); },
{
selector: 'edge',
style: {
'width': 2,
'line-color': document.documentElement.classList.contains('dark') ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'target-arrow-color': document.documentElement.classList.contains('dark') ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'curve-style': 'bezier'
}
},
{
selector: 'node:selected',
style: {
'background-color': 'data(color)',
'border-width': 3,
'border-color': '#8b5cf6',
'width': 40,
'height': 40,
'font-size': 14,
'font-weight': 'bold',
'text-background-color': '#8b5cf6',
'text-background-opacity': 0.9
}
}
],
layout: {
name: 'cose',
animate: true,
animationDuration: 800,
nodeDimensionsIncludeLabels: true,
refresh: 30,
randomize: true,
componentSpacing: 100,
nodeRepulsion: 8000,
nodeOverlap: 20,
idealEdgeLength: 200,
edgeElasticity: 100,
nestingFactor: 1.2,
gravity: 80,
fit: true,
padding: 30
} }
});
// Event-Listener für Knoteninteraktionen
mindmap.on('tap', 'node', function(evt) {
const node = evt.target;
const nodeData = node.data();
// Dark Mode-Änderungen überwachen // Update Info-Panel
document.addEventListener('darkModeToggled', function(event) { updateNodeInfoPanel(nodeData);
updateDarkModeStyles(event.detail.isDark);
});
// Initial fit und center // Lade verbundene Knoten
setTimeout(() => { updateConnectedNodes(node);
});
// Toolbar-Buttons aktivieren
if (fitButton) {
fitButton.addEventListener('click', () => {
mindmap.fit(); mindmap.fit();
mindmap.center(); mindmap.center();
}, 100); });
} }
/** if (resetButton) {
* Konvertiert die Backend-Daten ins Cytoscape-Format resetButton.addEventListener('click', () => {
*/ mindmap.layout({
function convertToCytoscapeFormat(data) { name: 'cose',
const elements = { animate: true,
nodes: [], randomize: true,
edges: [] fit: true
}; }).run();
});
// Nodes hinzufügen }
if (data.nodes && data.nodes.length > 0) {
data.nodes.forEach(node => { if (toggleLabelsButton) {
elements.nodes.push({ let labelsVisible = true;
data: { toggleLabelsButton.addEventListener('click', () => {
id: String(node.id), labelsVisible = !labelsVisible;
name: node.name,
description: node.description || 'Keine Beschreibung verfügbar', if (labelsVisible) {
category: node.category || 'Allgemein', mindmap.style()
color: node.color_code || getRandomColor() .selector('node')
} .style('label', 'data(name)')
}); .update();
} else {
// Kante zum Elternknoten hinzufügen (falls vorhanden) mindmap.style()
if (node.parent_id) { .selector('node')
elements.edges.push({ .style('label', '')
data: { .update();
id: `edge-${node.parent_id}-${node.id}`, }
source: String(node.parent_id), });
target: String(node.id) }
}
}); // Dark Mode-Änderungen überwachen
document.addEventListener('darkModeToggled', function(event) {
updateDarkModeStyles(event.detail.isDark);
});
// Initial fit und center
setTimeout(() => {
mindmap.fit();
mindmap.center();
}, 100);
}
/**
* Konvertiert die Backend-Daten ins Cytoscape-Format
*/
function convertToCytoscapeFormat(data) {
const elements = {
nodes: [],
edges: []
};
// Nodes hinzufügen
if (data.nodes && data.nodes.length > 0) {
data.nodes.forEach(node => {
elements.nodes.push({
data: {
id: String(node.id),
name: node.name,
description: node.description || 'Keine Beschreibung verfügbar',
category: node.category || 'Allgemein',
color: node.color_code || getRandomColor()
} }
}); });
// Zusätzliche Kanten zwischen Knoten hinzufügen (falls in den Daten vorhanden) // Kante zum Elternknoten hinzufügen (falls vorhanden)
if (data.edges && data.edges.length > 0) { if (node.parent_id) {
data.edges.forEach(edge => { elements.edges.push({
elements.edges.push({ data: {
data: { id: `edge-${node.parent_id}-${node.id}`,
id: `edge-${edge.source}-${edge.target}`, source: String(node.parent_id),
source: String(edge.source), target: String(node.id)
target: String(edge.target) }
}
});
}); });
} }
} });
return elements; // Zusätzliche Kanten zwischen Knoten hinzufügen (falls in den Daten vorhanden)
} if (data.edges && data.edges.length > 0) {
data.edges.forEach(edge => {
/** elements.edges.push({
* Aktualisiert das Informations-Panel mit den Knotendaten data: {
*/ id: `edge-${edge.source}-${edge.target}`,
function updateNodeInfoPanel(nodeData) { source: String(edge.source),
if (nodeInfoPanel && nodeDescription) { target: String(edge.target)
// Panel anzeigen }
nodeInfoPanel.style.display = 'block';
// Titel und Beschreibung aktualisieren
const titleElement = nodeInfoPanel.querySelector('.info-panel-title');
if (titleElement) {
titleElement.textContent = nodeData.name;
}
if (nodeDescription) {
nodeDescription.textContent = nodeData.description || 'Keine Beschreibung verfügbar';
}
}
}
/**
* Aktualisiert die Liste der verbundenen Knoten
*/
function updateConnectedNodes(node) {
if (connectedNodes) {
// Leere den Container
connectedNodes.innerHTML = '';
// Hole verbundene Knoten
const connectedEdges = node.connectedEdges();
if (connectedEdges.length === 0) {
connectedNodes.innerHTML = '<div class="text-sm italic">Keine verbundenen Knoten</div>';
return;
}
// Füge alle verbundenen Knoten hinzu
connectedEdges.forEach(edge => {
const targetNode = edge.target().id() === node.id() ? edge.source() : edge.target();
const targetData = targetNode.data();
const nodeLink = document.createElement('div');
nodeLink.className = 'node-link';
nodeLink.innerHTML = `
<span class="w-2 h-2 rounded-full" style="background-color: ${targetData.color}"></span>
${targetData.name}
`;
// Klick-Event zum Fokussieren des Knotens
nodeLink.addEventListener('click', () => {
mindmap.center(targetNode);
targetNode.select();
updateNodeInfoPanel(targetData);
updateConnectedNodes(targetNode);
}); });
connectedNodes.appendChild(nodeLink);
}); });
} }
} }
/** return elements;
* Aktualisiert die Styles bei Dark Mode-Änderungen }
*/
function updateDarkModeStyles(isDark) { /**
if (!mindmap) return; * Aktualisiert das Informations-Panel mit den Knotendaten
*/
function updateNodeInfoPanel(nodeData) {
if (nodeInfoPanel && nodeDescription) {
// Panel anzeigen
nodeInfoPanel.style.display = 'block';
const textColor = isDark ? '#f1f5f9' : '#334155'; // Titel und Beschreibung aktualisieren
const textBgColor = isDark ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)'; const titleElement = nodeInfoPanel.querySelector('.info-panel-title');
const edgeColor = isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)'; if (titleElement) {
titleElement.textContent = nodeData.name;
}
mindmap.style() if (nodeDescription) {
.selector('node') nodeDescription.textContent = nodeData.description || 'Keine Beschreibung verfügbar';
.style({ }
'color': textColor,
'text-background-color': textBgColor
})
.selector('edge')
.style({
'line-color': edgeColor,
'target-arrow-color': edgeColor
})
.update();
} }
}
/**
* Aktualisiert die Liste der verbundenen Knoten
*/
function updateConnectedNodes(node) {
if (connectedNodes) {
// Leere den Container
connectedNodes.innerHTML = '';
// Hole verbundene Knoten
const connectedEdges = node.connectedEdges();
if (connectedEdges.length === 0) {
connectedNodes.innerHTML = '<div class="text-sm italic">Keine verbundenen Knoten</div>';
return;
}
// Füge alle verbundenen Knoten hinzu
connectedEdges.forEach(edge => {
const targetNode = edge.target().id() === node.id() ? edge.source() : edge.target();
const targetData = targetNode.data();
const nodeLink = document.createElement('div');
nodeLink.className = 'node-link';
nodeLink.innerHTML = `
<span class="w-2 h-2 rounded-full" style="background-color: ${targetData.color}"></span>
${targetData.name}
`;
// Klick-Event zum Fokussieren des Knotens
nodeLink.addEventListener('click', () => {
mindmap.center(targetNode);
targetNode.select();
updateNodeInfoPanel(targetData);
updateConnectedNodes(targetNode);
});
connectedNodes.appendChild(nodeLink);
});
}
}
/**
* Aktualisiert die Styles bei Dark Mode-Änderungen
*/
function updateDarkModeStyles(isDark) {
if (!mindmap) return;
/** const textColor = isDark ? '#f1f5f9' : '#334155';
* Generiert eine zufällige Farbe const textBgColor = isDark ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)';
*/ const edgeColor = isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)';
function getRandomColor() {
const colors = [ mindmap.style()
'#4299E1', // Blau .selector('node')
'#9F7AEA', // Lila .style({
'#48BB78', // Grün 'color': textColor,
'#ED8936', // Orange 'text-background-color': textBgColor
'#ED64A6', // Pink })
'#F56565' // Rot .selector('edge')
]; .style({
return colors[Math.floor(Math.random() * colors.length)]; 'line-color': edgeColor,
} 'target-arrow-color': edgeColor
} })
.update();
}
/**
* Generiert eine zufällige Farbe
*/
function getRandomColor() {
const colors = [
'#4299E1', // Blau
'#9F7AEA', // Lila
'#48BB78', // Grün
'#ED8936', // Orange
'#ED64A6', // Pink
'#F56565' // Rot
];
return colors[Math.floor(Math.random() * colors.length)];
}
// Initialisiere die Mindmap-Seite
initMindmapPage();

View File

@@ -6,19 +6,83 @@
// Warte bis DOM geladen ist // Warte bis DOM geladen ist
document.addEventListener('DOMContentLoaded', function() { 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'); const cyContainer = document.getElementById('cy');
console.log('Container gefunden:', cyContainer);
if (!cyContainer) { if (!cyContainer) {
console.log('Kein Mindmap-Container gefunden, überspringe Initialisierung.'); console.error('Mindmap-Container #cy nicht gefunden!');
return; return;
} }
// 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 (kannst du später ersetzen)
const elements = [
{ data: { id: 'a', label: 'Neuron A' } },
{ data: { id: 'b', label: 'Neuron B' } },
{ data: { id: 'c', label: 'Neuron C' } },
{ data: { source: 'a', target: 'b' } },
{ data: { source: 'a', target: 'c' } }
];
console.log('Initialisiere Cytoscape...');
// Auf das Laden der Mindmap warten // Initialisiere Cytoscape
document.addEventListener('mindmap-loaded', function() { window.cy = cytoscape({
console.log('Mindmap geladen, wende neuronales Netzwerk-Design an...'); container: cyContainer,
updateMindmap(); elements: elements,
style: [
{
selector: 'node',
style: {
'background-color': '#60a5fa',
'label': 'data(label)',
'color': '#fff',
'text-background-color': '#222a',
'text-background-opacity': 0.7,
'text-background-padding': '4px',
'text-valign': 'center',
'text-halign': 'center',
'font-size': 18,
'width': 40,
'height': 40,
'border-width': 4,
'border-color': '#a78bfa',
'shadow-blur': 20,
'shadow-color': '#a78bfa',
'shadow-opacity': 0.7,
}
},
{
selector: 'edge',
style: {
'width': 4,
'line-color': '#a78bfa',
'target-arrow-color': '#a78bfa',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier'
}
}
],
layout: {
name: 'cose',
animate: true
}
}); });
console.log('Cytoscape initialisiert');
// Event auslösen, damit andere Scripte reagieren können
document.dispatchEvent(new Event('mindmap-loaded'));
console.log('mindmap-loaded Event ausgelöst');
}); });
// Mindmap-Daten // Mindmap-Daten
@@ -158,6 +222,15 @@ const mindmapData = {
] ]
}; };
// Kategorie-Farben definieren
const categoryColors = {
'Philosophie': '#60a5fa',
'Wissenschaft': '#8b5cf6',
'Technologie': '#10b981',
'Künste': '#f59e0b',
'Psychologie': '#ef4444'
};
// Mindmap aktualisieren // Mindmap aktualisieren
function updateMindmap() { function updateMindmap() {
if (!cy) return; if (!cy) return;
@@ -173,7 +246,13 @@ function updateMindmap() {
id: node.id, id: node.id,
label: node.label, label: node.label,
category: node.category, category: node.category,
description: node.description description: node.description,
color: categoryColors[node.category] || '#60a5fa',
neuronSize: Math.floor(Math.random() * 8) + 3,
neuronActivity: Math.random() * 0.7 + 0.3,
pulseFrequency: Math.random() * 4 + 2,
refractionPeriod: Math.random() * 300 + 700,
threshold: Math.random() * 0.3 + 0.6
} }
}); });
}); });
@@ -185,30 +264,126 @@ function updateMindmap() {
data: { data: {
source: edge.source, source: edge.source,
target: edge.target, target: edge.target,
label: edge.label label: edge.label,
strength: Math.random() * 0.6 + 0.2,
conductionVelocity: Math.random() * 0.5 + 0.3,
latency: Math.random() * 100 + 50
} }
}); });
}); });
// Neuronales Netzwerk-Styling anwenden
cy.style([
{
selector: 'node',
style: {
'label': 'data(label)',
'text-valign': 'center',
'text-halign': 'center',
'text-wrap': 'wrap',
'text-max-width': '100px',
'font-size': '14px',
'color': '#ffffff',
'text-outline-color': '#0a0e19',
'text-outline-width': 1.5,
'text-outline-opacity': 0.9,
'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,
'text-background-color': 'rgba(24, 28, 45, 0.7)',
'text-background-opacity': 0.8,
'text-background-shape': 'roundrectangle',
'text-background-padding': '4px',
'transition-property': 'background-color, shadow-blur, shadow-opacity, background-opacity',
'transition-duration': '0.5s'
}
},
{
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%',
'transition-property': 'line-color, width, line-style, line-opacity',
'transition-duration': '0.4s'
}
},
{
selector: ':selected',
style: {
'background-color': '#f59e0b',
'shadow-blur': 32,
'shadow-color': '#f59e0b',
'shadow-opacity': 1,
'border-width': 4,
'border-color': '#fff'
}
},
{
selector: '.highlighted',
style: {
'background-color': '#10b981',
'shadow-blur': 28,
'shadow-color': '#10b981',
'shadow-opacity': 1,
'line-color': '#10b981',
'target-arrow-color': '#10b981',
'transition-property': 'background-color, shadow-blur, shadow-opacity, line-color',
'transition-duration': '0.3s'
}
}
]);
// Layout anwenden // Layout anwenden
cy.layout({ cy.layout({
name: 'cose', name: 'cose',
idealEdgeLength: 100, animate: true,
nodeOverlap: 20, animationDuration: 1800,
refresh: 20, nodeDimensionsIncludeLabels: true,
fit: true, padding: 100,
padding: 30, spacingFactor: 1.8,
randomize: false, randomize: false,
fit: true,
componentSpacing: 100, componentSpacing: 100,
nodeRepulsion: 400000, nodeRepulsion: 8000,
edgeElasticity: 100, edgeElasticity: 100,
nestingFactor: 5, nestingFactor: 1.2,
gravity: 80, gravity: 80
numIter: 1000,
initialTemp: 200,
coolingFactor: 0.95,
minTemp: 1.0
}).run(); }).run();
// Pulsierende Soma-Effekte starten
if (window.pulseInterval) clearInterval(window.pulseInterval);
window.pulseInterval = setInterval(() => {
cy.nodes().forEach(node => {
const baseBlur = 18;
const pulse = 0.7 + 0.3 * Math.sin(Date.now() / 400 + node.id().charCodeAt(0));
node.style('shadow-blur', baseBlur * pulse);
node.style('shadow-opacity', 0.6 + 0.3 * pulse);
node.style('background-opacity', 0.85 + 0.1 * pulse);
});
}, 80);
// Neuronale Aktivität simulieren
startNeuralActivitySimulation(cy);
} }
/** /**
@@ -358,160 +533,91 @@ function applyNeuralNetworkStyle(cy) {
* @param {Object} cy - Cytoscape-Instanz * @param {Object} cy - Cytoscape-Instanz
*/ */
function startNeuralActivitySimulation(cy) { function startNeuralActivitySimulation(cy) {
// Neuronen-Zustand für die Simulation if (window.neuralInterval) clearInterval(window.neuralInterval);
const neuronStates = new Map();
const nodes = cy.nodes();
const edges = cy.edges();
let currentTime = Date.now();
// Neuronale Aktivität simulieren
function simulateNeuralActivity() {
currentTime = Date.now();
// Initialisieren aller Neuronen-Zustände // Zufällige Neuronen "feuern" lassen
cy.nodes().forEach(node => { nodes.forEach(node => {
neuronStates.set(node.id(), { const data = node.data();
potential: Math.random() * 0.3, // Startpotential const lastFired = data.lastFired || 0;
lastFired: 0, // Zeitpunkt der letzten Aktivierung const timeSinceLastFire = currentTime - lastFired;
isRefractory: false, // Refraktärphase
refractoryUntil: 0 // Ende der Refraktärphase // 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
}); });
// Neuronale Aktivität simulieren // Nach kurzer Zeit zurück zum Normalzustand
function simulateNeuralActivity() { setTimeout(() => {
const currentTime = Date.now(); node.style({
const nodes = cy.nodes().toArray(); '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);
// Zufällige Stimulation eines Neurons // Prüfen ob Zielneuron feuern kann
if (Math.random() > 0.7) { if (timeSinceLastFire > targetData.refractionPeriod) {
const randomNodeIndex = Math.floor(Math.random() * nodes.length); // Signalstärke berechnen
const randomNode = nodes[randomNodeIndex]; const signalStrength = edgeData.strength *
edgeData.conductionVelocity *
const state = neuronStates.get(randomNode.id()); sourceNode.data('neuronActivity');
if (state && !state.isRefractory) {
state.potential += 0.5; // Erhöhe das Potential durch externe Stimulation // Neuron feuern lassen wenn Signal stark genug
} if (signalStrength > targetData.threshold) {
fireNeuron(targetNode, true, currentTime + latency);
}
} }
}, latency);
// Neuronen aktualisieren });
nodes.forEach(node => { }
const nodeId = node.id();
const state = neuronStates.get(nodeId); // Simulation starten
const threshold = node.data('threshold') || 0.7; window.neuralInterval = setInterval(simulateNeuralActivity, 100);
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 // Hilfe-Funktion zum Hinzufügen eines Flash-Hinweises

View File

@@ -172,57 +172,63 @@
<!-- Kontrollpanel --> <!-- Kontrollpanel -->
<div class="control-panel"> <div class="control-panel">
<button class="control-button" id="zoom-in"> <button id="zoomIn" class="control-button">
<i class="fas fa-search-plus"></i> Einzoomen <i class="fas fa-search-plus"></i>
<span>Vergrößern</span>
</button> </button>
<button class="control-button" id="zoom-out"> <button id="zoomOut" class="control-button">
<i class="fas fa-search-minus"></i> Auszoomen <i class="fas fa-search-minus"></i>
<span>Verkleinern</span>
</button> </button>
<button class="control-button" id="reset-view"> <button id="resetView" class="control-button">
<i class="fas fa-compress-arrows-alt"></i> Ansicht zurücksetzen <i class="fas fa-sync"></i>
<span>Zurücksetzen</span>
</button> </button>
<button class="control-button" id="toggle-legend"> <button id="toggleLegend" class="control-button">
<i class="fas fa-layer-group"></i> Legende ein/aus <i class="fas fa-layer-group"></i>
<span>Legende</span>
</button> </button>
</div> </div>
<!-- Info-Panel --> <!-- Info-Panel -->
<div class="info-panel" id="node-info"> <div id="infoPanel" class="info-panel">
<h3 class="info-title">Knoteninformationen</h3> <h3 class="info-title">Knotendetails</h3>
<div class="info-content"> <div class="info-content"></div>
<p>Wählen Sie einen Knoten aus, um Details anzuzeigen.</p>
</div>
</div> </div>
<!-- Kategorie-Legende --> <!-- Kategorie-Legende -->
<div class="category-legend"> <div id="categoryLegend" class="category-legend">
<div class="category-item"> <div class="category-item">
<span class="category-color" style="background: #60a5fa;"></span> <div class="category-color" style="background-color: #60a5fa;"></div>
Philosophie <span>Philosophie</span>
</div> </div>
<div class="category-item"> <div class="category-item">
<span class="category-color" style="background: #8b5cf6;"></span> <div class="category-color" style="background-color: #8b5cf6;"></div>
Wissenschaft <span>Wissenschaft</span>
</div> </div>
<div class="category-item"> <div class="category-item">
<span class="category-color" style="background: #10b981;"></span> <div class="category-color" style="background-color: #10b981;"></div>
Technologie <span>Technologie</span>
</div> </div>
<div class="category-item"> <div class="category-item">
<span class="category-color" style="background: #f59e0b;"></span> <div class="category-color" style="background-color: #f59e0b;"></div>
Künste <span>Künste</span>
</div> </div>
<div class="category-item"> <div class="category-item">
<span class="category-color" style="background: #ef4444;"></span> <div class="category-color" style="background-color: #ef4444;"></div>
Psychologie <span>Psychologie</span>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script src="{{ url_for('static', filename='js/cytoscape.min.js') }}"></script> <!-- Cytoscape und Erweiterungen -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape-cose-bilkent/4.1.0/cytoscape-cose-bilkent.min.js"></script>
<!-- Unsere JavaScript-Dateien -->
<script src="{{ url_for('static', filename='js/mindmap-interaction.js') }}"></script>
<script src="{{ url_for('static', filename='js/mindmap-init.js') }}"></script> <script src="{{ url_for('static', filename='js/mindmap-init.js') }}"></script>
<script src="{{ url_for('static', filename='js/update_mindmap.js') }}"></script> <script src="{{ url_for('static', filename='js/update_mindmap.js') }}"></script>
<script src="{{ url_for('static', filename='js/mindmap-interaction.js') }}"></script>
{% endblock %} {% endblock %}