Browse Source

Merge pull request #1070 from statonlab/phylotree_addscale

Phylotree scale options, bug fixes
Bradford Condon 4 years ago
parent
commit
6378b2bc69

+ 129 - 110
legacy/tripal_phylogeny/theme/js/tripal_phylogeny.js

@@ -2,129 +2,148 @@
 
 (function ($) {
 
-  var height = 0; // will be dynamically sized
+  "use strict";
 
-  $(document).ready( function () {
+  // Will be dynamically sized.
+  var height = 0;
 
-    // Callback function to determine node size.
-    var nodeSize = function(d) {
-      var size;
-      if (d.cvterm_name == "phylo_root") {
-        size = treeOptions['root_node_size']; 
-      }
-      if (d.cvterm_name == "phylo_interior") {
-        size = treeOptions['interior_node_size']; 
-      }
-      if (d.cvterm_name == "phylo_leaf") {
-        size = treeOptions['leaf_node_size']; 
-      }
-      return size;
+  // Store our function as a property of Drupal.behaviors.
+  Drupal.behaviors.TripalPhylotree = {
+    attach: function (context, settings) {
+
+      // Retrieve the data for this tree.
+      var data_url = Drupal.settings.tripal_chado.phylotree_url;
+      $.getJSON(data_url, function(treeData) {
+        phylogeny_display_data(treeData);
+        $('.phylogram-ajax-loader').hide();
+      });
     }
+  }
 
-    // Callback function to determine the node color.
-    var organismColor = function(d) {
-      var color = null;
-      if (d.genus) {
-        color = organismColors[d.genus + ' ' + d.species];
-      }
-      if (color) { 
-        return color; 
-      }
-      else { 
-        return 'grey'; 
-      }
-    };
+  // Callback function to determine node size.
+  var phylogeny_node_size = function(d) {
+    var size;
+    var tree_options = Drupal.settings.tripal_chado.tree_options;
+    if (d.cvterm_name == "phylo_root") {
+      size = tree_options['root_node_size'];
+    }
+    if (d.cvterm_name == "phylo_interior") {
+      size = tree_options['interior_node_size'];
+    }
+    if (d.cvterm_name == "phylo_leaf") {
+      size = tree_options['leaf_node_size'];
+    }
+    return size;
+  }
 
-    // Callback for mouseover event on graph node d.
-    var nodeMouseOver = function(d) {
-      var el = $(this);
-      el.attr('cursor', 'pointer');
-      var circle = el.find('circle');
-      // highlight in yellow no matter if leaf or interior node
-      circle.attr('fill', 'yellow');
-      if(!d.children) {
-        // only leaf nodes have descriptive text
-        var txt = el.find('text');
-        txt.attr('font-weight', 'bold');
-      }
-    };
-    
-    // Callback for mouseout event on graph node d.
-    var nodeMouseOut = function(d) {
-      var el = $(this);
-      el.attr('cursor', 'default');
-      var circle = el.find('circle');
-      if(!d.children) {
-        // restore the color based on organism id for leaf nodes
-        circle.attr('fill', organismColor(d));
-        var txt = el.find('text');
-        txt.attr('font-weight', 'normal');
+  // Callback function to determine the node color.
+  var phylogeny_organism_color = function(d) {
+    var organism_color = Drupal.settings.tripal_chado.org_colors;
+    var color = null;
+
+    if (d.fo_genus) {
+      color = organism_color[d.fo_organism_id];
+    }
+    if (color) {
+      return color;
+    }
+    else {
+      return 'grey';
+    }
+  };
+
+  // Callback for mouseover event on graph node d.
+  var phylogeny_node_mouse_over = function(d) {
+    var el = $(this);
+    el.attr('cursor', 'pointer');
+    var circle = el.find('circle');
+    // highlight in yellow no matter if leaf or interior node
+    circle.attr('fill', 'yellow');
+    if(!d.children) {
+      // only leaf nodes have descriptive text
+      var txt = el.find('text');
+      txt.attr('font-weight', 'bold');
+    }
+  };
+
+  // Callback for mouseout event on graph node d.
+  var phylogeny_node_mouse_out = function(d) {
+    var el = $(this);
+    el.attr('cursor', 'default');
+    var circle = el.find('circle');
+    if(!d.children) {
+      // restore the color based on organism id for leaf nodes
+      circle.attr('fill', phylogeny_organism_color(d));
+      var txt = el.find('text');
+      txt.attr('font-weight', 'normal');
+    }
+    else {
+      // restore interior nodes to white
+      circle.attr('fill', 'white');
+    }
+  };
+
+  // Callback for mousedown/click event on graph node d.
+  var phylogeny_node_mouse_down = function(d) {
+    var el = $(this);
+    var title = (! d.children ) ? d.name : 'interior node ' + d.phylonode_id;
+
+    if(d.children) {
+      // interior node
+      if(d.phylonode_id) {
       }
       else {
-        // restore interior nodes to white
-        circle.attr('fill', 'white');
+        // this shouldn't happen but ok
       }
-    };
-    
-    // Callback for mousedown/click event on graph node d.
-    var nodeMouseDown = function(d) {
-      var el = $(this);
-      var title = (! d.children ) ? d.name : 'interior node ' + d.phylonode_id;
+    }
+    else {
+      if(d.feature_eid) {
+        window.location.href = baseurl + '/bio_data/' + d.feature_eid;
 
-      if(d.children) {
-        // interior node
-        if(d.phylonode_id) {
-        }
-        else {
-          // this shouldn't happen but ok
-        }
+        return;
       }
-      else {
-        // If this node is not associated with a feature but it has an 
-        // organism node then this is a taxonomic node and we want to
-        // link it to the organism page.
-        if (!d.feature_id && d.organism_node_id) {
-          window.location.replace(baseurl + '/node/' + d.organism_node_id);
-        }
-        // leaf node
+      // If this node is not associated with a feature but it has an
+      // organism node then this is a taxonomic node and we want to
+      // link it to the organism page.
+      if (!d.feature_id && d.organism_nid) {
+        window.location.replace(baseurl + '/node/' + d.organism_nid);
+      }
+      if (!d.feature_id && d.organism_eid) {
+        window.location.replace(baseurl + '/bio_data/' + d.organism_eid);
       }
-    };
+      // leaf node
+    }
+  };
 
-    // AJAX function for retrieving the tree data.
-    $.getJSON(phylotreeDataURL, function(treeData) {
-      displayData(treeData);
-      $('.phylogram-ajax-loader').hide();
+  // Creates the tree using the d3.phylogram.js library.
+  function phylogeny_display_data(treeData) {
+    var height = phylogeny_graph_height(treeData);
+    var tree_options = Drupal.settings.tripal_chado.tree_options;
+    d3.phylogram.build('#phylogram', treeData, {
+      'width' : tree_options['phylogram_width'],
+      'height' : height,
+      'fill' : phylogeny_organism_color,
+      'size' : phylogeny_node_size,
+      'nodeMouseOver' : phylogeny_node_mouse_over,
+      'nodeMouseOut' : phylogeny_node_mouse_out,
+      'nodeMouseDown' : phylogeny_node_mouse_down,
+      'skipTicks' : tree_options['skipTicks']
     });
+  }
 
-    // Creates the tree using the d3.phylogram.js library.
-    function displayData(treeData) {
-      height = graphHeight(treeData);
-      d3.phylogram.build('#phylogram', treeData, {
-        'width' : treeOptions['phylogram_width'],
-        'height' : height,
-        'fill' : organismColor,
-        'size' : nodeSize,
-        'nodeMouseOver' : nodeMouseOver,
-        'nodeMouseOut' : nodeMouseOut,
-        'nodeMouseDown' : nodeMouseDown,
-        'skipTicks' : treeOptions['skipTicks']
-      });
-    }
-
-    /* graphHeight() generate graph height based on leaf nodes */
-    function graphHeight(data) {
-      function countLeafNodes(node) {
-        if(! node.children) {
-          return 1;
-        }
-        var ct = 0;
-        node.children.forEach( function(child) {
-          ct+= countLeafNodes(child);
-        });
-        return ct;
+  /* graphHeight() generate graph height based on leaf nodes */
+  function phylogeny_graph_height(data) {
+    function count_leaf_nodes(node) {
+      if(! node.children) {
+        return 1;
       }
-      var leafNodeCt = countLeafNodes(data);
-      return 22 * leafNodeCt;
+      var ct = 0;
+      node.children.forEach( function(child) {
+        ct+= count_leaf_nodes(child);
+      });
+      return ct;
     }
-  });
-})(jQuery);
+    var leafNodeCt = count_leaf_nodes(data);
+    return 22 * leafNodeCt;
+  }
+})(jQuery);

+ 50 - 33
tripal_chado/includes/tripal_chado.phylotree.inc

@@ -4,7 +4,8 @@
  *
  * @param $phylotree
  */
-function tripal_phylogeny_prepare_tree_viewer($phylotree) {
+function tripal_phylogeny_prepare_tree_viewer($phylotree)
+{
 
   // If the phylotree is not provided then just return;
   if (!$phylotree) {
@@ -59,6 +60,7 @@ function tripal_phylogeny_prepare_tree_viewer($phylotree) {
         'interior_node_size' => variable_get('tripal_phylogeny_default_interior_node_size', 1),
         'leaf_node_size' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
         'skipTicks' => $skip_ticks,
+        'phylogram_scale' => variable_get('tripal_phylogeny_default_phylogram_scale', 1),
       ],
       'org_colors' => $colors,
     ],
@@ -99,7 +101,8 @@ function tripal_phylogeny_prepare_tree_viewer($phylotree) {
  *
  * @ingroup tripal_phylogeny
  */
-function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
+function tripal_phylogeny_ajax_get_tree_json($phylotree_id)
+{
 
   $phylotree = chado_generate_var('phylotree', ['phylotree_id' => $phylotree_id]);
 
@@ -135,8 +138,7 @@ function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
         LEFT OUTER JOIN [chado_organism] co       ON co.organism_id = o.organism_id
       WHERE n.phylotree_id = :phylotree_id
     ";
-  }
-  else {
+  } else {
     $sql = "
       SELECT
         n.phylonode_id, n.parent_phylonode_id, n.label AS name, n.distance AS length,
@@ -165,13 +167,13 @@ function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
 
   if ($results) {
     while ($r = $results->fetchObject()) {
-      $phylonode_id = (int) $r->phylonode_id;
+      $phylonode_id = (int)$r->phylonode_id;
 
       // expect all nodes to have these properties
       $node = [
         'phylonode_id' => $phylonode_id,
-        'parent_phylonode_id' => (int) $r->parent_phylonode_id,
-        'length' => (double) $r->length,
+        'parent_phylonode_id' => (int)$r->parent_phylonode_id,
+        'length' => (double)$r->length,
         'cvterm_name' => $r->cvterm_name,
       ];
 
@@ -186,30 +188,28 @@ function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
       }
       // If this node is associated with a feature then add in the details
       if ($r->feature_id) {
-        $node['feature_id'] = (int) $r->feature_id;
+        $node['feature_id'] = (int)$r->feature_id;
         $node['feature_name'] = $r->feature_name;
         if (module_exists('tripal_phylogeny')) {
-          $node['feature_nid'] = (int) $r->feature_nid;
-        }
-        else {
+          $node['feature_nid'] = (int)$r->feature_nid;
+        } else {
           $entity_id = chado_get_record_entity_by_table('feature', $r->feature_id);
-          $node['feature_eid'] = (int) $entity_id;
+          $node['feature_eid'] = (int)$entity_id;
         }
       }
       // Add in the organism fields when they are available via the
       // phylonode_organism table.
       if ($r->organism_id) {
-        $node['organism_id'] = (int) $r->organism_id;
+        $node['organism_id'] = (int)$r->organism_id;
         $node['common_name'] = $r->common_name;
         $node['abbreviation'] = $r->abbreviation;
         $node['genus'] = $r->genus;
         $node['species'] = $r->species;
         if (module_exists('tripal_phylogeny')) {
-          $node['organism_nid'] = (int) $r->organism_nid;
-        }
-        else {
+          $node['organism_nid'] = (int)$r->organism_nid;
+        } else {
           $entity_id = chado_get_record_entity_by_table('organism', $r->organism_id);
-          $node['organism_eid'] = (int) $entity_id;
+          $node['organism_eid'] = (int)$entity_id;
         }
         // If the node does not have a name but is linked to an organism
         // then set the name to be that of the genus and species.
@@ -220,17 +220,16 @@ function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
       // Add in the organism fields when they are available via the
       // the phylonode.feature_id FK relationship.
       if ($r->fo_organism_id) {
-        $node['fo_organism_id'] = (int) $r->fo_organism_id;
+        $node['fo_organism_id'] = (int)$r->fo_organism_id;
         $node['fo_common_name'] = $r->fo_common_name;
         $node['fo_abbreviation'] = $r->fo_abbreviation;
         $node['fo_genus'] = $r->fo_genus;
         $node['fo_species'] = $r->fo_species;
         if (module_exists('tripal_phylogeny')) {
-          $node['fo_organism_nid'] = (int) $r->fo_organism_nid;
-        }
-        else {
+          $node['fo_organism_nid'] = (int)$r->fo_organism_nid;
+        } else {
           $entity_id = chado_get_record_entity_by_table('organism', $r->fo_organism_id);
-          $node['fo_organism_eid'] = (int) $entity_id;
+          $node['fo_organism_eid'] = (int)$entity_id;
         }
       }
 
@@ -244,8 +243,7 @@ function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
         $parent_ref = &$phylonodes[$node['parent_phylonode_id']];
         // Append node refernce to children.
         $parent_ref['children'][] = &$node;
-      }
-      else {
+      } else {
         $root_phylonode_ref = &$node;
       }
     }
@@ -262,7 +260,8 @@ function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
  *
  */
 
-function tripal_phylogeny_admin_phylotrees_listing() {
+function tripal_phylogeny_admin_phylotrees_listing()
+{
   $output = '';
 
   // set the breadcrumb
@@ -278,8 +277,7 @@ function tripal_phylogeny_admin_phylotrees_listing() {
   $view = views_embed_view('tripal_phylogeny_admin_phylotree', 'default');
   if (isset($view)) {
     $output .= $view;
-  }
-  else {
+  } else {
     $output .= '<p>The Phylotree module uses primarily views to provide an '
       . 'administrative interface. Currently one or more views needed for this '
       . 'administrative interface are disabled. <strong>Click each of the following links to '
@@ -297,7 +295,8 @@ function tripal_phylogeny_admin_phylotrees_listing() {
  * @param unknown $form
  * @param unknown $form_state
  */
-function tripal_phylogeny_default_plots_form($form, &$form_state) {
+function tripal_phylogeny_default_plots_form($form, &$form_state)
+{
   $form = [];
 
   $form['plot_settings'] = [
@@ -319,6 +318,19 @@ function tripal_phylogeny_default_plots_form($form, &$form_state) {
     '#size' => 5,
   ];
 
+  $form['plot_settings']['phylogram_scale'] = [
+    '#type' => 'select',
+    '#title' => t('Phylogram Scale'),
+    '#description' => 'Please specify the scale to use.',
+    '#default_value' => variable_get('tripal_phylogeny_default_phylogram_scale', 1),
+    '#options' => array(
+      1 => t('Linear'),
+      2 => t('Logarithmic'),
+    ),
+    '#size' => 2,
+  ];
+
+
   $form['node_settings'] = [
     '#type' => 'fieldset',
     '#title' => t('Node Settings'),
@@ -447,7 +459,8 @@ function tripal_phylogeny_default_plots_form($form, &$form_state) {
  *
  * @ingroup tripal_phylogeny
  */
-function tripal_phylogeny_default_plots_form_validate($form, &$form_state) {
+function tripal_phylogeny_default_plots_form_validate($form, &$form_state)
+{
 
 }
 
@@ -456,7 +469,8 @@ function tripal_phylogeny_default_plots_form_validate($form, &$form_state) {
  * @param unknown $form
  * @param unknown $form_state
  */
-function tripal_phylogeny_default_plots_form_submit($form, &$form_state) {
+function tripal_phylogeny_default_plots_form_submit($form, &$form_state)
+{
   // Rebuild this form after submission so that any changes are reflected in
   // the flat tables.
   $form_state['rebuild'] = TRUE;
@@ -467,6 +481,7 @@ function tripal_phylogeny_default_plots_form_submit($form, &$form_state) {
     variable_set('tripal_phylogeny_default_root_node_size', $form_state['values']['root_node_size']);
     variable_set('tripal_phylogeny_default_interior_node_size', $form_state['values']['interior_node_size']);
     variable_set('tripal_phylogeny_default_leaf_node_size', $form_state['values']['leaf_node_size']);
+    variable_set('tripal_phylogeny_default_phylogram_scale', $form_state['values']['phylogram_scale']);
 
     $num_orgs = $form_state['values']['num_orgs'];
     variable_set("tripal_phylogeny_num_orgs", $num_orgs);
@@ -491,7 +506,8 @@ function tripal_phylogeny_default_plots_form_submit($form, &$form_state) {
  *
  * @param unknown $variables
  */
-function theme_tripal_phylogeny_admin_org_color_tables($variables) {
+function theme_tripal_phylogeny_admin_org_color_tables($variables)
+{
   $fields = $variables['element'];
   $num_orgs = $fields['num_orgs']['#value'];
   $headers = ['Organism', 'Color', ''];
@@ -524,7 +540,8 @@ function theme_tripal_phylogeny_admin_org_color_tables($variables) {
  * @param $form
  * @param $form_state
  */
-function tripal_phylogeny_default_plots_form_ajax_callback($form, $form_state) {
+function tripal_phylogeny_default_plots_form_ajax_callback($form, $form_state)
+{
 
   return $form['node_settings']['org_table'];
-}
+}

+ 236 - 178
tripal_chado/theme/js/d3.phylogram.js

@@ -41,7 +41,7 @@
       selector: selector of an element that will contain the SVG
       nodes: JS object of nodes
     Options:
-      width       
+      width
         Width of the vis, will attempt to set a default based on the width of
         the container.
       height
@@ -63,219 +63,256 @@
         Skip the tick rule.
       skipBranchLengthScaling
         Make a dendrogram instead of a phylogram.
-  
+
   d3.phylogram.buildRadial(selector, nodes, options)
     Creates a radial dendrogram.
-    Options: same as build, but without diagonal, skipTicks, and 
+    Options: same as build, but without diagonal, skipTicks, and
       skipBranchLengthScaling
-  
+
   d3.phylogram.rightAngleDiagonal()
     Similar to d3.diagonal except it create an orthogonal crook instead of a
     smooth Bezier curve.
-    
+
   d3.phylogram.radialRightAngleDiagonal()
     d3.phylogram.rightAngleDiagonal for radial layouts.
 */
 
-if (!d3) { throw "d3 wasn't included!"};
-(function() {
+if (!d3) {
+  throw "d3 wasn't included!"
+}
+;
+(function () {
   d3.phylogram = {}
-  d3.phylogram.rightAngleDiagonal = function() {
-    var projection = function(d) { return [d.y, d.x]; }
-    
-    var path = function(pathData) {
-      return "M" + pathData[0] + ' ' + pathData[1] + " " + pathData[2];
+  d3.phylogram.rightAngleDiagonal = function () {
+    var projection = function (d) {
+      return [d.y, d.x];
+    }
+
+    var path = function (pathData) {
+      return "M" + pathData[0] + " " + pathData[1] + " " + pathData[2];
     }
-    
+
     function diagonal(diagonalPath, i) {
       var source = diagonalPath.source,
-          target = diagonalPath.target,
-          midpointX = (source.x + target.x) / 2,
-          midpointY = (source.y + target.y) / 2,
-          pathData = [source, {x: target.x, y: source.y}, target];
+        target = diagonalPath.target,
+        midpointX = (source.x + target.x) / 2,
+        midpointY = (source.y + target.y) / 2,
+        pathData = [source, {x: target.x, y: source.y}, target];
       pathData = pathData.map(projection);
-      return path(pathData)
+      return path(pathData);
     }
-    
-    diagonal.projection = function(x) {
-      if (!arguments.length) return projection;
+
+    diagonal.projection = function (x) {
+      if (!arguments.length) {
+        return projection;
+      }
       projection = x;
       return diagonal;
     };
-    
-    diagonal.path = function(x) {
-      if (!arguments.length) return path;
+
+    diagonal.path = function (x) {
+      if (!arguments.length) {
+        return path;
+      }
       path = x;
       return diagonal;
     };
-    
+
     return diagonal;
   }
-  
-  d3.phylogram.radialRightAngleDiagonal = function() {
+
+  d3.phylogram.radialRightAngleDiagonal = function () {
     return d3.phylogram.rightAngleDiagonal()
-      .path(function(pathData) {
+      .path(function (pathData) {
         var src = pathData[0],
-            mid = pathData[1],
-            dst = pathData[2],
-            radius = Math.sqrt(src[0]*src[0] + src[1]*src[1]),
-            srcAngle = d3.phylogram.coordinateToAngle(src, radius),
-            midAngle = d3.phylogram.coordinateToAngle(mid, radius),
-            clockwise = Math.abs(midAngle - srcAngle) > Math.PI ? midAngle <= srcAngle : midAngle > srcAngle,
-            rotation = 0,
-            largeArc = 0,
-            sweep = clockwise ? 0 : 1;
+          mid = pathData[1],
+          dst = pathData[2],
+          radius = Math.sqrt(src[0] * src[0] + src[1] * src[1]),
+          srcAngle = d3.phylogram.coordinateToAngle(src, radius),
+          midAngle = d3.phylogram.coordinateToAngle(mid, radius),
+          clockwise = Math.abs(midAngle - srcAngle) > Math.PI ? midAngle <= srcAngle : midAngle > srcAngle,
+          rotation = 0,
+          largeArc = 0,
+          sweep = clockwise ? 0 : 1;
         return 'M' + src + ' ' +
-          "A" + [radius,radius] + ' ' + rotation + ' ' + largeArc+','+sweep + ' ' + mid +
+          "A" + [radius, radius] + ' ' + rotation + ' ' + largeArc + ',' + sweep + ' ' + mid +
           'L' + dst;
       })
-      .projection(function(d) {
+      .projection(function (d) {
         var r = d.y, a = (d.x - 90) / 180 * Math.PI;
         return [r * Math.cos(a), r * Math.sin(a)];
-      })
+      });
   }
-  
+
   // Convert XY and radius to angle of a circle centered at 0,0
-  d3.phylogram.coordinateToAngle = function(coord, radius) {
+  d3.phylogram.coordinateToAngle = function (coord, radius) {
     var wholeAngle = 2 * Math.PI,
-        quarterAngle = wholeAngle / 4
-    
+      quarterAngle = wholeAngle / 4
+
     var coordQuad = coord[0] >= 0 ? (coord[1] >= 0 ? 1 : 2) : (coord[1] >= 0 ? 4 : 3),
-        coordBaseAngle = Math.abs(Math.asin(coord[1] / radius))
-    
+      coordBaseAngle = Math.abs(Math.asin(coord[1] / radius))
+
     // Since this is just based on the angle of the right triangle formed
-    // by the coordinate and the origin, each quad will have different 
+    // by the coordinate and the origin, each quad will have different
     // offsets
     switch (coordQuad) {
       case 1:
         coordAngle = quarterAngle - coordBaseAngle
-        break
+        break;
       case 2:
         coordAngle = quarterAngle + coordBaseAngle
-        break
+        break;
       case 3:
-        coordAngle = 2*quarterAngle + quarterAngle - coordBaseAngle
-        break
+        coordAngle = 2 * quarterAngle + quarterAngle - coordBaseAngle
+        break;
       case 4:
-        coordAngle = 3*quarterAngle + coordBaseAngle
+        coordAngle = 3 * quarterAngle + coordBaseAngle
     }
-    return coordAngle
+    return coordAngle;
   }
 
-  function scaleBranchLengths(nodes, w) {
+  function scaleBranchLengths(nodes, w, options) {
     // Visit all nodes and adjust y pos width distance metric
-    var visitPreOrder = function(root, callback) {
+    var visitPreOrder = function (root, callback) {
       callback(root)
       if (root.children) {
-        for (var i = root.children.length - 1; i >= 0; i--){
+        for (var i = root.children.length - 1; i >= 0; i--) {
           visitPreOrder(root.children[i], callback);
-        };
+        }
       }
     }
-    visitPreOrder(nodes[0], function(node) {
-      node.rootDist = (node.parent ? node.parent.rootDist : 0) + (node.length || 0)
+    visitPreOrder(nodes[0], function (node) {
+      node.rootDist = (node.parent ? node.parent.rootDist : 0) + (node.length || 0);
     })
-    var rootDists = nodes.map(function(n) { return n.rootDist; });
-    var yscale = d3.scale.linear()
-      .domain([0, d3.max(rootDists)])
-      .range([0, w]);
-    visitPreOrder(nodes[0], function(node) {
-      node.y = yscale(node.rootDist)
+    var rootDists = nodes.map(function (n) {
+      return n.rootDist;
+    });
+    var yscale;
+    switch (parseInt(options.phylogram_scale)) {
+      case 1:
+        yscale = d3.scale.linear()
+          .domain([0, d3.max(rootDists)])
+          .range([0, w]);
+        break;
+      case 2:
+        yscale = d3.scale.log()
+          .domain([0.01, d3.max(rootDists)])
+          .range([0, w]);
+        break;
+      default: // shouldn't happen
+        break;
+    }
+    visitPreOrder(nodes[0], function (node) {
+      node.y = yscale(node.rootDist);
     })
-    return yscale
+    return yscale;
   }
-  
-  d3.phylogram.build = function(selector, nodes, options) {
+
+  d3.phylogram.build = function (selector, nodes, options) {
     options = options || {}
     var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
-        h = options.height || d3.select(selector).style('height') || d3.select(selector).attr('height'),
-        w = parseInt(w),
-        h = parseInt(h);
-    var fill = options.fill || function(d) {
+      h = options.height || d3.select(selector).style('height') || d3.select(selector).attr('height'),
+      w = parseInt(w),
+      h = parseInt(h);
+    var fill = options.fill || function (d) {
       return 'cyan';
     };
-    var size = options.size || function(d) {
+    var size = options.size || function (d) {
       return 6;
     }
-    var nodeMouseOver = options.nodeMouseOver || function(d) {};
-    var nodeMouseOut  = options.nodeMouseOut  || function(d) {};
-    var nodeMouseDown = options.nodeMouseDown || function(d) {};
-    
+    var nodeMouseOver = options.nodeMouseOver || function (d) {
+    };
+    var nodeMouseOut = options.nodeMouseOut || function (d) {
+    };
+    var nodeMouseDown = options.nodeMouseDown || function (d) {
+    };
+
     var tree = options.tree || d3.layout.cluster()
       .size([h, w])
-      .sort(function(node) { return node.children ? node.children.length : -1; })
-      .children(options.children || function(node) {
+      .sort(function (node) {
+        return node.children ? node.children.length : -1;
+      })
+      .children(options.children || function (node) {
         return node.children;
       });
     var diagonal = options.diagonal || d3.phylogram.rightAngleDiagonal();
     var vis = options.vis || d3.select(selector).append("svg:svg")
-        .attr("width", w + 300)
-        .attr("height", h + 30)
-        .append("svg:g")
-        .attr("transform", "translate(20, 20)");
-    var nodes = tree(nodes);
-    
+      .attr("width", w + 300)
+      .attr("height", h + 30)
+      .append("svg:g")
+      .attr("transform", "translate(20, 20)");
+    nodes = tree(nodes);
+
     if (options.skipBranchLengthScaling) {
-      var yscale = d3.scale.linear()
-        .domain([0, w])
-        .range([0, w]);
-    } 
-    else {
-      var yscale = scaleBranchLengths(nodes, w)
+      switch (options.phylogram_scale) {
+        case 1:
+          var yscale = d3.scale.linear()
+            .domain([0, d3.max(rootDists)])
+            .range([0, w]);
+        case 2:
+          var yscale = d3.scale.log()
+            .domain([0.01, d3.max(rootDists)])
+            .range([0, w]);
+      }
+
+    } else {
+      yscale = scaleBranchLengths(nodes, w, options);
     }
-    
+
     if (!options.skipTicks) {
       vis.selectAll('line')
-          .data(yscale.ticks(10))
-          .enter().append('svg:line')
-          .attr('y1', 0)
-          .attr('y2', h)
-          .attr('x1', yscale)
-          .attr('x2', yscale)
-          .attr("stroke", "#ddd");
+        .data(yscale.ticks(10))
+        .enter().append('svg:line')
+        .attr('y1', 0)
+        .attr('y2', h)
+        .attr('x1', yscale)
+        .attr('x2', yscale)
+        .attr("stroke", "#ddd");
 
       vis.selectAll("text.rule")
-          .data(yscale.ticks(10))
-          .enter().append("svg:text")
-          .attr("class", "rule")
-          .attr("x", yscale)
-          .attr("y", 0)
-          .attr("dy", -3)
-          .attr("text-anchor", "middle")
-          .attr('font-size', '9px')
-          .attr('fill', 'grey')
-          .text(function(d) { return Math.round(d*100) / 100; });
+        .data(yscale.ticks(10))
+        .enter().append("svg:text")
+        .attr("class", "rule")
+        .attr("x", yscale)
+        .attr("y", 0)
+        .attr("dy", -3)
+        .attr("text-anchor", "middle")
+        .attr('font-size', '9px')
+        .attr('fill', 'grey')
+        .text(function (d) {
+          return Math.round(d * 100) / 100;
+        });
     }
-        
+
     var link = vis.selectAll("path.link")
-        .data(tree.links(nodes))
-        .enter().append("svg:path")
-        .attr("class", "link")
-        .attr("d", diagonal)
-        .attr("fill", "none")
-        .attr("stroke", "#aaa")
-        .attr("stroke-width", "4px");
-        
+      .data(tree.links(nodes))
+      .enter().append("svg:path")
+      .attr("class", "link")
+      .attr("d", diagonal)
+      .attr("fill", "none")
+      .attr("stroke", "#aaa")
+      .attr("stroke-width", "4px");
+
     var node = vis.selectAll("g.node")
-        .data(nodes)
-        .enter().append("svg:g")
-        .attr("class", function(n) {
-          if (n.children) {
-            if (n.depth == 0) {
-              return "root node"
-            } 
-            else {
-              return "inner node"
-            }
-          } 
-          else {
-            return "leaf node"
+      .data(nodes)
+      .enter().append("svg:g")
+      .attr("class", function (n) {
+        if (n.children) {
+          if (n.depth == 0) {
+            return "root node"
+          } else {
+            return "inner node"
           }
-        })
-        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
+        } else {
+          return "leaf node"
+        }
+      })
+      .attr("transform", function (d) {
+        return "translate(" + d.y + "," + d.x + ")";
+      })
 
-     // style the root node
-     vis.selectAll('g.root.node')
+    // style the root node
+    vis.selectAll('g.root.node')
       .append('svg:circle')
       .on('click', nodeMouseDown)
       .on('mouseover', nodeMouseOver)
@@ -305,16 +342,16 @@ if (!d3) { throw "d3 wasn't included!"};
       .attr('stroke', 'dimgrey')
       .attr('stroke-width', '2px')
       .attr('fill', 'white');
-    
+
     if (!options.skipLabels) {
       vis.selectAll('g.inner.node')
         .append("svg:text")
-          .attr("dx", -6)
-          .attr("dy", -6)
-          .attr("text-anchor", 'end')
-          .attr('font-size', '9px')
-          .attr('fill', 'black')
-        //.text(function(d) { return d.length.toFixed(4); }); // hide length
+        .attr("dx", -6)
+        .attr("dy", -6)
+        .attr("text-anchor", 'end')
+        .attr('font-size', '9px')
+        .attr('fill', 'black')
+      //.text(function(d) { return d.length.toFixed(4); }); // hide length
 
       vis.selectAll('g.leaf.node').append("svg:text")
         .attr("dx", 8)
@@ -323,80 +360,101 @@ if (!d3) { throw "d3 wasn't included!"};
         .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
         .attr('font-size', '10px')
         .attr('fill', 'black')
-        .text(function(d) {
+        .text(function (d) {
           // return d.name + ' (' + d.length.toFixed(4) + ')'; // hide length
           return d.name;
-         });
+        });
     }
     return {tree: tree, vis: vis}
   }
-  
-  d3.phylogram.buildRadial = function(selector, nodes, options) {
+
+  d3.phylogram.buildRadial = function (selector, nodes, options) {
     options = options || {};
-    
-    var fill = options.fill || function(d) {
+
+    var fill = options.fill || function (d) {
       return 'cyan';
     };
-    var size = options.size || function(d) {
+    var size = options.size || function (d) {
       return 6;
     }
-    var nodeMouseOver = options.nodeMouseOver || function(d) {};
-    var nodeMouseOut = options.nodeMouseOut || function(d) {};
-    var nodeMouseDown = options.nodeMouseDown || function(d) {};
-    
+    var nodeMouseOver = options.nodeMouseOver || function (d) {
+    };
+    var nodeMouseOut = options.nodeMouseOut || function (d) {
+    };
+    var nodeMouseDown = options.nodeMouseDown || function (d) {
+    };
+
     var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
-        r = w / 2,
-        labelWidth = options.skipLabels ? 10 : options.labelWidth || 120;
-    
+      r = w / 2,
+      labelWidth = options.skipLabels ? 10 : options.labelWidth || 120;
+
     var vis = d3.select(selector).append("svg:svg")
-        .attr("width", r * 2)
-        .attr("height", r * 2)
-        .append("svg:g")
-        .attr("transform", "translate(" + r + "," + r + ")");
-        
+      .attr("width", r * 2)
+      .attr("height", r * 2)
+      .append("svg:g")
+      .attr("transform", "translate(" + r + "," + r + ")");
+
     var tree = d3.layout.tree()
       .size([360, r - labelWidth])
-      .sort(function(node) { return node.children ? node.children.length : -1; })
-      .children(options.children || function(node) {
+      .sort(function (node) {
+        return node.children ? node.children.length : -1;
+      })
+      .children(options.children || function (node) {
         return node.children;
       })
-      .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
+      .separation(function (a, b) {
+        return (a.parent == b.parent ? 1 : 2) / a.depth;
+      });
 
     var phylogram = d3.phylogram.build(selector, nodes, {
       vis: vis,
       tree: tree,
-      fill : fill,
+      fill: fill,
       size: size,
-      nodeMouseOver : nodeMouseOver,
-      nodeMouseOut : nodeMouseOut,
-      nodeMouseDown : nodeMouseDown,
+      nodeMouseOver: nodeMouseOver,
+      nodeMouseOut: nodeMouseOut,
+      nodeMouseDown: nodeMouseDown,
       skipBranchLengthScaling: true,
       skipTicks: true,
       skipLabels: options.skipLabels,
       diagonal: d3.phylogram.radialRightAngleDiagonal()
     })
     vis.selectAll('g.node')
-      .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
-    
+      .attr("transform", function (d) {
+        return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
+      })
+
     if (!options.skipLabels) {
       vis.selectAll('g.leaf.node text')
-        .attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
+        .attr("dx", function (d) {
+          return d.x < 180 ? 8 : -8;
+        })
         .attr("dy", ".31em")
-        .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
-        .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
+        .attr("text-anchor", function (d) {
+          return d.x < 180 ? "start" : "end";
+        })
+        .attr("transform", function (d) {
+          return d.x < 180 ? null : "rotate(180)";
+        })
         .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
         .attr('font-size', '10px')
         .attr('fill', 'black')
-        .text(function(d) {
+        .text(function (d) {
           return d.name;
         });
 
       vis.selectAll('g.inner.node text')
-        .attr("dx", function(d) { return d.x < 180 ? -6 : 6; })
-        .attr("text-anchor", function(d) { return d.x < 180 ? "end" : "start"; })
-        .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; });
+        .attr("dx", function (d) {
+          return d.x < 180 ? -6 : 6;
+        })
+        .attr("text-anchor", function (d) {
+          return d.x < 180 ? "end" : "start";
+        })
+        .attr("transform", function (d) {
+          return d.x < 180 ? null : "rotate(180)";
+        });
     }
-    
+
     return {tree: tree, vis: vis}
   }
 }());

+ 23 - 16
tripal_chado/theme/js/tripal_phylogeny.js

@@ -1,16 +1,16 @@
 /* phylotree d3js graphs */
 
 (function ($) {
-    
+
   "use strict";
 
   // Will be dynamically sized.
-  var height = 0; 
+  var height = 0;
 
   // Store our function as a property of Drupal.behaviors.
   Drupal.behaviors.TripalPhylotree = {
     attach: function (context, settings) {
-      
+
       // Retrieve the data for this tree.
       var data_url = Drupal.settings.tripal_chado.phylotree_url;
       $.getJSON(data_url, function(treeData) {
@@ -25,13 +25,13 @@
     var size;
     var tree_options = Drupal.settings.tripal_chado.tree_options;
     if (d.cvterm_name == "phylo_root") {
-      size = tree_options['root_node_size']; 
+      size = tree_options['root_node_size'];
     }
     if (d.cvterm_name == "phylo_interior") {
-      size = tree_options['interior_node_size']; 
+      size = tree_options['interior_node_size'];
     }
     if (d.cvterm_name == "phylo_leaf") {
-      size = tree_options['leaf_node_size']; 
+      size = tree_options['leaf_node_size'];
     }
     return size;
   }
@@ -40,14 +40,15 @@
   var phylogeny_organism_color = function(d) {
     var organism_color = Drupal.settings.tripal_chado.org_colors;
     var color = null;
-    if (d.genus) {
-      color = organism_color[d.organism_id];
+
+    if (d.fo_genus) {
+      color = organism_color[d.fo_organism_id];
     }
-    if (color) { 
-      return color; 
+    if (color) {
+      return color;
     }
-    else { 
-      return 'grey'; 
+    else {
+      return 'grey';
     }
   };
 
@@ -64,7 +65,7 @@
       txt.attr('font-weight', 'bold');
     }
   };
-  
+
   // Callback for mouseout event on graph node d.
   var phylogeny_node_mouse_out = function(d) {
     var el = $(this);
@@ -81,7 +82,7 @@
       circle.attr('fill', 'white');
     }
   };
-  
+
   // Callback for mousedown/click event on graph node d.
   var phylogeny_node_mouse_down = function(d) {
     var el = $(this);
@@ -96,7 +97,12 @@
       }
     }
     else {
-      // If this node is not associated with a feature but it has an 
+      if(d.feature_eid) {
+        window.location.href = baseurl + '/bio_data/' + d.feature_eid;
+
+        return;
+      }
+      // If this node is not associated with a feature but it has an
       // organism node then this is a taxonomic node and we want to
       // link it to the organism page.
       if (!d.feature_id && d.organism_nid) {
@@ -121,7 +127,8 @@
       'nodeMouseOver' : phylogeny_node_mouse_over,
       'nodeMouseOut' : phylogeny_node_mouse_out,
       'nodeMouseDown' : phylogeny_node_mouse_down,
-      'skipTicks' : tree_options['skipTicks']
+      'skipTicks' : tree_options['skipTicks'],
+      'phylogram_scale' : tree_options['phylogram_scale']
     });
   }