Backup #897

ID897
Dateipfad/var/www/dev.campus.systemische-tools.de/src/View/docs/code-graph-global.php
Version1
Typ modified
Größe11.0 KB
Hash058dcf2e4c8c70605172046b2c547c3497d3e7b2b0caddadfa37791e035ff530
Datum2025-12-23 15:56:53
Geändert vonclaude-code-hook
GrundClaude 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> &raquo;
    <span>Projekt-Graph</span>
</nav>

<h1>Projekt Dependency Graph</h1>
<p style="color: var(--text-muted); margin-bottom: 1rem;">
    Vererbungs- und Interface-Beziehungen im Projekt (extends/implements)
</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>
    </div>
    <div style="margin-left: auto; display: flex; gap: 0.5rem; align-items: center;">
        <label for="namespace-filter" style="font-size: 0.875rem;">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">&larr; 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'
    };

    // 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'])
                .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

Herunterladen

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