瀏覽代碼

add fields prior to splitting props and annotations

bradfordcondon 6 年之前
父節點
當前提交
49b84ff6d7

+ 221 - 0
tripal_chado/includes/TripalFields/data__sequence_features/data__sequence_features.inc

@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * @class
+ * Purpose:
+ *
+ * Data:
+ * Assumptions:
+ */
+class data__sequence_features extends ChadoField {
+
+  // --------------------------------------------------------------------------
+  //                     EDITABLE STATIC CONSTANTS
+  //
+  // The following constants SHOULD be set for each descendant class.  They are
+  // used by the static functions to provide information to Drupal about
+  // the field and it's default widget and formatter.
+  // --------------------------------------------------------------------------.
+  /**
+   * The default label for this field.
+   */
+  public static $default_label = 'Child Features';
+
+  /**
+   * The default description for this field.
+   */
+  public static $default_description = 'Gathers information about all subfeatures (mRNA, CDS, proteins) associated with a top-level feature (gene)';
+
+  /**
+   * The default widget for this field.
+   */
+  public static $default_widget = 'data__sequence_features_widget';
+
+  /**
+   * The default formatter for this field.
+   */
+  public static $default_formatter = 'data__sequence_features_formatter';
+
+  // The module that manages this field.
+  // If no module manages the field (IE it's added via libraries)
+  /**
+   * Set this to 'tripal_chado'.
+   */
+  public static $module = 'tripal_manage_analyses';
+
+  // A list of global settings. These can be accessed within the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  // Once instances exist for a field type then these settings cannot be.
+  /**
+   * Changed.
+   */
+  public static $default_settings = array(
+    'storage' => 'field_chado_storage',
+     // It is expected that all fields set a 'value' in the load() function.
+     // In many cases, the value may be an associative array of key/value pairs.
+     // In order for Tripal to provide context for all data, the keys should
+     // be a controlled vocabulary term (e.g. rdfs:type). Keys in the load()
+     // function that are supported by the query() function should be
+     // listed here.
+    'searchable_keys' => array(),
+  );
+
+  // Indicates the download formats for this field.  The list must be the.
+  /**
+   * Name of a child class of the TripalFieldDownloader.
+   */
+  public static $download_formatters = array(
+    'TripalTabDownloader',
+    'TripalCSVDownloader',
+  );
+
+  // Provide a list of instance specific settings. These can be access within
+  // the instanceSettingsForm.  When the instanceSettingsForm is submitted
+  // then Drupal with automatically change these settings for the instance.
+  // It is recommended to put settings at the instance level whenever possible.
+  // If you override this variable in a child class be sure to replicate the
+  // term_name, term_vocab, term_accession and term_fixed keys as these are.
+  /**
+   * Required for all TripalFields.
+   */
+  public static $default_instance_settings = array(
+    // The DATABASE name, as it appears in chado.db.  This also builds the link-out url.  In most cases this will simply be the CV name.  In some cases (EDAM) this will be the SUBONTOLOGY.
+    'term_vocabulary' => 'data',
+    // The name of the term.
+    'term_name' => 'Sequence features',
+    // The unique ID (i.e. accession) of the term.
+    'term_accession' => '1255',
+    // Set to TRUE if the site admin is not allowed to change the term
+    // type, otherwise the admin can change the term mapped to a field.
+    'term_fixed' => FALSE,
+    // Indicates if this field should be automatically attached to display
+    // or web services or if this field should be loaded separately. This
+    // is convenient for speed.  Fields that are slow should for loading
+    // should have auto_attach set to FALSE so tha their values can be
+    // attached asynchronously.
+    'auto_attach' => FALSE,
+    // The table in Chado that the instance maps to.
+    'chado_table' => '',
+    // The column of the table in Chado where the value of the field comes from.
+    'chado_column' => '',
+    // The base table.
+    'base_table' => '',
+  );
+
+  // A boolean specifying that users should not be allowed to create
+  // fields and instances of this field type through the UI. Such
+  // fields can only be created programmatically with field_create_field()
+  /**
+   * And field_create_instance().
+   */
+  public static $no_ui = FALSE;
+
+  // A boolean specifying that the field will not contain any data. This
+  // should exclude the field from web services or downloads.  An example
+  // could be a quick search field that appears on the page that redirects.
+  /**
+   * The user but otherwise provides no data.
+   */
+  public static $no_data = FALSE;
+
+  /**
+   * Load field.
+   *
+   * @see ChadoField::load()
+   */
+  public function load($entity) {
+
+    // ChadoFields automatically load the chado column specified in the
+    // default settings above. If that is all you need then you don't even
+    // need to implement this function. However, if you need to add any
+    // additional data to be used in the display, you should add it here.
+    parent::load($entity);
+
+    $field = get_class($this);
+
+    $parent = $entity->chado_record->feature_id;
+
+    $children = $this->findChildFeatures($parent);
+
+    $i = 0;
+    foreach ($children as $child_id => $child) {
+      $entity->{$field}['und'][$i]['value'] = $child;
+
+      $i++;
+    }
+
+    return $entity;
+  }
+
+  /**
+   * @see ChadoField::query()
+   **/
+  public function query($query, $condition) {
+  }
+
+  /**
+   * @see ChadoField::queryOrder()
+   **/
+  public function queryOrder($query, $order) {
+  }
+
+  /**
+   * @see ChadoField::elementInfo()
+   **/
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array('eq', 'ne', 'contains', 'starts'),
+        'sortable' => TRUE,
+        'searchable' => TRUE,
+      ),
+    );
+  }
+
+  /**
+   * For a given feature, find all child features.  For each child feature,
+   * return:
+   *  - the type name
+   *  - annotation names in feature_cvterm
+   *  - featureloc info, including source feature name.
+   *
+   * @param string $feature_id
+   *   Chado feature.feature_id.
+   *
+   * @return array
+   */
+  private function findChildFeatures(string $feature_id) {
+    $this_children = [];
+
+    $prev_db = chado_set_active('chado');
+
+    $query = db_select('chado.feature_relationship', 'fr')
+      ->fields('fr')
+      ->condition('object_id', $feature_id)
+      ->execute()
+      ->fetchAll();
+
+    foreach ($query as $child) {
+      $child_id = $child->subject_id;
+
+      // Expand var on this.
+      $feature = chado_generate_var('feature', ['feature_id' => $child_id]);
+      $feature = chado_expand_var($feature, 'field', 'feature.residues');
+      $feature = chado_expand_var($feature, 'table', 'featureloc');
+      $feature = chado_expand_var($feature, 'table', 'featureprop');
+      $feature = chado_expand_var($feature, 'table', 'feature_cvterm');
+
+      $this_children[$child_id]['info'] = $feature;
+
+      $grand_children = $this->findChildFeatures($child->subject_id);
+      if (!empty($grand_children)) {
+        $this_children[$child_id]['children'] = $grand_children;
+      }
+    }
+    chado_set_active($prev_db);
+    return $this_children;
+  }
+
+}

