746 lines
29 KiB
Smarty
746 lines
29 KiB
Smarty
<!-- Modal - Schlagwort Details mit semantischem Netz (2 Ebenen) -->
|
||
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-xl" style="max-width: 1400px;">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="DetailsModalHeadline">
|
||
<i class="fas fa-tag"></i> Detailanzeige
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
|
||
<div class="row">
|
||
<!-- Linke Spalte: Stammdaten -->
|
||
<div class="col-md-3">
|
||
<div class="detail-info-panel">
|
||
<div class="form-group mb-3">
|
||
<label class="form-label fw-bold">Schlagwort</label>
|
||
<input type="text" id="display_term" class="form-control" disabled />
|
||
<input type="hidden" id="display_id" />
|
||
</div>
|
||
|
||
<div class="form-group mb-3">
|
||
<label class="form-label fw-bold">Deskriptor</label>
|
||
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||
</div>
|
||
|
||
<div class="row mb-3">
|
||
<div class="col-6">
|
||
<label class="form-label fw-bold">Type</label>
|
||
<input type="text" id="display_type" class="form-control" disabled />
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label fw-bold">ID</label>
|
||
<input type="text" id="display_id_show" class="form-control" disabled />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group mb-3">
|
||
<label class="form-label fw-bold">Detailtype</label>
|
||
<input type="text" id="display_detailtype" class="form-control" disabled />
|
||
</div>
|
||
|
||
<div class="form-group mb-3">
|
||
<label class="form-label fw-bold">Klassifikation</label>
|
||
<input type="text" id="display_classification" class="form-control" disabled />
|
||
</div>
|
||
|
||
<div class="form-group mb-3">
|
||
<label class="form-label fw-bold">Scopenote</label>
|
||
<textarea class="form-control" id="display_scopenote" rows="3" disabled></textarea>
|
||
</div>
|
||
|
||
<div class="form-group mb-3">
|
||
<label class="form-label fw-bold"><i class="fas fa-equals me-1" style="color: #e91e63;"></i>Synonyme</label>
|
||
<textarea class="form-control" id="display_synonyms" rows="2" disabled style="font-size: 0.9rem;"></textarea>
|
||
</div>
|
||
|
||
<!-- Lade-Status -->
|
||
<div id="network-status" class="mt-3">
|
||
<small class="text-muted">
|
||
<i class="fas fa-circle-notch fa-spin me-1"></i>
|
||
<span id="status-text">Lade Netzwerk...</span>
|
||
</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rechte Spalte: Semantisches Netz -->
|
||
<div class="col-md-9">
|
||
<div class="network-panel">
|
||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz (2 Ebenen)</h6>
|
||
<div class="network-toolbar">
|
||
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
|
||
<i class="fas fa-expand"></i>
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
|
||
<i class="fas fa-search-plus"></i>
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
|
||
<i class="fas fa-search-minus"></i>
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-secondary" onclick="toggleSecondLevel()" title="2. Ebene ein/aus" id="btn-level">
|
||
<i class="fas fa-layer-group"></i> <span id="btn-level-text">2</span>
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-secondary" onclick="togglePhysics()" title="Physik/Dynamik ein/aus" id="btn-physics">
|
||
<i class="fas fa-atom"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div id="network-container"></div>
|
||
<div class="network-legend">
|
||
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentraler Begriff</span>
|
||
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Oberbegriff (BT)</span>
|
||
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Unterbegriff (NT)</span>
|
||
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Verwandt (RT)</span>
|
||
<span class="legend-item"><span class="legend-dot" style="background: #e91e63;"></span> Synonym (SYN)</span>
|
||
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/UF</span>
|
||
<span class="legend-item"><span class="legend-dot" style="background: #adb5bd; opacity: 0.7;"></span> 2. Ebene</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Versteckter Container für alte Relationen-Tabelle (falls benötigt) -->
|
||
<div class="modal_content" style="display: none;"></div>
|
||
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||
<i class="fas fa-times"></i> schliessen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
/* Netzwerk-Container - größer für 2 Ebenen */
|
||
#network-container {
|
||
width: 100%;
|
||
height: 550px;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: var(--radius-md);
|
||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||
}
|
||
|
||
.network-panel {
|
||
background: white;
|
||
border-radius: var(--radius-md);
|
||
padding: 15px;
|
||
border: 1px solid var(--border-color);
|
||
}
|
||
|
||
.detail-info-panel {
|
||
background: var(--bg-light);
|
||
border-radius: var(--radius-md);
|
||
padding: 15px;
|
||
border: 1px solid var(--border-color);
|
||
}
|
||
|
||
.network-toolbar {
|
||
display: flex;
|
||
gap: 5px;
|
||
}
|
||
|
||
.network-toolbar .btn {
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
.network-legend {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
margin-top: 10px;
|
||
padding-top: 10px;
|
||
border-top: 1px solid var(--border-light);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
.legend-dot {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
display: inline-block;
|
||
}
|
||
|
||
/* Modal extra breit für 2 Ebenen */
|
||
#DetailsModal .modal-body {
|
||
max-height: 85vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
#network-status {
|
||
padding: 8px;
|
||
background: white;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border-light);
|
||
}
|
||
</style>
|
||
|
||
<!-- vis.js Network Bibliothek -->
|
||
<script src="/libs/vis-network/current/vis-network.min.js"></script>
|
||
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
|
||
|
||
<script>
|
||
var network = null;
|
||
var networkData = null;
|
||
var nodesDataSet = null;
|
||
var edgesDataSet = null;
|
||
var centralNodeId = null;
|
||
var firstLevelNodeIds = [];
|
||
var secondLevelNodeIds = [];
|
||
var secondLevelEdgeIds = [];
|
||
var secondLevelVisible = true;
|
||
var secondLevelLoaded = false;
|
||
var physicsEnabled = true;
|
||
|
||
// Farben für verschiedene Relationstypen
|
||
var relationColors = {
|
||
'BT': { node: '#28a745', edge: '#28a745', label: 'Oberbegriff' },
|
||
'BROADERTERM': { node: '#28a745', edge: '#28a745', label: 'Oberbegriff' },
|
||
'NT': { node: '#dc3545', edge: '#dc3545', label: 'Unterbegriff' },
|
||
'NARROWERTERM': { node: '#dc3545', edge: '#dc3545', label: 'Unterbegriff' },
|
||
'RT': { node: '#fd7e14', edge: '#fd7e14', label: 'Verwandt' },
|
||
'RELATEDTERM': { node: '#fd7e14', edge: '#fd7e14', label: 'Verwandt' },
|
||
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
|
||
'UF': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' },
|
||
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' },
|
||
'SYN': { node: '#e91e63', edge: '#e91e63', label: 'Synonym' },
|
||
'SYNONYM': { node: '#e91e63', edge: '#e91e63', label: 'Synonym' }
|
||
};
|
||
|
||
function createSemanticNetwork(centralTerm, centralId, relations, synonyms) {
|
||
console.log('🕸️ Erstelle semantisches Netz (2 Ebenen) für:', centralTerm);
|
||
console.log('📝 Synonyme:', synonyms);
|
||
|
||
centralNodeId = centralId;
|
||
firstLevelNodeIds = [];
|
||
secondLevelNodeIds = [];
|
||
secondLevelEdgeIds = [];
|
||
secondLevelVisible = true;
|
||
secondLevelLoaded = false;
|
||
physicsEnabled = true;
|
||
|
||
// Buttons zurücksetzen
|
||
var btn = document.getElementById('btn-level');
|
||
var btnText = document.getElementById('btn-level-text');
|
||
if (btn) {
|
||
btn.classList.remove('btn-warning');
|
||
btn.classList.add('btn-outline-secondary');
|
||
}
|
||
if (btnText) btnText.textContent = '2';
|
||
|
||
var btnPhysics = document.getElementById('btn-physics');
|
||
if (btnPhysics) {
|
||
btnPhysics.classList.remove('btn-warning');
|
||
btnPhysics.classList.add('btn-outline-secondary');
|
||
}
|
||
|
||
updateStatus('Baue erste Ebene auf...');
|
||
|
||
// DataSets erstellen
|
||
nodesDataSet = new vis.DataSet();
|
||
edgesDataSet = new vis.DataSet();
|
||
|
||
// Zentraler Knoten
|
||
nodesDataSet.add({
|
||
id: centralId,
|
||
label: wrapLabel(centralTerm, 20),
|
||
shape: 'ellipse',
|
||
color: {
|
||
background: '#1a3a5c',
|
||
border: '#0d1f30',
|
||
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
|
||
hover: { background: '#2a5a8c', border: '#1a3a5c' }
|
||
},
|
||
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
|
||
size: 40,
|
||
borderWidth: 3,
|
||
shadow: true,
|
||
level: 0
|
||
});
|
||
|
||
// Erste Ebene hinzufügen
|
||
if (relations && relations.length > 0) {
|
||
relations.forEach(function(rel, index) {
|
||
var relType = rel.Relationtype || 'RT';
|
||
var relColor = relationColors[relType] || relationColors['RT'];
|
||
var relId = rel.IDRelation || ('rel_' + index);
|
||
var relText = rel.TextRelation || 'Unbekannt';
|
||
|
||
// ID für späteres Nachladen speichern
|
||
if (relId && relId !== centralId) {
|
||
firstLevelNodeIds.push({
|
||
id: relId,
|
||
text: relText,
|
||
type: relType
|
||
});
|
||
}
|
||
|
||
// Knoten für die Relation (1. Ebene)
|
||
nodesDataSet.add({
|
||
id: relId,
|
||
label: wrapLabel(relText, 18),
|
||
shape: 'ellipse',
|
||
color: {
|
||
background: relColor.node,
|
||
border: shadeColor(relColor.node, -20),
|
||
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
|
||
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
|
||
},
|
||
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||
size: 28,
|
||
borderWidth: 2,
|
||
shadow: true,
|
||
title: relType + ': ' + relText + ' (ID: ' + relId + ')',
|
||
level: 1
|
||
});
|
||
|
||
// Kante zum zentralen Knoten
|
||
var arrowDirection = getArrowDirection(relType);
|
||
|
||
edgesDataSet.add({
|
||
from: centralId,
|
||
to: relId,
|
||
label: relType,
|
||
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
|
||
width: 2,
|
||
arrows: arrowDirection,
|
||
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||
});
|
||
});
|
||
}
|
||
|
||
// Synonyme hinzufügen (kommaseparierter String)
|
||
console.log('📝 Synonyme Typ:', typeof synonyms);
|
||
console.log('📝 Synonyme Wert:', synonyms);
|
||
console.log('📝 Synonyme Länge:', synonyms ? synonyms.length : 'null/undefined');
|
||
|
||
if (synonyms && typeof synonyms === 'string' && synonyms.trim().length > 0) {
|
||
var synColor = relationColors['SYN'];
|
||
var synArray = synonyms.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
|
||
|
||
console.log('📝 Synonym-Array:', synArray);
|
||
|
||
synArray.forEach(function(synText, index) {
|
||
var synId = 'syn_' + index + '_' + synText.replace(/\s+/g, '_');
|
||
|
||
console.log('➕ Füge Synonym hinzu:', synText, '→ ID:', synId);
|
||
|
||
// Knoten für das Synonym
|
||
nodesDataSet.add({
|
||
id: synId,
|
||
label: wrapLabel(synText, 18),
|
||
shape: 'ellipse',
|
||
color: {
|
||
background: synColor.node,
|
||
border: shadeColor(synColor.node, -20),
|
||
highlight: { background: shadeColor(synColor.node, 20), border: synColor.node },
|
||
hover: { background: shadeColor(synColor.node, 20), border: synColor.node }
|
||
},
|
||
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||
size: 28,
|
||
borderWidth: 2,
|
||
shadow: true,
|
||
title: 'Synonym: ' + synText,
|
||
level: 1,
|
||
isSynonym: true,
|
||
synonymText: synText
|
||
});
|
||
|
||
// Kante zum zentralen Knoten (bidirektional, keine Pfeile)
|
||
edgesDataSet.add({
|
||
from: centralId,
|
||
to: synId,
|
||
label: 'SYN',
|
||
color: { color: synColor.edge, highlight: synColor.edge, hover: synColor.edge },
|
||
width: 2,
|
||
arrows: { to: { enabled: false }, from: { enabled: false } },
|
||
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||
smooth: { type: 'curvedCW', roundness: 0.2 },
|
||
dashes: [5, 5]
|
||
});
|
||
});
|
||
|
||
console.log('✅ ' + synArray.length + ' Synonyme hinzugefügt');
|
||
} else {
|
||
console.log('⚠️ Keine Synonyme vorhanden oder leer');
|
||
}
|
||
|
||
// Netzwerk-Container
|
||
var container = document.getElementById('network-container');
|
||
|
||
// Netzwerk-Daten
|
||
networkData = {
|
||
nodes: nodesDataSet,
|
||
edges: edgesDataSet
|
||
};
|
||
|
||
// Optionen
|
||
var options = {
|
||
nodes: {
|
||
shape: 'ellipse',
|
||
scaling: { min: 15, max: 40 }
|
||
},
|
||
edges: {
|
||
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||
},
|
||
physics: {
|
||
enabled: true,
|
||
barnesHut: {
|
||
gravitationalConstant: -4000,
|
||
centralGravity: 0.2,
|
||
springLength: 180,
|
||
springConstant: 0.03,
|
||
damping: 0.09
|
||
},
|
||
stabilization: {
|
||
enabled: true,
|
||
iterations: 300,
|
||
updateInterval: 25
|
||
}
|
||
},
|
||
interaction: {
|
||
hover: true,
|
||
tooltipDelay: 200,
|
||
zoomView: true,
|
||
dragView: true
|
||
},
|
||
layout: {
|
||
improvedLayout: true
|
||
}
|
||
};
|
||
|
||
// Netzwerk erstellen
|
||
network = new vis.Network(container, networkData, options);
|
||
|
||
// Klick auf Knoten
|
||
network.on('click', function(params) {
|
||
if (params.nodes.length > 0) {
|
||
var clickedNodeId = params.nodes[0];
|
||
if (clickedNodeId != centralNodeId) {
|
||
console.log('🖱️ Knoten geklickt:', clickedNodeId);
|
||
ShowModalDetails(clickedNodeId);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Stabilisierung abgeschlossen - dann zweite Ebene laden
|
||
network.once('stabilizationIterationsDone', function() {
|
||
console.log('✅ Erste Ebene stabilisiert');
|
||
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||
|
||
// Zweite Ebene laden
|
||
setTimeout(function() {
|
||
loadSecondLevel();
|
||
}, 500);
|
||
});
|
||
}
|
||
|
||
function loadSecondLevel() {
|
||
if (firstLevelNodeIds.length === 0) {
|
||
updateStatus('Fertig - keine weiteren Relationen');
|
||
hideStatus();
|
||
return;
|
||
}
|
||
|
||
updateStatus('Lade 2. Ebene (0/' + firstLevelNodeIds.length + ')...');
|
||
|
||
var loadedCount = 0;
|
||
var totalCount = firstLevelNodeIds.length;
|
||
|
||
// Für jeden Knoten der ersten Ebene die Relationen laden
|
||
firstLevelNodeIds.forEach(function(nodeInfo, index) {
|
||
setTimeout(function() {
|
||
loadRelationsForNode(nodeInfo.id, nodeInfo.text, function() {
|
||
loadedCount++;
|
||
updateStatus('Lade 2. Ebene (' + loadedCount + '/' + totalCount + ')...');
|
||
|
||
if (loadedCount >= totalCount) {
|
||
secondLevelLoaded = true;
|
||
updateStatus('Fertig - ' + nodesDataSet.length + ' Begriffe geladen');
|
||
|
||
// Netzwerk neu stabilisieren
|
||
network.stabilize(100);
|
||
|
||
setTimeout(function() {
|
||
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||
hideStatus();
|
||
}, 1000);
|
||
}
|
||
});
|
||
}, index * 100); // Gestaffeltes Laden um Server nicht zu überlasten
|
||
});
|
||
}
|
||
|
||
function loadRelationsForNode(nodeId, nodeText, callback) {
|
||
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||
authType: 'Subject',
|
||
offset: 0,
|
||
id: nodeId,
|
||
sort: 'Anchor.Text',
|
||
limit: 1
|
||
}, function(data, status) {
|
||
if (status === "success") {
|
||
try {
|
||
var metadata = JSON.parse(data);
|
||
var row = metadata['rows'];
|
||
|
||
if (row && row[0] && row[0].Relations) {
|
||
row[0].Relations.forEach(function(rel) {
|
||
var relId = rel.IDRelation;
|
||
var relText = rel.TextRelation;
|
||
var relType = rel.Relationtype || 'RT';
|
||
|
||
// Nicht den zentralen Knoten oder bereits existierende Knoten
|
||
if (relId && relId != centralNodeId) {
|
||
var existingNode = nodesDataSet.get(relId);
|
||
|
||
if (!existingNode) {
|
||
// Neuer Knoten der 2. Ebene (kleiner, blasser)
|
||
var relColor = relationColors[relType] || relationColors['RT'];
|
||
var fadedColor = fadeColor(relColor.node, 0.6);
|
||
|
||
nodesDataSet.add({
|
||
id: relId,
|
||
label: wrapLabel(relText, 15),
|
||
shape: 'ellipse',
|
||
color: {
|
||
background: fadedColor,
|
||
border: shadeColor(fadedColor, -15),
|
||
highlight: { background: relColor.node, border: shadeColor(relColor.node, -20) },
|
||
hover: { background: relColor.node, border: shadeColor(relColor.node, -20) }
|
||
},
|
||
font: { color: '#ffffff', size: 10, face: 'Arial' },
|
||
size: 18,
|
||
borderWidth: 1,
|
||
shadow: false,
|
||
title: '2. Ebene - ' + relType + ': ' + relText + ' (ID: ' + relId + ')',
|
||
level: 2
|
||
});
|
||
|
||
// ID für Toggle speichern
|
||
secondLevelNodeIds.push(relId);
|
||
}
|
||
|
||
// Kante hinzufügen (wenn noch nicht vorhanden)
|
||
var edgeId = nodeId + '_' + relId;
|
||
var reverseEdgeId = relId + '_' + nodeId;
|
||
|
||
if (!edgesDataSet.get(edgeId) && !edgesDataSet.get(reverseEdgeId)) {
|
||
var relColor = relationColors[relType] || relationColors['RT'];
|
||
var arrowDirection = getArrowDirection(relType);
|
||
|
||
edgesDataSet.add({
|
||
id: edgeId,
|
||
from: nodeId,
|
||
to: relId,
|
||
color: { color: fadeColor(relColor.edge, 0.5), highlight: relColor.edge, hover: relColor.edge },
|
||
width: 1,
|
||
arrows: arrowDirection,
|
||
dashes: true,
|
||
smooth: { type: 'curvedCW', roundness: 0.3 }
|
||
});
|
||
|
||
// ID für Toggle speichern
|
||
secondLevelEdgeIds.push(edgeId);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
} catch (e) {
|
||
console.warn('⚠️ Fehler beim Parsen für Node', nodeId, e);
|
||
}
|
||
}
|
||
|
||
if (callback) callback();
|
||
}).fail(function() {
|
||
console.warn('⚠️ AJAX-Fehler für Node', nodeId);
|
||
if (callback) callback();
|
||
});
|
||
}
|
||
|
||
function getArrowDirection(relType) {
|
||
if (relType === 'BT' || relType === 'BROADERTERM') {
|
||
return { to: { enabled: true, scaleFactor: 1 } };
|
||
} else if (relType === 'NT' || relType === 'NARROWERTERM') {
|
||
return { from: { enabled: true, scaleFactor: 1 } };
|
||
}
|
||
return { to: { enabled: false }, from: { enabled: false } };
|
||
}
|
||
|
||
function updateStatus(text) {
|
||
$('#status-text').text(text);
|
||
$('#network-status').show();
|
||
}
|
||
|
||
function hideStatus() {
|
||
setTimeout(function() {
|
||
$('#network-status').fadeOut();
|
||
}, 2000);
|
||
}
|
||
|
||
// Hilfsfunktion: Farbe verblassen
|
||
function fadeColor(color, opacity) {
|
||
var R = parseInt(color.substring(1,3), 16);
|
||
var G = parseInt(color.substring(3,5), 16);
|
||
var B = parseInt(color.substring(5,7), 16);
|
||
|
||
// Mit Weiß mischen für verblassten Effekt
|
||
R = Math.round(R + (255 - R) * (1 - opacity));
|
||
G = Math.round(G + (255 - G) * (1 - opacity));
|
||
B = Math.round(B + (255 - B) * (1 - opacity));
|
||
|
||
var RR = R.toString(16).padStart(2, '0');
|
||
var GG = G.toString(16).padStart(2, '0');
|
||
var BB = B.toString(16).padStart(2, '0');
|
||
|
||
return "#" + RR + GG + BB;
|
||
}
|
||
|
||
// Hilfsfunktion: Label umbrechen
|
||
function wrapLabel(text, maxLength) {
|
||
if (!text) return '';
|
||
if (text.length <= maxLength) return text;
|
||
|
||
var words = text.split(' ');
|
||
var lines = [];
|
||
var currentLine = '';
|
||
|
||
words.forEach(function(word) {
|
||
if ((currentLine + ' ' + word).trim().length <= maxLength) {
|
||
currentLine = (currentLine + ' ' + word).trim();
|
||
} else {
|
||
if (currentLine) lines.push(currentLine);
|
||
currentLine = word;
|
||
}
|
||
});
|
||
if (currentLine) lines.push(currentLine);
|
||
|
||
return lines.join('\n');
|
||
}
|
||
|
||
// Hilfsfunktion: Farbe aufhellen/abdunkeln
|
||
function shadeColor(color, percent) {
|
||
var R = parseInt(color.substring(1,3), 16);
|
||
var G = parseInt(color.substring(3,5), 16);
|
||
var B = parseInt(color.substring(5,7), 16);
|
||
|
||
R = parseInt(R * (100 + percent) / 100);
|
||
G = parseInt(G * (100 + percent) / 100);
|
||
B = parseInt(B * (100 + percent) / 100);
|
||
|
||
R = (R < 255) ? R : 255;
|
||
G = (G < 255) ? G : 255;
|
||
B = (B < 255) ? B : 255;
|
||
R = (R > 0) ? R : 0;
|
||
G = (G > 0) ? G : 0;
|
||
B = (B > 0) ? B : 0;
|
||
|
||
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
|
||
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
|
||
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
|
||
|
||
return "#" + RR + GG + BB;
|
||
}
|
||
|
||
// Netzwerk-Steuerung
|
||
function networkFit() {
|
||
if (network) {
|
||
network.fit({
|
||
animation: { duration: 500, easingFunction: 'easeInOutQuad' }
|
||
});
|
||
}
|
||
}
|
||
|
||
function networkZoomIn() {
|
||
if (network) {
|
||
var scale = network.getScale() * 1.3;
|
||
network.moveTo({ scale: scale, animation: { duration: 300 } });
|
||
}
|
||
}
|
||
|
||
function networkZoomOut() {
|
||
if (network) {
|
||
var scale = network.getScale() / 1.3;
|
||
network.moveTo({ scale: scale, animation: { duration: 300 } });
|
||
}
|
||
}
|
||
|
||
function toggleSecondLevel() {
|
||
if (!network || !secondLevelLoaded) {
|
||
console.log('⚠️ 2. Ebene noch nicht geladen');
|
||
return;
|
||
}
|
||
|
||
secondLevelVisible = !secondLevelVisible;
|
||
|
||
var btn = document.getElementById('btn-level');
|
||
var btnText = document.getElementById('btn-level-text');
|
||
|
||
if (secondLevelVisible) {
|
||
// 2. Ebene einblenden
|
||
secondLevelNodeIds.forEach(function(nodeId) {
|
||
nodesDataSet.update({ id: nodeId, hidden: false });
|
||
});
|
||
secondLevelEdgeIds.forEach(function(edgeId) {
|
||
edgesDataSet.update({ id: edgeId, hidden: false });
|
||
});
|
||
|
||
btn.classList.remove('btn-warning');
|
||
btn.classList.add('btn-outline-secondary');
|
||
btnText.textContent = '2';
|
||
|
||
console.log('✅ 2. Ebene eingeblendet');
|
||
} else {
|
||
// 2. Ebene ausblenden
|
||
secondLevelNodeIds.forEach(function(nodeId) {
|
||
nodesDataSet.update({ id: nodeId, hidden: true });
|
||
});
|
||
secondLevelEdgeIds.forEach(function(edgeId) {
|
||
edgesDataSet.update({ id: edgeId, hidden: true });
|
||
});
|
||
|
||
btn.classList.remove('btn-outline-secondary');
|
||
btn.classList.add('btn-warning');
|
||
btnText.textContent = '1';
|
||
|
||
console.log('✅ 2. Ebene ausgeblendet');
|
||
}
|
||
|
||
// Ansicht anpassen
|
||
setTimeout(function() {
|
||
network.fit({ animation: { duration: 300, easingFunction: 'easeInOutQuad' } });
|
||
}, 100);
|
||
}
|
||
|
||
function togglePhysics() {
|
||
if (!network) return;
|
||
|
||
physicsEnabled = !physicsEnabled;
|
||
network.setOptions({ physics: { enabled: physicsEnabled } });
|
||
|
||
var btn = document.getElementById('btn-physics');
|
||
if (physicsEnabled) {
|
||
btn.classList.remove('btn-warning');
|
||
btn.classList.add('btn-outline-secondary');
|
||
console.log('✅ Physik aktiviert');
|
||
} else {
|
||
btn.classList.remove('btn-outline-secondary');
|
||
btn.classList.add('btn-warning');
|
||
console.log('✅ Physik deaktiviert - Knoten können manuell verschoben werden');
|
||
}
|
||
}
|
||
</script>
|