今天给你们带来的是如何在2D可视化图形中加入经过鼠标拖动圈选功能,以力导向图为例。css
咱们是要在节点的上方绘制一个矩形覆盖节点的视觉效果,可是为了和原来的节点拖动不冲突,就须要对事件的target作判断。
当鼠标在空白区域时才能圈选。还不明白的话,返回电脑桌面试一下拖动鼠标。面试
数据
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