2016-10-01 97 views
2

我已經使用svg設置了一個使用d3.js的力指向圖,但最終該圖變得很大,並且它有性能問題。我決定嘗試在一個畫布上做,因爲我讀過它會更好,更快地渲染垃圾。但現在我遇到了縮放問題。我已經正確實現了縮放行爲(我猜),但是我只能在圖表靜止時縮放。在模擬之前發現平衡點縮放行爲不起作用。任何想法爲什麼?或者我應該怎麼做的任何提示?放大畫布上的d3js力仿真

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

回答

2

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>

+0

這是唯一的d3v4畫布被迫圖,我發現到目前爲止這是工作和短,易於理解! :) – Guntram