/* * global vars and data * */ var r, // objects shapes_by_id = {}, shapes = [], target_shapes = [], texts = [], connections = {}, id = 0, next_grid = 0, // layout margin = 30, padding = margin/3, xbegin = 80, ybegin = 5, box_height = 30, line_margin = 80, ysource = ybegin, ytarget = ysource+line_margin; font_size = 10, font_width = -1, stroke_width = 1, stroke_width_hi = 3, align_stroke = "#eee", align_stroke_hi = "#000", text_att = { "fill": "#000", "stroke": "none", "text-anchor": "start", "font-size": font_size, "font-family": "Times New Roman" }, shape_att = { "fill": "#eee", "stroke": "#000", "fill-opacity": 0, "stroke-width": stroke_width } // dragging cur_drag = null, new_pos = -1, old_pos = -1; // connecting connect_mode = false, connect_mode_shape = null, new_conns = [], // editing cur_ed = null, cur_ed_shape = null, edit_mode = false, // removing rm_shape = null, // data source = null, target = null, align = null; /* * connection * */ Raphael.fn.connection = function (obj1, obj2, line, bg) { if (obj1.line && obj1.from && obj1.to) { line = obj1; obj1 = line.from; obj2 = line.to; } if (!obj1.getBBox() || !obj2.getBBox()) return; var bb1 = obj1.getBBox(), bb2 = obj2.getBBox(), x1 = bb1.x+bb1.width/2, y1 = bb1.y+bb1.height, x2 = bb2.x+bb2.width/2, y2 = bb2.y, path = ["M", x1, y1, "L", x2, y2]; if (line && line.line) { line.bg && line.bg.attr({path: path}); line.line.attr({path: path}); } else { return { bg: bg && bg.split && this.path(path).attr({stroke: bg.split("|")[0], fill: "none", "stroke-width": bg.split("|")[1] || 3}), line: this.path(path).attr({stroke: align_stroke, fill: "none"}), from: obj1, to: obj2 }; } }; var conn_str = function (obj1, obj2) { return obj1["id_"]+"-"+obj2["id_"]; } var make_conn = function(obj1, obj2) { connections[conn_str(obj1,obj2)] = r.connection(obj1, obj2); if (connect_mode) { new_conns.push(connections[conn_str(obj1,obj2)]); connections[conn_str(obj1,obj2)].line.attr({"stroke":align_stroke_hi,"stroke-width":stroke_width_hi}); } }, rm_conn = function(id1, id2) { var b = false; for (var i=0; i obj["grid_tmp_"]) { // right -> left if (cur_drag.getBBox().width < obj.getBBox().width && cur_drag.getBBox().x > (obj.getBBox().x+obj.getBBox().width/1000)) { // ignored tolerance, when return; // dragging onto shapes } att = { x: obj.attr("x")+cur_drag.getBBox().width+(margin-2*padding) }; obj.attr(att); att = { x: obj.pair.attr("x")+cur_drag.getBBox().width+(margin-2*padding) }; obj.pair.attr(att); } else { // left -> right if (cur_drag.getBBox().width < obj.getBBox().width && cur_drag.getBBox().x < (obj.getBBox().x+obj.getBBox().width/1000)) { return; } att = { x: obj.attr("x")-(cur_drag.getBBox().width+(margin-2*padding)) }; obj.attr(att); att = { x: obj.pair.attr("x")-(cur_drag.getBBox().width+(margin-2*padding)) }; obj.pair.attr(att); } // grid pos new_pos = obj["grid_tmp_"]; var tmp_pos = cur_drag["grid_tmp_"]; cur_drag["grid_tmp_"] = obj["grid_tmp_"]; obj["grid_tmp_"] = tmp_pos; }, debug = function () { var s = ""; for (var i=0; i del_grid) { target_shapes[i]["grid_"] -= 1; } if (g > max) max = g; } next_grid = g; if (!next_grid) // empty next_grid = 0; cur_drag = null; snap_to_grid(true); return; } if (this.type != "text") this.animate({"fill-opacity": 0}, 500); if (this.pair.type != "text") this.pair.animate({"fill-opacity": 0}, 500); snap_to_grid(true); }, snap_to_grid = function (anim=false) { // just x coord, y is fixed in drag var d = xbegin; // just target objs target_shapes.sort(function(a, b) { return a["grid_"]-b["grid_"]; }); // switch if (cur_drag) { // fix glitch when calling from add_obj() and up() cur_drag["grid_"] = new_pos; cur_id = cur_drag["id_"]; if (new_pos > old_pos) { // left -> right for (var i=0; i < target_shapes.length; i++) { pos = target_shapes[i]["grid_"]; id_ = target_shapes[i]["id_"]; if (id_ == cur_id) continue; if (pos >= old_pos && pos <= new_pos) { target_shapes[i]["grid_"] -= 1; } else { continue; } } } else if (new_pos < old_pos) { // right -> left for (var i=0; i < target_shapes.length; i++) { pos = target_shapes[i]["grid_"]; id_ = target_shapes[i]["id_"]; if (id_ == cur_id) continue; if (pos >= new_pos && pos <= old_pos) { target_shapes[i]["grid_"] += 1; } else { continue; } } } } // ^ fix glitch // sort by grid pos target_shapes.sort(function(a, b) { return a["grid_"]-b["grid_"]; }); // fix box layout for (var i = 0; i < target_shapes.length; i++) { var obj = target_shapes[i]; if (!obj || !obj.attrs) { // removed return; } att = { x:d }; if (anim) { obj.attr(att); } else { obj.attr(att); } att = { x: obj.getBBox().x+padding }; if (anim) { obj.pair.animate(att,125); } else { obj.pair.attr(att); } d += obj.getBBox().width+(margin-2*padding); // fix tmp grid obj["grid_tmp_"] = obj["grid_"]; } for (key in connections) { r.connection(connections[key]); } }; /* * objs * */ var make_obj = function(x, text, type) { var y; if (type == "source") { y = ysource; } else if (type == "target") { y = ytarget; } // make text obj texts.push(r.text(x, y+(box_height/2), text).attr(text_att)); // make shape obj var x_shape = texts[texts.length-1].getBBox().x-padding, x_width = texts[texts.length-1].getBBox().width+(2*padding); shapes.push(r.rect(x_shape, y, x_width, box_height, 5).attr(shape_att)); tx = texts[texts.length-1]; sh = shapes[shapes.length-1]; // fix z-index tx.toBack(); sh.toFront(); // pair text/shape tx.pair = shapes[shapes.length-1]; sh.pair = texts[texts.length-1]; // meta sh["type_"] = type; sh["id_"] = id; shapes_by_id[id] = sh; if (type == "target") { sh.drag(move, dragger, up).onDragOver( function(obj) { collide(obj); }) sh.attr({ cursor: "move" }); tx.drag(move, dragger, up); sh["grid_"] = next_grid; sh["grid_tmp_"] = next_grid; sh.click(function() { if (connect_mode) { if (connections[conn_str(connect_mode_shape,this)]) { rm_conn(connect_mode_shape["id_"], this["id_"]); } else { make_conn(connect_mode_shape, this); } connect_mode_shape.attr({"fill-opacity": 0}); connect_mode = false; connect_mode_shape = null; } }); target_shapes.push(sh); // inline text editing r.inlineTextEditing(tx); sh.dblclick(function(){ if (edit_mode) return; edit_mode = true; this.pair.toFront(); cur_ed = this.pair; cur_ed_shape = this; var input = cur_ed.inlineTextEditing.startEditing(); input.addEventListener("keypress", function(e) { if (e.keyCode==27||e.keyCode==37||e.keyCode==38||e.keyCode==39||e.keyCode==40) { // esc, arrows, backspace return; } else if (e.keyCode == 8) { // backspace cur_ed_shape.animate({width:cur_ed_shape.getBBox().width-font_width},125); setTimeout(function(){snap_to_grid(true);},125); } else if (e.keyCode == 13) { // return e.preventDefault(); cur_ed.inlineTextEditing.stopEditing(); cur_ed_shape.toFront(); cur_ed.toBack(); cur_ed_shape.animate({width:cur_ed.getBBox().width+(margin-padding)},125); setTimeout(function(){snap_to_grid(true);},125); edit_mode = false; } else { // input cur_ed_shape.animate({width:(this.value.length*font_width)+2*font_width+2*padding},25); setTimeout(function(){ snap_to_grid(true); r.setSize(r.width+font_width, r.height); },25); } }); input.addEventListener("blur", function(e) { cur_ed.inlineTextEditing.stopEditing(); cur_ed_shape.toFront(); cur_ed.toBack(); cur_ed_shape.animate({width:cur_ed.getBBox().width+(margin-padding)},125); setTimeout(function(){snap_to_grid(true);},125); edit_mode = false; }, true); }); } else if (type == "source") { sh.click(function() { if (connect_mode) { if (this != connect_mode_shape) return; this.animate({"fill-opacity": 0}, 250); connect_mode = false; connect_mode_shape = null; } else { this.animate({"fill-opacity": .5}, 250); connect_mode = true; connect_mode_shape = this; } }); } // mouseover -out sh.mouseover(function() { var idx, other_idx; if (this["type_"] == "target") { idx = 1; other_idx = 0; } else { idx = 0; other_idx = 1; } for (c in connections) { if (parseInt(c.split("-")[idx]) == this["id_"]) { connections[c].line.attr({"stroke":align_stroke_hi,"stroke-width":stroke_width_hi}); shapes_by_id[parseInt(c.split("-")[other_idx])].attr({"stroke-width":stroke_width_hi}); } } this.animate({"stroke-width":stroke_width_hi}) }); sh.mouseout(function() { var idx, other_idx; if (this["type_"] == "target") { idx = 1; other_idx = 0; } else { idx = 0; other_idx = 1; } for (c in connections) { if (parseInt(c.split("-")[idx]) == this["id_"]) { connections[c].line.attr({"stroke":align_stroke,"stroke-width":stroke_width}); shapes_by_id[parseInt(c.split("-")[other_idx])].attr({"stroke-width":stroke_width}); } } this.animate({"stroke-width":stroke_width}) for (var i=0; i max) { max_idx = i; max = shapes[i]["grid_"]; } } if (!shapes[max_idx]) { make_obj(xbegin+padding, "X", "target"); } else { make_obj(shapes[max_idx].getBBox().x2+(margin-padding), "X", "target"); } r.setSize(r.width+target_shapes[target_shapes.length-1].getBBox().width+margin, r.height); cur_drag = null; snap_to_grid(true); }, make_objs = function (a, type) { for (var i=0; i < a.length; i++) { var x = 0; if (i == 0) { x = xbegin+padding; } else { x = margin+texts[texts.length-1].getBBox().x2; } make_obj(x, a[i], type); } }; /* * data * */ var extract_data = function () { el = document.getElementById("data"); d = {}; d["source"] = []; d["target"] = []; d["align"] = []; // target var ids = []; target_shapes.sort(function(a, b) { return a["grid_"]-b["grid_"]; }); for (var i=0; i