您的位置:首页 > Web前端 > JavaScript

【数据可视化】可放缩可拖拽画布的力导向图

2016-07-26 12:47 609 查看
以力导向图为蓝本合并力导向图和放缩图,实现了可放缩可拖拽画布的力导向图
Mike Bostock给出的两个例子(http://bl.ocks.org/mbostock)
力导向图(Force Dragging III http://bl.ocks.org/mbostock/ad70335eeef6d167bc36fd3c04378048)

放缩图(Pan & Zoom http://bl.ocks.org/mbostock/2b534b091d80a8de39219dd076b316cd)

效果:



拖拽并缩小后:



1、确认主次

以力导向图为蓝本


2、增加缩放控制变量

var transform = d3.zoomIdentity;


3、响应放缩事件

d3.select(canvas)
.call(
d3.zoom()
.scaleExtent([1/10, 10])
.on("zoom",
function(){
transform = d3.event.transform;
render();
}
)
)


4、拖拽事件的碰撞检测和坐标转换

// 合并两个dragsubject()函数
function subject_from_event() {
//1、查找对象时,事件对象的坐标是屏幕坐标,需要转换成实际坐标
var ex = transform.invertX(d3.event.x),
ey = transform.invertY(d3.event.y);
var node = simulation.find(ex,ey);
//2、计算鼠标是否落在图形内部,如果落在图形外返回空以实现画布的拖拽
var dx = ex - node.x,
dy = ey - node.y;
if(dx*dx+dy*dy <radius*radius){
//3、力导向图拖拽事件需要节点的屏幕坐标,使用[rx,ry]保存实际坐标,把[x,y]替换成屏幕坐标
node.rx = node.x;
node.ry = node.y;
node.x = transform.applyX(node.rx);
node.y = transform.applyY(node.ry);
return node;
}
return null;
}
function drag_started(){
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
// 力学坐标应为实际坐标
d3.event.subject.fx = d3.event.subject.rx;
d3.event.subject.fy = d3.event.subject.ry;
}
function dragged(){
// 力学坐标应为实际坐标
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
}
function drag_ended(){
if (!d3.event.active) simulation.alphaTarget(0);
// 还原[x,y]为实际坐标
d3.event.subject.x = d3.event.subject.rx;
d3.event.subject.y = d3.event.subject.ry;
d3.event.subject.fx = null;
d3.event.subject.fy = null;
}


4、图元渲染过程画布的放缩和移动

// 更换ticked()为render(),渲染前进行画布的放缩和移动
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);


5、模块化代码(force_zoom_canvas.js)

function force_zoom_canvas(canvas_id){
// TODO 内部变量
var graph={},
canvas = document.getElementById (canvas_id),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
transform = d3.zoomIdentity,
distance = 47,
radius = 13,
simulation = d3.forceSimulation();
// TODO 配置D3
function initialize(nodes,links){
graph.nodes = nodes;
graph.links = links;
simulation
.force("link", d3.forceLink().distance(distance).strength(1).id(function(n) { return n.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.nodes(graph.nodes)
.on("tick",render);
simulation.force("link")
.links(graph.links);
d3.select(canvas)
.call(d3.drag().container(canvas).subject(subject_from_event).on("start", drag_started).on("drag", dragged).on("end", drag_ended))
.call(d3.zoom().scaleExtent([1/10, 10]).on("zoom", function(){transform = d3.event.transform;render();}))
.call(render);
//TODO 图元发现
function subject_from_event() {
var ex = transform.invertX(d3.event.x),
ey = transform.invertY(d3.event.y);
var node = simulation.find(ex,ey);
var dx = ex - node.x,
dy = ey - node.y;
if(dx*dx+dy*dy <radius*radius){
node.rx = node.x;
node.ry = node.y;
node.x = transform.applyX(node.rx);
node.y = transform.applyY(node.ry);
return node;
}
return null;
}
//TODO 图元拖拽
function drag_started(){
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d3.event.subject.fx = d3.event.subject.rx;
d3.event.subject.fy = d3.event.subject.ry;
}
function dragged(){
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
}
function drag_ended(){
if (!d3.event.active) simulation.alphaTarget(0);
d3.event.subject.x = d3.event.subject.rx;
d3.event.subject.y = d3.event.subject.ry;
d3.event.subject.fx = null;
d3.event.subject.fy = null;
}
}
// TODO 图元渲染
function render(){
context.save();
context.clearRect(0, 0, width, height);
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);
graph.links.forEach(function(l){
context.beginPath();
context.moveTo(l.source.x, l.source.y);
context.lineTo(l.target.x, l.target.y);
context.strokeStyle = "#aaa";
context.stroke();
});
graph.nodes.forEach(function(n){
context.fillStyle= "#777";
context.beginPath();
context.moveTo(n.x+radius, n.y);
context.arc(n.x, n.y,radius,0,Math.PI*2);
context.fill();
context.fillStyle= "#fff";
context.stroke();
var w = context.measureText(n.id).width;
var h = context.measureText(n.id.substr(0,1)).width;
context.fillText(n.id, n.x-w/2, n.y+h/2);
});
context.restore();
}
// TODO 接口
graph = {
"Run":initialize
};
return graph;
}


6、force_zoom_canvas.js调用方式

<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="d3.v4.js"></script>
<script src="force_zoom_canvas.js"></script>
<canvas width="1280" height="720" id="force_zoom"></canvas><p/>
</head>
<body>
<script>
var fzc = force_zoom_canvas("force_zoom");
fzc.Run([
{"id": "2"},{"id": "3"},{"id": "5"},{"id": "7"},{"id": "11"},{"id": "13"},{"id": "17"},{"id": "19"},{"id": "23"},{"id": "29"},{"id": "31"},{"id": "37"},{"id": "41"},{"id": "43"},{"id": "47"},{"id": "51"},{"id": "53"},{"id": "1"},{"id": "8"},{"id": "21"},{"id": "34"},{"id": "55"}
],[
{"source": "55", "target": "1"},{"source": "55", "target": "8"},{"source": "55", "target": "21"},{"source": "55", "target": "34"},{"source": "1", "target": "2"},{"source": "1", "target": "3"},{"source": "1", "target": "5"},{"source": "1", "target": "7"},{"source": "8", "target": "11"},{"source": "8", "target": "13"},{"source": "8", "target": "17"},{"source": "8", "target": "19"},{"source": "21", "target": "23"},{"source": "21", "target": "29"},{"source": "21", "target": "31"},{"source": "34", "target": "37"},{"source": "34", "target": "41"},{"source": "34", "target": "43"},{"source": "34", "target": "47"},{"source": "34", "target": "51"},{"source": "34", "target": "53"}
]);
</script>
</body>
</html>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息