Browse Source

Moved back old tripal_phylogeny legacy code for backwards compatibilty. Stil refactoring Tripal v3 phylogeny

Stephen Ficklin 7 years ago
parent
commit
068bc57613

+ 0 - 265
legacy/tripal_phylogeny/includes/tripal_phylogeny.admin.inc

@@ -1,38 +1,4 @@
 <?php
-/**
- * @file
- * This file contains the functions used for administration of the module
- *
- */
-
-function tripal_phylogeny_admin_phylotrees_listing() {
-  $output = '';
-
-  // set the breadcrumb
-  $breadcrumb = array();
-  $breadcrumb[] = l('Home', '<front>');
-  $breadcrumb[] = l('Administration', 'admin');
-  $breadcrumb[] = l('Tripal', 'admin/tripal');
-  $breadcrumb[] = l('Chado', 'admin/tripal/legacy');
-  $breadcrumb[] = l('Phylotrees', 'admin/tripal/extension/tripal_phylogeny');
-  drupal_set_breadcrumb($breadcrumb);
-
-  // Add the view
-  $view = views_embed_view('tripal_phylogeny_admin_phylotree','default');
-  if (isset($view)) {
-    $output .= $view;
-  }
-  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 '
-      . 'enable the pertinent views</strong>:</p>';
-    $output .= '<ul>';
-      $output .= '<li>'.l('Phylotree View', 'admin/tripal/extension/tripal_phylogeny/views/phylotree/enable').'</li>';
-    $output .= '</ul>';
-  }
-  return $output;
-}
 
 /**
  * Administrative settings form
@@ -85,235 +51,4 @@ function tripal_phylogeny_admin() {
 
   return system_settings_form($form);
 
-}
-
-/**
- *
- * @param unknown $form
- * @param unknown $form_state
- */
-function tripal_phylogeny_default_plots_form($form, &$form_state) {
-  $form = array();
-
-  $form['plot_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Plot Settings'),
-    '#description' => t('You can customize settings for each plot'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE
-  );
-
-  $form['plot_settings']['phylogram_width'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Tree Width',
-    '#description' => 'Please specify the width in pixels for the phylogram',
-    '#default_value' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
-    '#element_validate' => array(
-      'element_validate_integer_positive'
-    ),
-    '#size' => 5,
-  );
-
-  $form['node_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Node Settings'),
-    '#description' => t('You can customize settings for the nodes on the trees.'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE
-  );
-  $form['node_settings']['root_node_size'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Root Node Size',
-    '#description' => 'Please specify a size for the root node size. If set to zero, the node will not appear.',
-    '#default_value' => variable_get('tripal_phylogeny_default_root_node_size', 3),
-    '#element_validate' => array(
-      'element_validate_integer'
-    ),
-    '#size' => 3,
-  );
-  $form['node_settings']['interior_node_size'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Interor Node Size',
-    '#description' => 'Please specify a size for the interior node size. If set to zero, the node will not appear.',
-    '#default_value' => variable_get('tripal_phylogeny_default_interior_node_size', 0),
-    '#element_validate' => array(
-      'element_validate_integer'
-    ),
-    '#size' => 3,
-  );
-  $form['node_settings']['leaf_node_size'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Leaf Node Size',
-    '#description' => 'Please specify a size for the leaf node size. If set to zero, the node will not appear.',
-    '#default_value' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
-    '#element_validate' => array(
-      'element_validate_integer'
-    ),
-    '#size' => 3,
-  );
-
-  // Get the number of organism colors that already exist. If the site admin
-  // has set colors then those settings will be in a Drupal variable which we
-  // will retrieve.  Otherwise the num_orgs defaults to 1 and a single
-  // set of fields is provided.
-  $num_orgs = variable_get("tripal_phylogeny_num_orgs", 1);
-  if (array_key_exists('values', $form_state) and array_key_exists('num_orgs', $form_state['values'])) {
-    $num_orgs = $form_state['values']['num_orgs'];
-  }
-  // The default values for each organism color are provided in a d
-  // Drupal variable that gets set when the form is set.
-  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
-
-  $form['node_settings']['desc'] = array(
-    '#type' => 'item',
-    '#title' => t('Node Colors by Organism'),
-    '#markup' => t('If the trees are associated with features (e.g. proteins)
-      then the nodes can be color-coded by their organism.  This helps the user
-      visualize which nodes belong to each organism.  Please enter the
-      name of the organism and it\'s corresponding color in HEX code (e.g. #FF0000 == red).
-      Organisms that are not given a color will be gray.'),
-  );
-  $form['node_settings']['org_table']['num_orgs'] = array(
-    '#type' => 'value',
-    '#value' => $num_orgs,
-  );
-
-  // Iterate through the number of organism colors and add a field for each one.
-  for ($i = 0; $i < $num_orgs; $i++) {
-    $form['node_settings']['org_table']['organism_' . $i] = array(
-      '#type' => 'textfield',
-      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['organism'] : '',
-      '#autocomplete_path' => "admin/tripal/legacy/tripal_organism/organism/auto_name",
-      '#description' => t('Please enter the name of the organism.'),
-      '#size' => 30,
-    );
-    $form['node_settings']['org_table']['color_' . $i] = array(
-      '#type' => 'textfield',
-      '#description' => t('Please provide a color in Hex format (e.g. #FF0000).'),
-      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['color'] : '',
-      '#suffix' => "<div id=\"color-box-$i\" style=\"width: 30px;\"></div>",
-      '#size' => 10,
-    );
-  }
-  $form['node_settings']['org_table']['add'] = array(
-    '#type' => 'submit',
-    '#name' => 'add',
-    '#value' => 'Add',
-    '#ajax' => array(
-      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
-      'wrapper'  => 'tripal_phylogeny_default_plots_form',
-      'effect'   => 'fade',
-      'method'   => 'replace',
-    ),
-  );
-  $form['node_settings']['org_table']['remove'] = array(
-    '#type' => 'submit',
-    '#name' => 'remove',
-    '#value' => 'Remove',
-    '#ajax' => array(
-      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
-      'wrapper'  => 'tripal_phylogeny_default_plots_form',
-      'effect'   => 'fade',
-      'method'   => 'replace',
-    ),
-  );
-  $form['node_settings']['org_table']['#theme'] = 'tripal_phylogeny_admin_org_color_tables';
-  $form['node_settings']['org_table']['#prefix'] = '<div id="tripal_phylogeny_default_plots_form">';
-  $form['node_settings']['org_table']['#suffix'] = '</div>';
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#name' => 'submit',
-    '#value' => 'Save Configuration',
-  );
-
-  $form['#submit'][] = 'tripal_phylogeny_default_plots_form_submit';
-
-  return $form;
-}
-
-/**
- * Validate the phylotree settings forms
- *
- * @ingroup tripal_phylogeny
- */
-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) {
-  // Rebuild this form after submission so that any changes are reflected in
-  // the flat tables.
-  $form_state['rebuild'] = TRUE;
-
-  if ($form_state['clicked_button']['#name'] == 'submit') {
-    variable_set('tripal_phylogeny_default_phylogram_width', $form_state['values']['phylogram_width']);
-
-    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']);
-
-    $num_orgs = $form_state['values']['num_orgs'];
-    variable_set("tripal_phylogeny_num_orgs", $num_orgs);
-    $colors = array();
-    for ($i = 0; $i < $num_orgs ;$i++) {
-      $colors[$i] = array(
-        'organism' => $form_state['values']['organism_' . $i],
-        'color' => $form_state['values']['color_' . $i]
-      );
-    }
-    variable_set("tripal_phylogeny_org_colors", $colors);
-  }
-  if ($form_state['clicked_button']['#name'] == 'add') {
-    $form_state['values']['num_orgs']++;
-  }
-  if ($form_state['clicked_button']['#name'] == 'remove') {
-    $form_state['values']['num_orgs']--;
-  }
-}
-
-/**
- *
- * @param unknown $variables
- */
-function theme_tripal_phylogeny_admin_org_color_tables($variables){
-   $fields = $variables['element'];
-   $num_orgs = $fields['num_orgs']['#value'];
-   $headers = array('Organism', 'Color', '');
-   $rows = array();
-   for ($i = 0; $i < $num_orgs; $i++) {
-     $add_button = ($i == $num_orgs - 1) ? drupal_render($fields['add']) : '';
-     $del_button = ($i == $num_orgs - 1 and $i != 0) ? drupal_render($fields['remove']) : '';
-     $rows[] = array(
-       drupal_render($fields['organism_' . $i]),
-       drupal_render($fields['color_' . $i]),
-       $add_button . $del_button,
-     );
-   }
-   $table_vars = array(
-     'header' => $headers,
-     'rows' => $rows,
-     'attributes' => array(),
-     'sticky' => FALSE,
-     'colgroups' => array(),
-     'empty' => '',
-   );
-   $form['orgs']['num_orgs'] = $fields['num_orgs'];
-   return theme('table', $table_vars);
-}
-
-
-/**
- * Ajax callback function for the gensas_job_view_panel_form.
- *
- * @param $form
- * @param $form_state
- */
-function tripal_phylogeny_default_plots_form_ajax_callback($form, $form_state) {
-
-  return $form['node_settings']['org_table'];
 }

