diff options
| author | Alon Levy <alon@pobox.com> | 2014-12-30 10:31:39 +0200 |
|---|---|---|
| committer | Alon Levy <alon@pobox.com> | 2015-01-05 13:23:57 +0200 |
| commit | 54aa65a3dab029f28741875eb11a40adb06aff51 (patch) | |
| tree | 872e9c2723644795d5ac633ec9ae7820f8e3f69d /src/client | |
| parent | e1203339003c32eb646b199d2ae69b7082228cc9 (diff) | |
introduce commit_and_tx_diff__topo & commit_diff__topo
Multiple fixes / changes. _bugs introduced_
Changes:
graph has two change functions (i.e. the internal API):
- commit_and_tx_diff_topo
This currently transmits, later will optimistically apply first
- commit_diff__topo
This only applies. Used on the ajax success path for commit_and_tx_diff__topo
and when changes are only local.
It signals on the diffBus
- diffBus - a Bacon.Bus that all UI listens to
- selection UI
- node/link dialogs
- main view (rz_core/update_view__graph)
Small changes:
whitespace
rename filer->filter
Completely broken:
- text entry. should use commit_diff__topo
Problems introduced:
selection doesn't work correctly.
editing not verified yet
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/model/graph.js | 246 | ||||
| -rw-r--r-- | src/client/rz_core.js | 95 | ||||
| -rw-r--r-- | src/client/textanalysis.js | 73 | ||||
| -rw-r--r-- | src/client/textanalysis.ui.js | 1 | ||||
| -rw-r--r-- | src/client/view/selection.js | 39 |
5 files changed, 228 insertions, 226 deletions
diff --git a/src/client/model/graph.js b/src/client/model/graph.js index 12190cd7..b97fd117 100644 --- a/src/client/model/graph.js +++ b/src/client/model/graph.js @@ -9,7 +9,10 @@ function Graph() { var nodes = [], id_to_node_map = {}, + id_to_node_index_map = {}, links = [], + id_to_link_map = {}, + id_to_link_index_map = {}, diffBus = new Bacon.Bus(); // All operations done on the graph. When the server is used (i.e. always) this @@ -29,18 +32,36 @@ function Graph() { */ this.addNode = function(spec) { var node = this.__addNode(spec); - if (node) { - return node; - } + return node; + } + + var nodes_to_touched_links = function (node_id_set) { + var touched_links = []; + + node_id_set.forEach(function (n_id) { + var n = id_to_node_map[n_id]; + links.forEach(function (link) { + if ((link['__src'].equals(n)) || (link['__dst'].equals(n))) { // compare by id + touched_links.push(link); + } + }); + }); + console.dir(touched_links); + + return touched_links.map(function(l){ return l.id; }); } /** * * @param a topo_diff but that might be missing a few things, sanitize it first. * all sanitation should be idempotent, but probably isn't. + * + * NOTE: currently this function transmits only. Later we want to optimistically + * first commit and then transmit. */ - this.commit_diff__topo = function (topo_diff, on_success, on_error) { + this.commit_and_tx_diff__topo = function (topo_diff) { var name_to_node = {}; + $.merge(topo_diff.link_set_rm, nodes_to_touched_links(topo_diff.node_set_rm)); topo_diff.node_set_add = topo_diff.node_set_add.map(function(n) { if (n.id === undefined) { var existing = findNodeByName(n.name); @@ -83,16 +104,13 @@ function Graph() { topo_diff.node_set_add = topo_diff.node_set_add.filter(function(n) { return !hasNodeByName(n.name); }); + var graph_on_success = function(diff) { - on_backend__diff(diff); - if (on_success) { - on_success(nodes); - } + commit_diff__topo(diff); } var graph_on_error = function(error) { - if (on_error) { - on_error(error); - } + console.log('error:'); + console.dir(error); } console.log("COMMIT DIFF TOPO"); console.dir(topo_diff); @@ -136,8 +154,7 @@ function Graph() { } util.assert(undefined != node.id, '__addNode: node id missing'); - nodes.push(node); - id_to_node_map[node.id] = node; + _node_add_helper(node); console.log('__addNode: node added: id: ' + node.id + ' state ' + node.state + (rz_config.backend_enabled && peer_notify ? ' _commit_ ' : '')); @@ -160,70 +177,45 @@ function Graph() { } this.__addNode = __addNode; - this._remove_node_set = function(ns, peer_notify) { - - peer_notify = undefined === peer_notify ? true : peer_notify; - - var cascade_link_rm_set = [], // track cascading link removals - has_non_temp = false; - for (var j = 0; j < ns.length; j++) { - var n = ns[j]; - var i = 0; - if (n.state != 'temp') { - has_non_temp = true; - } - while (i < links.length) { - var link = links[i]; - if ((link['__src'].equals(n)) || (link['__dst'].equals(n))) { // compare by id - links.splice(i, 1); - cascade_link_rm_set.push(link); - } - else { - i++; - } - } - var index = findNodeIndex(n.id, n.state); - if (index !== undefined) { - nodes.splice(index, 1); - - util.assert(undefined != n.id, '_remove_node_set: node id missing'); - delete id_to_node_map[n.id]; - } - } + var _node_remove_helper_by_index = function (i) { + var id = nodes[i].id; + delete id_to_node_index_map[id]; + delete id_to_node_map[id]; + nodes.splice(i, 1); + } - cascade_link_rm_set.forEach(function(n){ - console.log('_remove_node_set: removed node: id: ' + n.id); - }); + var _node_add_helper = function (node) { + id_to_node_map[node.id] = node; + id_to_node_index_map[node.id] = nodes.length; + nodes.push(node); + util.assert(nodes[id_to_node_index_map[node.id]] === node, "master?"); + } - if (rz_config.backend_enabled && peer_notify && has_non_temp) { - var topo_diff = model_diff.new_topo_diff({ - node_set_rm : ns.map(function(n){ return n.id; }), - link_set_rm : cascade_link_rm_set.map(function(l){ return l.id; }), - }); - var on_success = function(){ - // FIXME: handle possible outcomes: - // - rm cascade of connected links - }; - var on_error = function(){ - // TODO: add problem emblem to node - }; - rz_api_backend.commit_diff__topo(topo_diff, on_success, on_error); - } + var _link_remove_helper_by_index = function (i) { + var id = links[i].id; + delete id_to_link_index_map[id]; + delete id_to_link_map[id]; + links.splice(i, 1); } - this.removeNode = function(id) { - var n = find_node__by_id(id); - if (n === undefined) { - console.log('bug: nonexistent node cannot be removed: ' + id); - return; - } - this._remove_node_set([n]); + var _link_add_helper = function (link) { + id_to_link_map[link.id] = link; + id_to_link_index_map[link.id] = links.length; + links.push(link); + util.assert(links[id_to_link_index_map[link.id]] === link, "whaaa?"); } - this.removeNodes = function(n_filer) { - var ns = find_node_set_by_filer(n_filer); - this._remove_node_set(ns); + var _remove_node_set = function(node_id_set) { + node_id_set.forEach(function (id) { + if (undefined === id_to_node_map[id]) { + console.log("warning: server returned an id we don't have " + id); + return; + } + _node_remove_helper_by_index(id_to_node_index_map[id]); + console.log('_remove_node_set: ' + id); + }); } + this._remove_node_set = _remove_node_set; /** * @@ -417,7 +409,7 @@ function Graph() { if (undefined == existing_link) { - links.push(link); + _link_add_helper(link); if (rz_config.backend_enabled && peer_notify){ var topo_diff = model_diff.new_topo_diff({ @@ -451,15 +443,15 @@ function Graph() { attr_diff.add_link_attr_write(link.id, key, new_link_spec[key]); } - var on_ajax_success = function(id_to_link_map){ + var on_ajax_success = function(id_to_link_map) { var link_id = link.id; // original link id - if (id_to_link_map[link_id].id != link_id){ + if (id_to_link_map[link_id].id != link_id) { // TODO: handle incoming ID update util.assert(false, 'update_link: id attr change'); } var ret_link = id_to_link_map[link_id]; - for (var key in ret_link){ + for (var key in ret_link) { if ('id' == key){ continue; } @@ -575,8 +567,17 @@ function Graph() { return this._editProperty(id, state, 'type', newtype); } + function new_attr_diff_prop_value(id, prop, value) + { + var diff = rz_diff.new_attr_diff(); + + diff.add_node_attr_write(id, prop, value); + return diff; + } + this._editProperty = function(id, state, prop, value) { var n = find_node__by_id(id); + if (state !== null && state != n.state){ return false; } @@ -589,6 +590,7 @@ function Graph() { console.log('warning: I hope robot is doing this not-through-backend property change'); } n[prop] = value; + diffBus.push(new_attr_diff_prop_value(id, prop, value)); return true; } @@ -601,56 +603,34 @@ function Graph() { } } - var linkGetIndexFromId = function(link_id) { - for (var i = 0 ; i < links.length ; ++i) { - if (links[i].id == link_id) { - return i; - } - } - } + this.removeLink = function(link) { - this.removeLink = function(link, on_success, on_error) { - var i; + util.assert(link.id !== undefined, 'bug: link without an id'); - if (link.id === undefined) { - console.log('bug: link without an id'); - } + this._remove_link_set([link]); + } - function graph_on_success(ret) { - ret.link_rm.forEach(function (link_id) { - links.splice(linkGetIndexFromId(link_id), 1); - }); - if (on_success) { - on_success(); + var _remove_link_set = function(link_id_set) { + link_id_set.forEach(function (id) { + var index = id_to_link_index_map[id]; + if (undefined === index) { + console.log("warning: server returned an id we don't have " + id); + return; } - } + _link_remove_helper_by_index(index); + console.log('_remove_link_set: ' + id); + }); + } + this._remove_link_set = _remove_link_set; - for (i = 0 ; i < links.length; ++i) { - if (link.id !== undefined) { - if (link.id === links[i].id) { - if (link.state == 'temp') { - links.splice(i, 1); - if (on_success) { - on_success(); - } - } else { - if (rz_config.backend_enabled) { - var topo_diff = model_diff.new_topo_diff({ - link_set_rm: [link.id] - }); - rz_api_backend.commit_diff__topo(topo_diff, graph_on_success, on_error); - } - } - return; - } - } else { - if (link.__src.id === links[i].__src.id && link.__dst.id === links[i].__dst.id) { - links.splice(i, 1); - return; - } - } - } - console.log('bug: attempt to remove non existant link'); + this.removeNodes = function(state) { + var temp_node_ids = 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, + }); + + this.commit_and_tx_diff__topo(topo_diff); } this.removeLinks = function(state) { @@ -722,9 +702,9 @@ function Graph() { } /** - * @param filer: must return true in order for node to be included in the returned set + * @param filter: must return true in order for node to be included in the returned set */ - var find_node_set_by_filer = function(filter) { + var find_node_set_by_filter = function(filter) { var ret = []; nodes.map(function(n){ if (true == filter(n)){ @@ -752,13 +732,6 @@ function Graph() { return foundNodes; } - var findNodeIndex = function(id, state) { - for (var i = 0; i < nodes.length; i++) { - if ((id && nodes[i].id === id) || (state && nodes[i].state === state)) - return i; - }; - } - function clear() { nodes.length = 0; links.length = 0; @@ -801,15 +774,18 @@ function Graph() { delete l_ptr.__src_id; delete l_ptr.__dst_id; var link_spec = l_ptr; + var link_spec = l_ptr; var link = model_core.create_link_from_spec(src, dst, link_spec); var l = addLink(link, false); } - function on_backend__diff(data) { - data['node_set'].map(on_backend__node_add); - - data['link_set'].map(on_backend__link_add); - diffBus.push(data); + function commit_diff__topo(diff) { + // done under protest + diff.node_set_add.map(on_backend__node_add); + diff.link_set_add.map(on_backend__link_add); + _remove_link_set(diff.link_set_rm); + _remove_node_set(diff.node_set_rm); + diffBus.push(diff); } /** @@ -820,8 +796,8 @@ function Graph() { // @ajax-trans function load_from_backend(on_success) { - function on_success__ajax(data) { - on_backend__diff(data); + function on_success__ajax(diff) { + commit_diff__topo(diff); undefined != on_success && on_success() } diff --git a/src/client/rz_core.js b/src/client/rz_core.js index f0cfb8a9..91a25196 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'], -function($, d3, consts, rz_bus, util, model_graph, model_core, view_helpers, view, rz_observer, selection, rz_config, rz_mesh) { +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) { var addednodes = [], vis, @@ -76,15 +76,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}, function() { - update_view__graph(true); - }); + graph.update_link(d, {name: newname}); } else { - graph.update_node(d, {name: newname}, function() { - update_view__graph(true); - }); - // TODO - 'updating' graphic + // TODO - display hourglass // TODO - use promises to make A follows B readable. + graph.update_node(d, {name: newname}); } } hide(); @@ -221,9 +217,21 @@ var initDrawingArea = function () { graph = new model_graph.Graph(); graph.diffBus.onValue(function (diff) { - var relayout = false == rz_diff.is_attr_diff(diff); + var relayout = (false == model_diff.is_attr_diff(diff)); + console.log('*************'); + console.dir(diff); update_view__graph(relayout); }); + // TODO: we are listening both on graph.diffBus and selection.selectionChangedBus, + // but the relation is actually: + // + // diffBus -> SelectedNodesBus -> us + // diffBus -> us + // + // we need to deduplicate this event + // but there is no coordination, resulting in double updates. + selection.selectionChangedBus.onValue( + function() { update_view__graph(false); }); var user_id = $('#user_id'), user = user_id.text(); @@ -277,9 +285,7 @@ var initDrawingArea = function () { // $('#canvas_d3').dblclick(canvas_handler_dblclick); - see #138 if (rz_config.backend_enabled){ - graph.load_from_backend( function(){ - update_view__graph(false); - }); + graph.load_from_backend(); } } @@ -333,8 +339,9 @@ function canvas_handler_dblclick(){ var n = model_core.create_node__set_random_id(); n.name = ''; // will be set by user - graph.addNode(n); - update_view__graph(); + graph.commit_and_tx_diff__topo(model_diff.new_topo({ + node_set_add : [n].map(model_util.adapt_format_write_node), + })); var n_ve = locate_visual_element(n); // locate visual element @@ -357,14 +364,15 @@ function canvas_handler_dblclick(){ /** * update view: graph */ -function update_view__graph(no_relayout) { +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'); + selected_link_group = document.querySelector('#selected-link-group'), + relayout = relayout || true; link = vis.selectAll("g.link") .data(graph.links(), function(d) { return d.id; }); @@ -395,15 +403,10 @@ function update_view__graph(no_relayout) { view.edge_info.on_delete(function () { view.edge_info.hide(); - graph.removeLink(that.link, function() { - update_view__graph(true); - }, function() { - console.log("error: could not remove link"); - }); + graph.removeLink(that.link); }); view.edge_info.show(d); selection.update([src, dst]); - update_view__graph(true); }); link.attr("class", function(d, i){ @@ -621,12 +624,12 @@ function update_view__graph(no_relayout) { force.nodes(graph.nodes()) .links(graph.links()) - if (no_relayout) { + 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(); - } else { - force.alpha(0.1).start(); } } @@ -722,31 +725,39 @@ function tick(e) { node.attr('visibility', 'visible'); } -function showNodeInfo(d, i) { - view.node_info.on_save(function(e, form_data) { +function showNodeInfo(node, i) { + var closed = false; - graph.update_node(d, form_data, function(){ - var old_type = d.type, + view.node_info.on_save(function(e, form_data) { + closed = true; + graph.update_node(node, form_data, function() { + var old_type = node.type, new_type = form_data.type; - if (new_type != old_type) { - view.node_info.show(d); - } - - view.node_info.hide(); - update_view__graph(true); }); - + view.node_info.hide(); return false; }); + // FIXME - attribute diff, ignore uninteresting diffs via filtering + graph.diffBus.onValue(function () { + if (closed) { + return Bacon.noMore; + } + view.node_info.show(node); + }); + view.node_info.on_delete(function() { - graph.removeNode(d.id); // async - update_view__graph(false); + var topo_diff = model_diff.new_topo_diff({ + node_set_rm: [node.id] + }); + console.log("closing node info"); + closed = true; view.node_info.hide(); + graph.commit_and_tx_diff__topo(topo_diff); }); - view.node_info.show(d); + view.node_info.show(node); } function svg_click_handler(e) { @@ -760,7 +771,7 @@ function svg_click_handler(e) { svgInput.hide(); selection.clear(); view.hide(); - update_view__graph(true); + update_view__graph(false); } return { @@ -769,7 +780,7 @@ return { load_from_json: function(result) { graph.load_from_json(result); recenterZoom(); - update_view__graph(false); + update_view__graph(true); }, update_view__graph : update_view__graph, } diff --git a/src/client/textanalysis.js b/src/client/textanalysis.js index 5e78f416..8ee4b05f 100644 --- a/src/client/textanalysis.js +++ b/src/client/textanalysis.js @@ -68,7 +68,8 @@ var typeStack = []; var lastnode; -var sugg = {}, // suggestions for autocompletion of node names +var sugg_name = {}, + id_to_name_map = {}, suggestions_options = new Bacon.Bus(); // TODO: Property: same as bus, but with initial value var ANALYSIS_NODE_START = 'ANALYSIS_NODE_START'; @@ -80,16 +81,27 @@ function selectedType() return nodetypes[typeindex]; } -function autoSuggestAddName(name) +/* + * id - undefined | node id + * + */ +function auto_suggest__update_name(name, id) { + if (id !== undefined && id_to_name_map[id] !== undefined) { + delete sugg[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); } -function autoSuggestRemoveName(name) +function auto_suggest_remove_name(name, id) { delete sugg[name]; + if (id !== undefined) { + delete sugg[id]; + } suggestions_options.push(sugg); } @@ -349,7 +361,7 @@ var textAnalyser = function (newtext, finalize) { if (finalize === true) { typesetter = "perm"; for (n = 0; n < token_set_new_node_names.length; n++) { - autoSuggestAddName(token_set_new_node_names[n]); + auto_suggest__update_name(token_set_new_node_names[n]); } } else { typesetter = "temp"; @@ -467,7 +479,7 @@ var textAnalyser = function (newtext, finalize) { } } else { // REINITIALISE GRAPH (DUMB BUT IT WORKS) - graph.removeNodes(function(n){ return "temp" == n.state; }); + graph.removeNodes("temp"); graph.removeLinks("temp"); if (!finalize) { // finalize done via topo diff below @@ -504,22 +516,12 @@ var textAnalyser = function (newtext, finalize) { // broadcast diff: // - finalize? // - broadcast_diff requested by caller - var on_success = function (ret) { - console.dir(ret); - rz_core.update_view__graph(); - } - var on_error = function () { - console.log('askeeeeeeeeeeeeew'); - } // 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_diff__topo(ret, on_success, on_error); + graph.commit_and_tx__topo(ret); } - - // update graph if not going through backend - rz_core.update_view__graph(!finalize && comp.graph_same); }; if (finalize) { @@ -532,24 +534,31 @@ var textAnalyser = function (newtext, finalize) { function init(graph) { // deal with new nodes - graph.diffBus.filter(function (diff) { - return diff.nodes && diff.nodes.added; - }).map(function (diff) { - return diff.nodes.added.toLowerCase(); - }).onValue(autoSuggestAddName); + graph.diffBus + .filter(function (diff) { + return diff.node_set_add && diff.node_set_add.length > 0; + }) + .map(".node_set_rm") + .flatMap(Bacon.fromArray) + .map(".name") + .map(function (name) { return name.toLowerCase(); }) + .onValue(auto_suggest__update_name); // deal with renamed links - graph.diffBus.filter(function (diff) { - return diff && diff.changed && diff.changed.links; - }).map(function (diff) { - return diff.changed.links; - }).flatMap(Bacon.fromArray) - .onValue(function (diff) { - console.log('renamed link ' + diff.removed + ' -> ' + diff.added); - autoSuggestRemoveName(diff.removed.toLowerCase()); - autoSuggestAddName(diff.added.toLowerCase()); - }); - + /* + // TODO renamed links - broken in server, DBO_attr_diff_commit doesn't return an Attr_Diff + graph.diffBus + .filter(function (diff) { + return diff && diff.link_set_rm && diff.link_set_rm.length > 0; + }) + .map(".link_set_rm") + .flatMap(Bacon.fromArray) + .onValue(function (name) { + var name = TODO, + id = TODO; + auto_suggest__update_name(name, id); + }); + */ // TODO renamed nodes, plus reuse part of the pipeline. } diff --git a/src/client/textanalysis.ui.js b/src/client/textanalysis.ui.js index a3b3becf..b06978c1 100644 --- a/src/client/textanalysis.ui.js +++ b/src/client/textanalysis.ui.js @@ -107,7 +107,6 @@ function changeType(arg) { typeselection.showChosenType(nodetype); rz_core.graph.findCoordinates(id); } - rz_core.update_view__graph(true); } return { diff --git a/src/client/view/selection.js b/src/client/view/selection.js index 27842e11..9a87494a 100644 --- a/src/client/view/selection.js +++ b/src/client/view/selection.js @@ -1,17 +1,31 @@ -define(['rz_core'], -function(rz_core) { +define(['rz_core', 'Bacon'], +function(rz_core, Bacon) { function get_rz_core() { // circular dependency on rz_core, so require.js cannot solve it. if (rz_core === undefined) { rz_core = require('rz_core'); - rz_core.graph.diffBus.onValue(updateSelectionOnDiff); + listen_on_diff_bus(rz_core.graph.diffBus); } return rz_core; } -var selected_nodes = []; +var selected_nodes = [], + selectionChangedBus = new Bacon.Bus(); + +function listen_on_diff_bus(diffBus) +{ + diffBus + .filter(".node_set_rm") + .onValue(function (diff) { + var node_node_cmp = (function (a, b) { return a.id > b.id; }), + node_id_cmp = (function (a, b) { return a.id === b ? 0 : (a.id > b ? 1 : -1); }); + + updateSelectedNodesBus(sortedArrayDiff(selected_nodes.sort(node_node_cmp), + diff.node_set_rm.sort(), node_id_cmp)); + }); +} function sortedArrayDiff(a, b, a_cmp_b) { @@ -38,18 +52,10 @@ function sortedArrayDiff(a, b, a_cmp_b) return ret; } -function updateSelectionOnDiff(diff) +function updateSelectedNodesBus(new_selected_nodes) { - var node_node_cmp = (function (a, b) { return a.id > b.id; }), - node_id_cmp = (function (a, b) { return a.id === b ? 0 : (a.id > b ? 1 : -1); }); - - if (diff.nodes.removed === undefined || selected_nodes.length == 0) { - return; - } - console.log("selection enter: " + String(selected_nodes.map(function(x) { return x.id; }))); - console.log("removed nodes enter: " + String(diff.nodes.removed)); - selected_nodes = sortedArrayDiff(selected_nodes.sort(node_node_cmp), diff.nodes.removed.sort(), node_id_cmp); - console.log("selection exit: " + String(selected_nodes.map(function(x) { return x.id; }))); + selected_nodes = new_selected_nodes; + selectionChangedBus.push(selected_nodes); } function byVisitors(node_selector, link_selector) { @@ -66,7 +72,7 @@ function connectedComponent(nodes) { link, data; - selected_nodes = nodes.map(function(x) { return x; }); + updateSelectedNodesBus(nodes.map(function(x) { return x; })); for (i = 0 ; i < connected.nodes.length ; ++i) { data = connected.nodes[i]; @@ -137,6 +143,7 @@ return { update: update, selected_class: selected_class, node_selected: node_selected, + selectionChangedBus: selectionChangedBus, }; }); |
