<commit_before>import React from "react";
import PropTypes from "prop-types";
import TidyTree from "../visualizations/TidyTree";
import PathogenLabel from "../ui/labels/PathogenLabel";

const TaxonLevels = [
  "species",
  "genus",
  "family",
  "order",
  "class",
  "phylum",
  "kingdom",
  "superkingdom"
];
class TaxonTreeVis extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      nodeHover: null
    };

    this.nameType = this.props.nameType;
    this.metric = this.props.metric || "aggregatescore";
    this.taxa = this.props.taxa;

    this.tree = null;
    this.treeVis = null;

    this.metrics = {
      aggregatescore: {
        label: "Aggregate Score",
        agg: arr => Math.max(...arr)
      },
      nt_r: { label: "NT r", agg: arr => arr.reduce((a, b) => a + b, 0) },
      nt_rpm: { label: "NT rpm", agg: arr => arr.reduce((a, b) => a + b, 0) },
      nr_r: { label: "NR r", agg: arr => arr.reduce((a, b) => a + b, 0) },
      nr_rpm: { label: "NR rpm", agg: arr => arr.reduce((a, b) => a + b, 0) }
    };

    this.onNodeHover = this.onNodeHover.bind(this);
    this.fillNodeValues = this.fillNodeValues.bind(this);
    this.renderTooltip = this.renderTooltip.bind(this);
  }

  componentDidMount() {
    this.treeVis = new TidyTree(
      this.treeContainer,
      this.createTree(this.taxa),
      {
        attribute: this.metric,
        useCommonName: this.isCommonNameActive(),
        onNodeHover: this.onNodeHover,
        onCreatedTree: this.fillNodeValues,
        tooltipContainer: this.treeTooltip
      }
    );
    this.treeVis.update();
  }

  componentDidUpdate() {
    let options = {};
    if (this.nameType != this.props.nameType) {
      this.nameType = this.props.nameType;
      options.useCommonName = this.isCommonNameActive();
    }

    if (this.metric != this.props.metric) {
      this.metric = this.props.metric;
      options.attribute = this.props.metric;
    }

    if (Object.keys(options).length) {
      this.treeVis.setOptions(options);
    }

    if (this.taxa != this.props.taxa) {
      this.taxa = this.props.taxa;
      this.treeVis.setTree(this.createTree(this.props.taxa));
    }
  }

  static createNode() {
    return {
      children: [],
      collapsed: false
    };
  }

  onNodeHover(node) {
    this.setState({ nodeHover: node });
  }

  getParentTaxId(taxon) {
    let originalTaxId = taxon.lineage[`${TaxonLevels[taxon.tax_level]}_taxid`];
    return originalTaxId > 0
      ? originalTaxId
      : `_${TaxonLevels[taxon.tax_level]}`;
  }

  isCommonNameActive() {
    return this.nameType.toLowerCase() == "common name";
  }

  fillNodeValues(root) {
    // cleaning up spurious nodes without children with data
    root.leaves().forEach(leaf => {
      if (!leaf.children && !leaf.data.values) {
        // delete node that has no children or values
        let ancestors = leaf.ancestors();
        for (let i = 1; i < ancestors.length; i++) {
          let ancestor = ancestors[i];
          ancestor.children = ancestor.children.filter(
            child => child.id !== ancestors[i - 1].id
          );
          if (ancestor.children && ancestor.children.length > 0) {
            // stop if ancestor still has children
            break;
          }
        }
      }
    });

    let topTaxaIds = new Set(
      this.props.topTaxa.map(taxon => taxon.tax_id.toString())
    );
    root.eachAfter(node => {
      if (topTaxaIds.has(node.id)) {
        node.data.highlight = true;
        node.ancestors().forEach(ancestor => {
          ancestor.data.highlight = true;
        });
      }

      for (let metric in this.metrics) {
        node.data.values || (node.data.values = {});
        if (
          this.metrics.hasOwnProperty(metric) &&
          !node.data.values.hasOwnProperty(metric)
        ) {
          node.data.values[metric] = this.metrics[metric].agg(
            node.children
              .filter(child => child.data.values[metric])
              .map(child => child.data.values[metric])
          );
        }
      }
    });
  }

  createTree(taxa) {
    // this function assumes that genus are always seen first
    let nodes = [{ id: "_" }];

    let seenNodes = new Set();
    taxa.forEach(taxon => {
      let parentId = nodes[0].id;
      for (let i = TaxonLevels.length - 1; i >= taxon.tax_level; i--) {
        let taxId = taxon.lineage[`${TaxonLevels[i]}_taxid`];
        let nodeId = taxId > 0 ? taxId : `${parentId}_${taxId}`;
        if (!seenNodes.has(nodeId)) {
          nodes.push({
            id: nodeId,
            taxId: taxId,
            parentId: parentId,
            scientificName:
              taxon.lineage[`${TaxonLevels[i]}_name`] ||
              `Uncategorized ${this.capitalize(TaxonLevels[i])}`,
            lineageRank: TaxonLevels[i]
          });
          seenNodes.add(nodeId);
        }
        parentId = nodeId;
      }

      let nodeId = taxon.tax_id;
      nodes.push({
        id: nodeId,
        taxId: nodeId,
        commonName: taxon.common_name,
        scientificName:
          taxon.tax_id > 0
            ? taxon.name
            : `Uncategorized ${this.capitalize(
                TaxonLevels[taxon.tax_level - 1]
              )}`,
        lineageRank: TaxonLevels[taxon.tax_level - 1],
        parentId: parentId,
        values: {
          aggregatescore: taxon.NT.aggregatescore,
          nt_r: taxon.NT.r,
          nt_rpm: parseFloat(taxon.NT.rpm),
          nt_zscore: taxon.NT.zscore,
          nr_r: taxon.NR.r,<commit_msg>Taoxn Tree: Fix case where genus tax id are negative. (#1572)<commit_after>import React from "react";
import PropTypes from "prop-types";
import TidyTree from "../visualizations/TidyTree";
import PathogenLabel from "../ui/labels/PathogenLabel";

const TaxonLevels = [
  "species",
  "genus",
  "family",
  "order",
  "class",
  "phylum",
  "kingdom",
  "superkingdom"
];
class TaxonTreeVis extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      nodeHover: null
    };

    this.nameType = this.props.nameType;
    this.metric = this.props.metric || "aggregatescore";
    this.taxa = this.props.taxa;

    this.tree = null;
    this.treeVis = null;

    this.metrics = {
      aggregatescore: {
        label: "Aggregate Score",
        agg: arr => Math.max(...arr)
      },
      nt_r: { label: "NT r", agg: arr => arr.reduce((a, b) => a + b, 0) },
      nt_rpm: { label: "NT rpm", agg: arr => arr.reduce((a, b) => a + b, 0) },
      nr_r: { label: "NR r", agg: arr => arr.reduce((a, b) => a + b, 0) },
      nr_rpm: { label: "NR rpm", agg: arr => arr.reduce((a, b) => a + b, 0) }
    };

    this.onNodeHover = this.onNodeHover.bind(this);
    this.fillNodeValues = this.fillNodeValues.bind(this);
    this.renderTooltip = this.renderTooltip.bind(this);
  }

  componentDidMount() {
    this.treeVis = new TidyTree(
      this.treeContainer,
      this.createTree(this.taxa),
      {
        attribute: this.metric,
        useCommonName: this.isCommonNameActive(),
        onNodeHover: this.onNodeHover,
        onCreatedTree: this.fillNodeValues,
        tooltipContainer: this.treeTooltip
      }
    );
    this.treeVis.update();
  }

  componentDidUpdate() {
    let options = {};
    if (this.nameType != this.props.nameType) {
      this.nameType = this.props.nameType;
      options.useCommonName = this.isCommonNameActive();
    }

    if (this.metric != this.props.metric) {
      this.metric = this.props.metric;
      options.attribute = this.props.metric;
    }

    if (Object.keys(options).length) {
      this.treeVis.setOptions(options);
    }

    if (this.taxa != this.props.taxa) {
      this.taxa = this.props.taxa;
      this.treeVis.setTree(this.createTree(this.props.taxa));
    }
  }

  static createNode() {
    return {
      children: [],
      collapsed: false
    };
  }

  onNodeHover(node) {
    this.setState({ nodeHover: node });
  }

  getParentTaxId(taxon) {
    let originalTaxId = taxon.lineage[`${TaxonLevels[taxon.tax_level]}_taxid`];
    return originalTaxId > 0
      ? originalTaxId
      : `_${TaxonLevels[taxon.tax_level]}`;
  }

  isCommonNameActive() {
    return this.nameType.toLowerCase() == "common name";
  }

  fillNodeValues(root) {
    // cleaning up spurious nodes without children with data
    root.leaves().forEach(leaf => {
      if (!leaf.children && !leaf.data.values) {
        // delete node that has no children or values
        let ancestors = leaf.ancestors();
        for (let i = 1; i < ancestors.length; i++) {
          let ancestor = ancestors[i];
          ancestor.children = ancestor.children.filter(
            child => child.id !== ancestors[i - 1].id
          );
          if (ancestor.children && ancestor.children.length > 0) {
            // stop if ancestor still has children
            break;
          }
        }
      }
    });

    let topTaxaIds = new Set(
      this.props.topTaxa.map(taxon => taxon.tax_id.toString())
    );
    root.eachAfter(node => {
      if (topTaxaIds.has(node.id)) {
        node.data.highlight = true;
        node.ancestors().forEach(ancestor => {
          ancestor.data.highlight = true;
        });
      }

      for (let metric in this.metrics) {
        node.data.values || (node.data.values = {});
        if (
          this.metrics.hasOwnProperty(metric) &&
          !node.data.values.hasOwnProperty(metric)
        ) {
          node.data.values[metric] = this.metrics[metric].agg(
            node.children
              .filter(child => child.data.values[metric])
              .map(child => child.data.values[metric])
          );
        }
      }
    });
  }

  createTree(taxa) {
    // this function assumes that genus are always seen first
    let nodes = [{ id: "_" }];

    let seenNodes = new Set();
    taxa.forEach(taxon => {
      let parentId = nodes[0].id;
      for (let i = TaxonLevels.length - 1; i >= taxon.tax_level; i--) {
        let taxId = taxon.lineage[`${TaxonLevels[i]}_taxid`];
        let nodeId = taxId > 0 ? taxId : `${parentId}_${taxId}`;
        if (!seenNodes.has(nodeId)) {
          nodes.push({
            id: nodeId,
            taxId: taxId,
            parentId: parentId,
            scientificName:
              taxon.lineage[`${TaxonLevels[i]}_name`] ||
              `Uncategorized ${this.capitalize(TaxonLevels[i])}`,
            lineageRank: TaxonLevels[i]
          });
          seenNodes.add(nodeId);
        }
        parentId = nodeId;
      }

      let nodeId =
        taxon.tax_id > 0 ? taxon.tax_id : `${parentId}_${taxon.tax_id}`;
      nodes.push({
        id: nodeId,
        taxId: taxon.tax_id,
        commonName: taxon.common_name,
        scientificName:
          taxon.tax_id > 0
            ? taxon.name
            : `Uncategorized ${this.capitalize(
                TaxonLevels[taxon.tax_level - 1]
              )}`,
        lineageRank: TaxonLevels[taxon.tax_level - 1],
        parentId: parentId,
        values: {
          aggregatescore: taxon.NT.aggregatescore,
          nt_r: taxon.NT.r,
          nt_rpm: parseFloat(taxon.NT.rpm),
          nt_zscore: taxon.NT.zscore,
          nr_r: taxon.NR.r,
{'fraction_non_alphanumeric': 0.14186108876374362, 'fraction_numerical': 0.0029498525073746312, 'mean_word_length': 2.1034674063800276, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 48, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2759895', 'n_tokens_mistral': 3795, 'n_tokens_neox': 3674, 'n_words': 698}