+ 42 - 0
legacy/tripal_phylogeny/includes/tripal_phylogeny.taxonomy.inc

@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Generates a page that contains the taxonomy view.
+ */
+function tripal_phylogeny_taxonomy_view() {
+  $values = array(
+    'type_id' => array(
+      'name' => 'taxonomy',
+    ),
+  );
+
+  $message = t('Site administrators:  This page is meant to provide
+      a heirarchical taxonomic tree for all of the organism present
+      in this site.  This may not be useful if you only have a few
+      species. If so, you can turn off this page by disabling this page on
+      the site\'s <a href="@menu">Navigation Menu</a>.  Otherwise, to generate the taxonomy go to this site\'s
+      <a href="@taxloader">NCBI taxonomy loader</a> to import the taxonomy information from NCBI.
+      <br><br>Note: If you add new species to this site, you should rerun the
+        NCBI taxonomy loader to update the view</p>',
+      array(
+        '@menu' => url('admin/structure/menu/manage/navigation'),
+        '@taxloader' => url('admin/tripal/loaders/ncbi_taxonomy_loader'
+            ))
+      );
+  $admin_message = tripal_set_message($message, TRIPAL_INFO, array('return_html' => TRUE));
+
+  $phylotree = chado_generate_var('phylotree', $values);
+  if ($phylotree) {
+    $node = new stdClass();
+    $node->phylotree = $phylotree;
+
+    $html =  theme('tripal_phylogeny_taxonomic_tree', array('node' => $node)) .
+    $admin_message;
+    return $html;
+  }
+
+  return array(
+    '#type' => 'markup',
+    '#markup' => t('This site has not yet prepared the taxonomy for viewing.') . $admin_message,
+  );
+}

+ 1 - 1
legacy/tripal_phylogeny/theme/css/tripal_phylogeny.css

@@ -399,4 +399,4 @@ if (!d3) { throw "d3 wasn't included!"};
     
     return {tree: tree, vis: vis}
   }
-}());
+}());

+ 1 - 1
legacy/tripal_phylogeny/theme/js/tripal_phylogeny.js

@@ -127,4 +127,4 @@
       return 22 * leafNodeCt;
     }
   });
-})(jQuery);
+})(jQuery);

+ 2 - 81
legacy/tripal_phylogeny/theme/tripal_phylogeny.theme.inc

@@ -1,86 +1,5 @@
 <?php
 
