Backup #1357
| ID | 1357 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/View/docs/code-graph-global.php |
| Version | 17 |
| Typ |
modified |
| Größe | 13.0 KB |
| Hash | 82cc2e39a3725bd0269461e7c55bf8de9c35c2901c88d09d637882764a3d9c81 |
| Datum | 2025-12-25 16:31:25 |
| Geändert von | claude-code-hook |
| Grund | Claude Code Pre-Hook Backup vor Edit-Operation |
| Datei existiert |
Ja
|
Dateiinhalt
<?php
declare(strict_types=1);
// @responsibility: View für globalen Projekt-Dependency-Graph
ob_start();
?>
<link rel="stylesheet" href="/css/graph.css">
<nav class="breadcrumb">
<a href="/docs/code-hygiene">Code-Analyse</a> »
<span>Projekt-Graph</span>
</nav>
<h1>Projekt Dependency Graph</h1>
<p class="graph-stats">Alle Dependency-Beziehungen im Projekt (nur interne Klassen)</p>
<div class="graph-controls">
<div class="graph-legend">
<span class="graph-legend-item"><span class="graph-legend-node graph-node-php"></span> PHP</span>
<span class="graph-legend-item"><span class="graph-legend-node graph-node-python"></span> Python</span>
<span class="graph-legend-item"><span class="graph-legend-node graph-node-js"></span> JavaScript</span>
<span class="graph-legend-item graph-legend-separator"><span class="graph-legend-link graph-link-extends"></span> extends</span>
<span class="graph-legend-item"><span class="graph-legend-link graph-link-implements"></span> implements</span>
<span class="graph-legend-item"><span class="graph-legend-link graph-link-constructor"></span> constructor</span>
<span class="graph-legend-item"><span class="graph-legend-link graph-link-use"></span> use/import</span>
</div>
<div class="graph-filters">
<label for="lang-filter" class="graph-filter-label">Sprache:</label>
<select id="lang-filter" class="graph-filter-select">
<option value="">Alle</option>
<option value="php">PHP</option>
<option value="py">Python</option>
<option value="js">JavaScript</option>
</select>
<label for="dep-type-filter" class="graph-filter-label">Typ:</label>
<select id="dep-type-filter" class="graph-filter-select">
<option value="">Alle</option>
<option value="extends">extends</option>
<option value="implements">implements</option>
<option value="constructor">constructor</option>
<option value="use">use</option>
<option value="trait">trait</option>
</select>
<label for="namespace-filter" class="graph-filter-label">Namespace:</label>
<select id="namespace-filter" class="graph-filter-select">
<option value="">Alle</option>
</select>
<button id="reset-zoom" class="btn btn--secondary graph-reset-btn">Reset Zoom</button>
</div>
</div>
<div id="graph-stats" class="graph-stats"></div>
<div id="graph-container" class="graph-container"></div>
<div class="graph-back-link">
<a href="/docs/code-hygiene" class="btn btn--secondary">← Zurück zur Übersicht</a>
</div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
(function() {
const container = document.getElementById('graph-container');
const width = container.clientWidth;
const height = container.clientHeight;
// Language-based colors
const langColors = {
php: '#6366f1', // Indigo
py: '#10b981', // Emerald (Python green)
js: '#f59e0b' // Amber (JavaScript yellow)
};
const linkColors = {
extends: '#f59e0b',
implements: '#3b82f6',
constructor: '#8b5cf6',
use: '#94a3b8',
trait: '#14b8a6'
};
container.innerHTML = '<div class="graph-loading">Lade Graph-Daten...</div>';
fetch('/docs/code-hygiene/graph-data')
.then(r => r.json())
.then(data => {
container.innerHTML = '';
// Count by language
const langCounts = { php: 0, py: 0, js: 0 };
data.nodes.forEach(n => {
const ext = n.extension || 'php';
if (langCounts[ext] !== undefined) langCounts[ext]++;
});
document.getElementById('graph-stats').textContent =
`${data.stats.nodes} Klassen | ${data.stats.links} Beziehungen | PHP: ${langCounts.php} | Python: ${langCounts.py} | JS: ${langCounts.js}`;
const namespaces = [...new Set(data.nodes.map(n => n.namespace))].sort();
const filter = document.getElementById('namespace-filter');
namespaces.forEach(ns => {
const opt = document.createElement('option');
opt.value = ns;
opt.textContent = ns;
filter.appendChild(opt);
});
const namespaceGroups = {};
data.nodes.forEach((node, i) => {
const ns = node.namespace || 'global';
if (!namespaceGroups[ns]) namespaceGroups[ns] = [];
namespaceGroups[ns].push(node);
});
const nsKeys = Object.keys(namespaceGroups).sort();
const nsCount = nsKeys.length;
const centerX = width / 2;
const centerY = height / 2;
const nsRadius = Math.min(width, height) * 0.35;
nsKeys.forEach((ns, nsIndex) => {
const nodes = namespaceGroups[ns];
const nsAngle = (2 * Math.PI * nsIndex) / nsCount - Math.PI / 2;
const nsCenterX = centerX + nsRadius * Math.cos(nsAngle);
const nsCenterY = centerY + nsRadius * Math.sin(nsAngle);
const nodeCount = nodes.length;
const nodeRadius = Math.min(80, 20 + nodeCount * 5);
nodes.forEach((node, nodeIndex) => {
if (nodeCount === 1) {
node.x = nsCenterX;
node.y = nsCenterY;
} else {
const nodeAngle = (2 * Math.PI * nodeIndex) / nodeCount;
node.x = nsCenterX + nodeRadius * Math.cos(nodeAngle);
node.y = nsCenterY + nodeRadius * Math.sin(nodeAngle);
}
});
});
const nodeIndex = {};
data.nodes.forEach((n, i) => nodeIndex[n.id] = i);
data.links.forEach(link => {
if (typeof link.source === 'number') {
link.sourceNode = data.nodes[link.source];
link.targetNode = data.nodes[link.target];
}
});
const svg = d3.select('#graph-container')
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g');
const zoom = d3.zoom()
.scaleExtent([0.1, 4])
.on('zoom', (event) => {
g.attr('transform', event.transform);
});
svg.call(zoom);
document.getElementById('reset-zoom').addEventListener('click', () => {
svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);
});
svg.append('defs').selectAll('marker')
.data(['extends', 'implements', 'constructor', 'use', 'trait'])
.enter().append('marker')
.attr('id', d => 'arrow-global-' + d)
.attr('viewBox', '0 -5 10 10')
.attr('refX', 15)
.attr('refY', 0)
.attr('markerWidth', 5)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', d => linkColors[d]);
const nsLabels = g.append('g').attr('class', 'ns-labels');
nsKeys.forEach((ns, nsIndex) => {
const nsAngle = (2 * Math.PI * nsIndex) / nsCount - Math.PI / 2;
const labelRadius = nsRadius + 100;
const labelX = centerX + labelRadius * Math.cos(nsAngle);
const labelY = centerY + labelRadius * Math.sin(nsAngle);
nsLabels.append('text')
.attr('x', labelX)
.attr('y', labelY)
.attr('text-anchor', 'middle')
.attr('fill', 'var(--text-muted)')
.attr('font-size', '10px')
.attr('font-weight', 'bold')
.text(ns.split('\\').pop());
});
const link = g.append('g')
.selectAll('line')
.data(data.links)
.enter().append('line')
.attr('class', 'graph-link')
.attr('data-type', d => d.type)
.attr('stroke', d => linkColors[d.type] || '#94a3b8')
.attr('stroke-width', 1.5)
.attr('stroke-opacity', 0.4)
.attr('marker-end', d => 'url(#arrow-global-' + d.type + ')')
.attr('x1', d => data.nodes[d.source].x)
.attr('y1', d => data.nodes[d.source].y)
.attr('x2', d => data.nodes[d.target].x)
.attr('y2', d => data.nodes[d.target].y);
const node = g.append('g')
.selectAll('g')
.data(data.nodes)
.enter().append('g')
.attr('class', 'graph-node')
.attr('data-namespace', d => d.namespace)
.attr('transform', d => `translate(${d.x},${d.y})`)
.attr('cursor', d => d.fileId ? 'pointer' : 'default')
.on('click', (event, d) => {
if (d.fileId) {
window.location.href = '/docs/code-hygiene/' + d.fileId;
}
});
node.append('circle')
.attr('r', 8)
.attr('fill', d => langColors[d.extension] || langColors.php)
.attr('stroke', '#fff')
.attr('stroke-width', 1.5);
node.append('text')
.text(d => d.label)
.attr('x', 0)
.attr('y', 20)
.attr('text-anchor', 'middle')
.attr('fill', 'var(--text-primary)')
.attr('font-size', '8px');
node.append('title')
.text(d => d.id);
const depTypeFilter = document.getElementById('dep-type-filter');
const langFilter = document.getElementById('lang-filter');
function applyFilters() {
const selectedNs = filter.value;
const selectedType = depTypeFilter.value;
const selectedLang = langFilter.value;
const connectedNodes = new Set();
if (selectedType) {
data.links.forEach(link => {
if (link.type === selectedType) {
connectedNodes.add(data.nodes[link.source].id);
connectedNodes.add(data.nodes[link.target].id);
}
});
}
d3.selectAll('.graph-link')
.style('opacity', d => {
const typeMatch = !selectedType || d.type === selectedType;
const srcNs = data.nodes[d.source].namespace;
const tgtNs = data.nodes[d.target].namespace;
const srcLang = data.nodes[d.source].extension || 'php';
const tgtLang = data.nodes[d.target].extension || 'php';
const nsMatch = !selectedNs || srcNs === selectedNs || tgtNs === selectedNs;
const langMatch = !selectedLang || srcLang === selectedLang || tgtLang === selectedLang;
if (!typeMatch) return 0;
if (!nsMatch) return 0.05;
if (!langMatch) return 0.05;
return 0.6;
});
d3.selectAll('.graph-node')
.style('opacity', d => {
const typeMatch = !selectedType || connectedNodes.has(d.id);
const nsMatch = !selectedNs || d.namespace === selectedNs;
const langMatch = !selectedLang || (d.extension || 'php') === selectedLang;
if (selectedType && !typeMatch) return 0.1;
if (selectedNs && !nsMatch) return 0.1;
if (selectedLang && !langMatch) return 0.1;
return 1;
});
}
filter.addEventListener('change', applyFilters);
depTypeFilter.addEventListener('change', applyFilters);
langFilter.addEventListener('change', applyFilters);
const bounds = g.node().getBBox();
const dx = bounds.width;
const dy = bounds.height;
const x = bounds.x + dx / 2;
const y = bounds.y + dy / 2;
const scale = 0.8 / Math.max(dx / width, dy / height);
const translate = [width / 2 - scale * x, height / 2 - scale * y];
svg.call(zoom.transform, d3.zoomIdentity
.translate(translate[0], translate[1])
.scale(scale));
})
.catch(err => {
container.innerHTML = '<p class="graph-error">Fehler beim Laden des Graphen: ' + err.message + '</p>';
});
})();
</script>
<?php $content = ob_get_clean(); ?>
<?php require VIEW_PATH . '/layout.php'; ?>
Vollständig herunterladen
Aktionen
Andere Versionen dieser Datei
| ID |
Version |
Typ |
Größe |
Datum |
| 1357 |
17 |
modified |
13.0 KB |
2025-12-25 16:31 |
| 1349 |
16 |
modified |
13.0 KB |
2025-12-25 16:29 |
| 926 |
15 |
modified |
12.3 KB |
2025-12-23 21:08 |
| 925 |
14 |
modified |
12.3 KB |
2025-12-23 21:08 |
| 924 |
13 |
modified |
12.0 KB |
2025-12-23 21:08 |
| 923 |
12 |
modified |
11.9 KB |
2025-12-23 21:08 |
| 922 |
11 |
modified |
11.7 KB |
2025-12-23 21:08 |
| 908 |
10 |
modified |
13.6 KB |
2025-12-23 16:40 |
| 906 |
9 |
modified |
13.6 KB |
2025-12-23 16:06 |
| 905 |
8 |
modified |
13.5 KB |
2025-12-23 16:06 |
| 904 |
7 |
modified |
13.1 KB |
2025-12-23 16:06 |
| 902 |
6 |
modified |
12.1 KB |
2025-12-23 16:05 |
| 901 |
5 |
modified |
12.1 KB |
2025-12-23 16:05 |
| 900 |
4 |
modified |
11.6 KB |
2025-12-23 16:04 |
| 899 |
3 |
modified |
11.6 KB |
2025-12-23 15:57 |
| 898 |
2 |
modified |
11.6 KB |
2025-12-23 15:56 |
| 897 |
1 |
modified |
11.0 KB |
2025-12-23 15:56 |
← Zurück zur Übersicht