+ 318 - 0
tripal_chado/includes/TripalFields/data__sequence_features/data__sequence_features_formatter.inc

@@ -0,0 +1,318 @@
+<?php
+
+/**
+ * @class
+ * Purpose:
+ *
+ * Display:
+ * Configuration:
+ */
+class data__sequence_features_formatter extends ChadoFieldFormatter {
+
+  /**
+   * The default label for this field.
+   */
+  public static $default_label = 'Transcript Information';
+
+  /**
+   * The list of field types for which this formatter is appropriate.
+   */
+  public static $field_types = ['data__sequence_features'];
+
+  /**
+   * The list of default settings for this formatter.
+   */
+  public static $default_settings = [
+    'setting1' => 'default_value',
+  ];
+
+
+  /**
+   * Featureloc start rel to parent.
+   */
+  private $parent_start;
+
+  /**
+   * Featureloc stop rel to parent.
+   */
+  private $parent_stop;
+
+  /**
+   * Featureloc strand rel to parent.
+   */
+  private $parent_strand;
+
+  /**
+   * Holds converted featureloc information for the feature viewer drawing.
+   */
+  private $feature_coords;
+
+  /**
+   * @see ChadoFieldFormatter::settingsForm()
+   **/
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   * @see ChadoFieldFormatter::View()
+   **/
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+
+    // Get the settings.
+    $settings = $display['settings'];
+
+    $parent = $entity->chado_record->feature_id;
+
+    drupal_add_js("https://cdn.rawgit.com/calipho-sib/feature-viewer/v1.0.0/dist/feature-viewer.bundle.js", [
+      'type' => 'external',
+      'scope' => 'header',
+      'group' => 15,
+      'every_page' => TRUE,
+      'weight' => 500,
+    ]);
+
+    $child_draw = [];
+    $sequence = $entity->data__sequence['und'][0]['value'];
+
+    if (!$sequence) {
+      // TODO: We cant draw without a sequence.
+      // Now what?
+    }
+
+    $coordinates = $entity->data__sequence_coordinates['und'][0]['value'];
+
+    $this->parent_start = $coordinates['local:fmin'];
+    $this->parent_stop = $coordinates['local:fmax'];
+    $this->parent_strand = $coordinates['data:0853'];
+
+    $child_draw['residues'] = $sequence;
+
+    foreach ($entity->{'data__sequence_features'}['und'] as $i => $data) {
+      $child = $data['value'];
+
+      $info = $child['info'];
+      $name = $info->uniquename;
+
+      $element[$i] = [
+        '#type' => 'fieldset',
+        '#title' => $name,
+        '#attributes' => [
+          'class' => [
+            'collapsible',
+            'collapsed',
+          ],
+        ],
+        // see: https://www.drupal.org/forum/support/module-development-and-code-questions/2012-02-07/drupal-render-fieldset-element
+        '#attached' => ['js' => ['misc/collapse.js', 'misc/form.js']],
+      ];
+      $element[$i]['drawing'] = [
+        '#type' => 'item',
+        '#title' => t('Drawing'),
+        '#prefix' => '<div id="tripal_manage_expression_featureloc_viewer_' . $i . '">',
+        '#suffix' => '</div>',
+      ];
+
+      $rows = $this->buildChildTable($child);
+
+      $this->build_featureviewer_data($i, $child);
+
+      if (empty($rows)) {
+        continue;
+      }
+
+      $header = [
+        'Name',
+        'Type',
+        'Location',
+      ];
+      $output = theme('table', ['header' => $header, 'rows' => $rows]);
+      $element[$i][$i . 'table'] = ['#markup' => $output];
+
+      unset($rows);
+
+    }
+
+    // Un-collapse the first fieldset.
+    $element[0]['#attributes']['class'] = ['collapsible'];
+
+    $child_draw['children'] = $this->feature_coords;
+    // Pass in the needed JS info.
+    drupal_add_js([
+      'children_draw_info' => $child_draw,
+    ], 'setting');
+
+    drupal_add_js(drupal_get_path('module', 'tripal_manage_analyses') . "/theme/js/tripal_manage_analyses_featureloc.js");
+
+  }
+
+  /**
+   *
+   */
+  private function build_featureviewer_data($i, $child) {
+
+    $info = $child['info'];
+
+    $grand_children = $child['children'] ?? NULL;
+
+    // Set base info
+    // All child features will be drawn on this one in 'data'
+    // Convert and store the coordinates for hte feature viewer.
+    $this->convertFeatureCoords($i, $info->feature_id, $info);
+
+    // Repeat for grandchildren;.
+    if ($grand_children) {
+      foreach ($grand_children as $grand_child) {
+        $this->build_featureviewer_data($i, $grand_child);
+      }
+    }
+  }
+
+  /**
+   * Builds featureloc string for display to user.
+   *
+   * @param $featureloc
+   *   The featureloc object returned from chado_expand_var on featureloc.
+   *
+   * @return string
+   */
+  private function buildFeatureString($featureloc) {
+
+    $info = $featureloc->feature_id;
+
+    $min = $info->fmin;
+    $max = $info->fmax;
+    $strand = $info->strand;
+    $parent = $info->srcfeature_id->name;
+
+    if (!$min or !$max or !$strand) {
+      return 'No location available.';
+    }
+
+    $out = "${parent}:  ${min}-${max} (${strand})";
+
+    return $out;
+  }
+
+  /**
+   * Converts featureloc coordinates to be based on the entity.
+   *
+   * @param $i
+   * @param $feature_id
+   * @param $info
+   */
+  private function convertFeatureCoords($i, $feature_id, $info) {
+
+    $featureloc = $info->featureloc->feature_id;
+    // TODO: what if theres no featureloc relative to a parent?
+    $parent_start = $this->parent_start;
+    $parent_stop = $this->parent_stop;
+    $strand = $this->parent_strand;
+
+    $min = $featureloc->fmin;
+    $max = $featureloc->fmax;
+    $strand = $featureloc->strand;
+
+    if ($strand == '+') {
+
+      // It doesnt matter what strand it is, we always do this.
+      // TODO: check that assertion.
+      $start = $min - $parent_start + 1;
+      $stop = $max - $parent_start + 1;
+    }
+
+    else {
+      $start = $min - $parent_start + 1;
+      $stop = $max - $parent_start + 1;
+    }
+
+    $type = $info->type_id->name;
+
+    $color = $this->get_feature_color($type);
+
+    if (!isset($this->feature_coords[$i][$type])) {
+
+      $this->feature_coords[$i][$type] = [
+        'name' => $type,
+        'color' => $color,
+        'type' => 'rect',
+      ];
+    }
+
+    $this->feature_coords[$i][$type]['data'][] = [
+      'x' => $start,
+      'y' => $stop,
+      'description' => $info->uniquename,
+    ];
+
+  }
+
+  /**
+   * A color lookup to pass different colors to different feature subtypes.
+   *
+   * @param string $name
+   *   The feature type name.
+   *
+   * @return string
+   *   a hex color code.
+   */
+  private function get_feature_color(string $name) {
+
+    switch ($name) {
+      case 'mRNA':
+        return '#12E09D';
+
+      case 'polypeptide':
+        return '#808080';
+
+      case 'CDS':
+        return '#FF0000';
+
+      case 'exon':
+        return '#F4D4AD';
+
+      case NULL:
+        return '#000000';
+    }
+
+  }
+
+  /**
+   *
+   */
+  private function buildChildTable($child) {
+
+    $rows = [];
+
+    $children = $child['children'];
+
+    foreach ($children as $gchild) {
+
+      $info = $gchild['info'];
+
+      $location = isset($info->featureloc) ? $this->buildFeatureString($info->featureloc) : 'Not available';
+
+      $rows[] = [
+        'Name' => $info->uniquename,
+        'Type' => $info->type_id->name,
+        'Locations' => $location,
+      ];
+
+      if (isset($gchild['children'])) {
+        $ggchild = $this->buildChildTable($gchild);
+
+        $rows = array_merge($rows, $ggchild);
+      }
+
+    }
+    return $rows;
+  }
+
+  /**
+   * @see ChadoFieldFormatter::settingsSummary()
+   **/
+  public function settingsSummary($view_mode) {
+    return '';
+  }
+
+}