-function tripal_phylogeny_prepare_tree_viewer($phylotree) {
-
-  // Don't prepare for viewing more than once.
-  if (property_exists($phylotree, 'prepared_to_view') and
-      $phylotree->prepared_to_view == TRUE) {
-    return;
-  }
-
-  $module_path = drupal_get_path('module', 'tripal_phylogeny');
-
-  drupal_add_js('https://d3js.org/d3.v3.min.js', 'external');
-
-  drupal_add_js("$module_path/theme/js/d3.phylogram.js");
-  drupal_add_js("$module_path/theme/js/tripal_phylogeny.js");
-  drupal_add_css("$module_path/theme/css/tripal_phylogeny.css");
-
-  drupal_add_library('system', 'ui.dialog');
-
-  // Don't show tick marks for the taxonomy tree.
-  $skip_ticks = 0;
-  if ($phylotree->type_id->name == 'taxonomy') {
-    $skip_ticks = 1;
-  }
-
-  // Get the tree options as set by the administrator.
-  $options = json_encode(array(
-    'phylogram_width' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
-    'root_node_size' => variable_get('tripal_phylogeny_default_root_node_size', 3),
-    'interior_node_size' => variable_get('tripal_phylogeny_default_interior_node_size', 0),
-    'leaf_node_size' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
-    'skipTicks' => $skip_ticks,
-  ));
-
-  // Get the node colors as set by the administrator.
-  $colors = array();
-  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
-  foreach ($color_defaults as $i => $details) {
-    if ($details['organism']) {
-      $colors[$details['organism']] =  $details['color'];
-    }
-  }
-  $colors = json_encode($colors);
-
-  // Add javascript data needed for this tree.
-  drupal_add_js(
-    ' // var having URL of json data source for charting
-      var phylotreeDataURL =  baseurl + "/ajax/chado_phylotree/' . $phylotree->phylotree_id . '/json";
-
-      // var with path to our theme, for use by javascript functions.
-      var pathToTheme = baseurl + "/' . $module_path . '/theme";
-
-      // var with custom options
-      var treeOptions = ' . $options . ';
-
-      // var with the organism colors
-      var organismColors = ' . $colors . ';',
-    'inline'
-  );
-
-  if (!property_exists($phylotree, 'has_nodes')) {
-    // If the nodes haven't loaded then set a value so the template can
-    // choose not to show the phylogram.
-    $values  = array('phylotree_id' => $phylotree->phylotree_id);
-    $options = array('limit' => 1, 'offset' => 0, 'has_record' => 1);
-    $phylotree->has_nodes = chado_select_record('phylonode', array('phylonode_id'), $values, $options);
-  }
-  if (!property_exists($phylotree, 'has_features')) {
-    // If the nodes haven't loaded then set a value so the template can
-    // choose not to show the circular dendrogram. The chado_select_record()
-    // API call can't do this query so we have to do it manually.
-    $sql = "
-      SELECT count(*) as num_features
-      FROM {phylonode}
-      WHERE NOT feature_id IS NULL and phylotree_id = :phylotree_id
-      LIMIT 1 OFFSET 0
-    ";
-    $phylotree->has_features = chado_query($sql, array(':phylotree_id' => $phylotree->phylotree_id))->fetchField();
-  }
-
-  $phylotree->prepared_to_view = TRUE;
-}
 /**
  * Implements hook_preprocess_hook()
  *
@@ -88,6 +7,7 @@ function tripal_phylogeny_prepare_tree_viewer($phylotree) {
  */
 function tripal_phylogeny_preprocess_tripal_phylogeny_phylogram(&$variables) {
   $phylotree = $variables['node']->phylotree;
+  module_load_include('inc', 'tripal_chado', 'includes/tripal_chado.phylotree');
   tripal_phylogeny_prepare_tree_viewer($phylotree);
 }
 
@@ -98,5 +18,6 @@ function tripal_phylogeny_preprocess_tripal_phylogeny_phylogram(&$variables) {
  */
 function tripal_phylogeny_preprocess_tripal_phylogeny_taxonomic_tree(&$variables) {
   $phylotree = $variables['node']->phylotree;
+  module_load_include('inc', 'tripal_chado', 'includes/tripal_chado.phylotree');
   tripal_phylogeny_prepare_tree_viewer($phylotree);
 }

+ 18 - 147
legacy/tripal_phylogeny/tripal_phylogeny.module

@@ -17,6 +17,7 @@ require_once 'theme/tripal_phylogeny.theme.inc';
 require_once 'includes/tripal_phylogeny.admin.inc';
 require_once 'includes/tripal_phylogeny.chado_node.inc';
 require_once 'includes/tripal_phylogeny.import_tree.inc';
+require_once 'includes/tripal_phylogeny.taxonomy.inc';
 
 /**
  * Implements hook_permission().
@@ -52,6 +53,15 @@ function tripal_phylogeny_permission() {
 function tripal_phylogeny_menu() {
   $items = array();
 
+  $items['taxonomy_view'] = array(
+    'title' => 'Taxonomy',
+    'description' => 'Taxonomic view of the species available on this site.',
+    'page callback' => 'tripal_phylogeny_taxonomy_view',
+    'access arguments' => array('access taxonomy content'),
+    'file' => '/includes/tripal_phylogeny.taxonomy.inc',
+    'type' => MENU_NORMAL_ITEM,
+  );
+
   // administration landing page. currently has no content but is
   // apparently required for the Sync and Help links to work.
   $items['admin/tripal/legacy/tripal_phylogeny'] = array(
@@ -84,16 +94,6 @@ function tripal_phylogeny_menu() {
     'weight' => 1
   );
 
-  $items['admin/tripal/legacy/tripal_phylogeny/plots'] = array(
-    'title' => 'Plot Defaults',
-    'description' => 'Set defaults for the trees',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('tripal_phylogeny_default_plots_form'),
-    'access arguments' => array('administer tripal phylotree'),
-    'type' => MENU_LOCAL_TASK,
-    'weight' => 2
-  );
-
   // sync menu item (will be rendered as a tab by tripal)
   $items['admin/tripal/legacy/tripal_phylogeny/sync'] = array(
     'title' => ' Sync',
@@ -114,13 +114,13 @@ function tripal_phylogeny_menu() {
     'type' => MENU_CALLBACK,
   );
 
-   // create a route for viewing json of all phylonodes having this phylotree_id
-   $items['ajax/chado_phylotree/%/json'] = array(
-     'page callback' => 'tripal_phylogeny_ajax_get_tree_json',
-     'page arguments' => array(2),
-     // allow all anonymous http clients
-     'access callback' => TRUE
-   );
+  // create a route for viewing json of all phylonodes having this phylotree_id
+  $items['ajax/chado_phylotree/%/json'] = array(
+    'page callback' => 'tripal_phylogeny_ajax_get_tree_json',
+    'page arguments' => array(2),
+    // allow all anonymous http clients
+    'access callback' => TRUE
+  );
 
   return $items;
 }
@@ -237,133 +237,4 @@ function tripal_phylogeny_help ($path, $arg) {
   if ($path == 'admin/help#tripal_phylogeny') {
     return theme('tripal_phylogeny_help', array());
   }
-}
-
-/**
- * Get json representation of a phylotree id.
- *
- * This function is meant to be called via AJAX.
- *
- * @param int $phylotree_id
- *   the ID of the phylotree node.
- *
- * @return string json
- *
- * @ingroup tripal_phylogeny
- */
-function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
-
-  $phylotree = chado_generate_var('phylotree', array('phylotree_id' => $phylotree_id));
-
-  // This SQL gets all of the phylonodes for a given tree as well as the
-  // features and organisms with which it is assocaited.  Each phylonode
-  // can be associated with an organism in one of two ways: 1) via a
-  // feature linked by the phylonode.feature_id field or 2) via a
-  // a record in the phylonde_organsim table.  Therefore both types of
-  // organism records are returned in the query below, but those
-  // retrieved via a FK link on features are prefixed with 'fo_'.
-  $sql = "
-    SELECT
-      n.phylonode_id, n.parent_phylonode_id, n.label AS name, n.distance AS length,
-      f.feature_id, f.name AS feature_name,
-      cvt.name AS cvterm_name,
-      o.organism_id, o.common_name, o.abbreviation, o.genus, o.species,
-      fo.organism_id AS fo_organism_id, fo.common_name AS fo_common_name,
-      fo.abbreviation AS fo_abbreviation, fo.genus as fo_genus, fo.species AS fo_species,
-      cf.nid AS feature_node_id,
-      fco.nid AS fo_organism_node_id,
-      co.nid AS organism_node_id
-    FROM {phylonode} n
-      LEFT OUTER JOIN {cvterm} cvt              ON n.type_id = cvt.cvterm_id
-      LEFT OUTER JOIN {feature} f               ON n.feature_id = f.feature_id
-      LEFT OUTER JOIN [chado_feature] cf        ON cf.feature_id = f.feature_id
-      LEFT OUTER JOIN {organism} fo             ON f.organism_id = fo.organism_id
-      LEFT OUTER JOIN [chado_organism] fco      ON fco.organism_id = fo.organism_id
-      LEFT OUTER JOIN {phylonode_organism} po   ON po.phylonode_id = n.phylonode_id
-      LEFT OUTER JOIN {organism} o              ON PO.organism_id = o.organism_id
-      LEFT OUTER JOIN [chado_organism] co       ON co.organism_id = o.organism_id
-    WHERE n.phylotree_id = :phylotree_id
-  ";
-  $args = array(':phylotree_id' => $phylotree_id);
-  $result = chado_query($sql, $args);
-
-  // Fetch all the phylonodes into an assoc array indexed by phylonode_id.
-  // Convert from resultset record to array, fixing datatypes. chado_query
-  // returns numeric as string and fun stuff like that.
-  $phylonodes = array();
-  $root_phylonode_ref = null;
-
-  foreach ($result as $r) {
-    $phylonode_id = (int) $r->phylonode_id;
-
-    // expect all nodes to have these properties
-    $node = array(
-      'phylonode_id' => $phylonode_id,
-      'parent_phylonode_id' => (int) $r->parent_phylonode_id,
-      'length' => (double) $r->length,
-      'cvterm_name' => $r->cvterm_name
-    );
-
-    // If the nodes are taxonomic then set an equal distance
-    if ($phylotree->type_id->name == 'taxonomy') {
-      $node['length'] = 0.001;
-    }
-
-    // Other props may exist only for leaf nodes
-    if ($r->name) {
-      $node['name'] = $r->name;
-    }
-    // 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_name'] = $r->feature_name;
-      $node['feature_node_id'] = (int) $r->feature_node_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['common_name'] = $r->common_name;
-      $node['abbreviation'] = $r->abbreviation;
-      $node['genus'] = $r->genus;
-      $node['species'] = $r->species;
-      $node['organism_node_id'] = (int) $r->organism_node_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.
-      if (!$r->name) {
-        $node['name'] = $r->genus . ' ' . $r->species;
-      }
-    }
-    // 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_common_name'] = $r->fo_common_name;
-      $node['fo_abbreviation'] = $r->fo_abbreviation;
-      $node['fo_genus'] = $r->fo_genus;
-      $node['fo_species'] = $r->fo_species;
-      $node['fo_organism_node_id'] = (int) $r->fo_organism_node_id;
-    }
-
-    // Add this node to the list, organized by ID.
-    $phylonodes[$phylonode_id] = $node;
-  }
-
-  // Populate the children[] arrays for each node.
-  foreach ($phylonodes as $key => &$node) {
-    if ($node['parent_phylonode_id'] !== 0) {
-      $parent_ref = &$phylonodes[ $node['parent_phylonode_id']];
-      // Append node refernce to children.
-      $parent_ref['children'][] = &$node;
-    }
-    else {
-      $root_phylonode_ref = &$node;
-    }
-  }
-
-  // dump datastructure as json to browser. drupal sets the mime-type correctly.
-  drupal_json_output($root_phylonode_ref);
-}
-
-
-
+}

