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>