title: D3 Forced Layout (Canvas) performance drop out after canvas.scale call
body: <p>I encountered very strange performance problems. . .
  I visualize some relationships within the network using the d3 force graph, to implement zoom I use d3-zoom. 
  When d3.zoomIdentity.k > 1, a significant decrease in performance occurs and i don't know why! :(
  <a href="https://i.stack.imgur.com/ajH95.png" rel="nofollow noreferrer">Chrome performance profile screenshot</a></p>
  
  <p>As you can see in the screenshot of the performance profile, immediately after the scroll (purple line) (zoom-in, k becomes> 1), the frame rendering time and the task (gray line) time increase.</p>
  
  <p>You can reproduce it like this:
   1. Open Index.html
   2. Drag some nodes and check fps (visually)
   3. Zoom-IN and now you can see sick performance decrease
   4. Zoom-OUT and everything is fine again :shrug:</p>
  
  <p>Here is the code (index.html):</p>
  
  <pre><code>&lt;meta charset="utf-8"&gt;
  &lt;script src="https://d3js.org/d3.v5.min.js"&gt;&lt;/script&gt;
  &lt;style&gt;
    .edge {
      stroke: white;
      stroke-width: 1;
    }
  
    .graphSVG {
      background-color: black;
    }
  
    div.container {
      width: 100%;
      border: 1px solid gray;
    }
  
    div.tooltip {
      position: absolute;
      text-align: center;
      width: 180px;
      padding: 2px;
      font: 12px sans-serif;
      background: lightsteelblue;
      border: 0px;
      border-radius: 8px;
      pointer-events: none;
    }
  &lt;/style&gt;
  
  &lt;body&gt;
    &lt;div id="graphDiv"&gt;&lt;/div&gt;
  
    &lt;script&gt;
      var radius = 5;
  
      var defaultNodeCol = "white",
        highlightCol = "yellow";
  
      var height = window.innerHeight;
      var graphWidth = window.innerWidth;
  
      var graphCanvas = d3.select('#graphDiv').append('canvas')
        .attr('width', graphWidth)
        .attr('height', height)
        .node();
  
      var context = graphCanvas.getContext('2d');
      graphCanvas.style.backgroundColor = "white";
  
      var div = d3.select("body").append("div")
        .attr("class", "tooltip")
        .style("opacity", 0);
  
      var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function (d) { return d.id; }))
        .force("center", d3.forceCenter(graphCanvas.width / 2, graphCanvas.height / 2))
        .force("x", d3.forceX(graphCanvas.width / 2).strength(0.2))
        .force("y", d3.forceY(graphCanvas.height / 2).strength(0.2))
        .force("charge", d3.forceManyBody().strength(-50))
  
  
      var transform = d3.zoomIdentity;
  
      d3.json("https://jsonstorage.net/api/items/7a90797a-6abe-4795-bf5c-20b08644fe1e").then(data =&gt; initGraph(data));
  
      function initGraph(tempData) {
        function zoomed() {
          transform = d3.event.transform;
          simulationUpdate();
        }
  
        d3.select(graphCanvas)
          .call(d3.drag().subject(dragsubject).on("start", dragstarted).on("drag", dragged).on("end", dragended))
          .call(d3.zoom().scaleExtent([1 / 10, 8]).on("zoom", zoomed))
  
  
  
        function dragsubject() {
          var i,
            x = transform.invertX(d3.event.x),
            y = transform.invertY(d3.event.y),
            dx,
            dy;
          for (i = tempData.nodes.length - 1; i &gt;= 0; --i) {
            node = tempData.nodes[i];
            dx = x - node.x;
            dy = y - node.y;
  
            if (dx * dx + dy * dy &lt; radius * radius) {
  
              node.x = transform.applyX(node.x);
              node.y = transform.applyY(node.y);
  
              return node;
            }
          }
        }
  
        function dragstarted() {
          if (!d3.event.active) simulation.alphaTarget(0.3).restart();
          d3.event.subject.fx = transform.invertX(d3.event.x);
          d3.event.subject.fy = transform.invertY(d3.event.y);
        }
  
        function dragged() {
          d3.event.subject.fx = transform.invertX(d3.event.x);
          d3.event.subject.fy = transform.invertY(d3.event.y);
  
        }
  
        function dragended() {
          if (!d3.event.active) simulation.alphaTarget(0);
          d3.event.subject.fx = null;
          d3.event.subject.fy = null;
        }
  
        simulation.nodes(tempData.nodes)
          .on("tick", simulationUpdate);
  
        simulation.force("link")
          .links(tempData.edges);
  
  
  
        function render() {
  
        }
  
        function simulationUpdate() {
          context.save();
          context.clearRect(0, 0, graphWidth, height);
          context.translate(transform.x, transform.y);
          context.scale(transform.k, transform.k);
  
          context.beginPath();
          context.strokeStyle = 'rgba(255, 0, 0, 1)';
          tempData.edges.forEach(function (d) {
            context.moveTo(d.source.x, d.source.y);
            context.lineTo(d.target.x, d.target.y);
          });
          context.stroke();
          context.closePath();
  
          const part1 = tempData.nodes.slice(0, tempData.nodes.length / 2);
          const part2 = tempData.nodes.slice(tempData.nodes.length / 2);
  
          context.fillStyle = 'black';
          context.beginPath();
          part1.forEach(function (d, i) {
            context.moveTo(d.x, d.y);
            context.arc(d.x, d.y, radius, 0, 2 * Math.PI, true);
          });
          context.fill();
          context.closePath();
  
          context.fillStyle = 'orange';
          context.beginPath();
          part2.forEach(function (d, i) {
            context.moveTo(d.x, d.y);
            context.arc(d.x, d.y, radius, 0, 2 * Math.PI, true);
          });
          context.fill();
          context.closePath();
  
          context.restore();
        }
      };
  
  
    &lt;/script&gt;
  &lt;/body&gt;```
  </code></pre>