+ 4 - 6
tripal_chado/api/tripal_chado.query.api.inc

@@ -1619,6 +1619,8 @@ function chado_select_record_check_value_type(&$op, &$value, $type) {
  * @ingroup tripal_chado_query_api
  */
 function chado_query($sql, $args = array()) {
+  $results = NULL;
+
   $is_local = isset($GLOBALS["chado_is_local"]) && $GLOBALS["chado_is_local"];
 
   // Args should be an array
@@ -1666,12 +1668,8 @@ function chado_query($sql, $args = array()) {
         chado_set_active($previous_db);
       }
       catch (Exception $e) {
-        try {
-          chado_set_active($previous_db);
-        }
-        catch (Exception $e2) {
-          throw new Exception($e->getMessage() . $e2->getMessage());
-        }
+        chado_set_active($previous_db);
+        throw $e;
       }
     }
     // for all other tables we should have everything in scope so just run the query

+ 504 - 0
tripal_chado/includes/tripal_chado.phylotree.inc

@@ -0,0 +1,504 @@
+<?php
+/**
+ * Prepares a phylogenetic tree for viewing.
+ *
+ * @param $phylotree
+ */
+function tripal_phylogeny_prepare_tree_viewer($phylotree) {
+
+  // Don't prepare for viewing more than once.
+  if (property_exists($phylotree, 'prepared_to_view') and
+      $phylotree->prepared_to_view == TRUE) {
+    return;
+  }
+
+  $module_path = drupal_get_path('module', 'tripal_chado');
+
+  drupal_add_js('https://d3js.org/d3.v3.min.js', 'external');
+
+  drupal_add_js("$module_path/theme/js/d3.phylogram.js");
+  drupal_add_js("$module_path/theme/js/tripal_phylogeny.js");
+  drupal_add_css("$module_path/theme/css/tripal_phylogeny.css");
+
+  drupal_add_library('system', 'ui.dialog');
+
+  // Don't show tick marks for the taxonomy tree.
+  $skip_ticks = 0;
+  if ($phylotree->type_id->name == 'taxonomy') {
+    $skip_ticks = 1;
+  }
+
+  // Get the tree options as set by the administrator.
+  $options = json_encode(array(
+    'phylogram_width' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
+    'root_node_size' => variable_get('tripal_phylogeny_default_root_node_size', 3),
+    'interior_node_size' => variable_get('tripal_phylogeny_default_interior_node_size', 0),
+    'leaf_node_size' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
+    'skipTicks' => $skip_ticks,
+  ));
+
+  // Get the node colors as set by the administrator.
+  $colors = array();
+  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
+  foreach ($color_defaults as $i => $details) {
+    if ($details['organism']) {
+      $colors[$details['organism']] =  $details['color'];
+    }
+  }
+  $colors = json_encode($colors);
+
+  drupal_add_js(array(
+    'tripal_chado' => array(
+      'phylotree_url' => url('phylotree/' . $phylotree->phylotree_id),
+      'phylotree_theme_url' => url($module_path . '/theme'),
+      'tree_options' => $options,
+      'org_colors' => $colors,
+    ),
+  ), 'setting');
+
+  if (!property_exists($phylotree, 'has_nodes')) {
+    // If the nodes haven't loaded then set a value so the template can
+    // choose not to show the phylogram.
+    $values  = array('phylotree_id' => $phylotree->phylotree_id);
+    $options = array('limit' => 1, 'offset' => 0, 'has_record' => 1);
+    $phylotree->has_nodes = chado_select_record('phylonode', array('phylonode_id'), $values, $options);
+  }
+  if (!property_exists($phylotree, 'has_features')) {
+    // If the nodes haven't loaded then set a value so the template can
+    // choose not to show the circular dendrogram. The chado_select_record()
+    // API call can't do this query so we have to do it manually.
+    $sql = "
+      SELECT count(*) as num_features
+      FROM {phylonode}
+      WHERE NOT feature_id IS NULL and phylotree_id = :phylotree_id
+      LIMIT 1 OFFSET 0
+    ";
+    $phylotree->has_features = chado_query($sql, array(':phylotree_id' => $phylotree->phylotree_id))->fetchField();
+  }
+
+  $phylotree->prepared_to_view = TRUE;
+}
+
+/**
+ * Get json representation of a phylotree id.
+ *
+ * This function is meant to be called via AJAX.
+ *
+ * @param int $phylotree_id
+ *   the ID of the phylotree node.
+ *
+ * @return string json
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
+
+  $phylotree = chado_generate_var('phylotree', array('phylotree_id' => $phylotree_id));
+
+  // For backwards compatibility with Tripal v2 and the legacy modules of
+  // Tripal v3 we have two different SQL statements.
+  if (module_exists('tripal_phylogeny')) {
+    // This SQL gets all of the phylonodes for a given tree as well as the
+    // features and organisms with which it is associated.  Each phylonode
+    // can be associated with an organism in one of two ways: 1) via a
+    // feature linked by the phylonode.feature_id field or 2) via a
+    // a record in the phylonde_organsim table.  Therefore both types of
+    // organism records are returned in the query below, but those
+    // retrieved via a FK link on features are prefixed with 'fo_'.
+    $sql = "
+      SELECT
+        n.phylonode_id, n.parent_phylonode_id, n.label AS name, n.distance AS length,
+        f.feature_id, f.name AS feature_name,
+        cvt.name AS cvterm_name,
+        o.organism_id, o.common_name, o.abbreviation, o.genus, o.species,
+        fo.organism_id AS fo_organism_id, fo.common_name AS fo_common_name,
+        fo.abbreviation AS fo_abbreviation, fo.genus as fo_genus, fo.species AS fo_species,
+        cf.nid AS feature_nid,
+        fco.nid AS fo_organism_nid,
+        co.nid AS organism_nid
+      FROM {phylonode} n
+        LEFT OUTER JOIN {cvterm} cvt              ON n.type_id = cvt.cvterm_id
+        LEFT OUTER JOIN {feature} f               ON n.feature_id = f.feature_id
+        LEFT OUTER JOIN [chado_feature] cf        ON cf.feature_id = f.feature_id
+        LEFT OUTER JOIN {organism} fo             ON f.organism_id = fo.organism_id
+        LEFT OUTER JOIN [chado_organism] fco      ON fco.organism_id = fo.organism_id
+        LEFT OUTER JOIN {phylonode_organism} po   ON po.phylonode_id = n.phylonode_id
+        LEFT OUTER JOIN {organism} o              ON PO.organism_id = o.organism_id
+        LEFT OUTER JOIN [chado_organism] co       ON co.organism_id = o.organism_id
+      WHERE n.phylotree_id = :phylotree_id
+    ";
+  }
+  else {
+    $sql = "
+      SELECT
+        n.phylonode_id, n.parent_phylonode_id, n.label AS name, n.distance AS length,
+        f.feature_id, f.name AS feature_name,
+        cvt.name AS cvterm_name,
+        o.organism_id, o.common_name, o.abbreviation, o.genus, o.species,
+        fo.organism_id AS fo_organism_id, fo.common_name AS fo_common_name,
+        fo.abbreviation AS fo_abbreviation, fo.genus as fo_genus, fo.species AS fo_species
+      FROM {phylonode} n
+        LEFT OUTER JOIN {cvterm} cvt              ON n.type_id = cvt.cvterm_id
+        LEFT OUTER JOIN {feature} f               ON n.feature_id = f.feature_id
+        LEFT OUTER JOIN {organism} fo             ON f.organism_id = fo.organism_id
+        LEFT OUTER JOIN {phylonode_organism} po   ON po.phylonode_id = n.phylonode_id
+        LEFT OUTER JOIN {organism} o              ON PO.organism_id = o.organism_id
+      WHERE n.phylotree_id = :phylotree_id
+    ";
+  }
+  $args = array(':phylotree_id' => $phylotree_id);
+  $results = chado_query($sql, $args);
+
+  // Fetch all the phylonodes into an assoc array indexed by phylonode_id.
+  // Convert from resultset record to array, fixing datatypes. chado_query
+  // returns numeric as string and fun stuff like that.
+  $phylonodes = array();
+  $root_phylonode_ref = null;
+
+  if ($results) {
+    while($r = $results->fetchObject()) {
+      $phylonode_id = (int) $r->phylonode_id;
+
+      // expect all nodes to have these properties
+      $node = array(
+        'phylonode_id' => $phylonode_id,
+        'parent_phylonode_id' => (int) $r->parent_phylonode_id,
+        'length' => (double) $r->length,
+        'cvterm_name' => $r->cvterm_name
+      );
+
+      // If the nodes are taxonomic then set an equal distance
+      if ($phylotree->type_id->name == 'taxonomy') {
+        $node['length'] = 0.001;
+      }
+
+      // Other props may exist only for leaf nodes
+      if ($r->name) {
+        $node['name'] = $r->name;
+      }
+      // 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_name'] = $r->feature_name;
+        if (module_exists('tripal_phylogeny')) {
+          $node['feature_nid'] = (int) $r->feature_nid;
+        }
+      }
+      // 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['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;
+        }
+        // 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.
+        if (!$r->name) {
+          $node['name'] = $r->genus . ' ' . $r->species;
+        }
+      }
+      // 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_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;
+        }
+      }
+
+      // Add this node to the list, organized by ID.
+      $phylonodes[$phylonode_id] = $node;
+    }
+
+    // Populate the children[] arrays for each node.
+    foreach ($phylonodes as $key => &$node) {
+      if ($node['parent_phylonode_id'] !== 0) {
+        $parent_ref = &$phylonodes[ $node['parent_phylonode_id']];
+        // Append node refernce to children.
+        $parent_ref['children'][] = &$node;
+      }
+      else {
+        $root_phylonode_ref = &$node;
+      }
+    }
+  }
+
+  // dump datastructure as json to browser. drupal sets the mime-type correctly.
+  drupal_json_output($root_phylonode_ref);
+}
+
+
+/**
+ * @file
+ * This file contains the functions used for administration of the module
+ *
+ */
+
+function tripal_phylogeny_admin_phylotrees_listing() {
+  $output = '';
+
+  // set the breadcrumb
+  $breadcrumb = array();
+  $breadcrumb[] = l('Home', '<front>');
+  $breadcrumb[] = l('Administration', 'admin');
+  $breadcrumb[] = l('Tripal', 'admin/tripal');
+  $breadcrumb[] = l('Data Storage', 'admin/tripal/storage');
+  $breadcrumb[] = l('Chado', 'admin/tripal/storage/chado');
+  drupal_set_breadcrumb($breadcrumb);
+
+  // Add the view
+  $view = views_embed_view('tripal_phylogeny_admin_phylotree','default');
+  if (isset($view)) {
+    $output .= $view;
+  }
+  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 '
+                . 'enable the pertinent views</strong>:</p>';
+                $output .= '<ul>';
+                $output .= '<li>'.l('Phylotree View', 'admin/tripal/extension/tripal_phylogeny/views/phylotree/enable').'</li>';
+                $output .= '</ul>';
+  }
+  return $output;
+}
+
+
+/**
+ *
+ * @param unknown $form
+ * @param unknown $form_state
+ */
+function tripal_phylogeny_default_plots_form($form, &$form_state) {
+  $form = array();
+
+  $form['plot_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Plot Settings'),
+    '#description' => t('You can customize settings for each plot'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE
+  );
+
+  $form['plot_settings']['phylogram_width'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Tree Width',
+    '#description' => 'Please specify the width in pixels for the phylogram',
+    '#default_value' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
+    '#element_validate' => array(
+      'element_validate_integer_positive'
+    ),
+    '#size' => 5,
+  );
+
+  $form['node_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Node Settings'),
+    '#description' => t('You can customize settings for the nodes on the trees.'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE
+  );
+  $form['node_settings']['root_node_size'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Root Node Size',
+    '#description' => 'Please specify a size for the root node size. If set to zero, the node will not appear.',
+    '#default_value' => variable_get('tripal_phylogeny_default_root_node_size', 3),
+    '#element_validate' => array(
+      'element_validate_integer'
+    ),
+    '#size' => 3,
+  );
+  $form['node_settings']['interior_node_size'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Interor Node Size',
+    '#description' => 'Please specify a size for the interior node size. If set to zero, the node will not appear.',
+    '#default_value' => variable_get('tripal_phylogeny_default_interior_node_size', 0),
+    '#element_validate' => array(
+      'element_validate_integer'
+    ),
+    '#size' => 3,
+  );
+  $form['node_settings']['leaf_node_size'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Leaf Node Size',
+    '#description' => 'Please specify a size for the leaf node size. If set to zero, the node will not appear.',
+    '#default_value' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
+    '#element_validate' => array(
+      'element_validate_integer'
+    ),
+    '#size' => 3,
+  );
+
+  // Get the number of organism colors that already exist. If the site admin
+  // has set colors then those settings will be in a Drupal variable which we
+  // will retrieve.  Otherwise the num_orgs defaults to 1 and a single
+  // set of fields is provided.
+  $num_orgs = variable_get("tripal_phylogeny_num_orgs", 1);
+  if (array_key_exists('values', $form_state) and array_key_exists('num_orgs', $form_state['values'])) {
+    $num_orgs = $form_state['values']['num_orgs'];
+  }
+  // The default values for each organism color are provided in a d
+  // Drupal variable that gets set when the form is set.
+  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
+
+  $form['node_settings']['desc'] = array(
+    '#type' => 'item',
+    '#title' => t('Node Colors by Organism'),
+    '#markup' => t('If the trees are associated with features (e.g. proteins)
+      then the nodes can be color-coded by their organism.  This helps the user
+      visualize which nodes belong to each organism.  Please enter the
+      name of the organism and it\'s corresponding color in HEX code (e.g. #FF0000 == red).
+      Organisms that are not given a color will be gray.'),
+  );
+  $form['node_settings']['org_table']['num_orgs'] = array(
+    '#type' => 'value',
+    '#value' => $num_orgs,
+  );
+
+  // Iterate through the number of organism colors and add a field for each one.
+  for ($i = 0; $i < $num_orgs; $i++) {
+    $form['node_settings']['org_table']['organism_' . $i] = array(
+      '#type' => 'textfield',
+      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['organism'] : '',
+      '#autocomplete_path' => "admin/tripal/legacy/tripal_organism/organism/auto_name",
+      '#description' => t('Please enter the name of the organism.'),
+      '#size' => 30,
+    );
+    $form['node_settings']['org_table']['color_' . $i] = array(
+      '#type' => 'textfield',
+      '#description' => t('Please provide a color in Hex format (e.g. #FF0000).'),
+      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['color'] : '',
+      '#suffix' => "<div id=\"color-box-$i\" style=\"width: 30px;\"></div>",
+      '#size' => 10,
+    );
+  }
+  $form['node_settings']['org_table']['add'] = array(
+    '#type' => 'submit',
+    '#name' => 'add',
+    '#value' => 'Add',
+    '#ajax' => array(
+      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
+      'wrapper'  => 'tripal_phylogeny_default_plots_form',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+    ),
+  );
+  $form['node_settings']['org_table']['remove'] = array(
+    '#type' => 'submit',
+    '#name' => 'remove',
+    '#value' => 'Remove',
+    '#ajax' => array(
+      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
+      'wrapper'  => 'tripal_phylogeny_default_plots_form',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+    ),
+  );
+  $form['node_settings']['org_table']['#theme'] = 'tripal_phylogeny_admin_org_color_tables';
+  $form['node_settings']['org_table']['#prefix'] = '<div id="tripal_phylogeny_default_plots_form">';
+  $form['node_settings']['org_table']['#suffix'] = '</div>';
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#name' => 'submit',
+    '#value' => 'Save Configuration',
+  );
+
+  $form['#submit'][] = 'tripal_phylogeny_default_plots_form_submit';
+
+  return $form;
+}
+
+/**
+ * Validate the phylotree settings forms
+ *
+ * @ingroup tripal_phylogeny
+ */
+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) {
+  // Rebuild this form after submission so that any changes are reflected in
+  // the flat tables.
+  $form_state['rebuild'] = TRUE;
+
+  if ($form_state['clicked_button']['#name'] == 'submit') {
+    variable_set('tripal_phylogeny_default_phylogram_width', $form_state['values']['phylogram_width']);
+
+    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']);
+
+    $num_orgs = $form_state['values']['num_orgs'];
+    variable_set("tripal_phylogeny_num_orgs", $num_orgs);
+    $colors = array();
+    for ($i = 0; $i < $num_orgs ;$i++) {
+      $colors[$i] = array(
+        'organism' => $form_state['values']['organism_' . $i],
+        'color' => $form_state['values']['color_' . $i]
+      );
+    }
+    variable_set("tripal_phylogeny_org_colors", $colors);
+  }
+  if ($form_state['clicked_button']['#name'] == 'add') {
+    $form_state['values']['num_orgs']++;
+  }
+  if ($form_state['clicked_button']['#name'] == 'remove') {
+    $form_state['values']['num_orgs']--;
+  }
+}
+
+/**
+ *
+ * @param unknown $variables
+ */
+function theme_tripal_phylogeny_admin_org_color_tables($variables){
+  $fields = $variables['element'];
+  $num_orgs = $fields['num_orgs']['#value'];
+  $headers = array('Organism', 'Color', '');
+  $rows = array();
+  for ($i = 0; $i < $num_orgs; $i++) {
+    $add_button = ($i == $num_orgs - 1) ? drupal_render($fields['add']) : '';
+    $del_button = ($i == $num_orgs - 1 and $i != 0) ? drupal_render($fields['remove']) : '';
+    $rows[] = array(
+      drupal_render($fields['organism_' . $i]),
+      drupal_render($fields['color_' . $i]),
+      $add_button . $del_button,
+    );
+  }
+  $table_vars = array(
+    'header' => $headers,
+    'rows' => $rows,
+    'attributes' => array(),
+    'sticky' => FALSE,
+    'colgroups' => array(),
+    'empty' => '',
+  );
+  $form['orgs']['num_orgs'] = $fields['num_orgs'];
+  return theme('table', $table_vars);
+}
+
+
+/**
+ * Ajax callback function for the gensas_job_view_panel_form.
+ *
+ * @param $form
+ * @param $form_state
+ */
+function tripal_phylogeny_default_plots_form_ajax_callback($form, $form_state) {
+
+  return $form['node_settings']['org_table'];
+}

