Files
website/static/js/modules/mindmap.js

419 lines
29 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Mindmap.js - Modul für die Mindmap-Visualisierung mit Cytoscape.js
* Version: 1.0.0
* Datum: 01.05.2025
*/
// Stellen Sie sicher, dass das globale MindMap-Objekt existiert
if (!window.MindMap) {
window.MindMap = {};
}
/**
* Mindmap-Visualisierungsklasse
*/
class MindmapVisualization {
/**
* Konstruktor für die Mindmap-Visualisierung
* @param {string} containerId - ID des Container-Elements
* @param {Object} options - Konfigurationsoptionen
*/
constructor(containerId, options = {}) {
this.containerId = containerId;
this.container = document.getElementById(containerId);
this.cy = null;
this.data = null;
this.options = Object.assign({
defaultLayout: 'cose',
darkMode: document.documentElement.classList.contains('dark'),
apiEndpoint: '/api/mindmap',
onNodeClick: null
}, options);
// Event-Listener für Dark Mode-Änderungen
document.addEventListener('darkModeToggled', (event) => {
this.options.darkMode = event.detail.isDark;
if (this.cy) {
this.updateStyles();
}
});
}
/**
* Initialisiert die Mindmap
*/
async initialize() {
console.log("Initialisiere Mindmap...");
try {
// Versuche, Daten vom Server zu laden
this.data = await this.loadDataFromServer();
console.log("Daten vom Server geladen:", this.data);
} catch (error) {
console.warn("Fehler beim Laden der Daten vom Server:", error);
console.log("Verwende Standarddaten als Fallback");
this.data = this.getDefaultData();
}
// Cytoscape initialisieren
this.initializeCytoscape();
return this;
}
/**
* Lädt Daten vom Server
* @returns {Promise} - Promise mit den geladenen Daten
*/
async loadDataFromServer() {
try {
// Zuerst versuchen wir, Daten von /api/mindmap zu laden
const response = await fetch(this.options.apiEndpoint);
if (!response.ok) {
throw new Error(`HTTP Fehler: ${response.status}`);
}
const data = await response.json();
// Wenn keine Daten vorhanden sind, versuche /api/refresh-mindmap
if (!data.nodes || data.nodes.length === 0) {
console.log("Keine Daten gefunden, versuche /api/refresh-mindmap");
const refreshResponse = await fetch('/api/refresh-mindmap');
if (!refreshResponse.ok) {
throw new Error(`HTTP Fehler beim Refresh: ${refreshResponse.status}`);
}
return await refreshResponse.json();
}
return data;
} catch (error) {
console.error("Fehler beim Laden der Daten:", error);
throw error;
}
}
/**
* Liefert Standarddaten als Fallback
* @returns {Object} - Standarddaten für die Mindmap
*/
getDefaultData() {
return {
nodes: [
{ id: "root", name: "Wissen", description: "Zentrale Wissensbasis", color_code: "#4299E1" },
{ id: "philosophy", name: "Philosophie", description: "Philosophisches Denken", color_code: "#9F7AEA" },
{ id: "science", name: "Wissenschaft", description: "Wissenschaftliche Erkenntnisse", color_code: "#48BB78" },
{ id: "technology", name: "Technologie", description: "Technologische Entwicklungen", color_code: "#ED8936" },
{ id: "arts", name: "Künste", description: "Künstlerische Ausdrucksformen", color_code: "#ED64A6" }
],
edges: [
{ source: "root", target: "philosophy" },
{ source: "root", target: "science" },
{ source: "root", target: "technology" },
{ source: "root", target: "arts" }
]
};
}
/**
* Initialisiert Cytoscape.js
*/
initializeCytoscape() {
if (!this.container) {
console.error(`Container mit ID '${this.containerId}' nicht gefunden.`);
return;
}
// Konvertiert die API-Daten ins Cytoscape-Format
const elements = this.convertToCytoscapeFormat(this.data);
// Erstellt die Cytoscape-Instanz
this.cy = cytoscape({
container: this.container,
elements: elements,
style: this.getStyles(),
layout: {
name: this.options.defaultLayout,
animate: true,
animationDuration: 800,
nodeDimensionsIncludeLabels: true,
padding: 30,
spacingFactor: 1.2,
randomize: true,
componentSpacing: 100,
nodeRepulsion: 8000,
edgeElasticity: 100,
nestingFactor: 1.2,
gravity: 80
}
});
// Event-Listener für Knotenklicks
this.cy.on('tap', 'node', (event) => {
const node = event.target;
this.handleNodeClick(node);
});
console.log("Cytoscape initialisiert");
}
/**
* Konvertiert API-Daten ins Cytoscape-Format
* @param {Object} data - Daten von der API
* @returns {Array} - Elemente im Cytoscape-Format
*/
convertToCytoscapeFormat(data) {
const elements = [];
// Überprüfen, ob wir Daten aus der API haben
if (data.nodes && Array.isArray(data.nodes)) {
// Füge zuerst einen Root-Knoten "Wissen" hinzu, falls er nicht existiert
let rootExists = data.nodes.some(node => node.name === "Wissen");
if (!rootExists) {
elements.push({
data: {
id: "root",
name: "Wissen",
description: "Zentrale Wissensbasis",
color: "#4299E1",
isRoot: true
}
});
}
// Knoten hinzufügen
data.nodes.forEach(node => {
// Prüfen, ob es sich um einen Knoten-Objekt oder einen Baum handelt
const nodeId = node.id || `node-${elements.length}`;
const nodeName = node.name;
const nodeDesc = node.description || "";
const nodeColor = node.color_code || '#8B5CF6';
elements.push({
data: {
id: nodeId,
name: nodeName,
description: nodeDesc,
color: nodeColor,
category: node.category_id,
isRoot: node.name === "Wissen"
}
});
// Falls es ein hierarchischer Baum ist mit Kindern
if (node.children && Array.isArray(node.children)) {
this.processChildNodes(node, elements, nodeId);
}
});
// Kanten hinzufügen - bei hierarchischen Daten
if (!data.edges || !Array.isArray(data.edges)) {
// Wenn keine Kanten definiert sind, verknüpfe alle Root-Knoten mit dem "Wissen" Knoten
const rootId = elements.find(el => el.data.isRoot)?.data.id || "root";
elements.forEach(element => {
if (element.data.id !== rootId && !element.data.hasParent) {
elements.push({
data: {
id: `${rootId}-${element.data.id}`,
source: rootId,
target: element.data.id
}
});
}
});
} else {
// Wenn Kanten definiert sind, diese direkt verwenden
data.edges.forEach(edge => {
elements.push({
data: {
id: `${edge.source}-${edge.target}`,
source: edge.source,
target: edge.target
}
});
});
}
}
return elements;
}
/**
* Verarbeitet Kindknoten rekursiv für hierarchische Daten
* @param {Object} node - Der aktuelle Knoten
* @param {Array} elements - Das Element-Array
* @param {string} parentId - Die ID des Elternknotens
*/
processChildNodes(node, elements, parentId) {
if (node.children && Array.isArray(node.children)) {
node.children.forEach(child => {
const childId = child.id || `node-${elements.length}`;
const childName = child.name;
const childDesc = child.description || "";
const childColor = child.color_code || '#8B5CF6';
elements.push({
data: {
id: childId,
name: childName,
description: childDesc,
color: childColor,
category: child.category_id,
hasParent: true
}
});
// Kante zum Elternknoten
elements.push({
data: {
id: `${parentId}-${childId}`,
source: parentId,
target: childId
}
});
// Rekursiv Kinder verarbeiten
this.processChildNodes(child, elements, childId);
});
}
}
/**
* Liefert die Styles für Cytoscape
* @returns {Array} - Cytoscape-Stylesheets
*/
getStyles() {
const darkMode = this.options.darkMode;
return [
{
selector: 'node',
style: {
'background-color': 'data(color)',
'label': 'data(name)',
'width': 40,
'height': 40,
'font-size': 12,
'text-valign': 'bottom',
'text-halign': 'center',
'text-margin-y': 8,
'color': darkMode ? '#f1f5f9' : '#334155',
'text-background-color': darkMode ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)',
'text-background-opacity': 0.8,
'text-background-padding': '2px',
'text-background-shape': 'roundrectangle',
'text-wrap': 'ellipsis',
'text-max-width': '100px'
}
},
{
selector: 'node[?isRoot]',
style: {
'width': 60,
'height': 60,
'font-size': 14,
'font-weight': 'bold',
'text-background-opacity': 0.9,
'text-background-color': '#4299E1'
}
},
{
selector: 'edge',
style: {
'width': 2,
'line-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'target-arrow-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'curve-style': 'bezier',
'target-arrow-shape': 'triangle'
}
},
{
selector: 'node:selected',
style: {
'background-color': 'data(color)',
'border-width': 3,
'border-color': '#8b5cf6',
'width': 50,
'height': 50,
'font-size': 14,
'font-weight': 'bold',
'text-background-color': '#8b5cf6',
'text-background-opacity': 0.9
}
}
];
}
/**
* Aktualisiert die Styles basierend auf dem Dark Mode
*/
updateStyles() {
this.cy.style(this.getStyles());
}
/**
* Behandelt den Klick auf einen Knoten
* @param {Object} node - Cytoscape-Knoten
*/
handleNodeClick(node) {
console.log("Knoten angeklickt:", node.id(), node.data());
// Callback aufrufen, falls definiert
if (typeof this.options.onNodeClick === 'function') {
this.options.onNodeClick(node.data());
}
}
/**
* Passt die Ansicht an alle Elemente an
*/
fitToElements() {
if (this.cy) {
this.cy.fit();
this.cy.center();
}
}
/**
* Setzt das Layout zurück
* @param {string} layoutName - Name des Layouts (optional)
*/
resetLayout(layoutName) {
if (this.cy) {
this.cy.layout({
name: layoutName || this.options.defaultLayout,
animate: true,
randomize: true,
fit: true
}).run();
}
}
}
// Globale Export
window.MindMap.Visualization = MindmapVisualization;
// Automatische Initialisierung, wenn das DOM geladen ist
document.addEventListener('DOMContentLoaded', function() {
const cyContainer = document.getElementById('cy');
if (cyContainer) {
console.log("Mindmap-Container gefunden, initialisiere...");
const mindmap = new MindmapVisualization('cy', {
onNodeClick: function(nodeData) {
console.log("Knoten ausgewählt:", nodeData);
// Hier könnte man weitere Aktionen durchführen
}
});
mindmap.initialize().then(() => {
console.log("Mindmap erfolgreich initialisiert");
// Speichere die Instanz global für den Zugriff von außen
window.mindmap = mindmap;
}).catch(error => {
console.error("Fehler bei der Initialisierung der Mindmap:", error);
});
}
});