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

来源:互联网 发布:ping用的是什么端口 编辑:程序博客网 时间:2024/06/16 10:47
以力导向图为蓝本合并力导向图和放缩图,实现了可放缩可拖拽画布的力导向图
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>
0 0
原创粉丝点击