+ 25 - 8
tripal_chado/includes/tripal_chado.taxonomy.inc

@@ -25,21 +25,38 @@ function tripal_phylogeny_taxonomy_view() {
   );
   $admin_message = tripal_set_message($message, TRIPAL_INFO, array('return_html' => TRUE));
 
+  $content['admin_message'] = array(
+    '#type' => 'markup',
+    '#markup' => t('This site has not yet prepared the taxonomy for viewing.') . $admin_message,
+  );
+
   $phylotree = chado_generate_var('phylotree', $values);
   if ($phylotree) {
     $node = new stdClass();
     $node->phylotree = $phylotree;
 
-    $html = theme('tripal_phylogeny_taxonomic_tree', array('node' => $node)) .
-      $admin_message;
-    return $html;
-  }
+    $phylotree = chado_expand_var($phylotree,'field','phylotree.comment');
+    module_load_include('inc', 'tripal_chado', 'includes/tripal_chado.phylotree');
+    tripal_phylogeny_prepare_tree_viewer($phylotree);
 
-  $content['admin_message'] = array(
-    '#type' => 'markup',
-    '#markup' => t('This site has not yet prepared the taxonomy for viewing.') . $admin_message,
-  );
+    if ($phylotree->type_id->name == "taxonomy" and $phylotree->has_nodes) {
+      $content['comment'] = array(
+        '#type' => 'markup',
+        '#markup' =>$phylotree->comment
+      );
+      $ajax_loader = url(drupal_get_path('module', 'tripal') . '/theme/images/ajax-loader.gif');
+      $content['phylogram'] = array(
+        '#type' => 'markup',
+        '#markup' => '
+          <div id="phylogram">
+            <img src="' . $ajax_loader . '" class="phylogram-ajax-loader"/>
+          </div>
+        '
+      );
+    }
+  }
 
   return $content;
 }
 
