JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://d3js.org/d3-array.v1.min.js"></script>
<div id="progress"></div>
<canvas width="960" height="960"></canvas>
<script type="javascript/worker">
importScripts("https://d3js.org/d3-collection.v1.min.js");
importScripts("https://d3js.org/d3-dispatch.v1.min.js");
importScripts("https://d3js.org/d3-quadtree.v1.min.js");
importScripts("https://d3js.org/d3-timer.v1.min.js");
importScripts("https://d3js.org/d3-force.v1.min.js");

onmessage = function(event) {
	console.time('cost1')

  var nodes = event.data.nodes,
      links = event.data.links;

  var simulation = d3.forceSimulation(nodes)
      .force("charge", d3.forceManyBody())
      .force("link", d3.forceLink(links).distance(20).strength(1))
      .force("x", d3.forceX())
      .force("y", d3.forceY())
      .alphaDecay(0.5)
      .stop();

  for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) {
    postMessage({type: "tick", progress: i / n});
    simulation.tick();
  }
	console.timeEnd('cost1')

  postMessage({type: "end", nodes: nodes, links: links});
};
</script>

CSS

#progress {
  position: fixed;
  top: 0;
  left: -6px;
  width: 0%;
  height: 2px;
  background: red;
}

#progress:before,
#progress:after {
  content: "";
  position: absolute;
  height: 2px;
  opacity: 0.6;
  box-shadow: red 1px 0 6px 1px;
  border-radius: 100%;
}

#progress:before {
  width: 20px;
  right: 0;
  clip: rect(-6px, 22px, 14px, 10px);
}

#progress:after {
  width: 180px;
  right: -80px;
  clip: rect(-6px, 90px, 14px, -6px);
}

JavaScript

function getInlineJS() {
  var js = document.querySelector('[type="javascript/worker"]').textContent;
  var blob = new Blob([js], {
    "type": "text\/plain"
  });
  return URL.createObjectURL(blob);
}
console.log(getInlineJS())
var worker = new Worker(getInlineJS());
var nodes = d3.range(10000).map(function(i) {
  return {
    index: i
  };
});

var links = d3.range(nodes.length - 1).map(function(i) {
  return {
    source: Math.floor(Math.sqrt(i)),
    target: i + 1
  };
});

var meter = document.querySelector("#progress"),
  canvas = document.querySelector("canvas"),
  context = canvas.getContext("2d"),
  width = canvas.width,
  height = canvas.height;


worker.postMessage({
  nodes: nodes,
  links: links
});

worker.onmessage = function(event) {
  switch (event.data.type) {
    case "tick":
      return ticked(event.data);
    case "end":
      return ended(event.data);
  }
};

function ticked(data) {
  var progress = data.progress;

  meter.style.width = 100 * progress + "%";
}

function ended(data) {
  var nodes = data.nodes,
    links = data.links;

  meter.style.display = "none";

  context.clearRect(0, 0, width, height);
  context.save();
  context.translate(width / 2, height / 2);

  context.beginPath();
  links.forEach(drawLink);
  context.strokeStyle = "#aaa";
  context.stroke();

  context.beginPath();
  nodes.forEach(drawNode);
  context.fill();
  context.strokeStyle = "#fff";
  context.stroke();

  context.restore();
}

function drawLink(d) {
  context.moveTo(d.source.x, d.source.y);
  context.lineTo(d.target.x, d.target.y);
}

function drawNode(d) {
  context.moveTo(d.x + 3, d.y);
  context.arc(d.x, d.y, 3, 0, 2 * Math.PI);
}
console.timeEnd('cost')