Download js/components/graph-visualization.js from secutorpro/spectre-analytics-dashboard: direct link, hf CLI and curl.
- Browser
- Download file 5.54 kB
-
https://huggingface.co/spaces/secutorpro/spectre-analytics-dashboard/resolve/main/js/components/graph-visualization.js
- Command line
-
hf download hf://spaces/secutorpro/spectre-analytics-dashboard/js/components/graph-visualization.js
-
curl -L -o graph-visualization.js https://huggingface.co/spaces/secutorpro/spectre-analytics-dashboard/resolve/main/js/components/graph-visualization.js
5.54 kB
| /** | |
| * Graph Visualization Component | |
| * D3.js Force-directed graph for entity relationships | |
| */ | |
| function initializeGraphVisualization() { | |
| const container = document.getElementById('graph-container'); | |
| if (!container) return; | |
| const width = container.clientWidth; | |
| const height = container.clientHeight || 384; | |
| // Clear existing | |
| const svg = d3.select('#entity-graph'); | |
| svg.selectAll('*').remove(); | |
| svg.attr('viewBox', [0, 0, width, height]); | |
| // Sample data based on entities | |
| const nodes = [ | |
| { id: 'QE001', name: 'Quantum Alpha LP', risk: 92, type: 'fund', group: 1 }, | |
| { id: 'SB002', name: 'Sigma Beta Fund', risk: 88, type: 'fund', group: 1 }, | |
| { id: 'AA003', name: 'AI Alpha Tech', risk: 65, type: 'manager', group: 2 }, | |
| { id: 'BS004', name: 'Beta Sigma Mgmt', risk: 58, type: 'manager', group: 2 }, | |
| { id: 'MT005', name: 'Mirror Trading', risk: 35, type: 'trading', group: 3 }, | |
| { id: 'SHELL1', name: 'Cayman Shell A', risk: 95, type: 'shell', group: 4 }, | |
| { id: 'SHELL2', name: 'BVI Holdings', risk: 87, type: 'shell', group: 4 } | |
| ]; | |
| const links = [ | |
| { source: 'AA003', target: 'QE001', value: 1, type: 'manages' }, | |
| { source: 'BS004', target: 'SB002', value: 1, type: 'manages' }, | |
| { source: 'QE001', target: 'SB002', value: 2, type: 'correlated' }, | |
| { source: 'SHELL1', target: 'AA003', value: 1, type: 'owns' }, | |
| { source: 'SHELL2', target: 'BS004', value: 1, type: 'owns' }, | |
| { source: 'MT005', target: 'QE001', value: 1, type: 'trades' }, | |
| { source: 'SHELL1', target: 'SHELL2', value: 3, type: 'entangled' } | |
| ]; | |
| // Simulation setup | |
| const simulation = d3.forceSimulation(nodes) | |
| .force('link', d3.forceLink(links).id(d => d.id).distance(100)) | |
| .force('charge', d3.forceManyBody().strength(-400)) | |
| .force('center', d3.forceCenter(width / 2, height / 2)) | |
| .force('collision', d3.forceCollide().radius(40)); | |
| // Arrow markers | |
| svg.append('defs').selectAll('marker') | |
| .data(['end']) | |
| .enter().append('marker') | |
| .attr('id', 'arrow') | |
| .attr('viewBox', '0 -5 10 10') | |
| .attr('refX', 25) | |
| .attr('refY', 0) | |
| .attr('markerWidth', 6) | |
| .attr('markerHeight', 6) | |
| .attr('orient', 'auto') | |
| .append('path') | |
| .attr('d', 'M0,-5L10,0L0,5') | |
| .attr('fill', '#2dd4bf'); | |
| // Links | |
| const link = svg.append('g') | |
| .selectAll('line') | |
| .data(links) | |
| .join('line') | |
| .attr('class', 'node-link') | |
| .attr('stroke-width', d => Math.sqrt(d.value) * 2) | |
| .attr('stroke', d => { | |
| if (d.type === 'entangled') return '#ef4444'; | |
| if (d.type === 'correlated') return '#f59e0b'; | |
| return '#2dd4bf'; | |
| }) | |
| .attr('stroke-opacity', 0.6); | |
| // Nodes | |
| const node = svg.append('g') | |
| .selectAll('g') | |
| .data(nodes) | |
| .join('g') | |
| .attr('class', 'node-circle') | |
| .call(d3.drag() | |
| .on('start', dragstarted) | |
| .on('drag', dragged) | |
| .on('end', dragended)); | |
| // Node circles | |
| node.append('circle') | |
| .attr('r', d => d.risk > 80 ? 20 : 15) | |
| .attr('fill', d => { | |
| if (d.risk > 80) return '#ef4444'; | |
| if (d.risk > 60) return '#f59e0b'; | |
| if (d.risk > 40) return '#14b8a6'; | |
| return '#22c55e'; | |
| }) | |
| .attr('stroke', '#0f172a') | |
| .attr('stroke-width', 2) | |
| .style('cursor', 'pointer'); | |
| // Node labels | |
| node.append('text') | |
| .text(d => d.name) | |
| .attr('x', 0) | |
| .attr('y', d => d.risk > 80 ? 28 : 23) | |
| .attr('text-anchor', 'middle') | |
| .attr('fill', '#94a3b8') | |
| .attr('font-size', '10px') | |
| .attr('font-family', 'Inter, sans-serif'); | |
| // Tooltips | |
| node.append('title') | |
| .text(d => `${d.name}\nRisk: ${d.risk}%\nType: ${d.type}`); | |
| // Click handlers | |
| node.on('click', (event, d) => { | |
| viewEntity(d.id); | |
| }); | |
| // Update positions | |
| simulation.on('tick', () => { | |
| link | |
| .attr('x1', d => d.source.x) | |
| .attr('y1', d => d.source.y) | |
| .attr('x2', d => d.target.x) | |
| .attr('y2', d => d.target.y); | |
| node.attr('transform', d => `translate(${d.x},${d.y})`); | |
| }); | |
| function dragstarted(event, d) { | |
| if (!event.active) simulation.alphaTarget(0.3).restart(); | |
| d.fx = d.x; | |
| d.fy = d.y; | |
| } | |
| function dragged(event, d) { | |
| d.fx = event.x; | |
| d.fy = event.y; | |
| } | |
| function dragended(event, d) { | |
| if (!event.active) simulation.alphaTarget(0); | |
| d.fx = null; | |
| d.fy = null; | |
| } | |
| // Zoom controls | |
| window.zoomGraph = (scale) => { | |
| const currentTransform = d3.zoomTransform(svg.node()); | |
| const newTransform = currentTransform.scale(scale); | |
| svg.transition().duration(750).call(d3.zoom().transform, newTransform); | |
| }; | |
| window.resetGraph = () => { | |
| svg.transition().duration(750).call(d3.zoom().transform, d3.zoomIdentity); | |
| }; | |
| // Add zoom behavior | |
| const zoom = d3.zoom() | |
| .scaleExtent([0.5, 4]) | |
| .on('zoom', (event) => { | |
| svg.selectAll('g').attr('transform', event.transform); | |
| }); | |
| svg.call(zoom); | |
| } | |
| // Expose to window | |
| window.initializeGraphVisualization = initializeGraphVisualization; |