summaryrefslogtreecommitdiff
path: root/src/client/rz_core.js
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/client/rz_core.js
parentd50ec127af2fe7c7276380e10fce00496580fd58 (diff)
initial graph_view introduction - mixing circle & force layouts
Diffstat (limited to 'src/client/rz_core.js')
-rw-r--r--src/client/rz_core.js529
1 files changed, 67 insertions, 462 deletions
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);
},