source: lan-frontend/src/components/SankeyViz.jsx@ e4c61dd

Last change on this file since e4c61dd was e4c61dd, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Prototype 1.1

  • Property mode set to 100644
File size: 3.1 KB
Line 
1import React, { useEffect, useMemo, useRef } from "react";
2import * as d3 from "d3";
3import { sankey, sankeyLinkHorizontal } from "d3-sankey";
4
5/**
6 * dataRows: [{ source: "PC1", target: "1.2.3.4:443", value: 12 }, ...]
7 */
8export default function SankeyViz({ dataRows, height = 520 }) {
9 const ref = useRef(null);
10
11 const graph = useMemo(() => {
12 const rows = Array.isArray(dataRows) ? dataRows : [];
13 const nodesMap = new Map();
14 const nodes = [];
15 const links = [];
16
17 function getNode(name) {
18 if (!nodesMap.has(name)) {
19 const id = nodes.length;
20 nodesMap.set(name, id);
21 nodes.push({ name });
22 }
23 return nodesMap.get(name);
24 }
25
26 for (const r of rows) {
27 if (!r?.source || !r?.target) continue;
28 const s = getNode(String(r.source));
29 const t = getNode(String(r.target));
30 const v = Number(r.value ?? 1);
31 links.push({ source: s, target: t, value: isFinite(v) ? v : 1 });
32 }
33
34 return { nodes, links };
35 }, [dataRows]);
36
37 useEffect(() => {
38 const el = ref.current;
39 if (!el) return;
40
41 el.innerHTML = "";
42
43 const width = el.clientWidth || 900;
44 const svg = d3
45 .select(el)
46 .append("svg")
47 .attr("width", "100%")
48 .attr("viewBox", `0 0 ${width} ${height}`)
49 .style("background", "rgba(255,255,255,0.02)")
50 .style("border", "1px solid rgba(255,255,255,0.08)")
51 .style("borderRadius", "14px");
52
53 if (!graph.nodes.length || !graph.links.length) {
54 svg
55 .append("text")
56 .attr("x", width / 2)
57 .attr("y", height / 2)
58 .attr("text-anchor", "middle")
59 .style("fill", "rgba(255,255,255,0.7)")
60 .style("font-size", "14px")
61 .text("No data for this selection.");
62 return;
63 }
64
65 const sk = sankey()
66 .nodeWidth(18)
67 .nodePadding(14)
68 .extent([
69 [20, 20],
70 [width - 20, height - 20],
71 ]);
72
73 const { nodes, links } = sk({
74 nodes: graph.nodes.map((d) => ({ ...d })),
75 links: graph.links.map((d) => ({ ...d })),
76 });
77
78 // Links
79 svg
80 .append("g")
81 .attr("fill", "none")
82 .selectAll("path")
83 .data(links)
84 .join("path")
85 .attr("d", sankeyLinkHorizontal())
86 .attr("stroke", "rgba(80,160,255,0.55)")
87 .attr("stroke-width", (d) => Math.max(1, d.width))
88 .attr("opacity", 0.9);
89
90 // Nodes
91 const node = svg
92 .append("g")
93 .selectAll("g")
94 .data(nodes)
95 .join("g");
96
97 node
98 .append("rect")
99 .attr("x", (d) => d.x0)
100 .attr("y", (d) => d.y0)
101 .attr("height", (d) => Math.max(1, d.y1 - d.y0))
102 .attr("width", (d) => d.x1 - d.x0)
103 .attr("rx", 6)
104 .attr("fill", "rgba(255,255,255,0.18)")
105 .attr("stroke", "rgba(255,255,255,0.15)");
106
107 node
108 .append("text")
109 .attr("x", (d) => (d.x0 < width / 2 ? d.x1 + 8 : d.x0 - 8))
110 .attr("y", (d) => (d.y0 + d.y1) / 2)
111 .attr("dy", "0.35em")
112 .attr("text-anchor", (d) => (d.x0 < width / 2 ? "start" : "end"))
113 .style("fill", "rgba(255,255,255,0.85)")
114 .style("font-size", "12px")
115 .text((d) => d.name);
116 }, [graph, height]);
117
118 return <div ref={ref} style={{ width: "100%" }} />;
119}
Note: See TracBrowser for help on using the repository browser.