svg_coordinate.js 用於計算矩形與連線的交點位置javascript
var coordinate = {}; /** * 判斷直線與水平線夾角 * @param x1 點1的x座標 * @param y1 點1的y座標 * @param x2 點2的x座標 * @param y2 點2的y座標 * @returns {number}角度 */ coordinate.findAngle= function(x1,y1,x2,y2){ var k = (y1-y2)/(x1-x2); var result = Math.atan(k)*180/Math.PI; return result; }; //計算箭頭座標 coordinate.findArrowPoint = function(x1,y1,x2,y2,rectWidth,rectHeight){ var rect_x = rectWidth/2; var rect_y = rectHeight/2; var x; var y; var selection = coordinate.findSelection(x1,y1,x2,y2); //若是與x軸水平,並在點2的左側 if(11 == selection){ x = x2 - rect_x; y = y2; return [x,y] } //若是與y軸水平,並在點2的上面 if(22 == selection){ x = x2; y = y2 - rect_y; return [x,y] } //若是與x軸水平,並在點2的右側 if(33 == selection){ y = y2; x = x2 + rect_x; return [x,y] } //若是與y軸水平,並在點2的下面 if(44 == selection){ x = x2; y = y2 + rect_y; return [x,y] } var angle = Math.abs(coordinate.findAngle(x1,y1,x2,y2)); var x_offset = 0; var y_offset = 0; if(1 == selection){ if(angle == 45){ x = x2 - rect_x; y = y2 - rect_y; return [x,y]; } if(angle < 45){ y_offset = rect_x * coordinate.tan(angle); y = y2 - y_offset; x = x2 - rect_x; return [x,y] } x_offset = rect_y / coordinate.tan(angle); y = y2 - rect_y; x = x2 - x_offset; return [x,y]; } if(2 == selection){ if(angle == 45){ x = x2 + rect_x; y = y2 - rect_y; return [x,y]; } if(angle < 45){ y_offset = rect_x * coordinate.tan(angle); y = y2 - y_offset; x = x2 + rect_x; return [x,y] } x_offset = rect_y / coordinate.tan(angle); y = y2 - rect_y; x = x2 + x_offset; return [x,y]; } if(3 == selection){ if(angle == 45){ x = x2 + rect_x; y = y2 + rect_y; return [x,y]; } if(angle < 45){ y_offset = rect_x * coordinate.tan(angle); y = y2 + y_offset; x = x2 + rect_x; return [x,y] } x_offset = rect_y / coordinate.tan(angle); y = y2 + rect_y; x = x2 + x_offset; return [x,y]; } if(4 == selection){ if(angle == 45){ x = x2 - rect_x; y = y2 + rect_y; return [x,y]; } if(angle < 45){ y_offset = rect_x * coordinate.tan(angle); y = y2 + y_offset; x = x2 - rect_x; return [x,y] } x_offset = rect_y / coordinate.tan(angle); y = y2 + rect_y; x = x2 - x_offset; return [x,y]; } }; coordinate.tan = function (angle){ return Math.tan(angle*Math.PI/180) }; coordinate.findSelection = function(x1,y1,x2,y2){ var up = 0; if(y1-y2 < 0 ){ up = 1 }else if(y1-y2 > 0){ up = -1 } var left = 0; if(x1 - x2 < 0){ left = 1; }else if(x1 - x2 > 0){ left = -1; } if(up > 0 && left > 0){ return 1; } if(up > 0 && left <0){ return 2; } if(up < 0 && left < 0){ return 3; } if(up < 0 && left > 0){ return 4; } if(up == 0 && left > 0){ return 11 } if(left == 0 && up > 0){ return 22 } if(up == 0 && left < 0){ return 33 } if(left == 0 && up <0){ return 44 } };
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> [role=entity]{ fill:rgb(44, 160, 44) } [role=concept]{ fill:rgb(0, 112, 192) } </style> <script src="./js/d3_v4.js"></script> <script type="text/javascript" src="./js/svg_coordinate.js"></script> </head> <body> <svg width="960" height="500"></svg> </body> <script type="text/javascript"> var nodes = [ { name: "北京",role:"entity"}, { name: "天津",role:"entity"}, { name: "地區",role:"concept"}, { name: "單位",role:"concept"}, { name: "單位住址",role:"concept"}, { name: "單位類型",role:"concept"}, { name: "民營",role:"entity"} ]; var links = [ { source : 0 , target: 2 } , { source : 1 , target: 2 } , { source : 3 , target: 2 } , { source : 3 , target: 4 } , { source : 3 , target: 5 } , { source : 6 , target: 5 } ]; //畫布寬度 var width = 1024; //畫布高度 var height = 738; //矩形寬度 var rect_width = 100; //矩形高度 var rect_height = 50; //畫布對象 var svg = d3.select("svg") .attr("width",width) .attr("height",height); // 經過佈局來轉換數據,而後進行繪製 var simulation = d3.forceSimulation(nodes) .force("link", d3.forceLink(links).distance(200)) .force("charge",d3.forceManyBody().strength(-100)) .force("center",d3.forceCenter(width/2, height/2)); //顏色對象 var color = d3.scaleOrdinal(d3.schemeCategory20); // 繪製線 var svg_links = svg.selectAll("path") .data(links) .enter() .append("path") .style("stroke","#ccc") .style("stroke-width",3); //節點對象 var svg_nodes = svg.selectAll("rect") .data(nodes) .enter() .append("rect") .attr("width",rect_width) .attr("height",rect_height) .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); /** * 節點(開始)拖拽事件 */ function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.002).restart(); d.fx = d.x; d.fy = d.y; } /** * 節點拖拽事件 */ function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } /** * 節點(結束)拖拽事件 */ function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); } //節點描述 var svg_text = svg.selectAll("text") .data(nodes) .enter() .append("text") .style("fill","#ffffff") .attr("dominant-baseline","middle") .attr("text-anchor", "middle")//在圓圈中加上數據 .text(function(d){return d.name;}); //箭頭 var marker= svg.append("marker") .attr("id", "resolved") .attr("markerUnits","userSpaceOnUse") .attr("viewBox", "0 -5 10 10")//座標系的區域 .attr("refX",10)//箭頭座標 .attr("refY", -1) .attr("markerWidth", 12)//標識的大小 .attr("markerHeight", 12) .attr("orient", "auto")//繪製方向,可設定爲:auto(自動確認方向)和 角度值 .attr("stroke-width",2)//箭頭寬度 .append("path") .attr("d", "M0,-5L10,0L0,5")//箭頭的路徑 .attr('fill','#000000');//箭頭顏色 //繪製節點 文本 直線 function draw(){ svg_nodes .attr("x",function(d){ return d.x - (rect_width/2); }) .attr("y",function(d){ return d.y - (rect_height/2); }) .attr("role",function (d) { return d.role; }); svg_text .attr("x", function(d){ return d.x; }) .attr("y", function(d){ return d.y; }); svg_links .attr("d",function(d){ var xAndy = coordinate.findArrowPoint(d.source.x,d.source.y,d.target.x,d.target.y,rect_width,rect_height); return 'M '+ d.source.x + ' '+d.source.y+' L '+ xAndy[0] + ' ' + xAndy[1] }) .attr("marker-end", "url(#resolved)"); } simulation.on("tick",draw); svg.call(d3.zoom().scaleExtent([0.05, 8]).on('zoom', () => { // 保存當前縮放的屬性值 var transform = d3.event.transform; svg_nodes.attr('transform', transform); svg_links.attr("transform",transform); svg_text.attr("transform",transform); })).on('dblclick.zoom', null); var e = { name: "河北",role:"entity"}; var f = {source : 7 , target: 2}; d3.timeout(function(){ nodes.push(e); links.push(f); restart() }, 4000); //動態添加節點 function restart() { svg_links = svg_links .data(links, (d) => { return d.source.name + "-" + d.target.name; }); svg_links = svg_links.enter() .append("path") .style("stroke","#ccc") .style("stroke-width",3) .merge(svg_links); svg_nodes = svg_nodes .data(nodes, (d) => d.name) .enter() .append("rect") .attr("width",rect_width) .attr("height",rect_height) .merge(svg_nodes).call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); var dom = document.querySelectorAll("rect"); for (let len = dom.length, i = 0; i < len; i++) { if (d3.select(dom[i]). select('rect').empty()) { svg_nodes.append("rect") .attr("fill", (d) => color(d.name)) .attr("width",rect_width) .attr("height",rect_height) } } //節點描述 svg_text = svg_text .data(nodes,(d) => d.name) .enter() .append("text") .style("fill","#ffffff") .attr("dominant-baseline","middle") .attr("text-anchor", "middle")//在圓圈中加上數據 .text(function(d){return d.name;}).merge(svg_text); //加載節點 simulation.nodes(nodes); simulation.force("link").links(links); simulation.alpha(1).restart(); } </script> </html>