Backup #901
| ID | 901 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/View/docs/code-graph-global.php |
| Version | 5 |
| Typ |
modified |
| Größe | 12.1 KB |
| Hash | 5b9825da8c460dfceb84ec60a21b0cdbaba7ef0f71e1a5d8284490ec7a99bc47 |
| Datum | 2025-12-23 16:05:04 |
| 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();
?>
<nav class="breadcrumb">
<a href="/docs/code">Code-Analyse</a> »
<span>Projekt-Graph</span>
</nav>
<h1>Projekt Dependency Graph</h1>
<p style="color: var(--text-muted); margin-bottom: 1rem;">
Vererbungs-, Interface- und Constructor-Beziehungen im Projekt
</p>
<div class="graph-controls" style="display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; align-items: center;">
<div class="graph-legend" style="display: flex; gap: 1rem; flex-wrap: wrap; font-size: 0.875rem;">
<span><span style="display: inline-block; width: 12px; height: 12px; background: #6366f1; border-radius: 50%; margin-right: 0.25rem;"></span> Class</span>
<span><span style="display: inline-block; width: 12px; height: 12px; background: #f59e0b; border-radius: 50%; margin-right: 0.25rem;"></span> Interface</span>
<span><span style="display: inline-block; width: 12px; height: 12px; background: #10b981; border-radius: 50%; margin-right: 0.25rem;"></span> Trait</span>
<span style="margin-left: 1rem; border-left: 1px solid var(--border-color); padding-left: 1rem;">
<span style="display: inline-block; width: 20px; height: 2px; background: #f59e0b; margin-right: 0.25rem; vertical-align: middle;"></span> extends
</span>
<span><span style="display: inline-block; width: 20px; height: 2px; background: #3b82f6; margin-right: 0.25rem; vertical-align: middle;"></span> implements</span>
<span><span style="display: inline-block; width: 20px; height: 2px; background: #8b5cf6; margin-right: 0.25rem; vertical-align: middle;"></span> constructor</span>
</div>
<div style="margin-left: auto; display: flex; gap: 0.5rem; align-items: center;">
<label for="dep-type-filter" style="font-size: 0.875rem;">Typ:</label>
<select id="dep-type-filter" style="padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-secondary);">
<option value="">Alle</option>
<option value="extends">extends</option>
<option value="implements">implements</option>
<option value="constructor">constructor</option>
</select>
<label for="namespace-filter" style="font-size: 0.875rem; margin-left: 0.5rem;">Namespace:</label>
<select id="namespace-filter" style="padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-secondary);">
<option value="">Alle</option>
</select>
<button id="reset-zoom" class="btn btn--secondary btn--small" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;">Reset Zoom</button>
</div>
</div>
<div id="graph-stats" style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 0.5rem;"></div>
<div id="graph-container" style="width: 100%; height: 700px; background: var(--bg-secondary); border-radius: 8px; overflow: hidden; position: relative;"></div>
<div style="margin-top: 1.5rem;">
<a href="/docs/code" 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;
const colors = {
class: '#6366f1',
interface: '#f59e0b',
trait: '#10b981'
};
const linkColors = {
extends: '#f59e0b',
implements: '#3b82f6',
constructor: '#8b5cf6'
};
// Loading indicator
container.innerHTML = '<div style="display: flex; justify-content: center; align-items: center; height: 100%; color: var(--text-muted);">Lade Graph-Daten...</div>';
fetch('/docs/code/graph-data')
.then(r => r.json())
.then(data => {
container.innerHTML = '';
// Stats
document.getElementById('graph-stats').textContent =
`${data.stats.nodes} Klassen | ${data.stats.links} Beziehungen | ${data.stats.namespaces} Namespaces`;
// Populate namespace filter
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);
});
// Group nodes by namespace for static layout
const namespaceGroups = {};
data.nodes.forEach((node, i) => {
const ns = node.namespace || 'global';
if (!namespaceGroups[ns]) namespaceGroups[ns] = [];
namespaceGroups[ns].push(node);
});
// Compute static positions (circular layout by namespace)
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);
}
});
});
// Create node index for links
const nodeIndex = {};
data.nodes.forEach((n, i) => nodeIndex[n.id] = i);
// Resolve link indices
data.links.forEach(link => {
if (typeof link.source === 'number') {
link.sourceNode = data.nodes[link.source];
link.targetNode = data.nodes[link.target];
}
});
// Create SVG with zoom
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);
// Reset zoom button
document.getElementById('reset-zoom').addEventListener('click', () => {
svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);
});
// Arrow markers
svg.append('defs').selectAll('marker')
.data(['extends', 'implements', 'constructor'])
.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]);
// Draw namespace labels
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());
});
// Draw links
const link = g.append('g')
.selectAll('line')
.data(data.links)
.enter().append('line')
.attr('class', 'graph-link')
.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);
// Draw nodes
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/' + d.fileId;
}
});
node.append('circle')
.attr('r', 8)
.attr('fill', d => colors[d.type] || colors.class)
.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);
// Namespace filter
filter.addEventListener('change', (e) => {
const selected = e.target.value;
if (!selected) {
// Show all
d3.selectAll('.graph-node').style('opacity', 1);
d3.selectAll('.graph-link').style('opacity', 0.4);
} else {
// Filter by namespace
d3.selectAll('.graph-node')
.style('opacity', d => d.namespace === selected ? 1 : 0.1);
d3.selectAll('.graph-link')
.style('opacity', d => {
const srcNs = data.nodes[d.source].namespace;
const tgtNs = data.nodes[d.target].namespace;
return (srcNs === selected || tgtNs === selected) ? 0.6 : 0.05;
});
}
});
// Initial zoom to fit
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 style="padding: 2rem; color: var(--text-danger);">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