達到的效果:javascript
實現原理:
利用raphael 畫出svg圖形,同時又自動生成生成相應的html, 這樣若是作交互能夠直接在html 上作交互。css
html:html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>json demo</title> <style type="text/css"> body {margin:0; padding:0; font-size: 12px;} p {margin:0; padding:0;} #content { border: 1px solid #7FC3E9; border-radius: 3px 3px 3px 3px; position: relative; } #graph div.graph { background: none repeat scroll 0 0 #ffffff; border: 1px solid #3C63C7; /*border-radius: 10px 10px 10px 10px;*/ box-shadow: 2px 1px 10px rgba(39, 57, 102, 0.44); color: #333333; display: inline-block; position: absolute; } #graph div.graph div.title { border-bottom: 1px solid #EEEEEE; overflow: auto; padding: 5px; background: #f0f6f7; } #graph div.graph div.content { padding: 5px; text-align: center; } #button {width: 200px; height: 200px; position: absolute; top:5px; right: 10px; display: none; font-size: 16px; font-weight: bold;} </style> </head> <body> <div id="content"> <div id="canvas"></div> <div id="graph"></div> <div id="button"> <a id="save" href="javascript:;">保存</a> </div> </div> <script type="text/javascript" src="../js/raphael.js"></script> <script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script> <script type="text/javascript" src="../js/underscore.js"></script> <script type="text/javascript" src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script> <script type="text/javascript" src="connection.js"></script> <script type="text/javascript" src="graph.js"></script> <script type="text/html" id="T-graph"> <div class="graph" data-index="<%=data.index%>" data-id="<%=data.id%>" style="left:<%=data.x%>px; top:<%=data.y%>px"> <div class="title"><%=data.name%></div> <div class="content"><%=data.chineseName%></div> </div> </script> </body> </html>
connection.js (這個繪製線,能夠通用)java
function getArrow(r,size,delta,x1,y1,x2,y2){ var angle = r.angle(x1,y1,x2,y2);//得到兩點之間的角度 var a45 = r.rad(angle - delta)//角度轉換成弧度 var a45m = r.rad(angle + delta); var x2a = Number(x2) + Math.cos(a45) * size; var y2a = Number(y2) + Math.sin(a45) * size; var x2b = Number(x2) + Math.cos(a45m)* size; var y2b = Number(y2) + Math.sin(a45m) * size; return [x2a.toFixed(3),y2a.toFixed(3),x2b.toFixed(3),y2b.toFixed(3)]; } Raphael.fn.connection = function (obj1, obj2, line,text) { if (obj1.line && obj1.from && obj1.to) { line = obj1; obj1 = line.from; obj2 = line.to; text = line.text; } var bb1 = obj1.getBBox(), bb2 = obj2.getBBox(), p = [{x: bb1.x + bb1.width / 2, y: bb1.y - 1}, {x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + 1}, {x: bb1.x - 1, y: bb1.y + bb1.height / 2}, {x: bb1.x + bb1.width + 1, y: bb1.y + bb1.height / 2}, {x: bb2.x + bb2.width / 2, y: bb2.y - 1}, {x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + 1}, {x: bb2.x - 1, y: bb2.y + bb2.height / 2}, {x: bb2.x + bb2.width + 1, y: bb2.y + bb2.height / 2}], d = {}, dis = []; for (var i = 0; i < 4; i++) { for (var j = 4; j < 8; j++) { var dx = Math.abs(p[i].x - p[j].x), dy = Math.abs(p[i].y - p[j].y); if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) { dis.push(dx + dy); d[dis[dis.length - 1]] = [i, j]; } } } if (dis.length == 0) { var res = [0, 4]; } else { res = d[Math.min.apply(Math, dis)]; } var x1 = p[res[0]].x, y1 = p[res[0]].y, x4 = p[res[1]].x, y4 = p[res[1]].y; dx = Math.max(Math.abs(x1 - x4) / 2, 10); dy = Math.max(Math.abs(y1 - y4) / 2, 10); var x2 = [x1, x1, x1 - dx, x1 + dx][res[0]].toFixed(3), y2 = [y1 - dy, y1 + dy, y1, y1][res[0]].toFixed(3), x3 = [0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx][res[1]].toFixed(3), y3 = [0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4][res[1]].toFixed(3); x1 = x1.toFixed(3); y1 = y1.toFixed(3); x4 = x4.toFixed(3); y4 = y4.toFixed(3); var arrow = getArrow(Raphael,10,20,x3,y3,x4,y4); var path = ["M", x1, y1, "C", x2, y2, x3, y3, x4, y4,"L",arrow[0],arrow[1],"M",x4,y4,"L",arrow[2],arrow[3]].join(","); // var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(","); if (line && line.line) { line.line.attr({path: path}); } else { var color = typeof line == "string" ? line : "#00B8FF"; return { text:text, line: line && line.split && this.path(path).attr({stroke: line.split("|")[0], fill: "none", "stroke-width": line.split("|")[1] || 1}), from: obj1, to: obj2 }; } };
graph.jsjquery
var util = util || {}; util.page = util.page || {}; util.page.getViewWidth = function () { var doc = document, client = doc.compatMode == 'BackCompat' ? doc.body : doc.documentElement; return client.clientWidth; }; util.page.getHeight = function () { var doc = document, body = doc.body, html = doc.documentElement, client = doc.compatMode == 'BackCompat' ? body : doc.documentElement; return Math.max(html.scrollHeight, body.scrollHeight, client.clientHeight); }; var test = test || {}; test = { w:960, h:480, styles:{ point:[170,60] }, r:null,//(function(){ connections:[],//關係集合 graphs:null,// rect集合 maps:[],//頂點和索引的對應關係,爲了創建索引關係 config:null } test.showGraph = function(){ var me = test, w = util.page.getViewWidth(), h=util.page.getHeight(); me.w=w - 280; me.h = h - 30; me.r = Raphael("canvas",me.w, me.h); me.graphs = me.r.set(); var w = me.w, h=me.h; var cellW = 240; var cellH = 120; var num = Math.floor(w / cellW); test.renderGraph(); } test.renderConnections = function(lines){ var me = test; $.each(lines, function(key, value){ var from = value.fromStateId; var to = value.toStateID; var r = me.r; var graphs = me.graphs, maps = me.maps; // [maps[from]] graphs[maps[to]] 是索引值 me.connections.push(r.connection(graphs[maps[from]], graphs[maps[to]],'#4848fe')); }); } test.redrawConnections = function(){ var me = test, r= me.r, connections = me.connections; $.each(connections, function(key, value){ r.connection(value); }); } test.handlerMove = function(target){ var me = test; var x = parseInt(target.style.left,10); var y = parseInt(target.style.top,10); if(Math.abs(x-me.ox) < 5 && Math.abs(y - me.oy) < 5){ return; } var att = {x:x,y:y}; //var id = T.dom.getAttr(target,'data-id'); var id = $(target).attr("data-id"); var rect = me.r.getById(id); rect.attr(att); me.redrawConnections(); me.r.safari(); } test.createGraph = function(i, graph){ var me = test; var template = ""; graph.index = i; // x, y 須要詳看 var x , y; if(graph.xCoordinate){ x = graph.xCoordinate; }else{ x = ((i%num) + 0.2)* cellW; } if(graph.yCoordinate){ y = graph.yCoordinate; }else{ y = 20 + (~~(i/num)*cellH ); } graph.x = x; graph.y = y; var temp = _.template($("#T-graph").html(), {"data": graph}); // render svg rect var stateR =me.r.rect(0,0,me.styles.point[0],me.styles.point[1]); stateR.id = graph.id; stateR.attr({x:x,y:y}); // 給rect 位置 stateR.attr({fill: '#fff', "fill-opacity": 0, "stroke-width":1, 'stroke-opacity':0,cursor: "move"}); $("#graph").append(temp); var graphDom = $(".graph").eq(i); var width = graphDom.width(); width = parseInt(width,10)+10+32+5; width = width < 150?150:width; graphDom.width(width); stateR.attr({width:width}); me.graphs.push(stateR); me.maps[graph.id] = i; graphDom.draggable({ cursor: "move", start:function(){ test.handlerMove(graphDom[0]); }, drag:function(){ test.handlerMove(graphDom[0]); } }); } test.renderGraph = function(){ var me = test; $.ajax({ url: '../json/position.json', type : 'POST', dataType: "json", data: {}, success: function(json){ if (json.success){ var data = json.data, graphsData = data.states, connectionData = data.relations; // render graph and svg rect // 這一塊能夠抽離出去 $.each(graphsData, function(i, graph){ test.createGraph(i, graph); }); test.renderConnections(connectionData); } else { alert(json.message); } }, error: function(){ alert("後端返回失敗"); } }); } $(function(){ test.showGraph(); });
position.jsonajax
{ "success": true, "message": "", "data": { "states": [ { "id": 31, "name": "updateBegin", "chineseName": "服務升級", "xCoordinate": 4, "yCoordinate": 193 }, { "id": 33, "name": "updating", "chineseName": "服務升級中", "xCoordinate": 219, "yCoordinate": 199 }, { "id": 34, "name": "updateSuccess", "chineseName": "服務升級成功", "xCoordinate": 456, "yCoordinate": 169 }, { "id": 35, "name": "updateFail", "chineseName": "服務升級失敗", "xCoordinate": 464, "yCoordinate": 285 } ], "relations": [ { "id": 30, "fromStateId": 31, "toStateID": 33 }, { "id": 31, "fromStateId": 33, "toStateID": 34 }, { "id": 32, "fromStateId": 33, "toStateID": 35 } ] } }