d3.js實現力導向圖圈選框選

d3.js實現力導向圖圈選框選

今天給你們帶來的是如何在2D可視化圖形中加入經過鼠標拖動圈選功能,以力導向圖爲例。css

最終效果

http://jsrun.net/5TqKpnode

代碼解析

咱們是要在節點的上方繪製一個矩形覆蓋節點的視覺效果,可是爲了和原來的節點拖動不衝突,就須要對事件的target作判斷。
當鼠標在空白區域時才能圈選。還不明白的話,返回電腦桌面試一下拖動鼠標。面試

clipboard.png

首先仍是先畫一個力導向圖

數據
var nodes = [{
  value: "66666666",
  type: "home",
  index: "0"
},
{
  value: "11111111111",
  type: "phone",
  index: "1"
},
{
  value: "22222222222",
  type: "phone",
  index: "2"
},
{
  value: "33333333333",
  type: "phone",
  index: "3"
},
{
  value: "44444444444",
  type: "phone",
  index: "4"
},
{
  value: "55555555555",
  type: "phone",
  index: "5"
},
{
  value: "aaa",
  type: "weixin",
  index: "6"
},
{
  value: "bbb",
  type: "weixin",
  index: "7"
},
{
  value: "ccc",
  type: "weixin",
  index: "8"
},
{
  value: "ddd",
  type: "weixin",
  index: "9"
},
{
  value: "eee",
  type: "weixin",
  index: "10"
},
{
  value: "fff",
  type: "weixin",
  index: "11"
},
];
var links = [{
  source: 0,
  target: 1
},
{
  source: 0,
  target: 2
},
{
  source: 0,
  target: 3
},
{
  source: 0,
  target: 4
},
{
  source: 0,
  target: 5
},
{
  source: 2,
  target: 6
},
{
  source: 2,
  target: 7
},
{
  source: 2,
  target: 8
},
{
  source: 3,
  target: 9
},
{
  source: 3,
  target: 10
},
{
  source: 3,
  target: 11
},
]

繪製力導向圖

var svg = d3.select("#forceMap").append("svg")
  .attr("width", width)
  .attr("height", height)
  .attr("id", "forceSvg");
var mapG = svg.append("g")
  .attr("id", "forceGroup");

var force = d3.layout.force()
  .nodes(nodes)
  .links(links)
  .size([width, height])
  .linkDistance(100)
  .charge([ - 1250])
  .gravity(0.5)
  .friction(0.5);
force.start();
var linkG = mapG.selectAll(".link")
  .data(links)
  .enter()
  .append("line")
  .attr("class", "link")
  .attr("stroke", "#ccc");
var nodeG = mapG.selectAll(".node")
  .data(nodes)
  .enter()
  .append("circle")
  .attr("class", "node unselected")//加入新的class,給選中和未選中作判斷
  .attr("r", 8)
  .attr("fill", function(d) {
  switch (d.type) {
  case "home":
    return "red";
    break;
  case "phone":
    return "blue";
    break;
  case "weixin":
    return "green";
    break;
  }
})
  .call(force.drag);

force.on("tick", function() {
  linkG.attr("x1", function(d) {
    return d.source.x;
  })
    .attr("y1", function(d) {
    return d.source.y;
  })
    .attr("x2", function(d) {
    return d.target.x;
  })
    .attr("y2", function(d) {
    return d.target.y;
  });

  nodeG.attr("cx", function(d) {
    return d.x
  })
    .attr("cy", function(d) {
    return d.y
  });
});

這裏和之前是有區別的,就是在繪製node時加入了新的class=「unselected」,這裏規定選中的node爲selected,未選中爲unselected。
兩種樣式在css裏實現。app

.unselected{
  opacity:0.3
}
.selected{
 opacity:1 
}

圈選功能

