Backup #1349

ID1349
Dateipfad/var/www/dev.campus.systemische-tools.de/src/View/docs/code-graph-global.php
Version16
Typ modified
Größe13.0 KB
Hash8ac5827e7ea8364660bef74520b4b56070d8b60c061b748e8f010a510768e879
Datum2025-12-25 16:29:19
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();
?>
<link rel="stylesheet" href="/css/graph.css">

<nav class="breadcrumb">
    <a href="/docs/code">Code-Analyse</a> &raquo;
    <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" 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;

    // 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/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/' + 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

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