diff options
| author | Alon Levy <alon@pobox.com> | 2014-11-02 17:15:55 +0200 |
|---|---|---|
| committer | Alon Levy <alon@pobox.com> | 2014-11-02 17:15:55 +0200 |
| commit | 20752887da5574a4381f169b997bf9606548eb4f (patch) | |
| tree | 00422b929631e38795eb3c4fac825711e2ebdc74 | |
| parent | 0c3012f96cf952ecccfa72498befbf360778b8fa (diff) | |
split off model/graph.js
| -rw-r--r-- | scripts/buttons.js | 4 | ||||
| -rw-r--r-- | scripts/model/graph.js | 503 | ||||
| -rw-r--r-- | scripts/rhizicore.js | 975 |
3 files changed, 751 insertions, 731 deletions
diff --git a/scripts/buttons.js b/scripts/buttons.js index 5f406d45..ef56594d 100644 --- a/scripts/buttons.js +++ b/scripts/buttons.js @@ -45,7 +45,7 @@ $('.file-load').on('change', function(event) { if (e.target.readyState === FileReader.DONE) { console.log('done reading ' + theFile.name); console.log('got #' + result.length + ' bytes in ' + typeof(result)); - RZ.graph.load_from_json(result); + RZ.load_from_json(result); } } })(file); @@ -57,7 +57,7 @@ $('.local-storage-load a').click(function(){ return; } var json_blob = localStorage.getItem(key) - RZ.graph.load_from_json(json_blob); + RZ.load_from_json(json_blob); }); $('a.set-user').click(function() { diff --git a/scripts/model/graph.js b/scripts/model/graph.js new file mode 100644 index 00000000..5f8043f2 --- /dev/null +++ b/scripts/model/graph.js @@ -0,0 +1,503 @@ +"use strict" + +define(['signal', 'consts', 'util', 'textanalysis'], function ( + signal, consts, util, textanalysis) { + +function myGraph(el) { + + var nodes = [] + var links = []; + + ///FUNCTIONS + this.addNode = function(id, type, state) { + var new_node = this.addNodeNoHistory( + {id:id, + name:id, + type:type, + state:state, + start:0, + end:0, + status:"unknown"}); + if (new_node) { + signal.signal(consts.APPLIED_GRAPH_DIFF, [{ + nodes: {add: [new_node]}}]); + } + } + + this.addNodeNoHistory = function(spec) { + // No history recorded - this is a helper for loading from files / constant graphs + var id = spec.id.toString().toLowerCase(); + var node = findNode(id, null); + var new_node = undefined; + + if (node === undefined) { + new_node = { + "id": id, + "name": spec.name, + "type": spec.type, + "state": spec.state, + "start": spec.start, + "end": spec.end, + "status": spec.status, + 'url': spec.url, + 'x': spec.x, + 'y': spec.y, + }; + nodes.push(new_node); + } + return new_node; + } + + this.removeNode = function(id, state) { + var i = 0; + id = id.toLowerCase(); + var n = findNode(id, state); + while (i < links.length) { + if ((links[i]['source'] === n) || (links[i]['target'] == n)) links.splice(i, 1); + else i++; + } + var index = findNodeIndex(id, state); + if (index !== undefined) { + nodes.splice(index, 1); + } + signal.signal(consts.APPLIED_GRAPH_DIFF, [{nodes: {removed: [id]}}]); + } + + this.removeNodes = function(state) { + var id = null; + var ns = findNodes(null, state); + for (var j = 0; j < ns.length; j++) { + var n = ns[j]; + var i = 0; + while (i < links.length) { + if ((links[i]['source'] === n) || (links[i]['target'] == n)) links.splice(i, 1); + else i++; + } + var index = findNodeIndex(id, state); + if (index !== undefined) { + nodes.splice(index, 1); + } + } + if (ns.length > 0) { + signal.signal(consts.APPLIED_GRAPH_DIFF, [{nodes: {removed: ns.map(function(n) { return n.id; })}}]); + } + } + + + this.highlightNode = function(id, state) { + var i = 0, + j = 0; + var n = findNode(id, state); + var adjacentnode; + $(".debug").html(n.state); + + //highlight node + if (n !== undefined && n.state !== "chosen" && n.state !== "temp") { + this.removeHighlight(); + n.state = "chosen"; + + while (i < links.length) { + if (links[i]['source'] === n) { + adjacentnode = findNode(links[i]['target'].id, null); + if (adjacentnode.state !== "temp") adjacentnode.state = "exit"; + links[i]['state'] = "exit"; + + if(links[i]['target'].type==="chainlink"){ + console.log("chain"); + while (j < links.length) { + if(links[i]['target'].id===links[j]['target'].id && links[j]['target'].type==="chainlink" && links[j]['target'].state!=="temp"){ + adjacentnode = findNode(links[j]['source'].id, null); + if (adjacentnode.state !== "temp") adjacentnode.state = "enter"; + links[j]['state'] = "enter"; + } + j++; + } + } + j=0; + } + if (links[i]['target'] === n) { + adjacentnode = findNode(links[i]['source'].id, null); + if (adjacentnode.state !== "temp") adjacentnode.state = "enter"; + links[i]['state'] = "enter"; + } + i++; + } + } + } + + this.removeHighlight = function() { + var k = 0; + while (k < nodes.length) { + if (nodes[k]['state'] === "enter" || nodes[k]['state'] === "exit" || nodes[k]['state'] === "chosen") { + nodes[k]['state'] = "perm"; + } + k++; + } + var j = 0; + //highlight all connections + while (j < links.length) { + links[j]['state'] = "perm"; + j++; + } + } + + /* compareSubset: + * state: one of the optional states that defines a subgraph + * new_nodes: array of objects with id + * new_links: array of length two arrays [source_id, target_id] + * returns: true if current and new graph are homomorphic up to + * a single node id change. false otherwise + */ + this.compareSubset = function(state, new_nodes, new_links) { + // Note: the nodes include a state=='temp', type=='bubble' node + // but it's ok since it exists both in new_nodes and in state_nodes + var state_nodes = findNodes(null, state); + var state_links = findLinks(state).map(function(link) { + return [link.source.id, link.target.id]; + }).sort(); + var k; + var changed_old_id = undefined, changed_new_id = undefined; + var state_source, state_target, new_source, new_target; + var changed_nodes; + var verbose = false; // XXX should be global. should have only one global. sigh. + var set_old_id, set_new_id; + var new_id_to_name = {}; + + new_nodes.map(function (f) { + if (!f.name) { + f.name = f.id; + } + f.id = f.id.toLowerCase(); + new_id_to_name[f.id] = f.name; + if (verbose) { + console.log('new_id_to_name ' + f.id + ' -> ' + new_id_to_name[f.id]); + } + }); + new_nodes.sort(); + new_links.sort(); + if (new_nodes.length != state_nodes.length || new_links.length != state_links.length) { + if (verbose) { + console.log('not same size'); + } + return {graph_same: false}; + } + changed_nodes = util.set_diff(util.set_from_array(state_nodes.map(function(d) { return d.id.toLowerCase(); })), + util.set_from_array(new_nodes.map(function (f) { return f.id.toLowerCase(); }))); + // we allow any number of changed nodes as long as we it is 1 or 2 :) + if (changed_nodes.a_b.length <= 2) { + set_old_id = util.set_from_array(changed_nodes.a_b); + set_new_id = util.set_from_array(changed_nodes.b_a); + } else { + if (verbose) { + console.log('changed too many nodes'); + console.log(changed_nodes); + } + return {graph_same: false}; + } + for (k = 0 ; k < state_links.length ; ++k) { + state_source = state_links[k][0]; + state_target = state_links[k][1]; + new_source = new_links[k][0]; + new_target = new_links[k][1]; + if ((state_source != new_source && + !(state_source in set_old_id && new_source in set_new_id)) + || + (state_target != new_target && + !(state_target in set_old_id && new_target in set_new_id))) { + if (verbose) { + console.log('not same link: ' + + state_source + '->' + state_target + ' != ' + + new_source + '->' + new_target); + console.log(set_old_id); + console.log(set_new_id); + } + return {graph_same: false}; + } + } + return {graph_same: true, old_id: changed_nodes.a_b, new_id: changed_nodes.b_a, + new_name: changed_nodes.b_a.map(function (k) { return new_id_to_name[k]; })}; + } + + this.addLink = function(sourceId, targetId, name, state, drop_conjugator_links) { + sourceId = sourceId && sourceId.toLowerCase(); + targetId = targetId && targetId.toLowerCase(); + var sourceNode = findNode(sourceId, null); + var targetNode = findNode(targetId, null); + var found = findLink(sourceId,targetId,name); + + if (drop_conjugator_links && name && (name.replace(/ /g,"") === "and")) { + state = "temp"; + } + if (sourceNode === undefined || targetNode === undefined) { + return; + } + if (!found) { + var link = { + "source": sourceNode, + "target": targetNode, + "name": name, + "state": state + }; + links.push(link); + signal.signal(consts.APPLIED_GRAPH_DIFF, [{links: {add: [link]}}]); + } else { + found.name = name; + found.state = state; + } + } + + this.editLink = function(sourceId, targetId, newname, newstate) { + var link = findLink(sourceId, targetId, newname); + if (link !== undefined) { + link.name = newname; + if (newstate !== undefined) { + link.state = newstate; + } + } + } + + this.editLinkTarget = function(sourceId, targetId, newTarget) { + var link = findLink(sourceId, targetId, null); + if (link !== undefined) { + link.target = findNode(newTarget, null); + + } else { + + } + } + + this.editName = function(id, type, newname) { + var new_name = newname; + id = id && id.toLowerCase(); + var new_id = newname.toLowerCase(); + var index2 = undefined; + if (id != new_id) { + index2 = findNode(new_id, type); + } + var index = findNode(id, type); + var acceptReplace=true; + + if ((index !== undefined)) { + if ((index2 !== undefined)) { + acceptReplace = confirm('"' + index2.id + '" will replace "' + index.id + '", are you sure?'); + if(acceptReplace){ + for (var i = 0; i < links.length; i++) { + if (links[i].source === index) { + links[i].source = index2; + } + if (links[i].target === index) { + links[i].target = index2; + } + } + graph.removeNode(index.id,null); + } + }else{ + index.id = new_id; + index.name = new_name; + } + } + } + + this.editDates = function(id, type, start, end) { + var index = findNode(id, type); + if ((index !== undefined)) { + index.start = start; + index.end = end; + } + } + + /** + * editType: + * @return true if type changed + */ + this.editType = function(id, state, newtype) { + var index = findNode(id, state); + if ((index !== undefined)) { + index.type = newtype; + } + } + + this.editURL = function(id, state, url) { + var index = findNode(id, state); + if ((index === undefined)) return; + index.url = url; + } + + this.editState = function(id, state, newstate) { + var index = findNode(id.toLowerCase(), state); + if ((index !== undefined)) { + index.state = newstate; + } + } + + this.findCoordinates = function(id, type) { + var index = findNode(id, type); + if ((index !== undefined)) { + $('.typeselection').css('top', index.y - 90); + $('.typeselection').css('left', index.x - 230); + } + } + + this.removeLinks = function(state) { + var id = null; + var ls = findLinks(state); + for (var j = 0; j < ls.length; j++) { + var l = ls[j]; + var i = 0; + while (i < links.length) { + if (links[i] === l) links.splice(i, 1); + else i++; + } + } + } + + var findLink = function(sourceId, targetId, name) { + for (var i = 0; i < links.length; i++) { + if (links[i].source.id === sourceId && links[i].target.id === targetId) { + return links[i]; + } + } + } + + var findLinks = function(state) { + var foundLinks = []; + for (var i = 0; i < links.length; i++) { + if (links[i].state == state) { + foundLinks.push(links[i]); + } + } + return foundLinks; + } + + + var hasNode = function(id, state) { + var i; + id = id.toLowerCase(); + for (i = 0 ; i < nodes.length; ++i) { + if (nodes[i].id == id && nodes[i].state == state) { + return true; + } + } + return false; + } + this.hasNode = hasNode; + + var findNode = function(id, state) { + id = id && id.toLowerCase(); + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].id === id || nodes[i].state === state) + return nodes[i] + }; + } + + var findNodes = function(id, state) { + //id=id.toLowerCase(); + var foundNodes = []; + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].id === id || nodes[i].state === state) + foundNodes.push(nodes[i]); + } + return foundNodes; + } + + var findNodeIndex = function(id, state) { + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].id === id || nodes[i].state === state) + return i + }; + } + + function clear() { + nodes.length = 0; + links.length = 0; + } + this.clear = clear; + + function empty() { + return nodes.length == 0 && links.length == 0; + } + this.empty = empty; + + function load_from_json(json) { + var data = JSON.parse(json); + var i, node, link; + + clear(); + if (data == null) { + console.log('load callback: no data to load'); + return; + } + for(i = 0; i < data["nodes"].length; i++){ + node = data.nodes[i]; + this.addNodeNoHistory({id:node.id, name:node.name ? node.name : node.id, + type:node.type,state:"perm", + start:new Date(node.start), + end:new Date(node.end), + status:node.status, + url:node.url, + x: node.x, + y: node.y, + }); + textanalysis.autoSuggestAddName(node.id); + } + for(i = 0; i < data["links"].length; i++){ + link = data.links[i]; + this.addLink(link.source,link.target,link.name,"perm"); + } + this.clear_history(); + } + this.load_from_json = load_from_json; + + function save_to_json() { + var d = {"nodes":[], "links":[]}; + for(var i = 0 ; i < nodes.length ; i++){ + var node = nodes[i]; + d['nodes'].push({ + "id": node.id, + "name": node.name, + "type":node.type, + "state":"perm", + "start":node.start, + "end":node.end, + "status": node.status, + "url": node.url, + "x": node.x, + "y": node.y, + }); + } + for(var j=0 ; j < links.length ; j++){ + var link = links[j]; + d['links'].push({ + "source":link.source.id, + "target":link.target.id, + "name":link.name + }); + } + return JSON.stringify(d); + } + this.save_to_json = save_to_json; + + function set_user(user) { + this.user = user; + this.history = new History(this.user, $('svg g.zoom')[0]); + console.log('new user: ' + user); + } + this.set_user = set_user; + + function clear_history() { + if (this.history !== undefined) { + this.history.clear(); + } + } + this.clear_history = clear_history; + + var get_nodes = function() { return nodes; }; + this.nodes = get_nodes; + + var get_links = function() { return links; }; + this.links = get_links; +}; + + + return myGraph; +}); diff --git a/scripts/rhizicore.js b/scripts/rhizicore.js index 29e3ff3d..61754f81 100644 --- a/scripts/rhizicore.js +++ b/scripts/rhizicore.js @@ -1,8 +1,8 @@ "use strict" define('rhizicore', -['jquery', 'd3', 'consts', 'signal', 'util', 'history', 'textanalysis'], -function($, d3, consts, signal, util, history, textanalysis) { +['jquery', 'd3', 'consts', 'signal', 'util', 'history', 'textanalysis', 'model/graph'], +function($, d3, consts, signal, util, history, textanalysis, myGraph) { var History = history.History; var addednodes = []; @@ -13,436 +13,70 @@ var graphinterval = 0; var ganttTimer = 0; -var boxedin = false; - var deliverables = []; -var boxedin, nodetext, linktext, link, links, node, nodes, circle; +var circle; // <-- should not be module globals. var scrollValue = 0, zoomObject; -var force; - -function myGraph(el) { - - this.update = function(no_relayout) { - update(no_relayout); - } - - ///FUNCTIONS - this.addNode = function(id, type, state) { - var new_node = this.addNodeNoHistory( - {id:id, - name:id, - type:type, - state:state, - start:0, - end:0, - status:"unknown"}); - if (new_node) { - signal.signal(consts.APPLIED_GRAPH_DIFF, [{ - nodes: {add: [new_node]}}]); - } - } - - this.addNodeNoHistory = function(spec) { - // No history recorded - this is a helper for loading from files / constant graphs - var id = spec.id.toString().toLowerCase(); - var node = findNode(id, null); - var new_node = undefined; - - if (node === undefined) { - new_node = { - "id": id, - "name": spec.name, - "type": spec.type, - "state": spec.state, - "start": spec.start, - "end": spec.end, - "status": spec.status, - 'url': spec.url, - 'x': spec.x, - 'y': spec.y, - }; - nodes.push(new_node); - } - return new_node; - } - - this.removeNode = function(id, state) { - var i = 0; - id = id.toLowerCase(); - var n = findNode(id, state); - while (i < links.length) { - if ((links[i]['source'] === n) || (links[i]['target'] == n)) links.splice(i, 1); - else i++; - } - var index = findNodeIndex(id, state); - if (index !== undefined) { - nodes.splice(index, 1); - } - signal.signal(consts.APPLIED_GRAPH_DIFF, [{nodes: {removed: [id]}}]); - } - - this.removeNodes = function(state) { - var id = null; - var ns = findNodes(null, state); - for (var j = 0; j < ns.length; j++) { - var n = ns[j]; - var i = 0; - while (i < links.length) { - if ((links[i]['source'] === n) || (links[i]['target'] == n)) links.splice(i, 1); - else i++; - } - var index = findNodeIndex(id, state); - if (index !== undefined) { - nodes.splice(index, 1); - } - } - if (ns.length > 0) { - signal.signal(consts.APPLIED_GRAPH_DIFF, [{nodes: {removed: ns.map(function(n) { return n.id; })}}]); - } - } - - - this.highlightNode = function(id, state) { - var i = 0, - j = 0; - var n = findNode(id, state); - var adjacentnode; - $(".debug").html(n.state); - - //highlight node - if (n !== undefined && n.state !== "chosen" && n.state !== "temp") { - this.removeHighlight(); - n.state = "chosen"; - - while (i < links.length) { - if (links[i]['source'] === n) { - adjacentnode = findNode(links[i]['target'].id, null); - if (adjacentnode.state !== "temp") adjacentnode.state = "exit"; - links[i]['state'] = "exit"; - - if(links[i]['target'].type==="chainlink"){ - console.log("chain"); - while (j < links.length) { - if(links[i]['target'].id===links[j]['target'].id && links[j]['target'].type==="chainlink" && links[j]['target'].state!=="temp"){ - adjacentnode = findNode(links[j]['source'].id, null); - if (adjacentnode.state !== "temp") adjacentnode.state = "enter"; - links[j]['state'] = "enter"; - } - j++; - } - } - j=0; - } - if (links[i]['target'] === n) { - adjacentnode = findNode(links[i]['source'].id, null); - if (adjacentnode.state !== "temp") adjacentnode.state = "enter"; - links[i]['state'] = "enter"; - } - i++; - } - } - } - - this.removeHighlight = function() { - var k = 0; - while (k < nodes.length) { - if (nodes[k]['state'] === "enter" || nodes[k]['state'] === "exit" || nodes[k]['state'] === "chosen") { - nodes[k]['state'] = "perm"; - } - k++; - } - var j = 0; - //highlight all connections - while (j < links.length) { - links[j]['state'] = "perm"; - j++; - } - } - - /* compareSubset: - * state: one of the optional states that defines a subgraph - * new_nodes: array of objects with id - * new_links: array of length two arrays [source_id, target_id] - * returns: true if current and new graph are homomorphic up to - * a single node id change. false otherwise - */ - this.compareSubset = function(state, new_nodes, new_links) { - // Note: the nodes include a state=='temp', type=='bubble' node - // but it's ok since it exists both in new_nodes and in state_nodes - var state_nodes = findNodes(null, state); - var state_links = findLinks(state).map(function(link) { - return [link.source.id, link.target.id]; - }).sort(); - var k; - var changed_old_id = undefined, changed_new_id = undefined; - var state_source, state_target, new_source, new_target; - var changed_nodes; - var verbose = false; // XXX should be global. should have only one global. sigh. - var set_old_id, set_new_id; - var new_id_to_name = {}; - - new_nodes.map(function (f) { - if (!f.name) { - f.name = f.id; - } - f.id = f.id.toLowerCase(); - new_id_to_name[f.id] = f.name; - if (verbose) { - console.log('new_id_to_name ' + f.id + ' -> ' + new_id_to_name[f.id]); - } - }); - new_nodes.sort(); - new_links.sort(); - if (new_nodes.length != state_nodes.length || new_links.length != state_links.length) { - if (verbose) { - console.log('not same size'); - } - return {graph_same: false}; - } - changed_nodes = util.set_diff(util.set_from_array(state_nodes.map(function(d) { return d.id.toLowerCase(); })), - util.set_from_array(new_nodes.map(function (f) { return f.id.toLowerCase(); }))); - // we allow any number of changed nodes as long as we it is 1 or 2 :) - if (changed_nodes.a_b.length <= 2) { - set_old_id = util.set_from_array(changed_nodes.a_b); - set_new_id = util.set_from_array(changed_nodes.b_a); - } else { - if (verbose) { - console.log('changed too many nodes'); - console.log(changed_nodes); - } - return {graph_same: false}; - } - for (k = 0 ; k < state_links.length ; ++k) { - state_source = state_links[k][0]; - state_target = state_links[k][1]; - new_source = new_links[k][0]; - new_target = new_links[k][1]; - if ((state_source != new_source && - !(state_source in set_old_id && new_source in set_new_id)) - || - (state_target != new_target && - !(state_target in set_old_id && new_target in set_new_id))) { - if (verbose) { - console.log('not same link: ' + - state_source + '->' + state_target + ' != ' + - new_source + '->' + new_target); - console.log(set_old_id); - console.log(set_new_id); - } - return {graph_same: false}; - } - } - return {graph_same: true, old_id: changed_nodes.a_b, new_id: changed_nodes.b_a, - new_name: changed_nodes.b_a.map(function (k) { return new_id_to_name[k]; })}; - } - - this.addLink = function(sourceId, targetId, name, state, drop_conjugator_links) { - sourceId = sourceId && sourceId.toLowerCase(); - targetId = targetId && targetId.toLowerCase(); - var sourceNode = findNode(sourceId, null); - var targetNode = findNode(targetId, null); - var found = findLink(sourceId,targetId,name); - - if (drop_conjugator_links && name && (name.replace(/ /g,"") === "and")) { - state = "temp"; - } - if (sourceNode === undefined || targetNode === undefined) { - return; - } - if (!found) { - var link = { - "source": sourceNode, - "target": targetNode, - "name": name, - "state": state - }; - links.push(link); - signal.signal(consts.APPLIED_GRAPH_DIFF, [{links: {add: [link]}}]); - } else { - found.name = name; - found.state = state; - } - } - - this.editLink = function(sourceId, targetId, newname, newstate) { - var link = findLink(sourceId, targetId, newname); - if (link !== undefined) { - link.name = newname; - if (newstate !== undefined) { - link.state = newstate; - } - } - } - - this.editLinkTarget = function(sourceId, targetId, newTarget) { - var link = findLink(sourceId, targetId, null); - if (link !== undefined) { - link.target = findNode(newTarget, null); +var graph; - } else { +var drag; - } - } - - this.editName = function(id, type, newname) { - var new_name = newname; - id = id && id.toLowerCase(); - var new_id = newname.toLowerCase(); - var index2 = undefined; - if (id != new_id) { - index2 = findNode(new_id, type); - } - var index = findNode(id, type); - var acceptReplace=true; - - if ((index !== undefined)) { - if ((index2 !== undefined)) { - acceptReplace = confirm('"' + index2.id + '" will replace "' + index.id + '", are you sure?'); - if(acceptReplace){ - for (var i = 0; i < links.length; i++) { - if (links[i].source === index) { - links[i].source = index2; - } - if (links[i].target === index) { - links[i].target = index2; - } - } - graph.removeNode(index.id,null); - } - }else{ - index.id = new_id; - index.name = new_name; - } - } - } - - this.editDates = function(id, type, start, end) { - var index = findNode(id, type); - if ((index !== undefined)) { - index.start = start; - index.end = end; - } - } - - this.editType = function(id, state, newtype) { - var index = findNode(id, state); - if ((index !== undefined)) { - index.type = newtype; - update(true); - } - } - - this.editURL = function(id, state, url) { - var index = findNode(id, state); - if ((index === undefined)) return; - index.url = url; - update(true); - } - - this.editState = function(id, state, newstate) { - var index = findNode(id.toLowerCase(), state); - if ((index !== undefined)) { - index.state = newstate; - } - } +var force; - this.findCoordinates = function(id, type) { - var index = findNode(id, type); - if ((index !== undefined)) { - $('.typeselection').css('top', index.y - 90); - $('.typeselection').css('left', index.x - 230); - } - } +var state_to_link_class = { + enter:'enterlink graph', + exit:'exitlink graph', +}; - this.recenterZoom = function() { - vis.attr("transform", "translate(0,0)scale(1)"); - } +function recenterZoom() { + vis.attr("transform", "translate(0,0)scale(1)"); +} - this.removeLinks = function(state) { - var id = null; - var ls = findLinks(state); - for (var j = 0; j < ls.length; j++) { - var l = ls[j]; - var i = 0; - while (i < links.length) { - if (links[i] === l) links.splice(i, 1); - else i++; - } - } - } +var initDrawingArea = function () { - var findLink = function(sourceId, targetId, name) { - for (var i = 0; i < links.length; i++) { - if (links[i].source.id === sourceId && links[i].target.id === targetId) { - return links[i]; - } + function zoom() { + if (graphstate === "GRAPH") { + vis.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); } - } - - var findLinks = function(state) { - var foundLinks = []; - for (var i = 0; i < links.length; i++) { - if (links[i].state == state) { - foundLinks.push(links[i]); - } + if (graphstate === "GANTT") { + vis.attr("transform", "translate(0,0)scale(1)"); } - return foundLinks; } - - var hasNode = function(id, state) { - var i; - id = id.toLowerCase(); - for (i = 0 ; i < nodes.length; ++i) { - if (nodes[i].id == id && nodes[i].state == state) { - return true; - } - } - return false; + function dragstarted(d) { + d3.event.sourceEvent.stopPropagation(); + d3.select(this).classed("dragging", true); + d.dragstart = {clientX:d3.event.sourceEvent.clientX, clientY:d3.event.sourceEvent.clientY}; + force.stop(); } - this.hasNode = hasNode; - var findNode = function(id, state) { - id = id && id.toLowerCase(); - for (var i = 0; i < nodes.length; i++) { - if (nodes[i].id === id || nodes[i].state === state) - return nodes[i] - }; + function dragged(d) { + d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y); + tick(); } - var findNodes = function(id, state) { - //id=id.toLowerCase(); - var foundNodes = []; - for (var i = 0; i < nodes.length; i++) { - if (nodes[i].id === id || nodes[i].state === state) - foundNodes.push(nodes[i]); + function dragended(d) { + d3.select(this).classed("dragging", false); + d3.select(this).classed("fixed", true); + d3.select(this).attr("dx", d3.event.x).attr("dy", d3.event.y); + if (d.dragstart.clientX - d3.event.sourceEvent.clientX != 0 || + d.dragstart.clientY - d3.event.sourceEvent.clientY != 0) { + tick(); + force.resume(); } - return foundNodes; - } - - var findNodeIndex = function(id, state) { - for (var i = 0; i < nodes.length; i++) { - if (nodes[i].id === id || nodes[i].state === state) - return i - }; } + var el = document.body; - - ///GRAPH BUILDER - var w = $(el).innerWidth(), - h = $(el).innerHeight(); - - var color = d3.scale.category20(); + graph = new myGraph(el); //Zoom scale behavior in zoom.js zoomObject = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom); - - vis = this.vis = d3.select(el).append("svg:svg") + + vis = d3.select(el).append("svg:svg") .attr("width", '100%') .attr("height", '100%') .attr("pointer-events", "all") @@ -463,129 +97,14 @@ function myGraph(el) { // all links are below the nodes we group them under a single g vis.append("g").attr("id", "link-group"); - function zoom() { - if (graphstate === "GRAPH") { - vis.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); - } - if (graphstate === "GANTT") { - vis.attr("transform", "translate(0,0)scale(1)"); - } - } - - var drag = d3.behavior.drag() + drag = d3.behavior.drag() .origin(function(d) { return d; }) .on("dragstart", dragstarted) .on("drag", dragged) .on("dragend", dragended); - function dragstarted(d) { - d3.event.sourceEvent.stopPropagation(); - d3.select(this).classed("dragging", true); - d.dragstart = {clientX:d3.event.sourceEvent.clientX, clientY:d3.event.sourceEvent.clientY}; - force.stop(); - } - - function dragged(d) { - d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y); - tick(); - } - - function dragended(d) { - d3.select(this).classed("dragging", false); - d3.select(this).classed("fixed", true); - d3.select(this).attr("dx", d3.event.x).attr("dy", d3.event.y); - if (d.dragstart.clientX - d3.event.sourceEvent.clientX != 0 || - d.dragstart.clientY - d3.event.sourceEvent.clientY != 0) { - tick(); - force.resume(); - } - } - - function clear() { - nodes.length = 0; - links.length = 0; - } - this.clear = clear; - - function empty() { - return nodes.length == 0 && links.length == 0; - } - this.empty = empty; - - function load_from_json(json) { - var data = JSON.parse(json); - var i, node, link; - - clear(); - if (data == null) { - console.log('load callback: no data to load'); - return; - } - for(i = 0; i < data["nodes"].length; i++){ - node = data.nodes[i]; - graph.addNodeNoHistory({id:node.id, name:node.name ? node.name : node.id, - type:node.type,state:"perm", - start:new Date(node.start), - end:new Date(node.end), - status:node.status, - url:node.url, - x: node.x, - y: node.y, - }); - textanalysis.autoSuggestAddName(node.id); - } - for(i = 0; i < data["links"].length; i++){ - link = data.links[i]; - graph.addLink(link.source,link.target,link.name,"perm"); - } - graph.recenterZoom(); - graph.update(false); - graph.clear_history(); - } - this.load_from_json = load_from_json; - - function save_to_json() { - var d = {"nodes":[], "links":[]}; - for(var i = 0 ; i < nodes.length ; i++){ - var node = nodes[i]; - d['nodes'].push({ - "id": node.id, - "name": node.name, - "type":node.type, - "state":"perm", - "start":node.start, - "end":node.end, - "status": node.status, - "url": node.url, - "x": node.x, - "y": node.y, - }); - } - for(var j=0 ; j < links.length ; j++){ - var link = links[j]; - d['links'].push({ - "source":link.source.id, - "target":link.target.id, - "name":link.name - }); - } - return JSON.stringify(d); - } - this.save_to_json = save_to_json; - - function set_user(user) { - this.user = user; - this.history = new History(this.user, $('svg g.zoom')[0]); - console.log('new user: ' + user); - } - this.set_user = set_user; - - function clear_history() { - if (this.history !== undefined) { - this.history.clear(); - } - } - this.clear_history = clear_history; + var w = $(el).innerWidth(), + h = $(el).innerHeight(); force = d3.layout.force() .distance(120) @@ -595,223 +114,219 @@ function myGraph(el) { .on("tick", tick) .start(); - nodes = force.nodes(); - links = force.links(); + graph.update = update; +} - var state_to_link_class = { - enter:'enterlink graph', - exit:'exitlink graph', - }; +initDrawingArea(); - var update = function(no_relayout) { - link = vis.select("#link-group").selectAll(".link") - .data(links); +function update(no_relayout) { + var node, link, linktext, nodetext; - link.enter().append("svg:defs").selectAll("marker") - .data(["end"]) // Different link/path types can be defined here - .enter().append("svg:marker") // This section adds in the arrows - .attr("id", String) - .attr("viewBox", "0 -5 10 10") - .attr("refX", 22) - .attr("refY", -1.5) - .attr("markerWidth", 4) - .attr("markerHeight", 4) - .attr("orient", "auto") - .attr("class", "graph") - .style("fill", function(d){ - if (d.state==="enter" || d.state==="exit") { - return "EDE275"; - } else { - return "#aaa"; - } - }) - .append("svg:path") - .attr("d", "M0,-5L10,0L0,5"); + link = vis.select("#link-group").selectAll(".link") + .data(graph.links()); - link.enter().append("path") - .attr("d", "M0,-5L10,0L0,5") - .attr("class", function(d) { - return state_to_link_class[d.state] || 'link graph'; + link.enter().append("svg:defs").selectAll("marker") + .data(["end"]) // Different link/path types can be defined here + .enter().append("svg:marker") // This section adds in the arrows + .attr("id", String) + .attr("viewBox", "0 -5 10 10") + .attr("refX", 22) + .attr("refY", -1.5) + .attr("markerWidth", 4) + .attr("markerHeight", 4) + .attr("orient", "auto") + .attr("class", "graph") + .style("fill", function(d){ + if (d.state==="enter" || d.state==="exit") { + return "EDE275"; + } else { + return "#aaa"; + } }) - .attr("marker-end", "url(#end)") - .on("click", function(d, i) { - //$('#textanalyser').val("node("+d.source.id+") -> "+d.name+" -> node("+d.target.id+")"); - });; - link.style("stroke-dasharray", function(d,i){ - if(d.name && d.name.replace(/ /g,"")=="and" && d.state==="temp") - return "3,3"; - else - return "0,0"; - }); - - link.exit().remove(); + .append("svg:path") + .attr("d", "M0,-5L10,0L0,5"); - linktext = vis.selectAll(".linklabel").data(links); - linktext.enter() - .append("text") - .attr("class", "linklabel graph") - .attr("text-anchor", "middle") - .on("click", function(d, i) { - if(d.state !== "temp") { - editLink(d, i); - } - }); + link.enter().append("path") + .attr("d", "M0,-5L10,0L0,5") + .attr("class", function(d) { + return state_to_link_class[d.state] || 'link graph'; + }) + .attr("marker-end", "url(#end)") + .on("click", function(d, i) { + //$('#textanalyser').val("node("+d.source.id+") -> "+d.name+" -> node("+d.target.id+")"); + });; + link.style("stroke-dasharray", function(d,i){ + if(d.name && d.name.replace(/ /g,"")=="and" && d.state==="temp") + return "3,3"; + else + return "0,0"; + }); - linktext - .text(function(d) { - var name = d.name || ""; - if (!(d.target.state === "temp" || - d.source.state === "chosen" || d.target.state === "chosen")) { - return ""; - } - if (name.length < 25 || d.source.state === "chosen" || - d.target.state === "chosen" || d.state==="temp") { - return name; - } else { - return name.substring(0, 14) + "..."; - } - }); + link.exit().remove(); - linktext.exit().remove(); + linktext = vis.selectAll(".linklabel").data(graph.links()); + linktext.enter() + .append("text") + .attr("class", "linklabel graph") + .attr("text-anchor", "middle") + .on("click", function(d, i) { + if(d.state !== "temp") { + editLink(d, i); + } + }); - node = vis.selectAll(".node") - .data(nodes, function(d) { - return d.id; - }); + linktext + .text(function(d) { + var name = d.name || ""; + if (!(d.target.state === "temp" || + d.source.state === "chosen" || d.target.state === "chosen")) { + return ""; + } + if (name.length < 25 || d.source.state === "chosen" || + d.target.state === "chosen" || d.state==="temp") { + return name; + } else { + return name.substring(0, 14) + "..."; + } + }); - var nodeEnter = node.enter() - .append("g").attr('class', 'node') - .attr('visibility', 'hidden') // made visible on first tick - .on("click", function(d, i) { - if (d3.event.defaultPrevented) { - // drag happened, ignore click https://github.com/mbostock/d3/wiki/Drag-Behavior#on - return; - } - if(d.state!=="temp"){ - editNode(d, i); - showInfo(d, i); - } - }) - .call(drag); + linktext.exit().remove(); - nodetext = nodeEnter.insert("text") - .attr("class", "nodetext graph") - .attr("dx", 15) - .attr("dy", ".30em"); + node = vis.selectAll(".node") + .data(graph.nodes(), function(d) { + return d.id; + }); - node.select('g.node text') - .text(function(d) { - if (!d.name) { - return ""; - } - if (d.state === "temp" || d.state === 'chosen') { - return d.name; - } else { - if (d.name.length < 28) { - return d.name; - } else { - return d.name.substring(0, 25) + "..."; - } - } - }); + var nodeEnter = node.enter() + .append("g").attr('class', 'node') + .attr('visibility', 'hidden') // made visible on first tick + .on("click", function(d, i) { + if (d3.event.defaultPrevented) { + // drag happened, ignore click https://github.com/mbostock/d3/wiki/Drag-Behavior#on + return; + } + if(d.state!=="temp"){ + editNode(d, i); + showInfo(d, i); + } + }) + .call(drag); - circle = nodeEnter.insert("circle"); - node.select('g.node circle') - .attr("class", "circle graph") - .attr("r", function(d) { - return customSize(d.type) - 2; - }) - .style("fill", function(d) { - return customColor(d.type); - }) - .style("stroke", function(d) { - if (d.state === "chosen") return "#EDE275"; - if (d.state === "enter") return "#EDE275"; - if (d.type === "bubble") return "#101010"; - if (d.state === "exit") return "#EDE275"; - if (d.type === "chainlink") return "#AAA"; + nodetext = nodeEnter.insert("text") + .attr("class", "nodetext graph") + .attr("dx", 15) + .attr("dy", ".30em"); - return "#fff"; - }) - .style("stroke-width", function(d) { - if (d.state === "temp" && d.type !== "empty" || d.state === "chosen") return "3px"; - else return "1.5px"; - }) - .style("box-shadow", function(d) { - if (d.state === "temp") return "0 0 40px #FFFF8F"; - else return "0 0 0px #FFFF8F"; - }) - .on("click", function(d, i) { - if (d3.event.defaultPrevented) { - // drag happened, ignore click https://github.com/mbostock/d3/wiki/Drag-Behavior#on - return; - } - d3.event.stopPropagation(); - if(d.state!=="temp") { - showInfo(d, i); + node.select('g.node text') + .text(function(d) { + if (!d.name) { + return ""; + } + if (d.state === "temp" || d.state === 'chosen') { + return d.name; + } else { + if (d.name.length < 28) { + return d.name; } else { - graph.removeHighlight(); + return d.name.substring(0, 25) + "..."; } - graph.update(true); - }); + } + }); - //if(graphstate==="GANTT"){ - nodeEnter.append("svg:image") - .attr("class", "status graph") - .attr('x', -7) - .attr('y', -8) - .attr('width', 15) - .attr('height', 15) - .attr("xlink:href", function(d) { - switch (d.status) { - case "done": - return "images/check.png"; - break; - case "current": - return "images/wait.png"; - break; - case "waiting": - return "images/cross.png"; - break; - } - }) - .on("click", function(d, i) { - if(d.state!=="temp")showInfo(d, i); - }); - //} + circle = nodeEnter.insert("circle"); + node.select('g.node circle') + .attr("class", "circle graph") + .attr("r", function(d) { + return customSize(d.type) - 2; + }) + .style("fill", function(d) { + return customColor(d.type); + }) + .style("stroke", function(d) { + if (d.state === "chosen") return "#EDE275"; + if (d.state === "enter") return "#EDE275"; + if (d.type === "bubble") return "#101010"; + if (d.state === "exit") return "#EDE275"; + if (d.type === "chainlink") return "#AAA"; - node.exit().remove(); + return "#fff"; + }) + .style("stroke-width", function(d) { + if (d.state === "temp" && d.type !== "empty" || d.state === "chosen") return "3px"; + else return "1.5px"; + }) + .style("box-shadow", function(d) { + if (d.state === "temp") return "0 0 40px #FFFF8F"; + else return "0 0 0px #FFFF8F"; + }) + .on("click", function(d, i) { + if (d3.event.defaultPrevented) { + // drag happened, ignore click https://github.com/mbostock/d3/wiki/Drag-Behavior#on + return; + } + d3.event.stopPropagation(); + if(d.state!=="temp") { + showInfo(d, i); + } else { + graph.removeHighlight(); + } + update(true); + }); - //update deliverables - deliverables = []; - for (var i = 0; i < nodes.length; i++) { - var current = nodes[i]; - if (current.type === "deliverable") { - deliverables.push({ - "id": nodes[i].id, - "startdate": nodes[i].start, - "enddate": nodes[i].end - }); + //if(graphstate==="GANTT"){ + nodeEnter.append("svg:image") + .attr("class", "status graph") + .attr('x', -7) + .attr('y', -8) + .attr('width', 15) + .attr('height', 15) + .attr("xlink:href", function(d) { + switch (d.status) { + case "done": + return "images/check.png"; + break; + case "current": + return "images/wait.png"; + break; + case "waiting": + return "images/cross.png"; + break; } - //Do something - } + }) + .on("click", function(d, i) { + if(d.state!=="temp")showInfo(d, i); + }); + //} - force.nodes(nodes) - .links(links) - if (no_relayout) { - // XXX If we are stopped we need to update the text of the links at least, - // and this is the simplest way - tick(); - } else { - force.alpha(0.1).start(); + node.exit().remove(); + + //update deliverables + deliverables = []; + var nodes = graph.nodes(); + for (var i = 0; i < nodes.length; i++) { + var current = nodes[i]; + if (current.type === "deliverable") { + deliverables.push({ + "id": nodes[i].id, + "startdate": nodes[i].start, + "enddate": nodes[i].end + }); } + //Do something } - update(false); + force.nodes(graph.nodes()) + .links(graph.links()) + if (no_relayout) { + // XXX If we are stopped we need to update the text of the links at least, + // and this is the simplest way + tick(); + } else { + force.alpha(0.1).start(); + } } -var graph = new myGraph(document.body); var debug_print = function(message) { var element = $(".debug"); @@ -834,6 +349,13 @@ var newnodes=1; function tick(e) { //console.log(e); //$(".debug").html(force.alpha()); + var node = vis.selectAll(".node") + .data(force.nodes(), function(d) { + return d.id; + }); + var link = vis.select("#link-group").selectAll(".link") + .data(graph.links()); + var linktext = vis.selectAll(".linklabel").data(graph.links()); function transform(d) { if (graphstate === "GRAPH" || d.type === "deliverable") { @@ -856,7 +378,7 @@ function tick(e) { var today = new Date(); var missingcounter = 0; - nodes.forEach(function(d, i) { + graph.nodes().forEach(function(d, i) { if ((d.start === 0 || d.end === 0)) { d.x = 450 + missingcounter * 100; d.y = window.innerWidth / 2; @@ -886,7 +408,7 @@ function tick(e) { //circles animation var tempcounter = 0; var temptotal = 0; - nodes.forEach(function(d, i) { + graph.nodes().forEach(function(d, i) { if (d.state === "temp" && d.type!=="chainlink" && d.type!=="bubble") { temptotal++; } @@ -898,7 +420,7 @@ function tick(e) { newnodes=temptotal; } if(newnodes<1)newnodes=1; - nodes.forEach(function(d, i) { + graph.nodes().forEach(function(d, i) { if (d.state === "temp") { tempcounter++; if(d.type==="chainlink" || d.type==="bubble"){ @@ -914,19 +436,6 @@ function tick(e) { }); } - if (boxedin) { - circle.attr("cx", function(d) { - return d.x = Math.max(14, Math.min(w - 14, d.x)); - }) - .attr("cy", function(d) { - return d.y = Math.max(114, Math.min(h - 14, d.y)); - }); - - nodetext.attr("transform", transform); - } else { - node.attr("transform", transform); - } - link.attr("d", function(d) { if (graphstate === "GRAPH") { var dx = d.target.x - d.source.x, @@ -958,6 +467,8 @@ function tick(e) { } }); + node.attr("transform", transform); + // After initial placement we can make the nodes visible. //links.attr('visibility', 'visible'); node.attr('visibility', 'visible'); @@ -1164,9 +675,15 @@ function expand(obj){ } obj.size = Math.max(obj.savesize, obj.value.length); } + return { expand: expand, graph: graph, force: force, + load_from_json: function(result) { + graph.load_from_json(result); + recenterZoom(); + update(false); + } } }); /* close define call */ |
