From 48a98c69202bb10bd2cb83f6af9fdf3458de77b0 Mon Sep 17 00:00:00 2001 From: Aaron Gussman Date: Wed, 3 Oct 2018 00:20:59 -0400 Subject: [PATCH] Instead of lines between edges, use paths. For each link/edge between nodes, calculate a linknum. This is used to adjust the arc between nodes so that multiple edges between nodes don't overlap. in graphConf there's a use_curved_edges variable. Setting to true will enable curved edges. Tweaked the CSS so that paths display and aren't filled. I also auto-linted graph_viz.js at some point which made a bunch of spacing changes, didn't realize at the time that this might be an issue. --- css/graphStyle.css | 8 +- scripts/graphConf.js | 1 + scripts/graphShapes.js | 2 +- scripts/graph_viz.js | 382 ++++++++++++++++++++++------------------- 4 files changed, 218 insertions(+), 175 deletions(-) diff --git a/css/graphStyle.css b/css/graphStyle.css index 2ebf745..9eeb10b 100644 --- a/css/graphStyle.css +++ b/css/graphStyle.css @@ -49,12 +49,18 @@ fill: none; pointer-events: all; } -/* + .links line { stroke: #999; stroke-opacity: 0.6; } +path { + fill: none; +} + +/* + .nodes circle { stroke: #fff; stroke-width: 1.5px; diff --git a/scripts/graphConf.js b/scripts/graphConf.js index 0a3c8cd..a0f2b0e 100644 --- a/scripts/graphConf.js +++ b/scripts/graphConf.js @@ -39,3 +39,4 @@ const node_position_y = 'graphexpy' const default_edge_stroke_width = 3; const default_edge_color = "#CCC"; const edge_label_color = "#111"; +const use_curved_edges = true; diff --git a/scripts/graphShapes.js b/scripts/graphShapes.js index 376f092..b9eac30 100644 --- a/scripts/graphShapes.js +++ b/scripts/graphShapes.js @@ -179,7 +179,7 @@ var graphShapes = (function(){ function decorate_link(edges,edgepaths,edgelabels){ - var edges_deco = edges.append("line").attr("class", "edge").classed("active_edge",true) + var edges_deco = edges.append("path").attr("class", "edge").classed("active_edge",true) .attr("source_ID",function(d) { return d.source;}) .attr("target_ID",function(d) { return d.target;}) .attr("ID",function(d) { return d.id;}); diff --git a/scripts/graph_viz.js b/scripts/graph_viz.js index 8fffc03..65a382c 100644 --- a/scripts/graph_viz.js +++ b/scripts/graph_viz.js @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Main module. Handle the viualization, display data and layers. +// Main module. Handle the visualization, display data and layers. -var graph_viz = (function(){ +var graph_viz = (function () { "use strict"; var _svg = {}; @@ -32,53 +32,53 @@ var graph_viz = (function(){ - function init(label){ + function init(label) { _svg = d3.select(label).select("svg"); _svg_width = +d3.select(label).node().getBoundingClientRect().width _svg_height = +d3.select(label).node().getBoundingClientRect().height; - _svg.attr("width",_svg_width).attr("height",_svg_height); + _svg.attr("width", _svg_width).attr("height", _svg_height); //console.log([_svg_width,_svg_height]) } - function get_simulation_handle(){ + function get_simulation_handle() { return _simulation; } - function svg_handle(){ + function svg_handle() { return _svg; } - function nodes(){ + function nodes() { return _nodes; } - function nodes_data(){ + function nodes_data() { return _Nodes; } - function node_data(id){ + function node_data(id) { // return data associated to the node with id 'id' - for (var node in _Nodes){ + for (var node in _Nodes) { //console.log(_Nodes[node]) - if (_Nodes[node].id==id){ + if (_Nodes[node].id == id) { var match = _Nodes[node]; break; } - } + } return match; } - function links(){ + function links() { return _links; } - function links_data(){ + function links_data() { return _Links; } - - function create_arrows(edge_in){ + + function create_arrows(edge_in) { var edge_data = edge_in.data(); var arrow_data = _svg.selectAll('.arrow').data(); var data = arrow_data.concat(edge_data); @@ -86,41 +86,41 @@ var graph_viz = (function(){ _svg.selectAll('.arrow') .data(data) .enter() - .append('marker') - .attr('class','arrow') - .attr('id', function(d){return 'marker_' + d.id}) + .append('marker') + .attr('class', 'arrow') + .attr('id', function (d) { return 'marker_' + d.id }) .attr('markerHeight', 5) .attr('markerWidth', 5) .attr('markerUnits', 'strokeWidth') .attr('orient', 'auto') - .attr('refX', function (d){ - var node = node_data(d.target); - return graphShapes.node_size(node)+graphShapes.node_stroke_width(node); - }) + .attr('refX', function (d) { + var node = node_data(d.target); + return graphShapes.node_size(node) + graphShapes.node_stroke_width(node); + }) .attr('refY', 0) - .attr('viewBox', "0 -5 10 10") + .attr('viewBox', "0 -5 10 10") .append('svg:path') .attr('d', "M0,-5L10,0L0,5") - .style('fill',function(d){return graphShapes.edge_color(d)}); + .style('fill', function (d) { return graphShapes.edge_color(d) }); } /////////////////////////////////////// // Remove force layout and data - function clear(){ + function clear() { console.log(_simulation) - if (Object.keys(_simulation).length != 0){ + if (Object.keys(_simulation).length != 0) { _simulation.stop(); _simulation.nodes([]); _simulation.force("link").links([]); } _svg.selectAll("*").remove(); - _Nodes = [],_Links =[]; + _Nodes = [], _Links = []; layers.clear_old(); _simulation = {}; } - function addzoom (){ + function addzoom() { // Add zoom to the svg object _svg.append("rect") .attr("width", _svg_width).attr("height", _svg_height) @@ -135,69 +135,94 @@ var graph_viz = (function(){ } ////////////////////////////////////////////////////////////// - var layers = (function(){ + var layers = (function () { // Submodule that handles layers of visualization var nb_layers = default_nb_of_layers; var old_Nodes = []; var old_Links = []; - function set_nb_layers(nb){ + function set_nb_layers(nb) { nb_layers = nb; } - function depth(){ + function depth() { return nb_layers; } - function push_layers(){ + function push_layers() { // old links and nodes become older // and are moved to the next deeper layer - for (var k=nb_layers;k>0;k--) { - var kp=k-1; - _svg.selectAll(".old_edge"+kp).classed("old_edge"+k,true); - _svg.selectAll(".old_node"+kp).classed("old_node"+k,true); - _svg.selectAll(".old_edgepath"+kp).classed("old_edgepath"+k,true); - _svg.selectAll(".old_edgelabel"+kp).classed("old_edgelabel"+k,true); + for (var k = nb_layers; k > 0; k--) { + var kp = k - 1; + _svg.selectAll(".old_edge" + kp).classed("old_edge" + k, true); + _svg.selectAll(".old_node" + kp).classed("old_node" + k, true); + _svg.selectAll(".old_edgepath" + kp).classed("old_edgepath" + k, true); + _svg.selectAll(".old_edgelabel" + kp).classed("old_edgelabel" + k, true); }; } - function clear_old(){ + function clear_old() { old_Nodes = []; old_Links = []; } - function update_data(d){ + function update_data(d) { // Save the data - var previous_nodes = _svg.selectAll("g").filter(".active_node"); + var previous_nodes = _svg.selectAll("g").filter(".active_node"); var previous_nodes_data = previous_nodes.data(); - old_Nodes = updateAdd(old_Nodes,previous_nodes_data); - var previous_links = _svg.selectAll(".active_edge"); + old_Nodes = updateAdd(old_Nodes, previous_nodes_data); + var previous_links = _svg.selectAll(".active_edge"); var previous_links_data = previous_links.data(); - old_Links = updateAdd(old_Links,previous_links_data); + old_Links = updateAdd(old_Links, previous_links_data); // handle the pinned nodes var pinned_Nodes = _svg.selectAll("g").filter(".pinned"); - var pinned_nodes_data = pinned_Nodes.data(); + var pinned_nodes_data = pinned_Nodes.data(); // get the node data and merge it with the pinned nodes _Nodes = d.nodes; - _Nodes = updateAdd(_Nodes,pinned_nodes_data); + _Nodes = updateAdd(_Nodes, pinned_nodes_data); // add coordinates to the new active nodes that already existed in the previous step - _Nodes = transfer_coordinates(_Nodes,old_Nodes); + _Nodes = transfer_coordinates(_Nodes, old_Nodes); // retrieve the links between nodes and pinned nodes _Links = d.links.concat(previous_links_data); // first gather the links - _Links = find_active_links(_Links,_Nodes); // then find the ones that are between active nodes + _Links = find_active_links(_Links, _Nodes); // then find the ones that are between active nodes + + // Sort links by source, then target, then label + // This is used to set linknum + _Links.sort(function (a, b) { + if (a.source > b.source) { return 1; } + else if (a.source < b.source) { return -1; } + else { + if (a.target > b.target) { return 1; } + if (a.target < b.target) { return -1; } + else { + if (a.label > b.label) { return 1; } + if (a.label < b.label) { return -1; } + else { return 0; } + } + } + }); + // Any links with duplicate source and target get an incremented 'linknum' + for (var i = 0; i < _Links.length; i++) { + if (i != 0 && + _Links[i].source == _Links[i - 1].source && + _Links[i].target == _Links[i - 1].target) { + _Links[i].linknum = _Links[i - 1].linknum + 1; + } + else { _Links[i].linknum = 1; }; + }; } - function updateAdd(array1,array2){ + function updateAdd(array1, array2) { // Update lines of array1 with the ones of array2 when the elements' id match // and add elements of array2 to array1 when they do not exist in array1 var arraytmp = array2.slice(0); var removeValFromIndex = []; - array1.forEach(function(d,index,thearray){ - for(var i=0;i= 0; i--) - arraytmp.splice(removeValFromIndex[i],1); + for (var i = removeValFromIndex.length - 1; i >= 0; i--) + arraytmp.splice(removeValFromIndex[i], 1); return array1.concat(arraytmp); } - function find_active_links(list_of_links,active_nodes){ + function find_active_links(list_of_links, active_nodes) { // find the links in the list_of_links that are between the active nodes and discard the others var active_links = []; list_of_links.forEach(function (row) { - for(var i=0; i < active_nodes.length; i++ ) { - for(var j=0; j < active_nodes.length; j++ ) { - if (active_nodes[i].id==row.source.id && active_nodes[j].id==row.target.id) { - var L_data={source:row.source.id, target:row.target.id, type:row.type, value:row.value, id:row.id}; + for (var i = 0; i < active_nodes.length; i++) { + for (var j = 0; j < active_nodes.length; j++) { + if (active_nodes[i].id == row.source.id && active_nodes[j].id == row.target.id) { + var L_data = { source: row.source.id, target: row.target.id, type: row.type, value: row.value, id: row.id }; var L_data = row; L_data['source'] = row.source.id; L_data['target'] = row.target.id; - active_links=active_links.concat(L_data); + active_links = active_links.concat(L_data); } - else if (active_nodes[i].id==row.source && active_nodes[j].id==row.target) { - var L_data=row; - active_links=active_links.concat(L_data); + else if (active_nodes[i].id == row.source && active_nodes[j].id == row.target) { + var L_data = row; + active_links = active_links.concat(L_data); } } } @@ -233,21 +258,21 @@ var graph_viz = (function(){ // the active links are in active_links but there can be some duplicates // remove duplicates links var dic = {}; - for ( var i=0; i < active_links.length; i++ ) - dic[active_links[i].id]=active_links[i]; // this will remove the duplicate links (with same id) + for (var i = 0; i < active_links.length; i++) + dic[active_links[i].id] = active_links[i]; // this will remove the duplicate links (with same id) var list_of_active_links = []; for (var key in dic) list_of_active_links.push(dic[key]); return list_of_active_links; - } + } + - - function transfer_coordinates(Nodes, old_Nodes){ + function transfer_coordinates(Nodes, old_Nodes) { // Transfer coordinates from old_nodes to the new nodes with the same id - for ( var i=0; i < old_Nodes.length; i++ ) { + for (var i = 0; i < old_Nodes.length; i++) { var exists = 0; - for(var j=0; j < Nodes.length; j++ ) { - if (Nodes[j].id==old_Nodes[i].id) { + for (var j = 0; j < Nodes.length; j++) { + if (Nodes[j].id == old_Nodes[i].id) { Nodes[j].x = old_Nodes[i].x; Nodes[j].y = old_Nodes[i].y; Nodes[j].fx = old_Nodes[i].x; @@ -260,16 +285,16 @@ var graph_viz = (function(){ return Nodes; } - function remove_duplicates(elem_class,elem_class_old){ + function remove_duplicates(elem_class, elem_class_old) { // Remove all the duplicate nodes and edges among the old_nodes and old_edges. // A node or an edge can not be on several layers at the same time. - d3.selectAll(elem_class).each(function(d){ - var ID=d.id; - for(var n=0;n