放大画布上的d3js力模拟

时间:2016-10-01 13:18:34

标签: javascript canvas d3.js

我使用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();
  }

1 个答案:

答案 0 :(得分:2)

tick函数无法识别缩放创建的任何变换。最好的方法是始终在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
}

完整运行代码:

&#13;
&#13;
<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@4.0.0" 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>
&#13;
&#13;
&#13;