+ 42 - 0
tripal_chado/includes/TripalFields/data__sequence_features/data__sequence_features_widget.inc

@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @class
+ * Purpose:
+ *
+ * Allowing edit?
+ * Data:
+ * Assumptions:
+ */
+class data__sequence_features_widget extends ChadoFieldWidget {
+
+  /**
+   * The default label for this field.
+   */
+  public static $default_label = 'Transcript Information';
+
+  /**
+   * The list of field types for which this formatter is appropriate.
+   */
+  public static $field_types = array('data__sequence_features');
+
+  /**
+   * @see ChadoFieldWidget::form()
+   **/
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+  }
+
+  /**
+   * @see ChadoFieldWidget::validate()
+   **/
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
+  }
+
+  /**
+   * @see ChadoFieldWidget::submit()
+   **/
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  }
+
+}

+ 169 - 0
tripal_chado/includes/TripalFields/local__child_properties/local__child_properties.inc

@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @class
+ * Purpose:
+ *
+ * Data:
+ * Assumptions:
+ */
+class local__child_properties extends ChadoField {
+
+  // --------------------------------------------------------------------------
+  //                     EDITABLE STATIC CONSTANTS
+  //
+  // The following constants SHOULD be set for each descendant class.  They are
+  // used by the static functions to provide information to Drupal about
+  // the field and it's default widget and formatter.
+  // --------------------------------------------------------------------------.
+  /**
+   * The default label for this field.
+   */
+  public static $default_label = 'Child Properties';
+
+  /**
+   * The default description for this field.
+   */
+  public static $default_description = 'All of the properties associated with child features.';
+
+  /**
+   * The default widget for this field.
+   */
+  public static $default_widget = 'local__child_properties_widget';
+
+  /**
+   * The default formatter for this field.
+   */
+  public static $default_formatter = 'local__child_properties_formatter';
+
+  // The module that manages this field.
+  // If no module manages the field (IE it's added via libraries)
+  /**
+   * Set this to 'tripal_chado'.
+   */
+  public static $module = 'tripal_manage_analyses';
+
+  // A list of global settings. These can be accessed within the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  // Once instances exist for a field type then these settings cannot be.
+  /**
+   * Changed.
+   */
+  public static $default_settings = [
+    'storage' => 'field_chado_storage',
+    // It is expected that all fields set a 'value' in the load() function.
+    // In many cases, the value may be an associative array of key/value pairs.
+    // In order for Tripal to provide context for all data, the keys should
+    // be a controlled vocabulary term (e.g. rdfs:type). Keys in the load()
+    // function that are supported by the query() function should be
+    // listed here.
+    'searchable_keys' => [],
+  ];
+
+  // Indicates the download formats for this field.  The list must be the.
+
+  /**
+   * Name of a child class of the TripalFieldDownloader.
+   */
+  public static $download_formatters = [
+    'TripalTabDownloader',
+    'TripalCSVDownloader',
+  ];
+
+  // Provide a list of instance specific settings. These can be access within
+  // the instanceSettingsForm.  When the instanceSettingsForm is submitted
+  // then Drupal with automatically change these settings for the instance.
+  // It is recommended to put settings at the instance level whenever possible.
+  // If you override this variable in a child class be sure to replicate the
+  // term_name, term_vocab, term_accession and term_fixed keys as these are.
+  /**
+   * Required for all TripalFields.
+   */
+  public static $default_instance_settings = [
+    // The DATABASE name, as it appears in chado.db.  This also builds the link-out url.  In most cases this will simply be the CV name.  In some cases (EDAM) this will be the SUBONTOLOGY.
+    'term_vocabulary' => 'local',
+    // The name of the term.
+    'term_name' => 'child_properties',
+    // The unique ID (i.e. accession) of the term.
+    'term_accession' => 'child_properties',
+    // Set to TRUE if the site admin is not allowed to change the term
+    // type, otherwise the admin can change the term mapped to a field.
+    'term_fixed' => FALSE,
+    // Indicates if this field should be automatically attached to display
+    // or web services or if this field should be loaded separately. This
+    // is convenient for speed.  Fields that are slow should for loading
+    // should have auto_attach set to FALSE so tha their values can be
+    // attached asynchronously.
+    'auto_attach' => FALSE,
+    // The table in Chado that the instance maps to.
+    'chado_table' => '',
+    // The column of the table in Chado where the value of the field comes from.
+    'chado_column' => '',
+    // The base table.
+    'base_table' => '',
+  ];
+
+  // A boolean specifying that users should not be allowed to create
+  // fields and instances of this field type through the UI. Such
+  // fields can only be created programmatically with field_create_field()
+  /**
+   * And field_create_instance().
+   */
+  public static $no_ui = FALSE;
+
+  // A boolean specifying that the field will not contain any data. This
+  // should exclude the field from web services or downloads.  An example
+  // could be a quick search field that appears on the page that redirects.
+  /**
+   * The user but otherwise provides no data.
+   */
+  public static $no_data = FALSE;
+
+  /**
+   * @see ChadoField::load()
+   **/
+  public function load($entity) {
+
+    // ChadoFields automatically load the chado column specified in the
+    // default settings above. If that is all you need then you don't even
+    // need to implement this function. However, if you need to add any
+    // additional data to be used in the display, you should add it here.
+    parent::load($entity);
+
+    $master = $entity->data__sequence_features['und'];
+
+    //TODO: filter this to just hte properties.  Right now we include everything because we are having this field do double duty for annotations.
+      $entity->local__child_properties['und'] = $master;
+
+
+
+  }
+
+  /**
+   * @see ChadoField::query()
+   **/
+  public function query($query, $condition) {
+  }
+
+  /**
+   * @see ChadoField::queryOrder()
+   **/
+  public function queryOrder($query, $order) {
+  }
+
+  /**
+   * @see ChadoField::elementInfo()
+   **/
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return [
+      $field_term => [
+        'operations' => ['eq', 'ne', 'contains', 'starts'],
+        'sortable' => TRUE,
+        'searchable' => TRUE,
+      ],
+    ];
+  }
+
+}

