Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add secondary clade-like values #985

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,24 +1,59 @@
import React from 'react'

import { get } from 'lodash'
import React, { useCallback, useMemo, useState } from 'react'
import { get, values } from 'lodash'
import styled from 'styled-components'

import type { AnalysisResult } from 'src/types'
import { getSafeId } from 'src/helpers/getSafeId'
import { TableSlimWithBorders } from 'src/components/Common/TableSlim'
import { Tooltip } from './Tooltip'

const Table = styled(TableSlimWithBorders)`
margin-top: 1rem;
`

export interface ColumnCustomNodeAttrProps {
sequence: AnalysisResult
attrKey: string
}

export function ColumnCustomNodeAttr({ sequence, attrKey }: ColumnCustomNodeAttrProps) {
const { index, seqName, customNodeAttributes } = sequence
const attrValue = get(customNodeAttributes, attrKey, '')
const [showTooltip, setShowTooltip] = useState(false)
const onMouseEnter = useCallback(() => setShowTooltip(true), [])
const onMouseLeave = useCallback(() => setShowTooltip(false), [])

const { id, attrValue, secondaryValues } = useMemo(() => {
const { index, seqName, customNodeAttributes } = sequence
const attr = get(customNodeAttributes, attrKey, undefined)

const secondaryValues = values(attr?.secondaryValues ?? {}).map(({ key, displayName, value }) => (
<tr key={key}>
<td>{displayName}</td>
<td>{value}</td>
</tr>
))

if (attr) {
secondaryValues.unshift(
<tr key={attr.key}>
<td>{attr.displayName}</td>
<td>{attr.value}</td>
</tr>,
)
}

const id = getSafeId('col-custom-attr', { index, seqName, attrKey })

const id = getSafeId('col-custom-attr', { index, seqName, attrKey })
return { id, attrValue: attr?.value, secondaryValues }
}, [attrKey, sequence])

return (
<div id={id} className="w-100">
{attrValue}
</div>
<>
<div id={id} className="w-100" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
{attrValue}
</div>
<Tooltip isOpen={secondaryValues.length > 0 && showTooltip} target={id} wide fullWidth>
<Table borderless>{secondaryValues}</Table>
</Tooltip>
</>
)
}
15 changes: 14 additions & 1 deletion packages_rs/nextclade-web/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,24 @@ export interface AnalysisResult {
privateAaMutations: Record<string, PrivateMutations>
coverage: number
qc: QcResult
customNodeAttributes: Record<string, string>
customNodeAttributes: Record<string, CustomNodeAttrValue>
warnings: PeptideWarning[]
missingGenes: string[]
}

export interface SecondaryCustomNodeAttrValue {
key: string
displayName: string
value: string
}

export interface CustomNodeAttrValue {
key: string
displayName: string
value: string
secondaryValues: Record<string, SecondaryCustomNodeAttrValue>
}

