github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/viz/static/index.html (about) 1 <!DOCTYPE html> 2 3 <div id="chart" style="border:1px solid red"> 4 </div> 5 6 <!-- Load d3.js --> 7 <script type="module"> 8 9 import * as d3 from "https://cdn.skypack.dev/d3@7"; 10 11 let miserables = { 12 nodes: [ 13 {id: "Myriel", group: 1}, 14 {id: "Napoleon", group: 1}, 15 {id: "Mlle.Baptistine", group: 1}, 16 ], 17 links: [ 18 {source: "Napoleon", target: "Myriel", value: 1}, 19 {source: "Mlle.Baptistine", target: "Myriel", value: 8}, 20 ], 21 } 22 23 let prev = ""; 24 25 function doIt() { 26 27 // fetch data from /api/graph 28 fetch('/api/map') 29 .then((response) => response.json()) 30 .then((data) => { 31 if (JSON.stringify(prev) != JSON.stringify(data)) { 32 // log last 100 characters of JSON.stringify(prev) 33 console.log(JSON.stringify(prev).substr(-100)); 34 console.log(JSON.stringify(data).substr(-100)); 35 let chart = ForceGraph(data, { 36 nodeId: d => d.id, 37 nodeGroup: d => d.group, 38 nodeTitle: d => `${d.id}\n${d.group}`, 39 linkStrokeWidth: l => Math.sqrt(l.value), 40 width: 1200, 41 height: 1200, 42 }) 43 document.getElementById("chart").replaceChildren(chart) 44 } 45 prev = data; 46 }); 47 } 48 49 doIt() 50 51 // every 5 seconds 52 setInterval(doIt, 1000) 53 54 // // after 5 seconds, add an item to nodes 55 // setTimeout(() => { 56 // miserables.nodes.push({id: "Mme.Burgon", group: 1}) 57 // miserables.links.push({source: "Mme.Burgon", target: "Myriel"}) 58 // doIt() 59 60 // }, 1000) 61 62 63 // Copyright 2021 Observable, Inc. 64 // Released under the ISC license. 65 // https://observablehq.com/@d3/force-directed-graph 66 function ForceGraph({ 67 nodes, // an iterable of node objects (typically [{id}, …]) 68 links // an iterable of link objects (typically [{source, target}, …]) 69 }, { 70 nodeId = d => d.id, // given d in nodes, returns a unique identifier (string) 71 nodeGroup, // given d in nodes, returns an (ordinal) value for color 72 nodeGroups, // an array of ordinal values representing the node groups 73 nodeTitle, // given d in nodes, a title string 74 nodeFill = "currentColor", // node stroke fill (if not using a group color encoding) 75 nodeStroke = "#fff", // node stroke color 76 nodeStrokeWidth = 1.5, // node stroke width, in pixels 77 nodeStrokeOpacity = 1, // node stroke opacity 78 nodeRadius = 5, // node radius, in pixels 79 nodeStrength, 80 linkSource = ({source}) => source, // given d in links, returns a node identifier string 81 linkTarget = ({target}) => target, // given d in links, returns a node identifier string 82 linkStroke = "#999", // link stroke color 83 linkStrokeOpacity = 0.6, // link stroke opacity 84 linkStrokeWidth = 1.5, // given d in links, returns a stroke width in pixels 85 linkStrokeLinecap = "round", // link stroke linecap 86 linkStrength, 87 colors = d3.schemeTableau10, // an array of color strings, for the node groups 88 width = 640, // outer width, in pixels 89 height = 400, // outer height, in pixels 90 invalidation // when this promise resolves, stop the simulation 91 } = {}) { 92 // Compute values. 93 const N = d3.map(nodes, nodeId).map(intern); 94 const LS = d3.map(links, linkSource).map(intern); 95 const LT = d3.map(links, linkTarget).map(intern); 96 if (nodeTitle === undefined) nodeTitle = (_, i) => N[i]; 97 const T = nodeTitle == null ? null : d3.map(nodes, nodeTitle); 98 const G = nodeGroup == null ? null : d3.map(nodes, nodeGroup).map(intern); 99 const W = typeof linkStrokeWidth !== "function" ? null : d3.map(links, linkStrokeWidth); 100 const L = typeof linkStroke !== "function" ? null : d3.map(links, linkStroke); 101 102 // Replace the input nodes and links with mutable objects for the simulation. 103 nodes = d3.map(nodes, (_, i) => ({id: N[i]})); 104 links = d3.map(links, (_, i) => ({source: LS[i], target: LT[i]})); 105 106 // Compute default domains. 107 if (G && nodeGroups === undefined) nodeGroups = d3.sort(G); 108 109 // Construct the scales. 110 const color = nodeGroup == null ? null : d3.scaleOrdinal(nodeGroups, colors); 111 112 // Construct the forces. 113 const forceNode = d3.forceManyBody().strength(-100); 114 const forceLink = d3.forceLink(links).id(({index: i}) => N[i]); 115 if (nodeStrength !== undefined) forceNode.strength(nodeStrength); 116 if (linkStrength !== undefined) forceLink.strength(linkStrength); 117 118 const simulation = d3.forceSimulation(nodes) 119 .force("link", forceLink) 120 .force("charge", forceNode) 121 .force("center", d3.forceCenter()) 122 .on("tick", ticked); 123 124 const svg = d3.create("svg") 125 .attr("width", width) 126 .attr("height", height) 127 .attr("viewBox", [-width / 2, -height / 2, width, height]) 128 .attr("style", "max-width: 100%; height: auto; height: intrinsic;"); 129 130 const link = svg.append("g") 131 .attr("stroke", typeof linkStroke !== "function" ? linkStroke : null) 132 .attr("stroke-opacity", linkStrokeOpacity) 133 .attr("stroke-width", typeof linkStrokeWidth !== "function" ? linkStrokeWidth : null) 134 .attr("stroke-linecap", linkStrokeLinecap) 135 .selectAll("line") 136 .data(links) 137 .join("line"); 138 139 const node = svg.append("g") 140 .attr("fill", nodeFill) 141 .attr("stroke", nodeStroke) 142 .attr("stroke-opacity", nodeStrokeOpacity) 143 .attr("stroke-width", nodeStrokeWidth) 144 .selectAll("circle") 145 .data(nodes) 146 .join("circle") 147 .attr("r", nodeRadius) 148 .call(drag(simulation)); 149 150 if (W) link.attr("stroke-width", ({index: i}) => W[i]); 151 if (L) link.attr("stroke", ({index: i}) => L[i]); 152 if (G) node.attr("fill", ({index: i}) => color(G[i])); 153 if (T) node.append("title").text(({index: i}) => T[i]); 154 if (invalidation != null) invalidation.then(() => simulation.stop()); 155 156 function intern(value) { 157 return value !== null && typeof value === "object" ? value.valueOf() : value; 158 } 159 160 function ticked() { 161 link 162 .attr("x1", d => d.source.x) 163 .attr("y1", d => d.source.y) 164 .attr("x2", d => d.target.x) 165 .attr("y2", d => d.target.y); 166 167 node 168 .attr("cx", d => d.x) 169 .attr("cy", d => d.y); 170 } 171 172 function drag(simulation) { 173 function dragstarted(event) { 174 if (!event.active) simulation.alphaTarget(0.3).restart(); 175 event.subject.fx = event.subject.x; 176 event.subject.fy = event.subject.y; 177 } 178 179 function dragged(event) { 180 event.subject.fx = event.x; 181 event.subject.fy = event.y; 182 } 183 184 function dragended(event) { 185 if (!event.active) simulation.alphaTarget(0); 186 event.subject.fx = null; 187 event.subject.fy = null; 188 } 189 190 return d3.drag() 191 .on("start", dragstarted) 192 .on("drag", dragged) 193 .on("end", dragended); 194 } 195 196 return Object.assign(svg.node(), {scales: {color}}); 197 } 198 </script>