Bläddra i källkod

This commit adds the option to change the horizontal scale in phylotrees to either linear (default) or logarithmic, as we've found that a logarithmic scale sometimes makes phylotrees a good deal more legible.
This commit also fixes two bugs in the phylotree code where the specified colors per feature were not working, and the feature names were not linking correctly. These bugs were fixed by @almasaeed2010.

Noah Caldwell 4 år sedan
förälder
incheckning
aa6c910082

+ 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);

+ 58 - 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', 6),
       ],
       '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, array $category = array())
+{
   $form = [];
 
   $form['plot_settings'] = [
@@ -319,6 +318,27 @@ function tripal_phylogeny_default_plots_form($form, &$form_state) {
     '#size' => 5,
   ];
 
+  $category += array(
+    'category' => '',
+    'recipients' => '',
+    'reply' => '',
+    'weight' => 0,
+    'selected' => 0,
+    'cid' => NULL,
+  );
+  $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 +467,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 +477,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 +489,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 +514,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 +548,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']
     });
   }