+

+ 0 - 0
tripal_chado/theme/css/tripal_phylogeny.css


+ 402 - 0
tripal_chado/theme/js/d3.phylogram.js

@@ -0,0 +1,402 @@
+/*
+  (agr@ncgr.org : this is a significantly modified version of
+  d3.phylogram.js....  retaining attribution/copyright per below)
+
+  d3.phylogram.js http://bl.ocks.org/kueda/1036776
+
+  Wrapper around a d3-based phylogram (tree where branch lengths are scaled)
+  Also includes a radial dendrogram visualization (branch lengths not scaled)
+  along with some helper methods for building angled-branch trees.
+
+  Copyright (c) 2013, Ken-ichi Ueda
+
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+  Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer. Redistributions in binary
+  form must reproduce the above copyright notice, this list of conditions and
+  the following disclaimer in the documentation and/or other materials
+  provided with the distribution.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+  POSSIBILITY OF SUCH DAMAGE.
+
+  DOCUEMENTATION
+
+  d3.phylogram.build(selector, nodes, options)
+    Creates a phylogram.
+    Arguments:
+      selector: selector of an element that will contain the SVG
+      nodes: JS object of nodes
+    Options:
+      width       
+        Width of the vis, will attempt to set a default based on the width of
+        the container.
+      height
+        Height of the vis, will attempt to set a default based on the height
+        of the container.
+      fill
+        Function for generating fill color for leaf nodes.
+      vis
+        Pre-constructed d3 vis.
+      tree
+        Pre-constructed d3 tree layout.
+      children
+        Function for retrieving an array of children given a node. Default is
+        to assume each node has an attribute called "children"
+      diagonal
+        Function that creates the d attribute for an svg:path. Defaults to a
+        right-angle diagonal.
+      skipTicks
+        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 
+      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() {
+  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];
+    }
+    
+    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];
+      pathData = pathData.map(projection);
+      return path(pathData)
+    }
+    
+    diagonal.projection = function(x) {
+      if (!arguments.length) return projection;
+      projection = x;
+      return diagonal;
+    };
+    
+    diagonal.path = function(x) {
+      if (!arguments.length) return path;
+      path = x;
+      return diagonal;
+    };
+    
+    return diagonal;
+  }
+  
+  d3.phylogram.radialRightAngleDiagonal = function() {
+    return d3.phylogram.rightAngleDiagonal()
+      .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;
+        return 'M' + src + ' ' +
+          "A" + [radius,radius] + ' ' + rotation + ' ' + largeArc+','+sweep + ' ' + mid +
+          'L' + dst;
+      })
+      .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) {
+    var wholeAngle = 2 * Math.PI,
+        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))
+    
+    // Since this is just based on the angle of the right triangle formed
+    // by the coordinate and the origin, each quad will have different 
+    // offsets
+    switch (coordQuad) {
+      case 1:
+        coordAngle = quarterAngle - coordBaseAngle
+        break
+      case 2:
+        coordAngle = quarterAngle + coordBaseAngle
+        break
+      case 3:
+        coordAngle = 2*quarterAngle + quarterAngle - coordBaseAngle
+        break
+      case 4:
+        coordAngle = 3*quarterAngle + coordBaseAngle
+    }
+    return coordAngle
+  }
+
+  function scaleBranchLengths(nodes, w) {
+    // Visit all nodes and adjust y pos width distance metric
+    var visitPreOrder = function(root, callback) {
+      callback(root)
+      if (root.children) {
+        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)
+    })
+    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)
+    })
+    return yscale
+  }
+  
+  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) {
+      return 'cyan';
+    };
+    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 tree = options.tree || d3.layout.cluster()
+      .size([h, w])
+      .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);
+    
+    if (options.skipBranchLengthScaling) {
+      var yscale = d3.scale.linear()
+        .domain([0, w])
+        .range([0, w]);
+    } 
+    else {
+      var yscale = scaleBranchLengths(nodes, w)
+    }
+    
+    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");
+
+      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; });
+    }
+        
+    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");
+        
+    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"
+          }
+        })
+        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
+
+     // style the root node
+     vis.selectAll('g.root.node')
+      .append('svg:circle')
+      .on('click', nodeMouseDown)
+      .on('mouseover', nodeMouseOver)
+      .on('mouseout', nodeMouseOut)
+      .attr("r", size)
+      .attr('fill', 'dimgrey')
+      .attr('stroke', 'black')
+      .attr('stroke-width', '2px');
+
+    // style the leaf nodes and add js event handlers
+    vis.selectAll('g.leaf.node')
+      .on('click', nodeMouseDown)
+      .on('mouseover', nodeMouseOver)
+      .on('mouseout', nodeMouseOut)
+      .append("svg:circle")
+      .attr("r", size)
+      .attr('stroke', 'dimgrey')
+      .attr('fill', fill)
+      .attr('stroke-width', '2px');
+
+    vis.selectAll('g.inner.node')
+      .on('click', nodeMouseDown)
+      .on('mouseover', nodeMouseOver)
+      .on('mouseout', nodeMouseOut)
+      .append("svg:circle")
+      .attr("r", size)
+      .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
+
+      vis.selectAll('g.leaf.node').append("svg:text")
+        .attr("dx", 8)
+        .attr("dy", 3)
+        .attr("text-anchor", "start")
+        .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
+        .attr('font-size', '10px')
+        .attr('fill', 'black')
+        .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) {
+    options = options || {};
+    
+    var fill = options.fill || function(d) {
+      return 'cyan';
+    };
+    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 w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
+        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 + ")");
+        
+    var tree = d3.layout.tree()
+      .size([360, r - labelWidth])
+      .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; });
+
+    var phylogram = d3.phylogram.build(selector, nodes, {
+      vis: vis,
+      tree: tree,
+      fill : fill,
+      size: size,
+      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 + ")"; })
+    
+    if (!options.skipLabels) {
+      vis.selectAll('g.leaf.node text')
+        .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('font-family', 'Helvetica Neue, Helvetica, sans-serif')
+        .attr('font-size', '10px')
+        .attr('fill', 'black')
+        .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)"; });
+    }
+    
+    return {tree: tree, vis: vis}
+  }
+}());

+ 140 - 0
tripal_chado/theme/js/tripal_phylogeny.js

@@ -0,0 +1,140 @@
+/* phylotree d3js graphs */
+
+(function ($) {
+    
+  "use strict";
+
+  // Will be dynamically sized.
+  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) {
+        phylogeny_display_data(treeData);
+        $('.phylogram-ajax-loader').hide();
+      });
+    }
+  }
+
+  // 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 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.genus) {
+      color = organism_color[d.genus + ' ' + d.species];
+    }
+    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 {
+        // this shouldn't happen but ok
+      }
+    }
+    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_nid) {
+        window.location.replace(baseurl + '/node/' + d.organism_nid);
+      }
+      // leaf node
+    }
+  };
+
+  // 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']
+    });
+  }
+
+  /* graphHeight() generate graph height based on leaf nodes */
+  function phylogeny_graph_height(data) {
+    function count_leaf_nodes(node) {
+      if(! node.children) {
+        return 1;
+      }
+      var ct = 0;
+      node.children.forEach( function(child) {
+        ct+= count_leaf_nodes(child);
+      });
+      return ct;
+    }
+    var leafNodeCt = count_leaf_nodes(data);
+    return 22 * leafNodeCt;
+  }
+})(jQuery);

+ 23 - 0
tripal_chado/tripal_chado.module

@@ -755,6 +755,29 @@ function tripal_chado_menu() {
     'file path' => drupal_get_path('module', 'tripal_chado'),
   );
 
+  // create a route for viewing json of all phylonodes having this phylotree_id
+  $items['phylotree/%'] = array(
+    'page callback' => 'tripal_phylogeny_ajax_get_tree_json',
+    'page arguments' => array(1),
+    // allow all anonymous http clients
+    'access callback' => TRUE,
+    'file' =>  'includes/tripal_chado.phylotree.inc',
+    'file path' => drupal_get_path('module', 'tripal_chado'),
+  );
+
+  $items['admin/tripal/storage/chado/phylogeny'] = array(
+    'title' => 'Phylogeny and Taxonomy',
+    'description' => 'Settings for display of phylogenetic and taxonomic trees.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('tripal_phylogeny_default_plots_form'),
+    'access arguments' => array('administer tripal phylotree'),
+    'type' => MENU_NORMAL_ITEM,
+    'weight' => 2,
+    'file' =>  'includes/tripal_chado.phylotree.inc',
+    'file path' => drupal_get_path('module', 'tripal_chado'),
+  );
+
+
   return $items;
 }