首先肯定思路,經過獲取鼠標按下時的座標位置和最後鼠標左鍵擡起時的結束位置來肯定圈選框的大小和繪製方式。
值得注意的是不一樣的拖動方向(左上,右上,左下,右下),繪製的方法有區別。
須要判斷點擊時是否鼠標在空白區域,防止和節點的拖動衝突。
作時間標記區別短暫點擊和拖動。
var clickTime = "";
var startLoc = [];
var endLoc = [];
var flag = "";
function drawSquare() {

  var rect = svg.append("rect")
    .attr("width", 0)
    .attr("height", 0)
    .attr("fill", "rgba(33,20,50,0.3)")
    .attr("stroke", "#ccc")
    .attr("stroke-width", "2px")
    .attr("transform", "translate(0,0)")
    .attr("id", "squareSelect");

  svg.on("mousedown", function() {

    clickTime = (new Date()).getTime();//mark start time
    flag = true;//以flag做爲可執行圈選的標記
    rect.attr("transform", "translate(" + d3.event.layerX + "," + d3.event.layerY + ")");
    startLoc = [d3.event.layerX, d3.event.layerY];

  });

  svg.on("mousemove", function() {
    //判斷事件target
    if (d3.event.target.localName == "svg" && flag == true || d3.event.target.localName == "rect" && flag == true) {

      var width = d3.event.layerX - startLoc[0];
      var height = d3.event.layerY - startLoc[1];
      if (width < 0) {
        rect.attr("transform", "translate(" + d3.event.layerX + "," + startLoc[1] + ")");
      }
      if (height < 0) {
        rect.attr("transform", "translate(" + startLoc[0] + "," + d3.event.layerY + ")");
      }
      if (height < 0 && width < 0) {
        rect.attr("transform", "translate(" + d3.event.layerX + "," + d3.event.layerY + ")");
      }
      rect.attr("width", Math.abs(width)).attr("height", Math.abs(height))
    }

  })
  
  svg.on("mouseup", function(){
            if(flag == true){
                flag = false;
                endLoc = [d3.event.layerX, d3.event.layerY];
                var leftTop = [];
                var rightBottom = []
                if(endLoc[0]>=startLoc[0]){
                    leftTop[0] = startLoc[0];
                    rightBottom[0] = endLoc[0];
                }else{
                    leftTop[0] = endLoc[0];
                    rightBottom[0] = startLoc[0];
                }

                if(endLoc[1]>=startLoc[1]){
                    leftTop[1] = startLoc[1];
                    rightBottom[1] = endLoc[1];
                }else{
                    leftTop[1] = endLoc[1];
                    rightBottom[1] = startLoc[1];
                }

                  //最後經過和node的座標比較,肯定哪些點在圈選範圍
                var nodes = d3.selectAll(".node").attr("temp", function(d){
                    if(d.x<rightBottom[0] && d.x>leftTop[0] && d.y>leftTop[1] && d.y<rightBottom[1]){
                        
                            d3.select(this).attr("class","node selected");
                    }
                })
                rect.attr("width",0).attr("height",0);
            }
            var times = (new Date()).getTime()-clickTime;
            if (times<100 && d3.event.target.id !== "squareSelect") {
                   var nodes = d3.selectAll(".node").attr("class", "node unselected")
                }
        
        })

}
drawSquare();

到這裏就完成了一個簡單的圈選功能。svg

總結

完成了圈選以後,在項目中,咱們一般須要拿到圈選到的元素的屬性顯示出來或者保存,這些就很簡單了,只須要遍歷有selected的元素便可。
這裏的方法不只適用於力導向圖,也一樣適用於類似的樹狀圖等。
若是你的圖帶縮放(Zoom),那就會複雜一點,由於圈選方法裏面的對範圍內節點的計算和scale有關係,須要把scale和translate加入計算。this

但在有座標系的柱狀圖和折線圖這類圖中實現圈選,這裏建議使用v4中的brush方法,從此的文章中會詳細介紹。spa

相關文章
相關標籤/搜索