Skip to content

Commit

Permalink
Read optional date ambiguity node attrs
Browse files Browse the repository at this point in the history
This reflects an upcoming change in Augur to export two (optional)
properties within the `num_date` node attr. `inferred: boolean` and
`raw_value:string`. There is no change to how dates are displayed for
datasets without these optional values. If they are present, then
showing the raw_value (ambiguous date string) is often very helpful
for understanding outbreaks which may have incomplete metadata.
  • Loading branch information
jameshadfield committed Feb 18, 2025
1 parent 8692b38 commit 2c36475
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 32 deletions.
28 changes: 15 additions & 13 deletions src/components/tree/infoPanels/click.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getTraitFromNode, getFullAuthorInfoFromNode, getVaccineFromNode,
getAccessionFromNode, getUrlFromNode } from "../../../util/treeMiscHelpers";
import { MutationTable } from "./MutationTable";
import { lhsTreeId} from "../tree";
import { nodeDisplayName } from "./helpers";
import { nodeDisplayName, dateInfo } from "./helpers";

export const styles = {
container: {
Expand Down Expand Up @@ -174,20 +174,22 @@ const StrainName = ({children}) => (
);

const SampleDate = ({isTerminal, node, t}) => {
const date = getTraitFromNode(node, "num_date");
const {date, dateRange, inferred, ambiguousDate} = dateInfo(node, isTerminal);
if (!date) return null;

const dateUncertainty = getTraitFromNode(node, "num_date", {confidence: true});
if (date && dateUncertainty && dateUncertainty[0] !== dateUncertainty[1]) {
return (
<>
{item(t(isTerminal ? "Inferred collection date" : "Inferred date"), numericToCalendar(date))}
{item(t("Date Confidence Interval"), `(${numericToCalendar(dateUncertainty[0])}, ${numericToCalendar(dateUncertainty[1])})`)}
</>
);
}
/* internal nodes are always inferred, regardless of whether uncertainty bounds are present */
return item(t(isTerminal ? "Collection date" : "Inferred date"), numericToCalendar(date));
const dateDescription = isTerminal ?
(inferred ? "Inferred collection date" : "Collection date") :
"Inferred date"; // hardcoded assumption that internal nodes are inferred

return (
<>
{item(t(dateDescription), date)}
{inferred && dateRange &&
item(t("Date Confidence Interval"), `(${dateRange.join(', ')})`)}
{ambiguousDate &&
item(t("Provided date"), ambiguousDate)}
</>
)
};

const getTraitsToDisplay = (node) => {
Expand Down
24 changes: 24 additions & 0 deletions src/components/tree/infoPanels/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,27 @@ export function nodeDisplayName(t, node, tipLabelKey, branch) {
/* TIP */
return tipLabel;
}

export function dateInfo(node, isTerminal) {
const num_date = getTraitFromNode(node, "num_date");
if (!num_date) return {};

// Decide if the date is inferred and, if so, attempt to get the underlying ambiguous date (for tips)
let inferred, ambiguousDate;
const dateUncertainty = getTraitFromNode(node, "num_date", {confidence: true});
if (!isTerminal) {
inferred=true;
} else if (Object.hasOwn(node.node_attrs.num_date, "inferred")) {
inferred = node.node_attrs.num_date.inferred;
ambiguousDate = getTraitFromNode(node, "num_date", {raw: true});
} else {
inferred = dateUncertainty && dateUncertainty[0] !== dateUncertainty[1];
}

const dateRange = dateUncertainty ?
[numericToCalendar(dateUncertainty[0]), numericToCalendar(dateUncertainty[1])] :
undefined;
const date = numericToCalendar(num_date);

return {date, dateRange, inferred, ambiguousDate};
}
31 changes: 14 additions & 17 deletions src/components/tree/infoPanels/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getTraitFromNode, getDivFromNode, getVaccineFromNode,
import { isValueValid, strainSymbol } from "../../../util/globals";
import { formatDivergence, getIdxOfInViewRootNode } from "../phyloTree/helpers";
import { parseIntervalsOfNsOrGaps } from "./MutationTable";
import { nodeDisplayName } from "./helpers";
import { nodeDisplayName, dateInfo } from "./helpers";

export const InfoLine = ({name, value, padBelow=false}) => {
const renderValues = () => {
Expand Down Expand Up @@ -37,30 +37,27 @@ export const InfoLine = ({name, value, padBelow=false}) => {
* A React component to display information about the branch's time & divergence (where applicable)
* @param {Object} props
* @param {Object} props.node branch node currently highlighted
* @param {boolean} props.isTerminal
*/
const BranchLength = ({node, t}) => {
const BranchLength = ({node, t, isTerminal}) => {
const elements = []; // elements to render
const divergence = getDivFromNode(node);
const numDate = getTraitFromNode(node, "num_date");

if (divergence) {
elements.push(<InfoLine name={t("Divergence")+":"} value={formatDivergence(divergence)} key="div"/>);
}

if (numDate !== undefined) {
const date = numericToCalendar(numDate);
const numDateConfidence = getTraitFromNode(node, "num_date", {confidence: true});
if (numDateConfidence && numDateConfidence[0] !== numDateConfidence[1]) {
elements.push(<InfoLine name={t("Inferred Date")+":"} value={date} key="inferredDate"/>);
const dateRange = [numericToCalendar(numDateConfidence[0]), numericToCalendar(numDateConfidence[1])];
if (dateRange[0] !== dateRange[1]) {
elements.push(<InfoLine name={t("Date Confidence Interval")+":"} value={`(${dateRange[0]}, ${dateRange[1]})`} key="dateConf"/>);
}
} else {
elements.push(<InfoLine name={t("Date")+":"} value={date} key="date"/>);
const {date, dateRange, inferred, ambiguousDate} = dateInfo(node, isTerminal);
if (date) {
const dateDescription = inferred ? 'Inferred Date' : 'Date';
elements.push(<InfoLine name={t(dateDescription)+":"} value={date} key="date"/>);
if (inferred && dateRange) {
elements.push(<InfoLine name={t("Date Confidence Interval")+":"} value={`(${dateRange.join(', ')})`} key="dateConf"/>);
}
if (ambiguousDate) {
elements.push(<InfoLine name={t("Provided Date")+":"} value={ambiguousDate} key="date"/>);
}
}

return elements;
};

Expand Down Expand Up @@ -409,7 +406,7 @@ const HoverInfoPanel = ({
{tipLabelKey!==strainSymbol && <InfoLine name="Node name:" value={node.name}/>}
<VaccineInfo node={node} t={t}/>
<TipMutations node={node} t={t}/>
<BranchLength node={node} t={t}/>
<BranchLength node={node} t={t} isTerminal={true}/>
<ColorBy node={node} colorBy={colorBy} colorByConfidence={colorByConfidence} colorScale={colorScale} colorings={colorings}/>
<AttributionInfo node={node}/>
<Comment>{t("Click on tip to display more info")}</Comment>
Expand All @@ -418,7 +415,7 @@ const HoverInfoPanel = ({
<>
<BranchDescendants node={node} t={t} tipLabelKey={tipLabelKey}/>
<BranchMutations node={node} geneSortFn={geneSortFn} observedMutations={observedMutations} t={t}/>
<BranchLength node={node} t={t}/>
<BranchLength node={node} t={t} isTerminal={false}/>
<ColorBy node={node} colorBy={colorBy} colorByConfidence={colorByConfidence} colorScale={colorScale} colorings={colorings}/>
<Comment>
{idxOfInViewRootNode === node.arrayIdx ? t('Click to zoom out to parent clade') : t('Click to zoom into clade')}
Expand Down
7 changes: 5 additions & 2 deletions src/util/treeMiscHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ james hadfield, nov 2019.
* NOTE: do not use this for "div", "vaccine" or other traits set on `node_attrs`
* which don't share the same structure as traits. See the JSON spec for more details.
*/
export const getTraitFromNode = (node, trait, {entropy=false, confidence=false}={}) => {
export const getTraitFromNode = (node, trait, {entropy=false, confidence=false, raw=false}={}) => {
if (!node.node_attrs) return undefined;

if (!entropy && !confidence) {
if (!entropy && !confidence && !raw) {
if (!node.node_attrs[trait]) {
if (trait === strainSymbol) return node.name;
return undefined;
Expand All @@ -42,6 +42,9 @@ export const getTraitFromNode = (node, trait, {entropy=false, confidence=false}=
} else if (confidence) {
if (node.node_attrs[trait]) return node.node_attrs[trait].confidence;
return undefined;
} else if (raw) {
if (node.node_attrs[trait]) return node.node_attrs[trait].raw_value;
return undefined;
}
return undefined;
};
Expand Down

0 comments on commit 2c36475

Please sign in to comment.