export interface AnalysisError {
index: number
seqName: string
Expand Down
2 changes: 1 addition & 1 deletion packages_rs/nextclade/src/io/nextclade_csv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ impl<W: VecWriter> NextcladeResultsCsvWriter<W> {
custom_node_attributes
.clone()
.into_iter()
.try_for_each(|(key, val)| self.add_entry(&key, &val))?;
.try_for_each(|(key, attr)| self.add_entry(&key, &attr.value))?;

self.add_entry("seqName", seq_name)?;
self.add_entry("clade", clade)?;
Expand Down
4 changes: 2 additions & 2 deletions packages_rs/nextclade/src/run/nextclade_run_one.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ pub fn nextclade_run_one(
let clade = node.clade();

let clade_node_attr_keys = tree.clade_node_attr_descs();
let clade_node_attrs = node.get_clade_node_attrs(clade_node_attr_keys);
let custom_node_attributes = node.get_clade_node_attrs(clade_node_attr_keys);

let private_nuc_mutations = find_private_nuc_mutations(
node,
Expand Down Expand Up @@ -203,7 +203,7 @@ pub fn nextclade_run_one(
divergence,
coverage,
qc,
custom_node_attributes: clade_node_attrs,
custom_node_attributes,
nearest_node_id,
is_reverse_complement,
},
Expand Down
71 changes: 58 additions & 13 deletions packages_rs/nextclade/src/tree/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ use crate::io::aa::Aa;
use crate::io::fs::read_file_to_string;
use crate::io::json::json_parse;
use crate::io::nuc::Nuc;
use crate::types::outputs::{CustomNodeAttr, SecondaryCustomNodeAttrValue};
use eyre::{Report, WrapErr};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::any::Any;
use std::collections::BTreeMap;
use std::path::Path;
use std::slice::Iter;
Expand Down Expand Up @@ -144,29 +143,75 @@ impl AuspiceTreeNode {
self.node_attrs.clade_membership.value.clone()
}

/// Extracts clade-like node attributes, given a list of key descriptions
pub fn get_clade_node_attrs(&self, clade_node_attr_keys: &[CladeNodeAttrKeyDesc]) -> BTreeMap<String, String> {
clade_node_attr_keys
fn _extract_clade_node_attr(&self, key: &str) -> Option<String> {
match self.node_attrs.other.get(key) {
Some(attr) => attr.get("value").and_then(|value| value.as_str().map(str::to_owned)),
None => None,
}
}

fn _extract_clade_node_attrs(&self, meta_attr: &CladeNodeAttrKeyDesc) -> Option<(String, CustomNodeAttr)> {
let secondary_values: BTreeMap<String, SecondaryCustomNodeAttrValue> = meta_attr
.secondary_attrs
.iter()
.filter_map(|attr| {
let key = &attr.name;
let attr_obj = self.node_attrs.other.get(key);
match attr_obj {
Some(attr) => attr.get("value"),
None => None,
}
.and_then(|val| val.as_str().map(|val| (key.clone(), val.to_owned())))
.filter_map(|sec_attr| {
self._extract_clade_node_attr(&sec_attr.name).map(|value| {
(
sec_attr.name.clone(),
SecondaryCustomNodeAttrValue {
key: sec_attr.name.clone(),
display_name: sec_attr.display_name.clone(),
value,
},
)
})
})
.collect();

let key = &meta_attr.name;
self._extract_clade_node_attr(key).map(|value| {
(
key.clone(),
CustomNodeAttr {
key: key.clone(),
display_name: meta_attr.display_name.clone(),
value,
secondary_values,
},
)
})
}

/// Extracts clade-like node attribute values, given a list of key descriptions from tree meta
pub fn get_clade_node_attrs(
&self,
clade_node_attr_key_descs: &[CladeNodeAttrKeyDesc],
) -> BTreeMap<String, CustomNodeAttr> {
clade_node_attr_key_descs
.iter()
.filter_map(|meta_attr| self._extract_clade_node_attrs(meta_attr))
.collect()
}
}

#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SecondaryCustomNodeAttr {
name: String,
display_name: String,
description: String,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CladeNodeAttrKeyDesc {
pub name: String,
pub display_name: String,
pub description: String,

#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub secondary_attrs: Vec<SecondaryCustomNodeAttr>,
}

#[derive(Clone, Serialize, Deserialize, Validate, Debug)]
Expand Down
19 changes: 18 additions & 1 deletion packages_rs/nextclade/src/types/outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ pub struct NextalignOutputs {
pub is_reverse_complement: bool,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SecondaryCustomNodeAttrValue {
pub key: String,
pub display_name: String,
pub value: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomNodeAttr {
pub key: String,
pub display_name: String,
pub value: String,
pub secondary_values: BTreeMap<String, SecondaryCustomNodeAttrValue>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NextcladeOutputs {
Expand Down Expand Up @@ -76,7 +93,7 @@ pub struct NextcladeOutputs {
pub divergence: f64,
pub coverage: f64,
pub qc: QcResult,
pub custom_node_attributes: BTreeMap<String, String>,
pub custom_node_attributes: BTreeMap<String, CustomNodeAttr>,
pub nearest_node_id: usize,
pub is_reverse_complement: bool,
}
Expand Down