?? graph.js
字號:
/*
* 作者: scriptfans(琳琳的小狗)
* QQ: 99352246
/*
* 可觀察模塊,供mixin使用
*/
var ObserveAble = {
fire: function(name,memo){
if(Object.isArray(this.observes)){
this.observes.invoke(name,memo);
}
},
observe: function(observe){
if(Object.isArray(this.observes) && !this.observes.include(observe))
this.observes.push(observe);
},
stopObserving: function(observe){
if(Object.isArray(this.observes))
this.observes = this.observes.without(observe);
}
}
/*
* 模型
*/
var VertexModel = Class.create({
initialize: function(id,x,y,type){
this.id = id;
this.offsetX = x||0;
this.offsetY = y||0;
this.type = type||"";
this.observes = [];
},
setOffset: function(x,y){
this.offsetX = x;
this.offsetY = y;
this.fire("onOffsetChange");
}
},ObserveAble);
var EdgeModel = Class.create({
initialize: function(start,end,type){
this.type = type;
this.start = start;
this.end = end;
this.observes = [];
},
modify: function(edge){
Object.keys(edge).without("observes").each(function(property){
if(edge.hasOwnProperty(property) && this[property]!=edge[property])
this[property] = edge[property];
}, this);
},
ownerVertex: function(vertex){
return [this.start, this.end].include(vertex);
}
},ObserveAble);
var GraphModel = Class.create({
initialize: function(){
this.vertexs = [];
this.edges = [];
this.observes = [];
},
addVertex: function(vertex){
var existed = this.vertexs.any(function(v){
return v.id==vertex.id;
});
if(!existed){
this.vertexs.push(vertex);
this.fire("onAddVertex",vertex);
}
},
removeVertex: function(vertex){
this.edges.each(function(edge){
if(edge.ownerVertex(vertex)){
this.removeEdge(edge);
}
},this);
var index = this.vertexs.indexOf(vertex);
this.vertexs.splice(index,1);
this.fire("onRemoveVertex",vertex);
},
addEdge: function(edge){
var existed = this.edges.find(function(item){
var intersect = [item.start,item.end].intersect([edge.start,edge.end]);
return intersect.size()==2;
});
if(existed){
existed.modify(edge);
this.fire("onModifyEdge",edge);
}else{
this.edges.push(edge)
this.fire("onAddEdge",edge);
}
},
removeEdge: function(edge){
this.edges = this.edges.without(edge);
this.fire("onRemoveEdge",edge);
},
findVertex: function(id){
return this.vertexs.find(function(vertex){
return vertex.id == id;
});
}
}, ObserveAble);
/*
* 視圖
*/
var GraphView = Class.create({
initialize: function(options){
this.options = Object.extend({
wrap: "canvas",
draggable: true,
deleteable: true,
drawable: "unilateral",
toolbar: "toolbar",
side: 8
},options);
this.active = null;
},
//關聯控制器,初始化視圖顯示
show: function(model,controller){
this.model = model;
this.controller = controller;
this.element = $(this.options.wrap);
if(this.options.toolbar){
this.initDrop(this.options.toolbar);
}
var canvas = this.element.getElementsByTagName("canvas");
this.main_ctx = canvas[0].getContext("2d");
if(this.options.drawable){
this.line_handles = [];
this.sub_ctx = canvas[1].getContext("2d");
this.initDraw();
}
if(this.options.deleteable){
//添加刪除事件
document.observe("keydown",this.remove.bindAsEventListener(this));
}
},
//刪除當前選中的邊,或者頂點
remove: function(event){
if(event.keyCode==Event.KEY_DELETE){
if(this.active){
if(this.active.constructor==EdgeModel){
var edge = this.active;
this.active = null;
this.controller.removeEdge(edge);
}else if(this.active.constructor==VertexModel){
var vertex = this.active;
this.active = null;
this.controller.removeVertex(vertex);
}
}
}
},
initDrop: function(toolbar){
Droppables.add(this.element,{
containment: toolbar,
//拖放結束回調函數,在此處執行自定義操作
onDrop: function(element){
//根據邊欄拖放圖標信息生成節點id
if(!$(element.id+"_")){
var top = element.cumulativeOffset().top
- this.element.cumulativeOffset().top;
var left = element.cumulativeOffset().left
- this.element.cumulativeOffset().left;
this.controller.addVertex(element.id+"_", left, top);
}
}.bind(this)
});
},
initDrag: function(dom){
var drag = new Draggable(dom,{
snap: this.snap,
revert: this.revert.bind(this),
reverteffect: function(element, top_offset, left_offset){
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
new Effect.Move(element, { x: -left_offset, y: -top_offset,
duration: dur, afterUpdate: this.update.bind(this)
});
}.bind(this),
onDrag: this.update.bind(this),
onEnd: this.update.bind(this)
});
},
//控制拖動行為
snap: function(x, y, draggable){
function constrain(n, lower, upper) {
if (n > upper) return upper;
return (n < lower ? lower : n);
};
var elementDims = draggable.element.getDimensions();
var parentDims = Element.getDimensions(draggable.element.parentNode);
x = constrain(x, 0, parentDims.width - elementDims.width);
y = constrain(y, 0, parentDims.height - elementDims.height);
return [x,y];
},
revert: function(element){
var width = element.getWidth();
var height = element.getHeight();
var dragCumOffset = element.cumulativeOffset();
var left = dragCumOffset.left;
var top = dragCumOffset.top;
var left_top = [left,top];
var right_top = [left + width,top];
var left_bottom = [left, top + height];
var right_bottom = [left + width, top + height];
var corners = [left_top, right_top, left_bottom, right_bottom];
for (var i = 0; i < this.model.vertexs.size(); i++) {
var vertex = this.model.vertexs[i];
if (vertex.id == element.id) continue;
var other = $(vertex.id);
var other_width = other.getWidth();
var other_height = other.getHeight();
var other_offset = other.cumulativeOffset();
var other_left = other_offset.left;
var other_top = other_offset.top;
var other_left_top = [other_left,other_top];
var other_right_top = [other_left + other_width,other_top];
var other_left_bottom = [other_left, other_top + other_height];
var other_right_bottom = [other_left + other_width, other_top + other_height];
//是否重疊
var overlap = !( left_top[0] > other_right_bottom[0] ||
right_bottom[1] <other_left_top[1] ||
right_bottom[0] < other_left_top[0] ||
left_top[1] > other_right_bottom[1] );
if(overlap) return true;
}
return false;
},
initDraw: function(){
this.element.observe("graph:drawline",function(event){
var dim = this.element.getDimensions();
this.sub_ctx.clearRect(0,0,dim.width,dim.height);
this.sub_ctx.strokeStyle = "rgba(0,0,255,0.5)";
this.sub_ctx.beginPath();
var startPosition = this.getCenter(event.memo.element);
this.sub_ctx.moveTo(startPosition[0],startPosition[1]);
var new_x = event.memo.endX-this.element.cumulativeOffset().left;
var new_y = event.memo.endY-this.element.cumulativeOffset().top;
this.sub_ctx.lineTo(new_x,new_y);
this.sub_ctx.stroke();
}.bind(this));
var button = $(this.options.drawable);
button.observe("click",this.draw.bind(this));
},
draw: function(event){
//取消繪圖按鈕點擊事件
var button = event.element();
button.stopObserving("click");
this.element.observe("click",function(event){
this.element.stopObserving("click");
var start = event.element();
if(start.hasClassName("vertex") && !start.hasClassName("start")){
//防止移動鼠標畫線未結束時刪除起始點
document.stopObserving("keydown");
start.addClassName("start");
this.element.observe("mousemove",function(event){
var endX = event.pointerX();
var endY = event.pointerY();
this.fire("graph:drawline",{element:start,endX:endX,endY:endY});
var element = event.element();
if(element.hasClassName("vertex")){
if(!element.hasClassName("start")){
element.addClassName("end");
}else{
return;
}
}else{
$$("div.end").invoke("removeClassName","end");
}
});
this.element.observe("click",function(event){
this.element.stopObserving("mousemove");
this.element.stopObserving("click");
var end = event.element();
if(end.hasClassName("vertex") && end.hasClassName("end")){
this.controller.addEdge(start.vertex,end.vertex);
}
var dim = this.element.getDimensions();
this.sub_ctx.clearRect(0,0,dim.width,dim.height);
$$("div.start").invoke("removeClassName","start");
$$("div.end").invoke("removeClassName","end");
if(this.options.deleteable){
//恢復鍵盤刪除事件
document.observe("keydown",this.remove.bindAsEventListener(this));
}
//回復繪圖按鈕點擊事件
button.observe("click",this.draw.bind(this));
}.bind(this));
}else{
button.observe("click",this.draw.bind(this));
}
}.bind(this));
},
getCenter: function(dom){
dom = $(dom);
var pos = dom.positionedOffset();
var width = dom.getWidth();
var height = dom.getHeight();
return [pos.left+width/2, pos.top+height/2];
},
select: function(obj){
if(obj!=this.active){
if(this.active && this.active.constructor==VertexModel){
$(this.active.id).removeClassName("active");
}
var flag = obj.constructor==EdgeModel || (this.active && this.active.constructor==EdgeModel);
this.active = obj;
if(flag) this.update();
if(this.active.constructor==VertexModel){
$(this.active.id).addClassName("active");
}
}
},
//重繪主畫板線段
update: function(){
this.main_ctx.clearRect(0,0,400,400);
this.model.edges.each(this.showLine.bind(this));
this.line_handles.invoke("show");
},
showLine: function(edge){
var color = (this.active == edge)?"#FF0000":"#0000FF";
var start = this.getCenter($(edge.start.id));
var end = this.getCenter($(edge.end.id));
var context = this.main_ctx;
context.beginPath();
context.moveTo(start[0],start[1])
context.lineTo(end[0],end[1]);
context.strokeStyle = context.fillStyle = color;
context.stroke();
if(edge.type=="unilateral"){
context.save();
var mid_x = (end[0]-start[0])/2 + start[0];
var mid_y = (end[1]-start[1])/2 + start[1];
context.translate(mid_x,mid_y);
var arc = (end[1]-start[1])/(end[0]-start[0]);
arc = isNaN(arc)?0:arc;
context.rotate(Math.atan(arc));
var side = this.options.side;
var r = side/2/Math.cos(Math.PI/6);
var reverse = side/2*Math.tan(Math.PI/6);
if(end[0]>=start[0]){
context.moveTo(r,0);
context.lineTo(-reverse,side/2);
context.moveTo(r,0);
context.lineTo(-reverse,-side/2);
}else{
context.moveTo(-r,0);
context.lineTo(reverse,side/2);
context.moveTo(-r,0);
context.lineTo(reverse,-side/2);
}
context.stroke();
context.restore();
}
},
//新增節點回調函數
onAddVertex: function(vertex){
var hash = {igate:"igate節點",dit:"dit節點",dat:"dat節點"}
var dom = new Element("div", {id:vertex.id, "class":"vertex"})
.setStyle({
top: vertex.offsetY + "px",
left: vertex.offsetX + "px"
}).addClassName(vertex.type).update(hash[vertex.type]);
if(vertex.type=="dit"){
dom.setStyle({width:"30px",height:"30px"});
}
dom.vertex = vertex;
this.element.insert(dom);
dom.observe("mousedown", this.select.bind(this,vertex));
if(this.options.draggable){
this.initDrag(dom);
}
this.select(vertex);
},
onRemoveVertex: function(vertex){
Element.remove(vertex.id);
},
//新增連線回調函數
onAddEdge: function(edge){
this.line_handles.push(new LineHandle(edge, this, this.element));
this.update();
},
//修改連線回調函數
onModifyEdge: function(edge){
this.update();
},
onRemoveEdge: function(edge){
var line_handle = this.line_handles.find(function(h){
return h.edge == edge;
});
if(line_handle){
this.line_handles = this.line_handles.without(line_handle);
line_handle.handle.remove();
}
this.update();
}
});
var LineHandle = Class.create({
initialize: function(model, view, parent){
this.edge = model;
this.view = view;
this.handle = new Element("div",{"class":"arrow"}).hide();
parent.insert(this.handle);
this.handle.observe("mousedown",this.select.bind(this));
},
select: function(event){
this.view.select(this.edge);
},
show: function(){
var start = this.view.getCenter($(this.edge.start.id));
var end = this.view.getCenter($(this.edge.end.id));
var x = (end[0]-start[0])/2 + start[0];
var y = (end[1]-start[1])/2 + start[1];
var r = this.view.options.side/2/Math.cos(Math.PI/6);
var left = x-r;
var top = y-r;
this.handle.setStyle({
left: left + "px",
top: top + "px",
width: 2*r + "px",
height: 2*r + "px"
}).show();
}
})
/*
* 控制器
*/
var GraphController = Class.create({
initialize: function(model,view){
this.model = model;
this.view = view;
//注冊視圖為模型的監聽器
this.model.observe(this.view);
//顯示視圖
this.view.show(model,this);
},
addVertex: function(id,x,y,type){
this.model.addVertex(new VertexModel(id,x,y,type));
return this;
},
removeVertex: function(vertex){
this.model.removeVertex(vertex);
return this;
},
setVertexOffset: function(x,y){
},
addEdge: function(start, end, type){
type = type || "unilateral";
this.model.addEdge(new EdgeModel(start, end, type));
},
removeEdge: function(edge){
this.model.removeEdge(edge);
}
});
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -