svg_coordinate.js 用于计算矩形与连线的交点位置
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>