summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlon Levy <alon@pobox.com>2015-01-04 16:44:38 +0200
committerAlon Levy <alon@pobox.com>2015-01-05 13:23:57 +0200
commitff5c984089c19467304dbec0b87b2aa019717b13 (patch)
tree91a10398f03988516f44672078e3705b8d2555cb /src
parentd50ec127af2fe7c7276380e10fce00496580fd58 (diff)
initial graph_view introduction - mixing circle & force layouts
Diffstat (limited to 'src')
-rw-r--r--src/client/buttons.js12
-rw-r--r--src/client/main.js7
-rw-r--r--src/client/model/graph.js87
-rw-r--r--src/client/model/util.js5
-rw-r--r--src/client/rz_core.js529
-rw-r--r--src/client/textanalysis.js217
-rw-r--r--src/client/textanalysis.ui.js14
-rw-r--r--src/client/view/graph_view.js492
-rw-r--r--src/client/view/helpers.js6
-rw-r--r--src/client/view/selection.js8
10 files changed, 685 insertions, 692 deletions
diff --git a/src/client/buttons.js b/src/client/buttons.js
index 860c8573..e29a1729 100644
--- a/src/client/buttons.js
+++ b/src/client/buttons.js
@@ -7,13 +7,13 @@ var key="#47989379";
$('.save a').click(function(){
- var json = rz_core.graph.save_to_json();
+ var json = rz_core.main_graph.save_to_json();
console.log('saving to local storage ' + json.length + ' bytes');
localStorage.setItem(key, json);
});
$('.export').click(function() {
- var json = rz_core.graph.save_to_json();
+ var json = rz_core.main_graph.save_to_json();
var filename = 'graph.json';
var blob = new Blob([json], {type: 'application/json'});
console.log('saving ' + json.length + ' bytes to ' + filename);
@@ -21,14 +21,14 @@ $('.export').click(function() {
});
$('.url-copy a').click(function() {
- var json = rz_core.graph.save_to_json();
+ var json = rz_core.main_graph.save_to_json();
// TODO use jquery BBQ $.param({json: json});
var encoded = document.location.origin + '/?json=' + encodeURIComponent(json);
window.prompt('Copy to clipboard: Ctrl-C, Enter (or Cmd-C for Mac)', encoded);
});
var really_load = function() {
- if (!rz_core.graph.empty()) {
+ if (!rz_core.main_graph.empty()) {
return confirm('All unsaved changes will be deleted, are you sure?');
}
return true;
@@ -73,10 +73,10 @@ logout_button.click(function() {
});
$('a.save-history').click(function() {
- if (rz_core.graph.history === undefined) {
+ if (rz_core.main_graph.history === undefined) {
throw "History is undefined";
}
- rz_core.graph.history.save_to_file();
+ rz_core.main_graph.history.save_to_file();
});
return {'buttons': 'nothing here'};
}); // define
diff --git a/src/client/main.js b/src/client/main.js
index 47c9de1d..ca7625aa 100644
--- a/src/client/main.js
+++ b/src/client/main.js
@@ -45,7 +45,8 @@ function(textanalysis_ui, textanalysis, buttons, history, drag_n_drop,
}
if (util.getParameterByName('debug')) {
$(document.body).addClass('debug');
- rz_core.graph.set_user('fakeuser');
+ rz_core.edit_graph.set_user('fakeuser');
+ rz_core.main_graph.set_user('fakeuser');
}
document.body.onkeyup = function(e) {
@@ -59,7 +60,7 @@ function(textanalysis_ui, textanalysis, buttons, history, drag_n_drop,
search.focus();
}
if (e.ctrlKey && key == 'z' && e.target.nodeName !== 'INPUT') {
- // TODO: rz_core.graph.undo();
+ // TODO: rz_core.main_graph.undo();
}
};
// TODO: move me somewhere
@@ -101,7 +102,7 @@ function(textanalysis_ui, textanalysis, buttons, history, drag_n_drop,
// TODO: interaction between the hack above and this
model_core.init(rz_config);
- textanalysis.init(rz_core.graph);
+ textanalysis.init(rz_core.main_graph);
}
return {
diff --git a/src/client/model/graph.js b/src/client/model/graph.js
index 46b0359b..61797886 100644
--- a/src/client/model/graph.js
+++ b/src/client/model/graph.js
@@ -5,7 +5,7 @@ function (Bacon, consts, util, model_core, model_util, model_diff, rz_api_backen
var debug = false;
-function Graph() {
+function Graph(temporary) {
var id_to_node_map = {},
node_map = {},
@@ -38,9 +38,11 @@ function Graph() {
*
* @return node if node was actually added
*/
- this.addTempNode = function(spec) {
- util.assert(spec.state === 'temp', "node is not a temp node");
- return this.__addNode(spec);
+ // FIXME: is this a good idea? i.e. changing API based on constructor? just in dynamic city
+ if (temporary) {
+ this.addTempNode = function(spec) {
+ return this.__addNode(spec);
+ }
}
var nodes_to_touched_links = function (node_id_set) {
@@ -141,11 +143,7 @@ function Graph() {
} else {
node = model_core.create_node__set_random_id(spec);
if (debug) {
- if ('bubble' != node.type){
- console.log('__addNode: stamping node id: ' + node.id + ', name: \'' + node.name + '\' (bubble)');
- }else {
- console.log('__addNode: stamping node id: ' + node.id + ', name: \'' + node.name + '\'');
- }
+ console.log('__addNode: stamping node id: ' + node.id + ', name: \'' + node.name + '\'');
}
}
} else {
@@ -228,7 +226,7 @@ function Graph() {
*
* NOTE: chainlinks are treated specially, they don't count for distance. So all their decendants will be added.
*
- * NOTE: temp state nodes (n.state === 'temp') are ignored.
+ * NOTE: Doesn't handle inter graph links
*
* @return - {
* 'node': [node]
@@ -271,19 +269,14 @@ function Graph() {
var adjacentnode;
if (same(link.__src, n)) {
adjacentnode = find_node__by_id(link.__dst.id);
- if (adjacentnode.state !== "temp") {
- addNode({type: 'exit', node: adjacentnode});
- }
+ addNode({type: 'exit', node: adjacentnode});
ret.links.push({type: 'exit', link: link});
if (link.__dst.type === "chainlink") {
links_forEach(function(link2) {
if (link.__dst.id === link2.__dst.id &&
- link2.__dst.type === "chainlink" &&
- link2.__dst.state !== "temp") {
+ link2.__dst.type === "chainlink") {
adjacentnode = find_node__by_id(link2.__src.id);
- if (adjacentnode.state !== "temp") {
- addNode({type: 'enter', node: adjacentnode});
- }
+ addNode({type: 'enter', node: adjacentnode});
ret.links.push({type: 'enter', link: link2});
}
});
@@ -291,9 +284,7 @@ function Graph() {
}
if (same(link.__dst, n)) {
adjacentnode = find_node__by_id(link.__src.id);
- if (adjacentnode.state !== "temp") {
- addNode({type: 'enter', node: adjacentnode});
- }
+ addNode({type: 'enter', node: adjacentnode});
ret.links.push({type: 'enter', link: link});
}
});
@@ -309,11 +300,7 @@ function Graph() {
* 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).filter(function (nd) {
- return nd.type !== 'bubble';
- });
+ var state_nodes = findNodes(null, state);
var state_links = findLinks(state).map(function(link) {
return [link.__src.name, link.__dst.name];
}).sort();
@@ -388,7 +375,7 @@ function Graph() {
return;
}
- util.assert(src.state === 'temp' && dst.state === 'temp' && state == "temp",
+ util.assert(temporary,
"creation of link not through commit_and_tx that isn't temporary");
var link = model_core.create_link__set_random_id(src, dst, { name: name,
@@ -416,9 +403,11 @@ function Graph() {
existing_link.state = link.state;
}
}
- this.addTempLink = function (link) {
- util.assert(link.state === "temp", "link is not temporary");
- return __addLink(link);
+ // FIXME: good idea to add API based on constructor parameter?
+ if (temporary) {
+ this.addTempLink = function (link) {
+ return __addLink(link);
+ }
}
this.update_link = function(link, new_link_spec, on_success, on_error) {
@@ -541,7 +530,7 @@ function Graph() {
if (n_eq_id.name == new_name) {
return;
}
- util.assert(n_eq_id.state === 'temp', 'editName should now only be used by textanalysis');
+ util.assert(temporary, 'editName should now only be used on temporary graphs');
util.assert(n_eq_name === undefined);
n_eq_id.name = new_name;
@@ -574,6 +563,7 @@ function Graph() {
if ((n === undefined)) {
return false;
}
+ console.debug("deprecated and broken probably");
// cannot assert state is temp since robot code still uses it
if (state != 'temp') {
console.log('warning: I hope robot is doing this not-through-backend property change');
@@ -613,10 +603,10 @@ function Graph() {
this._remove_link_set = _remove_link_set;
this.removeNodes = function(state) {
- var temp_node_ids = get_nodes().filter(function (n) { return n.state == state; })
+ var node_ids = get_nodes().filter(function (n) { return n.state == state; })
.map(function (n) { return n.id; }),
topo_diff = model_diff.new_topo_diff({
- node_set_rm : temp_node_ids,
+ node_set_rm : node_ids,
});
this.commit_diff__topo(topo_diff);
@@ -659,14 +649,6 @@ function Graph() {
}
this.hasNodeByName = hasNodeByName;
- var hasNodeByNameAndNotState = function(name, state) {
- util.assert(state === 'temp', "this is deprecated and to be removed but anyway just for temp");
- return get_nodes().filter(function(n) {
- return compareNames(n.name, name) && n.state !== state;
- }).length > 0;
- }
- this.hasNodeByNameAndNotState = hasNodeByNameAndNotState;
-
/**
* return node whose id matches the given id or undefined if no node was found
*/
@@ -707,8 +689,13 @@ function Graph() {
}
function clear() {
- nodes.length = 0;
- links.length = 0;
+ id_to_node_map = {};
+ node_map = {}
+ id_to_link_map = {};
+ link_map = {};
+ invalidate_links = true;
+ invalidate_nodes = true;
+ // FIXME: push on diffBus?
}
this.clear = clear;
@@ -764,8 +751,10 @@ function Graph() {
});
diff.link_set_add.map(function (link_spec) {
// resolve link ptr
- var src = find_node__by_id(link_spec.__src_id),
- dst = find_node__by_id(link_spec.__dst_id) || find_node__by_name(link_spec.__dst.name),
+ var src = find_node__by_id(link_spec.__src_id)
+ || (temporary && find_node__by_name(link_spec.__src.name)),
+ dst = find_node__by_id(link_spec.__dst_id)
+ || (temporary && find_node__by_name(link_spec.__dst.name)),
link = model_core.create_link_from_spec(src, dst, link_spec);
__addLink(link);
@@ -926,9 +915,6 @@ function Graph() {
for (i = 0 ; i < n_length; ++i) {
node = nodes[i];
- if (node.state == 'temp') {
- continue;
- }
if (node_visitor(node)) {
selected.push(node);
}
@@ -940,7 +926,7 @@ function Graph() {
removeRelated();
nodes_forEach(function (node) {
names.forEach(function (name) {
- if (compareNames(node.name, name) && node.state != 'temp') {
+ if (compareNames(node.name, name)) {
node.state = 'related';
}
});
@@ -949,8 +935,9 @@ function Graph() {
this.markRelated = markRelated;
function removeRelated() {
+ // FIXME: related should use separate variable, not overload 'state' (bad bad bad)
nodes_forEach(function (node) {
- if (node.state == 'related') {
+ if (node.state === 'related') {
node.state = 'perm';
}
});
diff --git a/src/client/model/util.js b/src/client/model/util.js
index 25d419bf..20472fda 100644
--- a/src/client/model/util.js
+++ b/src/client/model/util.js
@@ -106,11 +106,6 @@ define([ 'jquery', 'model/diff' ], function($, model_diff) {
var new_n_set = $.extend([], n_set);
var new_l_set = $.extend([], l_set);
- // filter out 'bubble' nodes
- new_n_set = new_n_set.filter(function(n) {
- return 'bubble' != n.type;
- });
-
new_n_set = $.map(new_n_set, function(n, _) {
return adapt_format_write_node(n);
})
diff --git a/src/client/rz_core.js b/src/client/rz_core.js
index 36d8058e..bd1dc141 100644
--- a/src/client/rz_core.js
+++ b/src/client/rz_core.js
@@ -1,7 +1,7 @@
"use strict"
-define(['jquery', 'd3', 'consts', 'rz_bus', 'util', 'model/graph', 'model/core', 'view/helpers', 'view/view', 'rz_observer', 'view/selection', 'rz_config', 'rz_mesh', 'model/diff'],
-function($, d3, consts, rz_bus, util, model_graph, model_core, view_helpers, view, rz_observer, selection, rz_config, rz_mesh, model_diff) {
+define(['jquery', 'd3', 'consts', 'rz_bus', 'util', 'model/graph', 'model/core', 'view/helpers', 'view/view', 'rz_observer', 'view/selection', 'rz_config', 'rz_mesh', 'model/diff', "view/graph_view"],
+function($, d3, consts, rz_bus, util, model_graph, model_core, view_helpers, view, rz_observer, selection, rz_config, rz_mesh, model_diff, graph_view) {
var addednodes = [],
vis,
@@ -10,9 +10,10 @@ var addednodes = [],
deliverables = [],
circle, // <-- should not be module globals.
scrollValue = 0,
- graph,
- drag,
- force;
+ main_graph,
+ edit_graph,
+ main_graph_view,
+ edit_graph_view;
// "CSS" for SVG elements. Reused for editing elements.
var node_text_dx = 15,
@@ -76,11 +77,11 @@ var svgInput = (function() {
d = jelement.data().d;
if (e.which == 13 && newname != d.name) {
if (d.hasOwnProperty('__src')) {
- graph.update_link(d, {name: newname});
+ main_graph.update_link(d, {name: newname});
} else {
// TODO - display hourglass
// TODO - use promises to make A follows B readable.
- graph.update_node(d, {name: newname});
+ main_graph.update_node(d, {name: newname});
}
}
hide();
@@ -175,57 +176,40 @@ var svgInput = (function() {
};
}());
+var zoomProgress = false;
+var zoomBus = new Bacon.Bus();
+var zoomProperty = zoomBus.toProperty();
+zoomBus.push(zoomProgress); // set initial value
+function svg_click_handler(e) {
+ if (zoomProgress) {
+ zoomProgress = false;
+ return;
+ }
+ if (e.originalEvent.target.nodeName != 'svg') {
+ return;
+ }
+ svgInput.hide();
+ selection.clear();
+ view.hide();
+ update_view__graph(false);
+}
function recenterZoom() {
vis.attr("transform", "translate(0,0)scale(1)");
}
-// zoom or drag
-var zoomInProgress = false;
+main_graph = new model_graph.Graph(false);
+edit_graph = new model_graph.Graph(true);
var initDrawingArea = function () {
function zoom() {
- zoomInProgress = true;
+ zoomProgress = true;
vis.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
d3.event.sourceEvent.stopPropagation();
}
- 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) {
- d.x = d3.event.x;
- d.y = d3.event.y;
- tick();
- }
-
- function dragended(d) {
- d3.select(this).classed("dragging", false);
- d3.select(this).classed("fixed", true); // TODO: this is broken since we override all the classes. Need to switch to class addition/removal (i.e. use classed for everything) or set class in one location (so here just set a value on the node, not the element)
- if (d.dragstart.clientX - d3.event.sourceEvent.clientX != 0 ||
- d.dragstart.clientY - d3.event.sourceEvent.clientY != 0) {
- tick();
- force.resume();
- }
- }
-
- graph = new model_graph.Graph();
- graph.diffBus.onValue(function (diff) {
- var relayout = (false == model_diff.is_attr_diff(diff));
- if (relayout) {
- console.log('****** graph relayout *******');
- } else {
- console.log('****** graph update *******');
- }
- console.dir(diff);
- update_view__graph(relayout);
- });
// TODO: we are listening both on graph.diffBus and selection.selectionChangedBus,
// but the relation is actually:
//
@@ -235,14 +219,16 @@ var initDrawingArea = function () {
// we need to deduplicate this event
// but there is no coordination, resulting in double updates.
selection.selectionChangedBus.onValue(
- function() { update_view__graph(false); });
+ function() { update_view__graph(false);
+ });
var user_id = $('#user_id'),
user = user_id.text();
if (user_id.length > 0) {
console.log('found user ID: \'' + user + '\'');
- graph.set_user(user);
+ main_graph.set_user(user);
+ edit_graph.set_user(user);
}
var el = document.body;
@@ -271,54 +257,47 @@ var initDrawingArea = function () {
* init zoom behavior
*/
var zoom_obj = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
- zoom_obj(d3.select('#canvas_d3'))
+ zoom_obj(d3.select('#canvas_d3'));
d3.select("svg").on("dblclick.zoom", null); // disable zoom on double click
$('svg').click(svg_click_handler);
- // SVG rendering order is last rendered on top, so to make sure
- // all links are below the nodes we group them under a single g
- vis.append("g").attr("id", "link-group");
- vis.append("g").attr("id", "selected-link-group");
-
- drag = d3.behavior.drag()
- .origin(function(d) { return d; })
- .on("dragstart", dragstarted)
- .on("drag", dragged)
- .on("dragend", dragended);
+ main_graph_view = graph_view.GraphView({
+ vis: vis.append('g'),
+ graph_name: "main",
+ graph: main_graph,
+ zoomProperty: zoomProperty,
+ forceEnabled: true,
+ node_text_dx: node_text_dx,
+ node_text_dy: node_text_dy,
+ });
+ edit_graph_view = graph_view.GraphView({
+ vis: vis.append('g'),
+ graph_name: "edit",
+ graph: edit_graph,
+ zoomProperty: zoomProperty,
+ forceEnabled: false,
+ node_text_dx: node_text_dx,
+ node_text_dy: node_text_dy,
+ });
// $('#canvas_d3').dblclick(canvas_handler_dblclick); - see #138
- if (rz_config.backend_enabled){
- graph.load_from_backend();
+ if (rz_config.backend_enabled) {
+ main_graph.load_from_backend();
}
}
-function init_force_layout(){
- var el = document.body;
- var w = $(el).innerWidth(),
- h = $(el).innerHeight();
-
- force = d3.layout.force()
- .distance(120)
- .gravity(0.12)
- .charge(-1800)
- .size([w, h])
- .on("tick", tick)
- .start();
-}
-
/**
* Initialize backend websocket connection - this will have no effect if
* rz_config.backend__maintain_ws_connection is set to 'false'
*/
function init_ws_connection(){
if (true == rz_config.backend__maintain_ws_connection){
- rz_mesh.init({graph: graph});
+ rz_mesh.init({graph: main_graph});
}
}
initDrawingArea();
-init_force_layout();
init_ws_connection();
/**
@@ -343,7 +322,7 @@ function canvas_handler_dblclick(){
var n = model_core.create_node__set_random_id();
n.name = ''; // will be set by user
- graph.commit_and_tx_diff__topo(model_diff.new_topo({
+ main_graph.commit_and_tx_diff__topo(model_diff.new_topo({
node_set_add : [n].map(model_util.adapt_format_write_node),
}));
@@ -365,380 +344,12 @@ function canvas_handler_dblclick(){
});
}
-/**
- * update view: graph
- */
-function update_view__graph(relayout) {
- var node,
- link,
- link_g,
- linktext,
- nodetext,
- unselected_link_group = document.querySelector('#link-group'),
- selected_link_group = document.querySelector('#selected-link-group'),
- relayout = relayout || true;
-
- //return;
-
- link = vis.selectAll("g.link")
- .data(graph.links(), function(d) { return d.id; });
-
- link_g = link.enter().append('g')
- .attr('id', function(d){ return d.id; }) // append link id to enable data->visual mapping
- .attr('class', 'link graph')
-
- link_g.append("path")
- .attr("class", function(d) {
- return d.state + ' link graph';
- })
- .attr('id', function(d){ return d.id; }) // append link id to enable data->visual mapping
- .attr("marker-end", "url(#end)");
-
- // second path for larger click area
- link_g.append("path")
- .attr("class", "ghostlink")
- .on("click", function(d, i) {
- if (zoomInProgress) {
- // don't disable zoomInProgress, it will be disabled by the svg_click_handler
- // after this events bubbles to the svg element
- return;
- }
- var that = this,
- src = this.link.__src,
- dst = this.link.__dst;
-
- view.edge_info.on_delete(function () {
- view.edge_info.hide();
- graph.removeLink(that.link);
- });
- view.edge_info.show(d);
- selection.update([src, dst]);
- });
-
- link.attr("class", function(d, i){
- var temp_and = (d.name && d.name.replace(/ /g,"")=="and" && d.state==="temp") ? "temp_and" : "";
-
- return ["graph link", temp_and, selection.selected_class(d)].join(' ');
- });
-
- link.selectAll('path.link')
- .attr('class', function(d) {
- return [d.state, selection.selected_class(d), "link graph"].join(' ');
- });
-
- link.exit().remove();
-
- vis.selectAll('.ghostlink')
- .data(graph.links())
- .each(function (d) {
- this.link = d;
- });
-
- linktext = vis.selectAll(".linklabel")
- .data(graph.links(), function(d) { return d.id; });
- linktext.enter()
- .append("text")
- .attr('id', function(d){ return d.id; }) // append link id to enable data->visual mapping
- .attr("class", function(d) {
- return ["linklabel graph", selection.selected_class(d)].join(' ');
- })
- .attr("text-anchor", "middle")
- .on("click", function(d, i) {
- if (d.state !== "temp") {
- svgInput.enable(this, d);
- }
- });
-
- linktext
- .text(function(d) {
- var name = d.name || "";
- if (!(d.__dst.state === "temp" ||
- d.__src.state === "chosen" || d.__dst.state === "chosen")) {
- return "";
- }
- if (name.length < 25 || d.__src.state === "chosen" ||
- d.__dst.state === "chosen" || d.state==="temp") {
- return name;
- } else {
- return name.substring(0, 14) + "...";
- }
- });
-
- linktext.exit().remove();
-
- node = vis.selectAll(".node")
- .data(graph.nodes(), function(d) {
- return d.id;
- });
- console.log("update update update update");
- console.dir(node);
-
- var nodeEnter = node.enter()
- .append("g")
- .attr('id', function(d){ return d.id; }) // append node id to enable data->visual mapping
- .attr('visibility', 'hidden') // made visible on first tick
- .call(drag);
-
- // reorder nodes so selected are last, and so rendered last, and so on top.
- (function () {
- var ontop = [],
- bubble;
-
- node.each(function (d) {
- this.node = d;
- })
- .attr('class', function(d) {
- if (selection.node_selected(d)) {
- if (d.type == 'bubble') {
- bubble = this;
- } else {
- ontop.push(this);
- }
- }
- return ['node', selection.selected_class(d)].join(' ');
- });
- if (bubble === undefined) {
- // nothing to do if there is no bubble
- return;
- }
- function reparent(new_parent, element) {
- if (element.parentNode == new_parent) {
- return;
- }
- new_parent.appendChild(element);
- }
- // move link to correct group
- // O(|links|*|ontop|)
- link.each(function (d) {
- if (ontop.some(function (node) {
- var d_node = node.node;
- return d.__src == d_node || d.__dst == d_node;
- }))
- {
- reparent(selected_link_group, this);
- } else {
- reparent(unselected_link_group, this);
- }
- });
- linktext.each(function (d) {
- if (selection.node_selected(d)) {
- ontop.push(this);
- }
- });
- function moveToEnd(e) {
- e.parentNode.appendChild(e);
- }
- moveToEnd(bubble);
- ontop.reverse().forEach(function (e) {
- moveToEnd(e);
- });
- var count_links = function() {
- return selected_link_group.childElementCount + unselected_link_group.childElementCount;
- };
- // put back on top link group on top
- selected_link_group.parentNode.insertBefore(selected_link_group, bubble.nextSibling);
- })();
-
- nodetext = nodeEnter.insert("text")
- .attr("class", "nodetext graph")
- .attr("dx", node_text_dx)
- .attr("dy", node_text_dy)
- .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") {
- svgInput.enable(this, d);
- selection.update([d]);
- showNodeInfo(this.parentNode.node, i);
- }
- d3.event.stopPropagation();
- });
-
- node.select('g.node text')
- .text(function(d) {
- if (!d.name) {
- return d.type == 'bubble' ? "" : "_";
- }
- if (d.state === "temp" || d.state === 'chosen'
- || d.state === "enter" || d.state === "exit") {
- return d.name;
- } else {
- if (d.name.length < 28) {
- return d.name;
- } else {
- return d.name.substring(0, 25) + "...";
- }
- }
- });
-
- circle = nodeEnter.insert("circle");
- node.select('g.node circle')
- .attr("class", function(d) {
- return d.type + " " + d.state + " circle graph";
- })
- .attr("r", function(d) {
- return view_helpers.customSize(d.type) - 2;
- })
- .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();
- selection.update([d]);
- if(d.state !== "temp") {
- showNodeInfo(d, i);
- }
- });
- circle.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 "res/img/check.png";
- break;
- case "current":
- return "res/img/wait.png";
- break;
- case "waiting":
- return "res/img/cross.png";
- break;
- }
- });
-
- 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 === "third-internship-proposal") {
- deliverables.push({
- "id": nodes[i].id,
- "startdate": nodes[i].start,
- "enddate": nodes[i].end
- });
- }
- //Do something
- }
-
- force.nodes(graph.nodes())
- .links(graph.links())
-
- if (relayout) {
- force.alpha(0.1).start();
- } else {
- // XXX If we are stopped we need to update the text of the links at least,
- // and this is the simplest way
- tick();
- }
-}
-
-
-
-var debug_print = function(message) {
- var element = $(".debug");
- if (element.length == 1) {
- element.html(message);
- } else {
- console.log(message);
- }
-}
-
-function check_for_nan(x) {
- if (Number.isNaN(x)) {
- console.log('nan problem');
- force.stop();
- }
- return Number.isNaN(x);
-}
-
-var newnodes=1;
-function tick(e) {
- //console.log(e);
- //$(".debug").html(force.alpha());
- var node = vis.selectAll(".node")
- .data(graph.nodes(), function(d) {
- return d.id;
- });
- var link = vis.selectAll("path.link")
- .data(graph.links(), function(d) {
- return d.id;
- });
- var linktext = vis.selectAll(".linklabel").data(graph.links());
-
- function transform(d) {
- if (check_for_nan(d.x) || check_for_nan(d.y)) {
- return;
- }
- return "translate(" + d.x + "," + d.y + ")";
- }
-
- //circles animation
- var tempcounter = 0,
- temptotal = graph.nodes().filter(function(d){
- return d.state === "temp" && d.type !== "chainlink" && d.type !== "bubble";
- }).length;
- if (temptotal !== newnodes) {
- newnodes += temptotal / 15 / (newnodes * newnodes);
- }
- newnodes = Math.max(1, Math.min(newnodes, temptotal));
- graph.nodes().forEach(function(d, i) {
- var r, a;
- if (d.state === "temp") {
- tempcounter++;
- if (d.type==="chainlink" || d.type==="bubble") {
- d.x = window.innerWidth / 2;
- d.y = window.innerHeight / 2;
- } else {
- r = 60 + newnodes * 20;
- a = -Math.PI + Math.PI * 2 * (tempcounter-1) / newnodes + 0.3;
- d.x = window.innerWidth / 2 + r * Math.cos(a);
- d.y = window.innerHeight / 2 + r * Math.sin(a);
- }
- check_for_nan(d.x);
- check_for_nan(d.y);
- }
- });
-
- link.attr("d", function(d, i) {
- var d_val,
- ghost;
-
- var dx = d.__dst.x - d.__src.x,
- dy = d.__dst.y - d.__src.y,
- dr = Math.sqrt(dx * dx + dy * dy);
- d_val = "M" + d.__src.x + "," + d.__src.y + "L" + d.__dst.x + "," + d.__dst.y;
- // update ghostlink position
- ghost = $(this.nextElementSibling);
- ghost.attr("d", d_val);
- return d_val;
- });
-
- linktext.attr("transform", function(d) {
- return "translate(" + (d.__src.x + d.__dst.x) / 2 + "," + (d.__src.y + d.__dst.y) / 2 + ")";
- });
-
- node.attr("transform", transform);
-
- // After initial placement we can make the nodes visible.
- //links.attr('visibility', 'visible');
- node.attr('visibility', 'visible');
-}
-
function showNodeInfo(node, i) {
var closed = false;
view.node_info.on_save(function(e, form_data) {
closed = true;
- graph.update_node(node, form_data, function() {
+ main_graph.update_node(node, form_data, function() {
var old_type = node.type,
new_type = form_data.type;
@@ -748,7 +359,7 @@ function showNodeInfo(node, i) {
});
// FIXME - attribute diff, ignore uninteresting diffs via filtering
- graph.diffBus.onValue(function () {
+ main_graph.diffBus.onValue(function () {
if (closed) {
return Bacon.noMore;
}
@@ -762,31 +373,25 @@ function showNodeInfo(node, i) {
console.log("closing node info");
closed = true;
view.node_info.hide();
- graph.commit_and_tx_diff__topo(topo_diff);
+ main_graph.commit_and_tx_diff__topo(topo_diff);
});
view.node_info.show(node);
}
-function svg_click_handler(e) {
- if (zoomInProgress) {
- zoomInProgress = false;
- return;
- }
- if (e.originalEvent.target.nodeName != 'svg') {
- return;
- }
- svgInput.hide();
- selection.clear();
- view.hide();
- update_view__graph(false);
+function update_view__graph(relayout)
+{
+ main_graph_view.update_view(relayout);
+ edit_graph_view.update_view(relayout);
}
return {
- graph: graph,
- force: force,
+ main_graph: main_graph,
+ edit_graph: edit_graph,
+ main_graph_view: main_graph_view,
+ edit_graph_view: edit_graph_view,
load_from_json: function(result) {
- graph.load_from_json(result);
+ main_graph.load_from_json(result);
recenterZoom();
update_view__graph(true);
},
diff --git a/src/client/textanalysis.js b/src/client/textanalysis.js
index ebfd3007..a34aac7f 100644
--- a/src/client/textanalysis.js
+++ b/src/client/textanalysis.js
@@ -70,7 +70,10 @@ var lastnode;
var sugg_name = {},
id_to_name_map = {},
- suggestions_options = new Bacon.Bus(); // TODO: Property: same as bus, but with initial value
+ suggestions_bus = new Bacon.Bus(),
+ suggestions_options = suggestions_bus.toProperty();
+
+suggestions_bus.push([]);
var ANALYSIS_NODE_START = 'ANALYSIS_NODE_START';
var ANALYSIS_NODE = 'ANALYSIS_NODE'
@@ -88,80 +91,24 @@ function selectedType()
function auto_suggest__update_name(name, id)
{
if (id !== undefined && id_to_name_map[id] !== undefined) {
- delete sugg[id_to_name_map[id]];
+ delete sugg_name[id_to_name_map[id]];
id_to_name_map[id] = name;
}
/* note that name can contain spaces - this is ok. We might want to limit this though? */
- sugg[name] = 1;
- suggestions_options.push(sugg);
+ sugg_name[name] = 1;
+ suggestions_bus.push(sugg_name);
}
function auto_suggest_remove_name(name, id)
{
- delete sugg[name];
- if (id !== undefined) {
- delete sugg[id];
- }
- suggestions_options.push(sugg);
-}
-
-/* up_to_two_renames:
- *
- * allow one letter or 'new node' to anything changes */
-function up_to_two_renames(graph, old_name, new_name, link_set_add)
-{
- var not_one_letter = false;
- var k;
- /* Allowed renames:
- * no change
- * s1 is substring of s2
- * older (s1) node being 'new node'
- */
- function allowed_rename(s1, s2)
- {
- return (s1 == s2 ||
- s1 == 'new node' ||
- s1.substr(0, s2.length) == s2 ||
- s2.substr(0, s1.length) == s1);
- }
-
- if (old_name.length != new_name.length) {
- console.log('bug: up_to_two_renames: not equal inputs');
- return;
+ if (name !== undefined) {
+ delete sugg_name[name];
}
- if (old_name.length > 2) {
- console.log('bug: up_to_two_renames: input length 2 < ' + old_name.length);
- return;
- }
- if (old_name.length == 2) {
- if (allowed_rename(old_name[0], new_name[1]) &&
- allowed_rename(old_name[1], new_name[0])) {
- old_name = [old_name[1], old_name[0]];
- } else {
- if (!allowed_rename(old_name[0], new_name[0]) ||
- !allowed_rename(old_name[1], new_name[1])) {
- not_one_letter = true;
- }
- }
- }
- if (not_one_letter) {
- console.log('bug: up_to_two_renames: not one letter changes');
- console.log(old_name);
- console.log(new_name);
- return;
- }
- for (k = 0 ; k < old_name.length ; ++k) {
- graph.editNameByName(old_name[k], new_name[k]);
- // very disappointing. suicide threats are ok?
- link_set_add.forEach(function (link) {
- if (link.__src.name == old_name[k]) {
- link.__src.name = new_name[k];
- }
- if (link.__dst.name == old_name[k]) {
- link.__dst.name = new_name[k];
- }
- });
+ if (id !== undefined) {
+ delete sugg_name[id_to_name_map[id]];
+ delete id_to_name_map[id];
}
+ suggestions_bus.push(sugg_name);
}
// TODO: add escape char, i.e. r"bla\"bla" -> ['bla"bla']
@@ -233,7 +180,7 @@ var textAnalyser = function (newtext, finalize) {
word,
completeSentence,
completeSentenceParts,
- typesetter, starGraph,
+ starGraph,
n,
link_hash = {},
yell_bug = false, // TODO: fix both issues
@@ -242,14 +189,14 @@ var textAnalyser = function (newtext, finalize) {
START = "START",
ret = model_diff.new_topo_diff();
- function __addNode(name, type, state) {
+ function __addNode(name, type) {
if (type === undefined) {
console.log('bug: textanalyser.addNode of type undefined');
}
- var node = model_core.create_node_from_spec(
- {'name':name,
- 'type':type,
- 'state':state});
+ var node = model_core.create_node_from_spec({
+ 'name':name,
+ 'type':type,
+ });
ret.node_set_add.push(node);
}
@@ -264,7 +211,7 @@ var textAnalyser = function (newtext, finalize) {
return {'existing': name};
};
- function __addLink(src_name, dst_name, name, state) {
+ function __addLink(src_name, dst_name, name) {
if (!src_name || !dst_name) {
if (yell_bug) {
console.log('bug - adding link (' + src_name + ', ' + dst_name + ')');
@@ -287,7 +234,6 @@ var textAnalyser = function (newtext, finalize) {
'__src': src_name,
'__dst': dst_name,
'name':name,
- 'state':state
};
ret.link_set_add.push(link);
}
@@ -376,19 +322,10 @@ var textAnalyser = function (newtext, finalize) {
nodeindex = 0;
//CHANGE TO PERMANENT STATE AND UPDATE SUGGESTIONLIST
- typesetter = "";
if (finalize === true) {
- typesetter = "perm";
for (n = 0; n < token_set_new_node_names.length; n++) {
auto_suggest__update_name(token_set_new_node_names[n]);
}
- } else {
- typesetter = "temp";
- }
-
- //ADD SURROUNDING BUBBLE
- if (orderStack.length > 0) {
- __addNode("", "bubble","temp");
}
//0-N ORDER STACK
@@ -400,11 +337,11 @@ var textAnalyser = function (newtext, finalize) {
}
break;
case NODE:
- __addNode(token_set_new_node_names[nodeindex], typeStack[nodeindex], typesetter);
+ __addNode(token_set_new_node_names[nodeindex], typeStack[nodeindex]);
if (!starGraph && nodeindex > 0 && token_set_new_link_names[linkindex] !== undefined) {
__addLink(token_set_new_node_names[nodeindex - 1],
token_set_new_node_names[nodeindex],
- token_set_new_link_names[linkindex], typesetter);
+ token_set_new_link_names[linkindex]);
}
nodeindex++;
break;
@@ -428,11 +365,11 @@ var textAnalyser = function (newtext, finalize) {
break;
case NODE:
typeStack[nodeindex] = selectedType();
- __addNode(token_set_new_node_names[nodeindex], typeStack[nodeindex], typesetter);
+ __addNode(token_set_new_node_names[nodeindex], typeStack[nodeindex]);
if (!starGraph && nodeindex > 0 && token_set_new_link_names[linkindex] !== undefined) {
__addLink(token_set_new_node_names[nodeindex - 1],
token_set_new_node_names[nodeindex],
- token_set_new_link_names[linkindex], typesetter);
+ token_set_new_link_names[linkindex]);
and_connect(token_set_new_node_names[nodeindex]);
}
ret.state = ANALYSIS_NODE_START;
@@ -455,9 +392,10 @@ var textAnalyser = function (newtext, finalize) {
if(token_set_new_link_names[x])if(token_set_new_link_names[x].replace(/ /g,"")!=="and"){
verb = token_set_new_link_names[x];
for(var y=0; y<x ;y++){
- __addLink(token_set_new_node_names[y], node, verb, typesetter);
+ __addLink(token_set_new_node_names[y], node, verb);
for(var z=x; z<token_set_new_node_names.length ;z++){
- __addLink(token_set_new_node_names[y], token_set_new_node_names[z], verb, typesetter);
+ __addLink(token_set_new_node_names[y],
+ token_set_new_node_names[z], verb);
}
}
}
@@ -466,81 +404,57 @@ var textAnalyser = function (newtext, finalize) {
//STAR CASE
if (starGraph) {
- __addNode(completeSentence, "chainlink", typesetter);
+ __addNode(completeSentence, "chainlink");
for (n = 0; n < token_set_new_node_names.length; n++) {
- __addLink(token_set_new_node_names[n], completeSentence, "chained", typesetter);
+ __addLink(token_set_new_node_names[n], completeSentence, "chained");
}
}
ret.drop_conjugator_links = and_count < linkindex;
- ret.applyToGraph = function(graph, backend_commit) {
- window.ret = ret;
+ ret.applyToGraph = function(spec) {
+ var main_graph = spec.main_graph,
+ edit_graph = spec.edit_graph,
+ backend_commit = spec.backend_commit;
- ret.link_set_add.forEach(function (link) {
- if (ret.drop_conjugator_links && (link.name.replace(/ /g,"") === "and")) {
- link.state = "temp";
- }
- link.__src = graph.find_node__by_name(link.__src) || __sourceNodeFromName(link.__src);
- link.__dst = graph.find_node__by_name(link.__dst) || __sourceNodeFromName(link.__dst);
- if (link.__src.id !== undefined) {
- link.__src_id = link.__src.id;
- }
- if (link.__dst.id !== undefined) {
- link.__dst_id = link.__dst.id;
- }
- });
+ util.assert(main_graph !== undefined &&
+ edit_graph !== undefined &&
+ backend_commit !== undefined, "missing inputs");
+ window.ret = ret;
- /*
- * generate fitered node set who:
- * - are not name-present in graph
- * - are not of type 'bubble'
- */
- var n_set = ret.node_set_add.filter(function(node) {
- return false == graph.hasNodeByNameAndNotState(node.name, "temp")
- && node.type !== 'bubble';
- }),
- link_set = ret.link_set_add.map(
- function (link) {
- return [link.__src.name, link.__dst.name];
- }),
- comp = graph.compareSubset('temp', n_set, link_set);
+ ret.link_set_add = ret.link_set_add
+ .filter(function (link) {
+ return !finalize ||
+ (ret.drop_conjugator_links &&
+ (link.name.replace(/ /g,"") === "and"));
+ })
+ .map(function (link) {
+ link.__src = edit_graph.find_node__by_name(link.__src) ||
+ __sourceNodeFromName(link.__src);
+ link.__dst = edit_graph.find_node__by_name(link.__dst) ||
+ __sourceNodeFromName(link.__dst);
+ if (link.__src.id !== undefined) {
+ link.__src_id = link.__src.id;
+ }
+ if (link.__dst.id !== undefined) {
+ link.__dst_id = link.__dst.id;
+ }
+ return link;
+ });
- if (false == finalize && comp.graph_same) {
- console.log('close enough');
- if (comp.old_name && comp.new_name) {
- up_to_two_renames(graph, comp.old_name, comp.new_name, ret.link_set_add);
- }
- } else {
- console.log('not close enough (or finalize)');
- // REINITIALISE GRAPH (DUMB BUT IT WORKS)
- //
- // FIXME: have temp nodes on a separete graph
- // finalize - take temp graph and create a single topo_diff
- // else - update temp graph with this topodiff, first clear it every time.
- graph.removeNodes("temp");
- graph.removeLinks("temp");
- }
+ // REINITIALISE GRAPH (DUMB BUT IT WORKS)
+ edit_graph.clear();
if (!finalize) {
- graph.markRelated(token_set_new_node_names);
+ main_graph.markRelated(token_set_new_node_names);
} else {
- graph.removeRelated();
+ main_graph.removeRelated();
}
if (finalize && backend_commit) {
- // broadcast diff:
- // - finalize?
- // - broadcast_diff requested by caller
- // drop bubble node
- ret.node_set_add = ret.node_set_add.filter(function(n) { return n.type != 'bubble'; });
- // drop and links
- ret.link_set_add = ret.link_set_add.filter(function(l) { return l.name !== 'and'; });
- graph.commit_and_tx_diff__topo(ret);
+ main_graph.commit_and_tx_diff__topo(ret);
} else {
- if (!comp.graph_same) {
- graph.commit_diff__topo(ret);
- }
+ edit_graph.commit_diff__topo(ret);
}
};
@@ -551,10 +465,10 @@ var textAnalyser = function (newtext, finalize) {
return ret;
};
-function init(graph)
+function init(main_graph)
{
// deal with new nodes
- graph.diffBus
+ main_graph.diffBus
.filter(function (diff) {
return diff.node_set_add && diff.node_set_add.length > 0;
})
@@ -562,12 +476,13 @@ function init(graph)
.flatMap(Bacon.fromArray)
.map(".name")
.map(function (name) { return name.toLowerCase(); })
+ // FIXME - should be removal of existing name via id, we know the id too.
.onValue(auto_suggest__update_name);
// deal with renamed links
/*
// TODO renamed links - broken in server, DBO_attr_diff_commit doesn't return an Attr_Diff
- graph.diffBus
+ main_graph.diffBus
.filter(function (diff) {
return diff && diff.link_set_rm && diff.link_set_rm.length > 0;
})
diff --git a/src/client/textanalysis.ui.js b/src/client/textanalysis.ui.js
index b06978c1..6e9709c0 100644
--- a/src/client/textanalysis.ui.js
+++ b/src/client/textanalysis.ui.js
@@ -65,7 +65,11 @@ function analyzeSentence(sentence, finalize)
break;
}
- ret.applyToGraph(rz_core.graph, rz_config.backend_enabled);
+ ret.applyToGraph({
+ main_graph: rz_core.main_graph,
+ edit_graph: rz_core.edit_graph,
+ backend_commit: rz_config.backend_enabled,
+ });
if (finalize || sentence.length == 0) {
typeselection.hide();
@@ -99,13 +103,13 @@ function changeType(arg) {
nodetype = (arg === 'up'? textanalysis.selected_type_next() : textanalysis.selected_type_prev());
if (arg === 'up') {
- rz_core.graph.editType(id, null, nodetype);
+ rz_core.edit_graph.editType(id, null, nodetype);
typeselection.showChosenType(nodetype);
- rz_core.graph.findCoordinates(id);
+ rz_core.edit_graph.findCoordinates(id);
} else {
- rz_core.graph.editType(id, null, nodetype);
+ rz_core.edit_graph.editType(id, null, nodetype);
typeselection.showChosenType(nodetype);
- rz_core.graph.findCoordinates(id);
+ rz_core.edit_graph.findCoordinates(id);
}
}
diff --git a/src/client/view/graph_view.js b/src/client/view/graph_view.js
new file mode 100644
index 00000000..baa321be
--- /dev/null
+++ b/src/client/view/graph_view.js
@@ -0,0 +1,492 @@
+/* view of a single graph in a graph tree.
+ *
+ * Usage is of a two graph system:
+ *
+ * <Master Graph>
+ *
+ * <Editing Graph>
+ *
+ * Nodes are drawn per graph.
+ * Edges connecting between the two graphs are drawn by the descendant graph.
+ *
+ * Updates are done using Bacon based events.
+ *
+ * TODO: fill in the event diagram here.
+ * TODO: do it via tests.
+ *
+ * Layout is either d3 force or custom. We use custom for the temporary graph.
+ *
+ * This avoids the need to compute similarity between two consecutive temporary graphs,
+ * i.e.
+ * #alon is #bore
+ * and
+ * #alon is #bored
+ *
+ * which resulted in overly complex (read: undefined/buggy) code.
+ */
+
+define(['d3', 'Bacon', 'util', 'view/selection', 'view/helpers', 'model/diff'],
+function(d3 , Bacon , util , selection , view_helpers, model_diff) {
+
+function GraphView(spec) {
+ var forceEnabled = spec.forceEnabled,
+ temporary = spec.forceEnabled, // graph wide variable, not per node/link
+ vis = spec.vis,
+ graph_name = spec.graph_name,
+ graph = spec.graph,
+ zoomProperty = spec.zoomProperty,
+ node_text_dx = spec.node_text_dx,
+ node_text_dy = spec.node_text_dy,
+
+ zoomInProgress = false,
+ force,
+ drag;
+
+ util.assert(vis !== undefined && graph_name !== undefined &&
+ graph !== undefined && zoomProperty !== undefined &&
+ (temporary !== undefined) && (forceEnabled !== undefined) &&
+ node_text_dx !== undefined && node_text_dy !== undefined,
+ "missing spec variable");
+
+ zoomProperty.onValue(function (val) {
+ zoomInProgress = val;
+ });
+
+ graph.diffBus.onValue(function (diff) {
+ var relayout = !temporary && (false == model_diff.is_attr_diff(diff));
+ if (relayout) {
+ console.log('****** ' + graph_name + ' graph relayout *******');
+ } else {
+ console.log('****** ' + graph_name + ' graph update *******');
+ }
+ console.dir(diff);
+ update_view(relayout);
+ });
+
+ 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};
+ if (forceEnabled) {
+ force.stop();
+ }
+ }
+
+ function dragged(d) {
+ d.x = d3.event.x;
+ d.y = d3.event.y;
+ tick();
+ }
+
+ function dragended(d) {
+ d3.select(this).classed("dragging", false);
+ d3.select(this).classed("fixed", true); // TODO: this is broken since we override all the classes. Need to switch to class addition/removal (i.e. use classed for everything) or set class in one location (so here just set a value on the node, not the element)
+ if (d.dragstart.clientX - d3.event.sourceEvent.clientX != 0 ||
+ d.dragstart.clientY - d3.event.sourceEvent.clientY != 0) {
+ tick();
+ if (forceEnabled) {
+ force.resume();
+ }
+ }
+ }
+
+
+ function update_view(relayout) {
+ var node,
+ link,
+ link_g,
+ linktext,
+ nodetext,
+ unselected_link_group = document.querySelector('#link-group'),
+ selected_link_group = document.querySelector('#selected-link-group');
+
+ relayout = relayout || true;
+
+ link = vis.selectAll("g.link")
+ .data(graph.links(), function(d) { return d.id; });
+
+ link_g = link.enter().append('g')
+ .attr('id', function(d){ return d.id; }) // append link id to enable data->visual mapping
+ .attr('class', 'link graph')
+
+ link_g.append("path")
+ .attr("class", function(d) {
+ return d.state + ' link graph';
+ })
+ .attr('id', function(d){ return d.id; }) // append link id to enable data->visual mapping
+ .attr("marker-end", "url(#end)");
+
+ // second path for larger click area
+ link_g.append("path")
+ .attr("class", "ghostlink")
+ .on("click", function(d, i) {
+ if (zoomInProgress) {
+ // don't disable zoomInProgress, it will be disabled by the svg_click_handler
+ // after this events bubbles to the svg element
+ return;
+ }
+ var that = this,
+ src = this.link.__src,
+ dst = this.link.__dst;
+
+ view.edge_info.on_delete(function () {
+ view.edge_info.hide();
+ graph.removeLink(that.link);
+ });
+ view.edge_info.show(d);
+ selection.update([src, dst]);
+ });
+
+ link.attr("class", function(d, i){
+ var temp_and = (d.name && d.name.replace(/ /g,"")=="and" && temporary) ? "temp_and" : "";
+
+ return ["graph link", temp_and, selection.selected_class(d)].join(' ');
+ });
+
+ link.selectAll('path.link')
+ .attr('class', function(d) {
+ return [d.state, selection.selected_class(d), "link graph"].join(' ');
+ });
+
+ link.exit().remove();
+
+ vis.selectAll('.ghostlink')
+ .data(graph.links())
+ .each(function (d) {
+ this.link = d;
+ });
+
+ linktext = vis.selectAll(".linklabel")
+ .data(graph.links(), function(d) { return d.id; });
+ linktext.enter()
+ .append("text")
+ .attr('id', function(d){ return d.id; }) // append link id to enable data->visual mapping
+ .attr("class", function(d) {
+ return ["linklabel graph", selection.selected_class(d)].join(' ');
+ })
+ .attr("text-anchor", "middle")
+ .on("click", function(d, i) {
+ if (!temporary) { // FIXME: if temporary don't even put a click handler
+ svgInput.enable(this, d);
+ }
+ });
+
+ linktext
+ .text(function(d) {
+ var name = d.name || "";
+ if (!(temporary ||
+ d.__src.state === "chosen" || d.__dst.state === "chosen")) {
+ return "";
+ }
+ if (name.length < 25 || d.__src.state === "chosen" ||
+ d.__dst.state === "chosen" || temporary) {
+ return name;
+ } else {
+ return name.substring(0, 14) + "...";
+ }
+ });
+
+ linktext.exit().remove();
+
+ node = vis.selectAll(".node")
+ .data(graph.nodes(), function(d) {
+ return d.id;
+ });
+ console.log("update update update update");
+ console.dir(node);
+
+ var nodeEnter = node.enter()
+ .append("g")
+ .attr('id', function(d){ return d.id; }) // append node id to enable data->visual mapping
+ .attr('visibility', 'hidden') // made visible on first tick
+ .call(drag);
+
+ // reorder nodes so selected are last, and so rendered last, and so on top.
+ // FIXME: with b-ubble removed this is probably broken. actually also before. links are not correctly ordered.
+ (function () {
+ var ontop = [];
+
+ node.each(function (d) {
+ this.node = d;
+ })
+ .attr('class', function(d) {
+ if (selection.node_selected(d)) {
+ ontop.push(this);
+ }
+ return ['node', selection.selected_class(d)].join(' ');
+ });
+ function reparent(new_parent, element) {
+ if (element.parentNode == new_parent) {
+ return;
+ }
+ new_parent.appendChild(element);
+ }
+ // move link to correct group
+ // O(|links|*|ontop|)
+ link.each(function (d) {
+ if (ontop.some(function (node) {
+ var d_node = node.node;
+ return d.__src == d_node || d.__dst == d_node;
+ }))
+ {
+ reparent(selected_link_group, this);
+ } else {
+ reparent(unselected_link_group, this);
+ }
+ });
+ linktext.each(function (d) {
+ if (selection.node_selected(d)) {
+ ontop.push(this);
+ }
+ });
+ function moveToEnd(e) {
+ e.parentNode.appendChild(e);
+ }
+ ontop.reverse().forEach(function (e) {
+ moveToEnd(e);
+ });
+ var count_links = function() {
+ return selected_link_group.childElementCount + unselected_link_group.childElementCount;
+ };
+ })();
+
+ nodetext = nodeEnter.insert("text")
+ .attr("class", "nodetext graph")
+ .attr("dx", node_text_dx)
+ .attr("dy", node_text_dy)
+ .on("click", function(d, i) {
+ if (d3.event.defaultPrevented) {
+ // drag happened, ignore click https://github.com/mbostock/d3/wiki/Drag-Behavior#on
+ return;
+ }
+ if (!temporary) {
+ svgInput.enable(this, d);
+ selection.update([d]);
+ showNodeInfo(this.parentNode.node, i);
+ }
+ d3.event.stopPropagation();
+ });
+
+ node.select('g.node text')
+ .text(function(d) {
+ if (!d.name) {
+ return "_";
+ }
+ if (temporary || d.state === 'chosen'
+ || d.state === "enter" || d.state === "exit") {
+ return d.name;
+ } else {
+ if (d.name.length < 28) {
+ return d.name;
+ } else {
+ return d.name.substring(0, 25) + "...";
+ }
+ }
+ });
+
+ circle = nodeEnter.insert("circle");
+ node.select('g.node circle')
+ .attr("class", function(d) {
+ return d.type + " " + d.state + " circle graph";
+ })
+ .attr("r", function(d) {
+ return view_helpers.customSize(d.type) - 2;
+ })
+ .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();
+ selection.update([d]);
+ if(!temporary) {
+ showNodeInfo(d, i);
+ }
+ });
+ circle.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 "res/img/check.png";
+ break;
+ case "current":
+ return "res/img/wait.png";
+ break;
+ case "waiting":
+ return "res/img/cross.png";
+ break;
+ }
+ });
+
+ 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 === "third-internship-proposal") {
+ deliverables.push({
+ "id": nodes[i].id,
+ "startdate": nodes[i].start,
+ "enddate": nodes[i].end
+ });
+ }
+ //Do something
+ }
+
+ if (forceEnabled) {
+ force.nodes(graph.nodes())
+ .links(graph.links())
+
+ if (relayout) {
+ force.alpha(0.1).start();
+ } else {
+ // XXX If we are stopped we need to update the text of the links at least,
+ // and this is the simplest way
+ tick();
+ }
+ }
+ }
+
+ var debug_print = function(message) {
+ var element = $(".debug");
+ if (element.length == 1) {
+ element.html(message);
+ } else {
+ console.log(message);
+ }
+ }
+
+ function check_for_nan(x) {
+ if (Number.isNaN(x)) {
+ console.log('nan problem');
+ if (forceEnabled) {
+ force.stop();
+ }
+ }
+ return Number.isNaN(x);
+ }
+
+ var newnodes=1;
+
+ function temporary_set_positions() {
+ //circles animation
+ var tempcounter = 0,
+ temptotal = graph.nodes().filter(function(d){
+ return temporary && d.type !== "chainlink";
+ }).length;
+
+ if (temptotal !== newnodes) {
+ newnodes += temptotal / 15 / (newnodes * newnodes);
+ }
+ newnodes = Math.max(1, Math.min(newnodes, temptotal));
+ graph.nodes().forEach(function(d, i) {
+ var r, a;
+ tempcounter++;
+ if (d.type==="chainlink") {
+ d.x = window.innerWidth / 2;
+ d.y = window.innerHeight / 2;
+ } else {
+ r = 60 + newnodes * 20;
+ a = -Math.PI + Math.PI * 2 * (tempcounter-1) / newnodes + 0.3;
+ d.x = window.innerWidth / 2 + r * Math.cos(a);
+ d.y = window.innerHeight / 2 + r * Math.sin(a);
+ }
+ check_for_nan(d.x);
+ check_for_nan(d.y);
+ });
+ }
+
+ function tick(e) {
+ //console.log(e);
+ //$(".debug").html(force.alpha());
+ var node = vis.selectAll(".node")
+ .data(graph.nodes(), function(d) {
+ return d.id;
+ });
+ var link = vis.selectAll("path.link")
+ .data(graph.links(), function(d) {
+ return d.id;
+ });
+ var linktext = vis.selectAll(".linklabel").data(graph.links());
+
+ function transform(d) {
+ if (check_for_nan(d.x) || check_for_nan(d.y)) {
+ return;
+ }
+ return "translate(" + d.x + "," + d.y + ")";
+ }
+
+ if (temporary) {
+ temporary_set_positions();
+ }
+
+ link.attr("d", function(d, i) {
+ var d_val,
+ ghost;
+
+ var dx = d.__dst.x - d.__src.x,
+ dy = d.__dst.y - d.__src.y,
+ dr = Math.sqrt(dx * dx + dy * dy);
+ d_val = "M" + d.__src.x + "," + d.__src.y + "L" + d.__dst.x + "," + d.__dst.y;
+ // update ghostlink position
+ ghost = $(this.nextElementSibling);
+ ghost.attr("d", d_val);
+ return d_val;
+ });
+
+ linktext.attr("transform", function(d) {
+ return "translate(" + (d.__src.x + d.__dst.x) / 2 + "," + (d.__src.y + d.__dst.y) / 2 + ")";
+ });
+
+ node.attr("transform", transform);
+
+ // After initial placement we can make the nodes visible.
+ //links.attr('visibility', 'visible');
+ node.attr('visibility', 'visible');
+ }
+
+ // SVG rendering order is last rendered on top, so to make sure
+ // all links are below the nodes we group them under a single g
+ vis.append("g").attr("id", "link-group");
+ vis.append("g").attr("id", "selected-link-group");
+
+ drag = d3.behavior.drag()
+ .origin(function(d) { return d; })
+ .on("dragstart", dragstarted)
+ .on("drag", dragged)
+ .on("dragend", dragended);
+
+ function init_force_layout(){
+ var el = document.body;
+ var w = $(el).innerWidth(),
+ h = $(el).innerHeight();
+
+ force = d3.layout.force()
+ .distance(120)
+ .gravity(0.12)
+ .charge(-1800)
+ .size([w, h])
+ .on("tick", tick)
+ .start();
+ }
+
+ if (forceEnabled) {
+ init_force_layout();
+ }
+ return {
+ update_view: update_view,
+ };
+}
+
+return {
+ GraphView: GraphView,
+};
+
+});
diff --git a/src/client/view/helpers.js b/src/client/view/helpers.js
index ad11763a..f01f812f 100644
--- a/src/client/view/helpers.js
+++ b/src/client/view/helpers.js
@@ -31,9 +31,6 @@ function customColor(type) {
case "chainlink":
color = "#363636"; //dark-grey
break;
- case "bubble":
- color = "rgba(255,255,255,0.2)"; // white, 0.2 opaque
- break;
default:
console.log('bug: unknown type ' + type);
color = '#d4d4d9'; //mid-light grey
@@ -69,9 +66,6 @@ function customSize(type) {
case "chainlink":
size = 8;
break;
- case "bubble":
- size = 180;
- break;
default:
size = 9;
break;
diff --git a/src/client/view/selection.js b/src/client/view/selection.js
index 9a87494a..a45f4b2b 100644
--- a/src/client/view/selection.js
+++ b/src/client/view/selection.js
@@ -6,7 +6,7 @@ function get_rz_core()
// circular dependency on rz_core, so require.js cannot solve it.
if (rz_core === undefined) {
rz_core = require('rz_core');
- listen_on_diff_bus(rz_core.graph.diffBus);
+ listen_on_diff_bus(rz_core.main_graph.diffBus);
}
return rz_core;
}
@@ -59,14 +59,14 @@ function updateSelectedNodesBus(new_selected_nodes)
}
function byVisitors(node_selector, link_selector) {
- var new_selected_nodes = get_rz_core().graph.findByVisitors(node_selector, link_selector);
+ var new_selected_nodes = get_rz_core().main_graph.findByVisitors(node_selector, link_selector);
clear();
connectedComponent(new_selected_nodes);
}
function connectedComponent(nodes) {
- var connected = get_rz_core().graph.getConnectedNodesAndLinks(nodes, 1),
+ var connected = get_rz_core().main_graph.getConnectedNodesAndLinks(nodes, 1),
i,
node,
link,
@@ -112,7 +112,7 @@ var selected_class = function(node) {
var clear = function() {
selected_nodes.length = 0;
- get_rz_core().graph.setRegularState();
+ get_rz_core().main_graph.setRegularState();
}
function arr_compare(a1, a2)