auspice
Version:
Web app for visualizing pathogen evolution
155 lines (146 loc) • 6.96 kB
JavaScript
import React from "react";
import { withTheme } from 'styled-components';
import * as icons from "../framework/svg-icons";
import { NODE_VISIBLE, nucleotide_gene } from "../../util/globals";
import { materialButton } from "../../globalStyles";
import * as helpers from "./helperFunctions";
import { getFullAuthorInfoFromNode } from "../../util/treeMiscHelpers";
import { getNumSelectedTips } from "../../util/treeVisibilityHelpers";
import { getDatasetNamesFromUrl } from "../../actions/loadData";
const RectangularTreeIcon = withTheme(icons.RectangularTree);
const PanelsGridIcon = withTheme(icons.PanelsGrid);
const MetaIcon = withTheme(icons.Meta);
const DatasetIcon = withTheme(icons.Dataset);
const iconWidth = 25;
/**
* A React Component displaying buttons which trigger data-downloads. Intended for display within the
* larger Download modal component
*/
export const DownloadButtons = ({dispatch, t, tree, entropy, metadata, colorBy, distanceMeasure, panelsToDisplay, panelLayout, visibility, relevantPublications}) => {
const totalTipCount = metadata.mainTreeNumTips;
const selectedTipsCount = getNumSelectedTips(tree.nodes, tree.visibility);
const partialData = selectedTipsCount !== totalTipCount;
const filePrefix = getFilePrefix();
const temporal = distanceMeasure === "num_date";
/* set gisaidProvenance based on supplied JSON metadata */
const gisaidProvenance = (metadata?.dataProvenance || []).filter((el) => el.name.toUpperCase()==='GISAID').length>0;
/* Verify that we can parse dataset name from URL to support Auspice JSON download */
const datasetNames = getDatasetNamesFromUrl(window.location.pathname);
const supportAuspiceJsonDownload = !gisaidProvenance && datasetNames.some(Boolean);
const entropyBar = entropy?.selectedCds===nucleotide_gene ? "nucleotide" : "codon";
return (
<>
<div style={{fontWeight: 500, marginTop: "0px", marginBottom: "20px"}}>
Downloaded data represents the currently displayed view.
By zooming the tree, changing the branch-length metric, applying filters etc, the downloaded data will change accordingly.
<p/>
NOTE: We do not support downloads of multiple subtrees, which are usually created with the date range filter or genotype filters.
Downloading multiple subtrees will result in an empty Newick tree!
<p/>
{partialData ? `Currently ${selectedTipsCount}/${totalTipCount} tips are displayed and will be downloaded.` : `Currently the entire dataset (${totalTipCount} tips) will be downloaded.`}
</div>
{supportAuspiceJsonDownload && (
<Button
name="Auspice (Nextstrain) JSON"
description={`The main Auspice dataset JSON(s) for the current view`}
icon={<DatasetIcon width={iconWidth} selected />}
onClick={() => helpers.auspiceJSON(dispatch, datasetNames)}
/>
)}
<Button
name={`${temporal ? 'TimeTree' : 'Tree'} (Newick)`}
description={`Phylogenetic tree in Newick format with branch lengths in units of ${temporal?'years':'divergence'}.`}
icon={<RectangularTreeIcon width={iconWidth} selected />}
onClick={() => helpers.exportTree({isNewick: true, dispatch, filePrefix, tree, temporal})}
/>
<Button
name={`${temporal ? 'TimeTree' : 'Tree'} (Nexus)`}
description={`Phylogeny in Nexus format with branch lengths in units of ${temporal?'years':'divergence'}. Colorings are included as annotations.`}
icon={<RectangularTreeIcon width={iconWidth} selected />}
onClick={() => helpers.exportTree({dispatch, filePrefix, tree, colorings: metadata.colorings, colorBy, temporal})}
/>
{gisaidProvenance ?
<Button
name="Acknowledgments (TSV)"
description={`Per-sample acknowledgments (n = ${selectedTipsCount}).`}
icon={<MetaIcon width={iconWidth} selected />}
onClick={() => helpers.acknowledgmentsTSV(dispatch, filePrefix, tree.nodes, tree.visibility)}
/> :
<Button
name="Metadata (TSV)"
description={`Per-sample metadata (n = ${selectedTipsCount}).`}
icon={<MetaIcon width={iconWidth} selected />}
onClick={() => helpers.strainTSV(dispatch, filePrefix, tree.nodes, tree.visibility)}
/>
}
{helpers.areAuthorsPresent(tree) && (
<Button
name="Author Metadata (TSV)"
description={`Metadata for ${selectedTipsCount} samples grouped by their ${getNumUniqueAuthors(tree.nodes, tree.visibility)} authors.`}
icon={<MetaIcon width={iconWidth} selected />}
onClick={() => helpers.authorTSV(dispatch, filePrefix, tree)}
/>
)}
{entropy.loaded && (
<Button
name="Genetic diversity data (TSV)"
description={`The data behind the diversity panel showing ${entropy.showCounts?`a count of changes across the tree`:`normalised shannon entropy`} per ${entropyBar}.`}
icon={<MetaIcon width={iconWidth} selected />}
onClick={() => helpers.entropyTSV(dispatch, filePrefix, entropy)}
/>
)}
<Button
name="Screenshot (SVG)"
description="Screenshot of the current nextstrain display in SVG format; CC-BY licensed."
icon={<PanelsGridIcon width={iconWidth} selected />}
onClick={() => helpers.SVG(
dispatch,
t,
metadata,
tree.nodes,
visibility,
getFilePrefix(),
panelsToDisplay,
panelLayout,
relevantPublications
)}
/>
</>
);
};
/**
* React Component for an individual button
*/
function Button({name, description, icon, onClick}) {
const buttonTextStyle = Object.assign({}, materialButton, {backgroundColor: "rgba(0,0,0,0)", paddingLeft: "10px", color: "white", minWidth: "300px", textAlign: "left" });
const buttonLabelStyle = { fontStyle: "italic", fontSize: "14px", color: "lightgray" };
return (
<div key={name} onClick={onClick} style={{cursor: 'pointer' }}>
{icon}
<button style={buttonTextStyle} name={name}>
{name}
</button>
<div style={{ display: "inline-block", height: "30px", verticalAlign: "top", paddingTop: "6px" }}>
<label style={buttonLabelStyle} htmlFor={name}>
{description}
</label>
</div>
</div>
);
}
function getNumUniqueAuthors(nodes, nodeVisibilities) {
const authors = new Set(nodes
.filter((n, i) => nodeVisibilities[i] === NODE_VISIBLE && n.shell.inView)
.map((n) => getFullAuthorInfoFromNode(n))
.filter((a) => a && a.value)
.map((a) => a.value)
);
return authors.size;
}
function getFilePrefix() {
return "nextstrain_" +
window.location.pathname
.replace(/^\//, '') // Remove leading slashes
.replace(/:/g, '-') // Change ha:na to ha-na
.replace(/\//g, '_'); // Replace slashes with spaces
}