spectre-analytics-dashboard / js /components /graph-visualization.js
secutorpro's picture
🐳 15/02 - 00:19 - t peux finir mon projet mon frere
91c86e7 verified
/**
* 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;