From 5d537da1f407b52975d82c365e3fc48d1f0aefa1 Mon Sep 17 00:00:00 2001 From: bricaud Date: Mon, 10 Jul 2017 17:00:36 +0200 Subject: [PATCH] add comments and clean up --- README.md | 35 ++++---- css/styles.css | 4 +- scripts/graphShapes.js | 31 +------ scripts/graph_viz.js | 170 +++++++++++++++----------------------- scripts/graphioGremlin.js | 19 ++--- scripts/infobox.js | 9 -- 6 files changed, 101 insertions(+), 167 deletions(-) diff --git a/README.md b/README.md index b341c14..ea07419 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Graphexp: graph explorer with D3.js -Graphexp is a web interface to explore and display a graph stored in the Gremlin graph database via the Gremlin server. Graphexp is under the Apache 2.0 licence. +Graphexp is a lightweight web interface to explore and display a graph stored in a Gremlin graph database, via the Gremlin server. + +Graphexp is under the Apache 2.0 license. ![graphexp](https://github.com/bricaud/graphexp/blob/master/images/graphexp2.png "Graph exploration") @@ -18,7 +20,7 @@ If the access to the Gremlin server is not `localhost:8182`, the address can be ## Getting started -### Intalling a Gremlin server +### Installing a Gremlin server If you have not yet installed a gremlin server, download the last release of the [Gremlin server](http://tinkerpop.apache.org/) and follow the [documentation](http://tinkerpop.apache.org/docs/current/reference/#gremlin-server). In the server folder just run ``` @@ -29,10 +31,10 @@ or on windows bin/gremlin-server.bat conf/gremlin-server-rest-modern.yaml ``` This default server comes with a small graph database of 6 nodes. -The server shoud start on port `8182`. Replace `gremlin-server-rest-modern.yaml` by `gremlin-server-modern.yaml` if you want to use websocket. +The server should start on port `8182`. Replace `gremlin-server-rest-modern.yaml` by `gremlin-server-modern.yaml` if you want to use websocket. -Alternatively, if you have Docker installed on your machine, you may run a Docker container with an already configured Gremlin server. You can find one on [this page](https://hub.docker.com/r/bricaud/gremlin-server-rest/). This server have a graph database containing a demo graph: the tree of life, with 35960 nodes and 35959 edges. You can download it and run it using +Alternatively, if you have Docker installed on your machine, you may run a Docker container with an already configured Gremlin server. You can find one on [this page](https://hub.docker.com/r/bricaud/gremlin-server-rest/). This server has a graph database containing a demo graph: the tree of life, with 35960 nodes and 35959 edges. You can download it and run it using ``` docker pull bricaud/gremlin-server-rest docker run -p 8182:8182 -it --name gremlin-server-rest bricaud/gremlin-server-rest @@ -49,26 +51,27 @@ To display a node, type in a property name and value, then click on the search b Leaving a blank value will display a part of the graph limited to the first 50 nodes found (with their connections). The node and edge properties can be automatically retrieved using the `get graph info` button. Pushing this button will also display some graph properties on the left side of the page. -When a node of the visualization is clicked, it will become 'active' with a circle surround it and its information will be display on the right side of the page. Moreover, this action will trigger the display of its neighbors. +When a node of the visualization is clicked, it will become 'active' with a circle surrounding it and its information will be display on the right side of the page. Moreover, this action will trigger the display of its neighbors. Clicking on an edge will show its properties (without highlighting the edge). When appearing for the first time the nodes will be positioned following a force layout. Drag and drop can be used to pin them in a particular position. Once dragged the nodes will stay at their position. Drag and drop is allowed only for the nodes on the active layer (most recent layer) with no connection with nodes in other layers. See "Visualization concepts" section for more information on the layers. ## Visualization concept -The visualization is based on a concept of layers of visualisation. The idea is to progress in the graph as in a jungle. The clicked node will show its neighbors, opening new paths for the exploration. If not clicked, the other displayed nodes will vanish little by little as we progress in the exploration. Coming back in the exploration paths allowed. Before it completely disappears, a node can be clicked and it will become active again. As in a jungle, you can not see the full jungle and there are so many things that you must focus on your direction and what is in front of you if you do not want to get lost. +The visualization is based on a concept of layers of visualization. The idea is to progress in the graph as in a jungle. The clicked node immediately shows its neighbors, opening new paths for the exploration. If not clicked, a node vanishes little by little as we progress in the exploration. Coming back during the exploration is allowed. Before it completely disappears, a node can be clicked and will become active again. +This visualization concept is aimed at providing a precise, local view rather than a global one. -During your exploration you can set up milestones by clicking on the small circle on the upper right side of a node. This will pin the node in time, preventing it to disappear. +During your exploration, you can set up milestones by clicking on the small circle on the upper right side of a node. This will pin the node in time, preventing it from disappearing. You may also freeze the exploration, by ticking the appropriate checkbox. The evolution of the exploration will stop, allowing to gather information on the nodes displayed, without displaying their neighbors. ## Node and edge information -The Id and label of each node can be displayed by hovering the cursor over the node. The full information on the properties are displayed on the right of the page when clicking on the node or edges. Once the `get graph info` button has been clicked, a choice of properties to display appear on the left side. +The Id and label of each node can be displayed by hovering the cursor over the node. The full information on the properties is displayed on the right of the page when clicking on the node or edges. Once the `get graph info` button has been clicked, a choice of properties to display appears on the left side. ## Node color -If a node property called 'color' exists in the node properties with an hexadecimal color code (string), it will be displayed automatically on the graph. Otherwise, the default node color can be set in the `graphConf.js` file. The node color can be set interactively after the `get graph info` button has been pressed. A select tab appears on the left side bar allowing to set the color according to one of the property values present in the graph. +If a node property called 'color' exists in the node properties with a hexadecimal color code (string), it will be displayed automatically on the graph. Otherwise, the default node color can be set in the `graphConf.js` file. The node color can be set interactively after the `get graph info` button has been pressed. A select tab appears on the left sidebar allowing to set the color according to one of the property values present in the graph. ## Program description @@ -84,16 +87,16 @@ This graph has a single type of nodes (label 'vertex') and a single type of edge The different node properties are displayed on the left. * `CHILDCOUNT` the number of descendent nodes * `name` the name of the species -* `HASPAGE` whether there is an page of information on the [Tree Of Life Project website](http://tolweb.org/tree/home.pages/downloadtree.html) +* `HASPAGE` whether there is a page of information on the [Tree Of Life Project website](http://tolweb.org/tree/home.pages/downloadtree.html) * `ID` Tree of Life Project unique id * `CONFIDENCE` confidence in classification, from confident (0) to less confident (1) and (2) -* `EXTINCT` wether the node is extinct (2) or not (0) +* `EXTINCT` whether the node is extinct (2) or not (0) * `LEAF` the node is a leaf of the tree (1) or the node does not represent a leaf (it has or will have descendent nodes on the Tree of Life) (0) -* `PHYLESIS` (0) monophylectic, (1) uncertain, (2) not monophylectic +* `PHYLESIS` (0) monophyletic, (1) uncertain, (2) not monophyletic On the top navigation bar, choose the field `name`, enter 'Dinosauria' as value in the input and click on the `Search` button. Do not forget the capital letter, as the search is case-sensitive. A single node, corresponding to the Dinosaurs clade, should appear in the middle of the page. Click on the node to display node details on the right as well as its ancestors and descendants on the graph. Check the box `name` on the left bar to display the node names. -You should see appearing the two subgroups of dinosaurs `Sauriscia` and `Ornithischia`, as in the [Wikipedia dinosaur page](https://en.wikipedia.org/wiki/Dinosaur_classification) and an additional `none` node which is the ancestor. This latter node is a taxon that has ancestors and descendants but does not have a name. Note that there are different version of the tree of life and it is always evolving as researchers find new species. +You should see appearing the two subgroups of dinosaurs `Saurischia` and `Ornithischia`, as in the [Wikipedia dinosaur page](https://en.wikipedia.org/wiki/Dinosaur_classification) and an additional `none` node which is the ancestor. This latter node is a taxon that has ancestors and descendants but does not have a name. Note that there are different versions of the tree of life and it is always evolving as researchers find new species. ![graphexptol2](https://github.com/bricaud/graphexp/blob/master/images/graphexptol2.png "Graph exploration Tree of life") You may now enjoy the exploration of the dinosaur order by clicking on nodes and following ascendant and descendant lines. The oldest nodes will vanish as you explore the data and if you want more nodes to be displayed, just increase the number of layers on the top navigation bar. @@ -101,7 +104,11 @@ You may also color the nodes according to the values of some of their properties ![graphexptol3](https://github.com/bricaud/graphexp/blob/master/images/graphexptol3.png "Graph exploration Tree of life") -If you want to explore the world of insects, you may start with the taxon 'Insecta' and follow the links. +During the exploration of the `Dinosauria` clade you may find the [bird](https://en.wikipedia.org/wiki/Bird) class `Aves`. They are the only survivors of the Dinosaur group and descendant of dinosaurs with feathers. To see it, enter `Aves` in the value field, press search and climb up the tree. + +If you want to explore the world of insects, you may start with the taxon `Insecta` and follow the links. Did you know that spiders are not insects but have they own group `Arachnida`? Can you tell what is the common ancestor between spiders and insects? + +You may also be interested in the `Homo` group. Have a try on the live demo of Graphexp on the [project Github page](https://bricaud.github.io/graphexp/). diff --git a/css/styles.css b/css/styles.css index 6e469bb..05d3e09 100644 --- a/css/styles.css +++ b/css/styles.css @@ -100,7 +100,7 @@ Layout styles { padding: 0em 0em; background-color: #fff; - height: 70vh; + height: 80vh; } .main, .aside @@ -115,7 +115,7 @@ Layout styles position: absolute; left: 0; width: 100%; - height: 70vh; + height: 80vh; } .left_bar diff --git a/scripts/graphShapes.js b/scripts/graphShapes.js index 25290e1..e3aeffe 100644 --- a/scripts/graphShapes.js +++ b/scripts/graphShapes.js @@ -100,7 +100,6 @@ var graphShapes = (function(){ // Create the circle shape var node_base_circle = node_deco.append("circle").classed("base_circle",true) - //.attr("r", 12) .attr("r",node_size) .style("stroke-width",node_stroke_width) .style("stroke","black") @@ -109,15 +108,11 @@ var graphShapes = (function(){ // Add the text to the nodes node_deco.append("text").classed("text_details",true) - //.attr("x", 12) .attr("x",function(d){return node_size(d)+2;}) - //.attr("y", ".31em") .text(node_text) .style("visibility", "hidden"); node_deco.append("text").classed("text_details",true) - //.attr("x", 12) - //.attr("y", 15) .attr("x",function(d){return node_size(d)+2;}) .attr("y",node_size) .text(node_subtext) @@ -208,7 +203,7 @@ var graphShapes = (function(){ // Attach the edge actions attach_edge_actions(edges_deco) - // add property info if checkbox checked + // Add property info if checkbox checked add_checkbox_prop('edges',edgelabels_deco) return [edges_deco,edgepaths_deco,edgelabels_deco] @@ -216,6 +211,7 @@ var graphShapes = (function(){ } function add_checkbox_prop(item,selected_items){ + // Add text from a property if the checkbox is checked on the sidebar if (item=='edges'){ var item_properties = graphioGremlin.get_edge_properties(); } else if (item=='nodes'){ @@ -232,18 +228,15 @@ var graphShapes = (function(){ } function create_edge_label(edgepaths,edgelabels){ - var edgepaths_deco = edgepaths.append('path') .attr('class','edgepath').classed("active_edgepath",true) .attr('fill-opacity',0) .attr('stroke-opacity',0) - //.attr('stroke-width',10) .attr('id',function (d, i) {return 'edgepath' + d.id;}) .attr("ID",function(d) { return d.id;}) .style("pointer-events", "none"); var edgelabels_deco = edgelabels.append('text') - //.attr('x',10) .attr('dy',-3) .style("pointer-events", "none") .attr('class','edgelabel').classed("active_edgelabel",true) @@ -272,13 +265,10 @@ var graphShapes = (function(){ function decorate_old_elements(nb_layers){ - // old links and nodes become older - // and move to the next layer + // Decrease the opacity of nodes and edges when they get old for (var k=0;k0;k--) { var kp=k-1; _svg.selectAll(".old_edge"+kp).classed("old_edge"+k,true); @@ -190,95 +190,79 @@ var graph_viz = (function(){ } - /////////////////////////////////////////////////////////////////////////// - //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 function updateAdd(array1,array2){ - 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); - return array1.concat(arraytmp); + // 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); + return array1.concat(arraytmp); } - - /////////////////////////////////////////////////////////////////// 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) { - //console.log('search for:') - //console.log(row) - //console.log(row.source,row.target) - 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) { - //console.log('found match!',active_nodes[i].id,active_nodes[j].id); - //console.log(row) - 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); - } - else if (active_nodes[i].id==row.source && active_nodes[j].id==row.target) { - //console.log('found match type 2!',active_nodes[i].id,active_nodes[j].id); - //console.log(row) - var L_data=row; - active_links=active_links.concat(L_data); + // 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}; + var L_data = row; + L_data['source'] = row.source.id; + L_data['target'] = row.target.id; + 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); + } + } } - } - } - }); - // 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) - var list_of_active_links = []; - for (var key in dic) - list_of_active_links.push(dic[key]); - return list_of_active_links; + }); + // 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) + var list_of_active_links = []; + for (var key in dic) + list_of_active_links.push(dic[key]); + return list_of_active_links; } - ////////////////////////////////////////////////////////////////// - // transfer coordinates from old_nodes to the nodes, for the nodes - // that already existed in old_nodes + function transfer_coordinates(Nodes, old_Nodes){ - 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) { - Nodes[j].x = old_Nodes[i].x; - Nodes[j].y = old_Nodes[i].y; - Nodes[j].fx = old_Nodes[i].x; - Nodes[j].fy = old_Nodes[i].y; - Nodes[j].vx = old_Nodes[i].vx; - Nodes[j].vy = old_Nodes[i].vy; - //console.log(old_Nodes[i].x,old_Nodes[i].y); - //console.log(Nodes[j].x,Nodes[j].y); - } + // Transfer coordinates from old_nodes to the new nodes with the same id + 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) { + Nodes[j].x = old_Nodes[i].x; + Nodes[j].y = old_Nodes[i].y; + Nodes[j].fx = old_Nodes[i].x; + Nodes[j].fy = old_Nodes[i].y; + Nodes[j].vx = old_Nodes[i].vx; + Nodes[j].vy = old_Nodes[i].vy; + } + } } - } - return Nodes; + return Nodes; } - -// function remove_duplicates(){ - // remove all the duplicate nodes and links among the old_nodes and old_links 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;n50) filtered_string = filtered_string.substring(0,50); // shorten long strings + var filtered_string = input_string;//You may add .replace(/\W+/g, ''); to refuse any character not in the alphabet + if (filtered_string.length>50) filtered_string = filtered_string.substring(0,50); // limit string length // Translate to Gremlin query if (input_string==""){ - //var gremlin_query_nodes = "nodes = g.V().limit(100)" - //var gremlin_query_edges = "edges = g.V().limit(100).aggregate('node').outE().as('edge').inV().where(within('node')).select('edge')" - //var gremlin_query = gremlin_query_nodes+"\n"+gremlin_query_edges+"\n"+"[nodes.toList(),edges.toList()]" var gremlin_query_nodes = "nodes = g.V().limit("+node_limit_per_request+")" var gremlin_query_edges = "edges = g.V().limit("+node_limit_per_request+").aggregate('node').outE().as('edge').inV().where(within('node')).select('edge')" var gremlin_query = gremlin_query_nodes+"\n"+gremlin_query_edges+"\n"+"[nodes.toList(),edges.toList()]" @@ -117,7 +114,7 @@ var graphioGremlin = (function(){ // while busy, show we're doing something in the messageArea. $('#messageArea').html('

(loading)

'); - // get the data from the server + // Get the data from the server $.ajax({ type: "POST", accept: "application/json", @@ -177,7 +174,7 @@ var graphioGremlin = (function(){ }; } - // Generate uuid for websocket requestId. Code found here + // Generate uuid for websocket requestId. Code found here: // https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { @@ -245,8 +242,8 @@ var graphioGremlin = (function(){ ///////////////////////////////////////////////////////////// function arrange_data(data) { - // Extracting node and edges from the data - // to create the graph object + // Extract node and edges from the data returned for 'search' request + // Create the graph object var nodes=[], links=[]; for (var key in data){ data[key].forEach(function (item) { @@ -260,8 +257,8 @@ var graphioGremlin = (function(){ } function arrange_data_path(data) { - // Extracting node and edges from the data - // to create the graph object + // Extract node and edges from the data returned for 'click' request + // Create the graph object var nodes=[], links=[]; for (var key in data){ data[key].objects.forEach(function (item) { diff --git a/scripts/infobox.js b/scripts/infobox.js index 23a95ac..00727a1 100644 --- a/scripts/infobox.js +++ b/scripts/infobox.js @@ -34,7 +34,6 @@ var infobox = (function(){ _table_Graphinfo = graph_bar.append("table").attr("id","tableGraph"); init_table(_table_Graphinfo,["Type","Count"]); - //side_bar.append("p").text("") var graphElem_bar = d3.select(label_graphElem); graphElem_bar.append("h2").text("Item Info") _table_IDinfo = graphElem_bar.append("table").attr("id","tableIdDetails"); @@ -102,8 +101,6 @@ var infobox = (function(){ // remove previous info _display_IDinfo(node_data) _display_DBinfo(node_data); - //_display_WIKIinfo(node_data,_side_summary,_side_image,_bottom_info); - //console.log('Node ID: '+node_data.id); } ////////////////////// @@ -127,9 +124,6 @@ var infobox = (function(){ if (d.type=='vertex'){ for (var key in d.properties){ for (var subkey in d.properties[key]){ - //console.log(subkey) - //console.log(d.properties[key]) - //console.log(d.properties[key][subkey]) var new_info_row = info_table.append("tr"); new_info_row.append("td").text(key).style("font-size",_font_size); new_info_row.append("td").text(d.properties[key][subkey].value).style("font-size",_font_size); @@ -147,8 +141,6 @@ var infobox = (function(){ } } - - return { create : create, display_info : display_info, @@ -156,5 +148,4 @@ var infobox = (function(){ hide_element : hide_element, show_element : show_element }; - })(); \ No newline at end of file