我已经使用svg在d3.js中建立了一个力向图,但最终该图变得很大,并且存在性能问题。我决定尝试在画布上执行此操作,因为我阅读它可以使东西变得更好,更快。但是现在我在缩放方面遇到了问题。我已经正确实现了缩放行为(我想),但是我只能在图形静止时进行缩放。在仿真发现平衡点之前,缩放行为不起作用。知道为什么吗?或有关该怎么办的任何提示?

var force = d3.forceSimulation()
            .force("link", d3.forceLink().id(function(d, i) { return i; }))
            .force("charge", d3.forceManyBody().strength( -5 ))
            .force("center", d3.forceCenter(width / 2, height / 2));

force.nodes(data.nodes)
    .on("tick", ticked)

force.force("link")
    .links(data.links);

function ticked(){
  context.clearRect(0, 0, width, height);

  // Draw the links
  data.links.forEach(function(d) {
      // Draw a line from source to target.
      context.beginPath();
      context.moveTo(d.source.x, d.source.y);
      context.lineTo(d.target.x, d.target.y);
      context.stroke();
  });
  // Draw the nodes
  data.nodes.forEach(function(d, i) {
     // Draws a complete arc for each node.
     context.beginPath();
     context.arc(d.x, d.y, d.radius, 0, 2 * Math.PI, true);
     context.fill();
  });
};

// now the zooming part
  canvas.call( d3.zoom().scaleExtent([0.2, 10]).on("zoom", zoomed) )

  function zoomed(d) {
    context.save();
    context.clearRect(0, 0, width, height);
    context.translate(d3.event.transform.x, d3.event.transform.y);
    context.scale(d3.event.transform.k, d3.event.transform.k);

    // Draw the links ...
    data.links.forEach(function(d) {
        context.beginPath();
        context.moveTo(d.source.x, d.source.y);
        context.lineTo(d.target.x, d.target.y);
        context.stroke();
    });
    // Draw the nodes ...
    data.nodes.forEach(function(d, i) {
        context.beginPath();
        context.arc(d.x, d.y, d.radius, 0, 2 * Math.PI, true);
        context.fill();
    });
    context.restore();
  }

最佳答案

您的tick函数不知道缩放所创建的任何变换。最好的方法是始终在刻度中应用变换(在任何缩放之前为identity transform)。这使您可以重用tick方法进行所有绘图。

var force = d3.forceSimulation()
  .force("link", d3.forceLink().id(function(d, i) {
    return d.id;
  }))
  .force("charge", d3.forceManyBody().strength(-5))
  .force("center", d3.forceCenter(width / 2, height / 2));

force.nodes(data.nodes)
  .on("tick", ticked);

force.force("link")
  .links(data.links)

var trans = d3.zoomIdentity; //<-- identity transform
function ticked() {
  context.save();
  context.clearRect(0, 0, width, height);
  context.translate(trans.x, trans.y); //<-- this always applies a transform
  context.scale(trans.k, trans.k);

  // Draw the links
  data.links.forEach(function(d) {
    // Draw a line from source to target.
    context.beginPath();
    context.moveTo(d.source.x, d.source.y);
    context.lineTo(d.target.x, d.target.y);
    context.stroke();
  });
  // Draw the nodes
  data.nodes.forEach(function(d, i) {
    // Draws a complete arc for each node.
    context.beginPath();
    context.arc(d.x, d.y, 5, 0, 2 * Math.PI, true);
    context.fill();
  });

  context.restore();
};

// now the zooming part
canvas.call(d3.zoom().scaleExtent([0.2, 10]).on("zoom", zoomed))

function zoomed(d) {
  trans = d3.event.transform; //<-- set to current transform
  ticked(); //<-- use tick to redraw regardless of event
}




完整的运行代码:



<!DOCTYPE html>
<html>

<head>
  <script data-require="[email protected]" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>

