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