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

Reinstate (improved) download ability #587

Merged
merged 18 commits into from
Jul 3, 2018
Merged
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
3,898 changes: 1,949 additions & 1,949 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"leaflet-image": "^0.4.0",
"linspace": "^1.0.0",
"lodash": "4.16.4",
"mousetrap": "^1.6.0",
"mousetrap": "^1.6.2",
"node-fetch": "^1.7.3",
"outer-product": "0.0.4",
"papaparse": "^4.3.5",
Expand Down
217 changes: 151 additions & 66 deletions src/components/download/downloadModal.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,45 @@
import React from "react";
import PropTypes from 'prop-types';
import Mousetrap from "mousetrap";
import { connect } from "react-redux";
import { DISMISS_DOWNLOAD_MODAL } from "../../actions/types";
import { materialButton, medGrey, infoPanelStyles } from "../../globalStyles";
import { materialButton, extraLightGrey, infoPanelStyles } from "../../globalStyles";
import { stopProp } from "../tree/infoPanels/click";
import { authorString } from "../../util/stringHelpers";
import * as helpers from "./helperFunctions";
import * as icons from "../framework/svg-icons";
import { getAcknowledgments, preambleText} from "../framework/footer";
import { getAcknowledgments, footerStyles} from "../framework/footer";
import { createSummary } from "../info/info";

const dataUsage = `
The data presented here is intended to rapidly disseminate analysis of important pathogens.
Unpublished data is included with permission of the data generators, and does not impact their right to publish.
Please contact the respective authors (available via the CSV files below) if you intend to carry out further research using their data.
Derived data, such as phylogenies, can be downloaded below - please contact the relevant authors where appropriate.
`;
const dataUsage = [
`The data presented here is intended to rapidly disseminate analysis of important pathogens.
Unpublished data is included with permission of the data generators, and does not impact their right to publish.`,
`Please contact the respective authors (available via the TSV files below) if you intend to carry out further research using their data.
Derived data, such as phylogenies, can be downloaded below - please contact the relevant authors where appropriate.`
];

export const publications = {
nextstrain: {
author: "Hadfield et al",
title: "Nextstrain: real-time tracking of pathogen evolution",
year: "2018",
journal: "Bioinformatics",
href: "https://doi.org/10.1093/bioinformatics/bty407"
},
treetime: {
author: "Sagulenko et al",
title: "TreeTime: Maximum-likelihood phylodynamic analysis",
journal: "Virus Evolution",
year: "2017",
href: "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5758920/"
},
titers: {
author: "Neher et al",
titleJournalYear: "Prediction, dynamics, and visualization of antigenic phenotypes of seasonal influenza viruses",
journal: "PNAS",
year: "2016",
href: "http://www.pnas.org/content/113/12/E1701.abstract"
}
};