<body>

  <canvas width="500" height="500"></canvas>

  <script>

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

    canvas = d3.select(canvas);

    var data = {
      "nodes": [{
        "id": "Myriel",
        "group": 1
      }, {
        "id": "Napoleon",
        "group": 1
      }, {
        "id": "Mlle.Baptistine",
        "group": 1
      }, {
        "id": "Mme.Magloire",
        "group": 1
      }, {
        "id": "CountessdeLo",
        "group": 1
      }, {
        "id": "Geborand",
        "group": 1
      }, {
        "id": "Champtercier",
        "group": 1
      }, {
        "id": "Cravatte",
        "group": 1
      }, {
        "id": "Count",
        "group": 1
      }, {
        "id": "OldMan",
        "group": 1
      }, {
        "id": "Labarre",
        "group": 2
      }, {
        "id": "Valjean",
        "group": 2
      }, {
        "id": "Marguerite",
        "group": 3
      }, {
        "id": "Mme.deR",
        "group": 2
      }, {
        "id": "Isabeau",
        "group": 2
      }, {
        "id": "Gervais",
        "group": 2
      }, {
        "id": "Tholomyes",
        "group": 3
      }, {
        "id": "Listolier",
        "group": 3
      }, {
        "id": "Fameuil",
        "group": 3
      }, {
        "id": "Blacheville",
        "group": 3
      }, {
        "id": "Favourite",
        "group": 3
      }, {
        "id": "Dahlia",
        "group": 3
      }, {
        "id": "Zephine",
        "group": 3
      }, {
        "id": "Fantine",
        "group": 3
      }, {
        "id": "Mme.Thenardier",
        "group": 4
      }, {
        "id": "Thenardier",
        "group": 4
      }, {
        "id": "Cosette",
        "group": 5
      }, {
        "id": "Javert",
        "group": 4
      }, {
        "id": "Fauchelevent",
        "group": 0
      }],
      "links": [{
        "source": "Napoleon",
        "target": "Myriel",
        "value": 1
      }, {
        "source": "Mlle.Baptistine",
        "target": "Myriel",
        "value": 8
      }, {
        "source": "Mme.Magloire",
        "target": "Myriel",
        "value": 10
      }, {
        "source": "Mme.Magloire",
        "target": "Mlle.Baptistine",
        "value": 6
      }, {
        "source": "CountessdeLo",
        "target": "Myriel",
        "value": 1
      }, {
        "source": "Geborand",
        "target": "Myriel",
        "value": 1
      }, {
        "source": "Champtercier",
        "target": "Myriel",
        "value": 1
      }, {
        "source": "Cravatte",
        "target": "Myriel",
        "value": 1
      }, {
        "source": "Count",
        "target": "Myriel",
        "value": 2
      }, {
        "source": "OldMan",
        "target": "Myriel",
        "value": 1
      }, {
        "source": "Valjean",
        "target": "Labarre",
        "value": 1
      }, {
        "source": "Valjean",
        "target": "Mme.Magloire",
        "value": 3
      }, {
        "source": "Valjean",
        "target": "Mlle.Baptistine",
        "value": 3
      }, {
        "source": "Valjean",
        "target": "Myriel",
        "value": 5
      }, {
        "source": "Marguerite",
        "target": "Valjean",
        "value": 1
      }, {
        "source": "Mme.deR",
        "target": "Valjean",
        "value": 1
      }, {
        "source": "Isabeau",
        "target": "Valjean",
        "value": 1
      }, {
        "source": "Gervais",
        "target": "Valjean",
        "value": 1
      }, {
        "source": "Listolier",
        "target": "Tholomyes",
        "value": 4
      }, {
        "source": "Fameuil",
        "target": "Tholomyes",
        "value": 4
      }, {
        "source": "Fameuil",
        "target": "Listolier",
        "value": 4
      }, {
        "source": "Blacheville",
        "target": "Tholomyes",
        "value": 4
      }, {
        "source": "Blacheville",
        "target": "Listolier",
        "value": 4
      }, {
        "source": "Blacheville",
        "target": "Fameuil",
        "value": 4
      }, {
        "source": "Favourite",
        "target": "Tholomyes",
        "value": 3
      }, {
        "source": "Favourite",
        "target": "Listolier",
        "value": 3
      }, {
        "source": "Favourite",
        "target": "Fameuil",
        "value": 3
      }, {
        "source": "Favourite",
        "target": "Blacheville",
        "value": 4
      }, {
        "source": "Dahlia",
        "target": "Tholomyes",
        "value": 3
      }, {
        "source": "Dahlia",
        "target": "Listolier",
        "value": 3
      }, {
        "source": "Dahlia",
        "target": "Fameuil",
        "value": 3
      }, {
        "source": "Dahlia",
        "target": "Blacheville",
        "value": 3
      }, {
        "source": "Dahlia",
        "target": "Favourite",
        "value": 5
      }, {
        "source": "Zephine",
        "target": "Tholomyes",
        "value": 3
      }, {
        "source": "Zephine",
        "target": "Listolier",
        "value": 3
      }, {
        "source": "Zephine",
        "target": "Fameuil",
        "value": 3
      }, {
        "source": "Zephine",
        "target": "Blacheville",
        "value": 3
      }, {
        "source": "Zephine",
        "target": "Favourite",
        "value": 4
      }, {
        "source": "Zephine",
        "target": "Dahlia",
        "value": 4
      }, {
        "source": "Fantine",
        "target": "Tholomyes",
        "value": 3
      }, {
        "source": "Fantine",
        "target": "Listolier",
        "value": 3
      }, {
        "source": "Fantine",
        "target": "Fameuil",
        "value": 3
      }, {
        "source": "Fantine",
        "target": "Blacheville",
        "value": 3
      }, {
        "source": "Fantine",
        "target": "Favourite",
        "value": 4
      }, {
        "source": "Fantine",
        "target": "Dahlia",
        "value": 4
      }, {
        "source": "Fantine",
        "target": "Zephine",
        "value": 4
      }, {
        "source": "Fantine",
        "target": "Marguerite",
        "value": 2
      }]
    }

    var force = d3.forceSimulation()
      .force("link", d3.forceLink().id(function(d, i) {
        return d.id;
      }))
      .force("charge", d3.forceManyBody().strength(-5))
      .force("center", d3.forceCenter(width / 2, height / 2));

    force.nodes(data.nodes)
      .on("tick", ticked);

    force.force("link")
      .links(data.links)

    var trans = d3.zoomIdentity;
    function ticked() {
      context.save();
      context.clearRect(0, 0, width, height);
      context.translate(trans.x, trans.y);
      context.scale(trans.k, trans.k);

      // Draw the links
      data.links.forEach(function(d) {
        // Draw a line from source to target.
        context.beginPath();
        context.moveTo(d.source.x, d.source.y);
        context.lineTo(d.target.x, d.target.y);
        context.stroke();
      });
      // Draw the nodes
      data.nodes.forEach(function(d, i) {
        // Draws a complete arc for each node.
        context.beginPath();
        context.arc(d.x, d.y, 5, 0, 2 * Math.PI, true);
        context.fill();
      });

      context.restore();
    };

    // now the zooming part
    canvas.call(d3.zoom().scaleExtent([0.2, 10]).on("zoom", zoomed))

    function zoomed(d) {
      trans = d3.event.transform;
      ticked();
    }
  </script>
</body>

</html>

关于javascript - 在 Canvas 上放大d3js强制模拟,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39807094/

10-11 13:45