+ 221 - 0
tripal_chado/includes/TripalFields/local__child_properties/local__child_properties_formatter.inc

@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * @class
+ * Purpose:
+ *
+ * Display:
+ * Configuration:
+ */
+class local__child_properties_formatter extends ChadoFieldFormatter {
+
+  /**
+   * The default label for this field.
+   */
+  public static $default_label = 'Child Properties';
+
+  /**
+   * The list of field types for which this formatter is appropriate.
+   */
+  public static $field_types = ['local__child_properties'];
+
+  /**
+   * The list of default settings for this formatter.
+   */
+  public static $default_settings = [
+    'setting1' => 'default_value',
+  ];
+
+  /**
+   * @see ChadoFieldFormatter::settingsForm()
+   **/
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   * @see ChadoFieldFormatter::View()
+   **/
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+
+    // Get the settings.
+    $settings = $display['settings'];
+
+    // For now, values come from the data__sequence_features field.
+    // $data = $entity->{'data__sequence_features'}['und'];.
+    // insert into chado.featureprop (feature_id, type_id, value, rank) VALUES (5505, 100, 'some madeup prop value on a protein', 0);.
+    $data = $entity->{'local__child_properties'}['und'];
+
+    if (!$data) {
+      return;
+    }
+
+    $i = 0;
+
+    foreach ($data as $i => $value) {
+
+      $child = $value['value'];
+
+      // TODO: this is an extra undefined!  bad.
+      // Assumption: we're on the gene entity.  We've got an array of mRNA feature ID's.
+      $header = [
+        'Feature Name',
+        'Feature Type',
+        'Property Name',
+        'Property Value',
+      ];
+
+      $info = $child['info'];
+      $name = $info->uniquename;
+
+      $element[0][$i] = [
+        '#type' => 'fieldset',
+        '#title' => $name,
+        '#attributes' => [
+          'class' => [
+            'collapsible',
+            'collapsed',
+          ],
+        ],
+        // see: https://www.drupal.org/forum/support/module-development-and-code-questions/2012-02-07/drupal-render-fieldset-element
+        '#attached' => ['js' => ['misc/collapse.js', 'misc/form.js']],
+      ];
+
+      $rows = $this->getPropRows($child);
+
+      $table = theme('table', ['rows' => $rows, 'header' => $header]);
+      $element[0][$i]['prop_table'] = [
+        '#markup' => $table,
+        '#title' => t("Child Properties for !root", ['!root' => $name]),
+      ];
+
+      $rows = $this->getAnnotationRows($child);
+      $header = ['name', 'type', 'annotation'];
+
+      $table = theme('table', ['rows' => $rows, 'header' => $header]);
+      $element[0][$i]['annotation_table'] = [
+        '#markup' => $table,
+        '#title' => t("Annotations for !root", ['!root' => $name]),
+      ];
+
+      $i++;
+    }
+  }
+
+  /**
+   * This functionality should move to a separate field.
+   *
+   * @param $data
+   */
+  private function getAnnotationRows($data) {
+
+    $rows = [];
+
+    $info = $data['info'];
+
+    $children = $data['children'] ?? NULL;
+
+    $annotations = $info->feature_cvterm;
+
+    if ($annotations) {
+
+      if (is_array($annotations)) {
+        foreach ($annotations as $ann) {
+
+          $annotation_name = $ann->cvterm_id->name;
+          if ($ann->is_not) {
+            $annotation_name = "is not " . $annotation_name;
+          }
+
+          $rows[] = [
+            $info->uniquename,
+            $info->type_id->name,
+            $annotation_name,
+          ];
+        }
+      }
+      else {
+        $annotation_name = $annotations->cvterm_id->name;
+        if ($annotations->is_not) {
+          $annotation_name = "is not " . $annotation_name;
+        }
+
+        $rows[] = [
+          $info->uniquename,
+          $info->type_id->name,
+          $annotation_name,
+        ];
+
+      }
+
+    }
+
+    if ($children && !empty($children)) {
+
+      foreach ($children as $child) {
+        $rows = array_merge($this->getAnnotationRows($child), $rows);
+      }
+    }
+    return $rows;
+
+  }
+
+  /**
+   * Recursively goes through the child feature array and builds an array of.
+   *
+   * @param $data
+   */
+  private function getPropRows($data) {
+
+    $rows = [];
+
+    $info = $data['info'];
+
+    $children = $data['children'] ?? NULL;
+
+    $props = $info->featureprop;
+
+    if ($props) {
+
+      // If there is only one property, it will be an object not an array.
+      if (is_array($props)) {
+        foreach ($props as $prop) {
+          $rows[] = [
+            $info->uniquename,
+            $info->type_id->name,
+            $prop->type_id->name,
+            $prop->value,
+          ];
+        }
+      }
+      else {
+        $rows[] = [
+          $info->uniquename,
+          $info->type_id->name,
+          $props->type_id->name,
+          $props->value,
+        ];
+
+      }
+
+    }
+
+    if ($children && !empty($children)) {
+
+      foreach ($children as $child) {
+        $result = array_merge($this->getPropRows($child), $rows);
+
+        $rows = $result;
+      }
+    }
+    return $rows;
+  }
+
+  /**
+   * @see ChadoFieldFormatter::settingsSummary()
+   **/
+  public function settingsSummary($view_mode) {
+    return '';
+  }
+
+}

+ 42 - 0
tripal_chado/includes/TripalFields/local__child_properties/local__child_properties_widget.inc

@@ -0,0 +1,42 @@
+<?php
+/**
+ * @class
+ * Purpose:
+ *
+ * Allowing edit?
+ * Data:
+ * Assumptions:
+ */
+class local__child_properties_widget extends ChadoFieldWidget {
+
+  // The default label for this field.
+  public static $default_label = 'Child Properties';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('local__child_properties');
+
+
+ /**
+  * @see ChadoFieldWidget::form()
+  *
+  **/
+
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+  }
+
+  /**
+  * @see ChadoFieldWidget::validate()
+  *
+  **/
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
+  }
+
+   /**
+  * @see ChadoFieldWidget::submit()
+  *
+  **/
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  }
+
+}