@connect((state) => ({
browserDimensions: state.browserDimensions.browserDimensions,
Expand All @@ -31,7 +55,9 @@ const dataUsage = `
visibleStateCounts: state.tree.visibleStateCounts,
filters: state.controls.filters,
visibility: state.tree.visibility,
treeAttrs: state.tree.attrs
treeAttrs: state.tree.attrs,
panelsToDisplay: state.controls.panelsToDisplay,
panelLayout: state.controls.panelLayout
}))
class DownloadModal extends React.Component {
constructor(props) {
Expand All @@ -40,10 +66,18 @@ class DownloadModal extends React.Component {
return {
behind: { /* covers the screen */
position: "absolute",
width: bw,
height: bh,
zIndex: 10000,
backgroundColor: "rgba(0, 0, 0, 0.3)"
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "all",
zIndex: 2000,
backgroundColor: "rgba(80, 80, 80, .20)",
display: "flex",
justifyContent: "center",
alignItems: "center",
wordWrap: "break-word",
wordBreak: "break-word"
},
title: {
fontWeight: 500,
Expand All @@ -70,6 +104,34 @@ class DownloadModal extends React.Component {
}
};
};
this.dismissModal = this.dismissModal.bind(this);
}
componentDidMount() {
Mousetrap.bind('d', () => {
helpers.SVG(this.props.dispatch, this.getFilePrefix(), this.props.panelsToDisplay, this.props.panelLayout, this.makeTextStringsForSVGExport());
});
}
getRelevantPublications() {
const x = [publications.nextstrain, publications.treetime];
if (["cTiter", "rb", "ep", "ne"].indexOf(this.props.colorBy) !== -1) {
x.push(publications.titers);
}
return x;
}
formatPublications(pubs) {
return (
<span>
<ul>
{pubs.map((pub) => (
<li key={pub.href}>
<a href={pub.href} target="_blank" rel="noreferrer noopener">
{authorString(pub.author)}, {pub.title}, <i>{pub.journal}</i> ({pub.year})
</a>
</li>
))}
</ul>
</span>
);
}
relevantPublications() {
const titer_related_keys = ["cTiter", "rb", "ep", "ne"];
Expand All @@ -80,35 +142,54 @@ class DownloadModal extends React.Component {
return (
<span>
<ul>
<li><a href="https://academic.oup.com/bioinformatics/article-lookup/doi/10.1093/bioinformatics/btv381">
Neher & Bedford, Nextflu: real-time tracking of seasonal influenza virus evolution in humans, Bioinformatics (2015)
<li><a href="https://doi.org/10.1093/bioinformatics/bty407" target="_blank" rel="noreferrer noopener">
{authorString("Hadfield et al")}, Nextstrain: real-time tracking of pathogen evolution, <i>Bioinformatics</i> (2018)
</a></li>
<li><a href="http://www.biorxiv.org/content/early/2017/06/21/153494">
{authorString("Sagulenko et al")}, TreeTime: maximum likelihood phylodynamic analysis, bioRxiv (2017)
<li><a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5758920/" target="_blank" rel="noreferrer noopener">
{authorString("Sagulenko et al")}, TreeTime: Maximum-likelihood phylodynamic analysis, <i>Virus Evolution</i> (2017)
</a></li>
{titer}
</ul>
</span>
);
}
getFilePrefix() {
return "nextstrain_" + this.props.datapath.replace(/^\//, '').replace(/\//g, '_');
}
makeTextStringsForSVGExport() {
const x = [];
x.push(this.props.metadata.title);
x.push(`Last updated ${this.props.metadata.updated}`);
const address = window.location.href.replace(/&/g, '&amp;');
x.push(`Downloaded from <a href="${address}">${address}</a> on ${new Date().toLocaleString()}`);
x.push(this.createSummaryWrapper().join(", "));
x.push("");
x.push(dataUsage[0] + ` A full list of sequence authors is available via <a href="https://nextstrain.org">nextstrain.org</a>.`);
x.push(`Relevant publications:`);
this.getRelevantPublications().forEach((pub) => {
x.push(`<a href="${pub.href}">${pub.author}, ${pub.title}, ${pub.journal} (${pub.year})</a>`);
});
return x;
}

downloadButtons() {
const filePrefix = "nextstrain_" + this.props.datapath.replace(/^\//, '').replace(/\//g, '_');
const filePrefix = this.getFilePrefix();
const iconWidth = 25;
const iconStroke = medGrey;
const iconStroke = extraLightGrey;
const buttons = [
["Tree (newick)", (<icons.RectangularTree width={iconWidth} stroke={iconStroke} />), () => helpers.newick(this.props.dispatch, filePrefix, this.props.nodes[0], false)],
["TimeTree (newick)", (<icons.RectangularTree width={iconWidth} stroke={iconStroke} />), () => helpers.newick(this.props.dispatch, filePrefix, this.props.nodes[0], true)],
["Strain Metadata (CSV)", (<icons.Meta width={iconWidth} stroke={iconStroke} />), () => helpers.strainCSV(this.props.dispatch, filePrefix, this.props.nodes, this.props.treeAttrs)],
["Author Metadata (CSV)", (<icons.Meta width={iconWidth} stroke={iconStroke} />), () => helpers.authorCSV(this.props.dispatch, filePrefix, this.props.metadata, this.props.tree)],
["Screenshot (SGV)", (<icons.PanelsGrid width={iconWidth} stroke={iconStroke} />), () => helpers.SVG(this.props.dispatch, filePrefix, this.props.metadata.panels)]
["Strain Metadata (TSV)", (<icons.Meta width={iconWidth} stroke={iconStroke} />), () => helpers.strainTSV(this.props.dispatch, filePrefix, this.props.nodes, this.props.treeAttrs)],
["Author Metadata (TSV)", (<icons.Meta width={iconWidth} stroke={iconStroke} />), () => helpers.authorTSV(this.props.dispatch, filePrefix, this.props.metadata, this.props.tree)],
["Screenshot (SGV)", (<icons.PanelsGrid width={iconWidth} stroke={iconStroke} />), () => helpers.SVG(this.props.dispatch, filePrefix, this.props.panelsToDisplay, this.props.panelLayout, this.makeTextStringsForSVGExport())]
];
const buttonTextStyle = Object.assign({}, materialButton, {backgroundColor: "rgba(0,0,0,0)", paddingLeft: "10px", color: "white"});
return (
<div className="row">
<div style={{display: "flex", flexWrap: "wrap", justifyContent: "space-around"}}>
{buttons.map((data) => (
<div key={data[0]} className="col-md-5" onClick={data[2]} style={{cursor: 'pointer'}}>
<div key={data[0]} onClick={data[2]} style={{cursor: 'pointer'}}>
{data[1]}
<button style={Object.assign({}, materialButton, {backgroundColor: "rgba(0,0,0,0)", paddingLeft: "10px"})}>
<button style={buttonTextStyle}>
{data[0]}
</button>
</div>
Expand All @@ -119,15 +200,8 @@ class DownloadModal extends React.Component {
dismissModal() {
this.props.dispatch({ type: DISMISS_DOWNLOAD_MODAL });
}
render() {
if (!this.props.show) {
return null;
}
const styles = this.getStyles(this.props.browserDimensions.width, this.props.browserDimensions.height);
const meta = this.props.metadata;


const summary = createSummary(
createSummaryWrapper() {
return createSummary(
this.props.metadata.virus_count,
this.props.nodes,
this.props.filters,
Expand All @@ -137,44 +211,55 @@ class DownloadModal extends React.Component {
this.props.dateMin,
this.props.dateMax
);
}
render() {
if (!this.props.show) {
return null;
}
const panelStyle = {...infoPanelStyles.panel};
panelStyle.width = this.props.browserDimensions.width * 0.66;
panelStyle.maxWidth = panelStyle.width;
panelStyle.maxHeight = this.props.browserDimensions.height * 0.66;
panelStyle.fontSize = 14;
panelStyle.lineHeight = 1.4;

const meta = this.props.metadata;
const summary = this.createSummaryWrapper();
return (
<div style={styles.behind} onClick={this.dismissModal.bind(this)}>
<div className="static container" style={styles.modal} onClick={(e) => stopProp(e)}>
<div className="row">
<div className="col-md-1"/>
<div className="col-md-7" style={styles.title}>
Download Data
</div>
<div style={infoPanelStyles.modalContainer} onClick={this.dismissModal}>
<div style={panelStyle} onClick={(e) => stopProp(e)}>
<p style={infoPanelStyles.topRightMessage}>
(click outside this box to return to the app)
</p>

<div style={infoPanelStyles.modalSubheading}>
{meta.title} (last updated {meta.updated})
</div>
<div className="row">
<div className="col-md-1" />
<div className="col-md-10">
<div style={styles.secondTitle}>
{meta.title} (last updated {meta.updated})
</div>
{summary.map((d, i) =>
(i + 1 !== summary.length ? <span key={i}>{`${d}, `}</span> : <span key={i}>{`${d}. `}</span>)
)}
<div style={styles.break}/>
{preambleText}
{" A full list of sequence authors is available via the CSV files below."}
<div style={styles.break}/>
{getAcknowledgments({})}

<h2>Data usage policy</h2>
{dataUsage}
{summary.map((d, i) =>
(i + 1 !== summary.length ? <span key={d}>{`${d}, `}</span> : <span key={d}>{`${d}. `}</span>)
)}
<div style={infoPanelStyles.break}/>
{" A full list of sequence authors is available via the TSV files below."}
<div style={infoPanelStyles.break}/>
{getAcknowledgments({}, {preamble: {fontWeight: 300}, acknowledgments: {fontWeight: 300}})}

<h2>The current data analysis relies on</h2>
{this.relevantPublications()}
<div style={infoPanelStyles.modalSubheading}>
Data usage policy
</div>
{dataUsage.join(" ")}

<div style={infoPanelStyles.modalSubheading}>
Please cite the authors who contributed genomic data (where relevant), as well as:
</div>
{this.formatPublications(this.getRelevantPublications())}

<h2>Download data as</h2>
{this.downloadButtons()}

<p style={infoPanelStyles.comment}>
(click outside this box to return to the app)
</p>
</div>
<div style={infoPanelStyles.modalSubheading}>
Download data:
</div>
{this.downloadButtons()}

</div>
</div>
);
Expand Down
Loading