Browse Source

Merge branch 'master' into 522-url-conf-intervals

master
eharkins 2 years ago
parent
commit
4de5ef336a
  1. 14
      CHANGELOG.md
  2. 10
      DEV_DOCS.md
  3. 1
      babel.config.js
  4. 14
      cli/server/parseNarrative.js
  5. 1
      docs-src/docs/advanced-functionality/view-settings.md
  6. 3
      docs/advanced-functionality/view-settings.html
  7. 3
      docs/advanced-functionality/view-settings/index.html
  8. 8
      docs/contributing/overview.html
  9. 8
      docs/contributing/overview/index.html
  10. 36
      docs/releases/changelog.html
  11. 36
      docs/releases/changelog/index.html
  12. 4
      docs/server/introduction.html
  13. 4
      docs/server/introduction/index.html
  14. 2746
      package-lock.json
  15. 3
      package.json
  16. 3
      src/components/controls/language.js
  17. 10
      src/components/main/index.js
  18. 13
      src/components/map/map.js
  19. 83
      src/components/narrative/ReactPageScroller.js
  20. 6
      src/components/narrative/index.js
  21. 6
      src/components/tree/infoPanels/hover.js
  22. 61
      src/components/tree/legend/legend.js
  23. 6
      src/components/tree/phyloTree/change.js
  24. 104
      src/components/tree/phyloTree/grid.js
  25. 20
      src/components/tree/phyloTree/labels.js
  26. 4
      src/components/tree/phyloTree/phyloTree.js
  27. 2
      src/components/tree/phyloTree/renderers.js
  28. 2
      src/index.js
  29. 2
      src/locales/de/translation.json
  30. 2
      src/locales/en/translation.json
  31. 2
      src/locales/es/translation.json
  32. 2
      src/locales/fr/translation.json
  33. 3
      src/locales/ja/language.json
  34. 33
      src/locales/ja/sidebar.json
  35. 91
      src/locales/ja/translation.json
  36. 2
      src/locales/lt/translation.json
  37. 2
      src/locales/pt/translation.json
  38. 2
      src/locales/ru/translation.json
  39. 10
      src/middleware/changeURL.js
  40. 3
      src/reducers/controls.js
  41. 11
      src/reducers/general.js
  42. 1
      src/util/globals.js
  43. 2
      src/version.js

14
CHANGELOG.md

@ -2,12 +2,22 @@
title: Changelog
---
## version 2.11.4 - 2020/04/04
## version 2.12.0 - 2020/04/08
## version 2.11.3 - 2020/04/02
* Add a legend to the map! [See PR 935](https://github.com/nextstrain/auspice/pull/935)
* Improve the animation smoothness, especially for datasets with limited temporal range. [See PR 920](https://github.com/nextstrain/auspice/pull/920)
* Add Japanese translation [See PR 1045](https://github.com/nextstrain/auspice/pull/1045)
* Reinstate the `lang` URL query to allow the URL to choose the language.
* Remove unnecessary lodash code from the bundles. [See PR 1018](https://github.com/nextstrain/auspice/pull/1018)
* Update how the `Root` component is imported. [See PR 1029](https://github.com/nextstrain/auspice/pull/1029)
* Fix errors in the sizing of the map buttons. [See PR 1040](https://github.com/nextstrain/auspice/pull/1040)
* Improve rendering of tree tip labels by only counting the selected (visible) tips. [See PR 1043](https://github.com/nextstrain/auspice/pull/1043)
## version 2.11.4 - 2020/04/04
* Temporarily remove SVG gradients in the tree due to bugs in multiple browsers. [See PR 1042](https://github.com/nextstrain/auspice/pull/1042)
## version 2.11.3 - 2020/04/02
* Add Portuguese translation. [See PR 1017](https://github.com/nextstrain/auspice/pull/1017)
* Fig bug where some branches would not display on certain browsers. [See PR 1022](https://github.com/nextstrain/auspice/pull/1022)
* Fix bug with selected dates shown when no genomes were selected. [See PR 1011](https://github.com/nextstrain/auspice/pull/1011)

10
DEV_DOCS.md

@ -11,9 +11,15 @@ This project strictly adheres to the [Contributor Covenant Code of Conduct](http
Please see the [project boards](https://github.com/orgs/nextstrain/projects) for currently available issues.
## Contributing code
[Please see the main auspice docs](https://nextstrain.github.io/auspice/introduction/install) for details on how to install and run auspice locally.
Code contributions are welcomed! [Please see the main auspice docs](https://nextstrain.github.io/auspice/introduction/install) for details on how to install and run auspice from source.
For pull requests, please use [eslint](https://eslint.org/) as much as possible (via `npm run lint`).
Please comment on an open issue if you are working on it.
For changes unrelated to an open issue, please make an issue outlining what you would like to change/add.
Please ensure there are no **linting** errors by running `npm run lint` (which uses [eslint](https://eslint.org/)).
In the future we will make this a requirement for PRs or commits.
Where possible, **please rebase** your work onto master rather than merging changes from master into your PR.
## Contributing to Documentation

1
babel.config.js

@ -18,6 +18,7 @@ module.exports = function babelConfig(api) {
"babel-plugin-styled-components",
"babel-plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime",
"lodash"
];
if (api.env("development")) {
if (process.env.BABEL_EXTENSION_PATH && !process.env.BABEL_EXTENSION_PATH.includes(__dirname)) {

14
cli/server/parseNarrative.js

@ -28,14 +28,14 @@ const makeFrontMatterBlock = (frontMatter) => {
}
/* create markdown to represent the title page */
const markdown = [];
markdown.push(`# ${frontMatter.title}`);
markdown.push(`## ${frontMatter.title}`);
if (frontMatter.authors) {
const authors = parseContributors(frontMatter, "authors", "authorLinks");
if (authors) {
markdown.push(`### Author: ${authors}`);
if (frontMatter.affiliations && typeof frontMatter.affiliations === "string") {
markdown[markdown.length-1] += " <sup> 1 </sup>";
markdown.push(`<sup> 1 </sup> ${frontMatter.affiliations}`);
markdown.push(`<sub><sup> 1 </sup> ${frontMatter.affiliations}</sub>`);
}
}
}
@ -45,14 +45,14 @@ const makeFrontMatterBlock = (frontMatter) => {
markdown.push(`### Translators: ${translators}`);
}
}
if (frontMatter.abstract && typeof frontMatter.abstract === "string") {
markdown.push(`### ${frontMatter.abstract}`);
}
if (frontMatter.date && typeof frontMatter.date === "string") {
markdown.push(`### Created: ${frontMatter.date}`);
markdown.push(`#### Created: ${frontMatter.date}`);
}
if (frontMatter.updated && typeof frontMatter.updated === "string") {
markdown.push(`### Updated: ${frontMatter.updated}`);
}
if (frontMatter.abstract && typeof frontMatter.abstract === "string") {
markdown.push(`#### ${frontMatter.abstract}`);
markdown.push(`#### Updated: ${frontMatter.updated}`);
}
const block = new Proxy({}, blockProxyHandler);

1
docs-src/docs/advanced-functionality/view-settings.md

@ -58,6 +58,7 @@ All URL queries modify the view away from the default settings -- if you change
| `r` | Geographic resolution | `r=region` |
| `m` | Phylogeny x-axis measure | `m=div` |
| `l` | Phylogeny layout | `l=clock` |
| `lang` | Language | `lang=ja` (Japanese) |
| `dmin` | Temporal range (minimum) | `dmin=2008-05-13` |
| `dmax` | Temporal range (maximum) | `dmax=2010-05-13` |
| `f_<name>` | Data filter. Multiple values per key are `,` separated. | `f_region=Oceania` |

3
docs/advanced-functionality/view-settings.html

@ -109,6 +109,7 @@ All URL queries modify the view away from the default settings -- if you change
<tr><td><code>r</code></td><td>Geographic resolution</td><td><code>r=region</code></td></tr>
<tr><td><code>m</code></td><td>Phylogeny x-axis measure</td><td><code>m=div</code></td></tr>
<tr><td><code>l</code></td><td>Phylogeny layout</td><td><code>l=clock</code></td></tr>
<tr><td><code>lang</code></td><td>Language</td><td><code>lang=ja</code> (Japanese)</td></tr>
<tr><td><code>dmin</code></td><td>Temporal range (minimum)</td><td><code>dmin=2008-05-13</code></td></tr>
<tr><td><code>dmax</code></td><td>Temporal range (maximum)</td><td><code>dmax=2010-05-13</code></td></tr>
<tr><td><code>f_&lt;name&gt;</code></td><td>Data filter. Multiple values per key are <code>,</code> separated.</td><td><code>f_region=Oceania</code></td></tr>
@ -129,4 +130,4 @@ All URL queries modify the view away from the default settings -- if you change
</table>
<p><strong>See this in action:</strong></p>
<p>For instance, go to <a href="https://nextstrain.org/flu/seasonal/h3n2/ha/2y?c=num_date&amp;d=tree,map&amp;m=div&amp;p=grid&amp;r=region">nextstrain.org/flu/seasonal/h3n2/ha/2y?c=num_date&amp;d=tree,map&amp;m=div&amp;r=region</a> and you'll see how we've changed the coloring to a temporal scale (<code>c=num_date</code>), we're only showing the tree &amp; map panels (<code>d=tree,map</code>), the tree x-axis is divergence (<code>m=div</code>) and the map resolution is region (<code>r=region</code>).</p>
</span></div></article></div><div class="docLastUpdate"><em>Last updated on 3/27/2020</em></div><div class="docs-prevnext"><a class="docs-prev button" href="/auspice/advanced-functionality/second-trees"><span class="arrow-prev"></span><span>Displaying multiple trees</span></a><a class="docs-next button" href="/auspice/advanced-functionality/drag-drop-csv-tsv"><span>Adding extra metadata via CSV/TSV</span><span class="arrow-next"></span></a></div></div></div><nav class="onPageNav"><ul class="toc-headings"><li><a href="#auspice-hardcoded-defaults">Auspice (hardcoded) defaults</a></li><li><a href="#dataset-json-configurable-defaults">Dataset (JSON) configurable defaults</a></li><li><a href="#url-query-options">URL query options</a></li></ul></nav></div><footer class="nav-footer" id="footer"><section class="sitemap"><div><a href="/auspice/"><img style="padding-left:20px" src="/auspice/img/logo-light.svg" alt="Auspice" width="66" height="58"/></a></div><div><h5>External Links</h5><a href="https://github.com/nextstrain/auspice">GitHub repo</a><a href="https://www.npmjs.com/package/auspice">NPM package</a><a href="https://nextstrain.org">Nextstrain</a></div><div><h5>Contact Us</h5><a href="mailto:hello@nextstrain.org">email</a><a href="https://twitter.com/hamesjadfield">twitter</a></div></section><section class="copyright">Website built by <a href="https://twitter.com/hamesjadfield">James Hadfield</a> using <a href="https://docusaurus.io">Docusaurus</a></section><section class="copyright">If you use auspice, please cite <a href="https://doi.org/10.1093/bioinformatics/bty407">Hadfield et al., 2018</a></section><section class="copyright">Copyright © 2014-2020 Richard Neher &amp; Trevor Bedford</section></footer></div></body></html>
</span></div></article></div><div class="docLastUpdate"><em>Last updated on 4/8/2020</em></div><div class="docs-prevnext"><a class="docs-prev button" href="/auspice/advanced-functionality/second-trees"><span class="arrow-prev"></span><span>Displaying multiple trees</span></a><a class="docs-next button" href="/auspice/advanced-functionality/drag-drop-csv-tsv"><span>Adding extra metadata via CSV/TSV</span><span class="arrow-next"></span></a></div></div></div><nav class="onPageNav"><ul class="toc-headings"><li><a href="#auspice-hardcoded-defaults">Auspice (hardcoded) defaults</a></li><li><a href="#dataset-json-configurable-defaults">Dataset (JSON) configurable defaults</a></li><li><a href="#url-query-options">URL query options</a></li></ul></nav></div><footer class="nav-footer" id="footer"><section class="sitemap"><div><a href="/auspice/"><img style="padding-left:20px" src="/auspice/img/logo-light.svg" alt="Auspice" width="66" height="58"/></a></div><div><h5>External Links</h5><a href="https://github.com/nextstrain/auspice">GitHub repo</a><a href="https://www.npmjs.com/package/auspice">NPM package</a><a href="https://nextstrain.org">Nextstrain</a></div><div><h5>Contact Us</h5><a href="mailto:hello@nextstrain.org">email</a><a href="https://twitter.com/hamesjadfield">twitter</a></div></section><section class="copyright">Website built by <a href="https://twitter.com/hamesjadfield">James Hadfield</a> using <a href="https://docusaurus.io">Docusaurus</a></section><section class="copyright">If you use auspice, please cite <a href="https://doi.org/10.1093/bioinformatics/bty407">Hadfield et al., 2018</a></section><section class="copyright">Copyright © 2014-2020 Richard Neher &amp; Trevor Bedford</section></footer></div></body></html>

3
docs/advanced-functionality/view-settings/index.html

@ -109,6 +109,7 @@ All URL queries modify the view away from the default settings -- if you change
<tr><td><code>r</code></td><td>Geographic resolution</td><td><code>r=region</code></td></tr>
<tr><td><code>m</code></td><td>Phylogeny x-axis measure</td><td><code>m=div</code></td></tr>
<tr><td><code>l</code></td><td>Phylogeny layout</td><td><code>l=clock</code></td></tr>
<tr><td><code>lang</code></td><td>Language</td><td><code>lang=ja</code> (Japanese)</td></tr>
<tr><td><code>dmin</code></td><td>Temporal range (minimum)</td><td><code>dmin=2008-05-13</code></td></tr>
<tr><td><code>dmax</code></td><td>Temporal range (maximum)</td><td><code>dmax=2010-05-13</code></td></tr>
<tr><td><code>f_&lt;name&gt;</code></td><td>Data filter. Multiple values per key are <code>,</code> separated.</td><td><code>f_region=Oceania</code></td></tr>
@ -129,4 +130,4 @@ All URL queries modify the view away from the default settings -- if you change
</table>
<p><strong>See this in action:</strong></p>
<p>For instance, go to <a href="https://nextstrain.org/flu/seasonal/h3n2/ha/2y?c=num_date&amp;d=tree,map&amp;m=div&amp;p=grid&amp;r=region">nextstrain.org/flu/seasonal/h3n2/ha/2y?c=num_date&amp;d=tree,map&amp;m=div&amp;r=region</a> and you'll see how we've changed the coloring to a temporal scale (<code>c=num_date</code>), we're only showing the tree &amp; map panels (<code>d=tree,map</code>), the tree x-axis is divergence (<code>m=div</code>) and the map resolution is region (<code>r=region</code>).</p>
</span></div></article></div><div class="docLastUpdate"><em>Last updated on 3/27/2020</em></div><div class="docs-prevnext"><a class="docs-prev button" href="/auspice/advanced-functionality/second-trees"><span class="arrow-prev"></span><span>Displaying multiple trees</span></a><a class="docs-next button" href="/auspice/advanced-functionality/drag-drop-csv-tsv"><span>Adding extra metadata via CSV/TSV</span><span class="arrow-next"></span></a></div></div></div><nav class="onPageNav"><ul class="toc-headings"><li><a href="#auspice-hardcoded-defaults">Auspice (hardcoded) defaults</a></li><li><a href="#dataset-json-configurable-defaults">Dataset (JSON) configurable defaults</a></li><li><a href="#url-query-options">URL query options</a></li></ul></nav></div><footer class="nav-footer" id="footer"><section class="sitemap"><div><a href="/auspice/"><img style="padding-left:20px" src="/auspice/img/logo-light.svg" alt="Auspice" width="66" height="58"/></a></div><div><h5>External Links</h5><a href="https://github.com/nextstrain/auspice">GitHub repo</a><a href="https://www.npmjs.com/package/auspice">NPM package</a><a href="https://nextstrain.org">Nextstrain</a></div><div><h5>Contact Us</h5><a href="mailto:hello@nextstrain.org">email</a><a href="https://twitter.com/hamesjadfield">twitter</a></div></section><section class="copyright">Website built by <a href="https://twitter.com/hamesjadfield">James Hadfield</a> using <a href="https://docusaurus.io">Docusaurus</a></section><section class="copyright">If you use auspice, please cite <a href="https://doi.org/10.1093/bioinformatics/bty407">Hadfield et al., 2018</a></section><section class="copyright">Copyright © 2014-2020 Richard Neher &amp; Trevor Bedford</section></footer></div></body></html>
</span></div></article></div><div class="docLastUpdate"><em>Last updated on 4/8/2020</em></div><div class="docs-prevnext"><a class="docs-prev button" href="/auspice/advanced-functionality/second-trees"><span class="arrow-prev"></span><span>Displaying multiple trees</span></a><a class="docs-next button" href="/auspice/advanced-functionality/drag-drop-csv-tsv"><span>Adding extra metadata via CSV/TSV</span><span class="arrow-next"></span></a></div></div></div><nav class="onPageNav"><ul class="toc-headings"><li><a href="#auspice-hardcoded-defaults">Auspice (hardcoded) defaults</a></li><li><a href="#dataset-json-configurable-defaults">Dataset (JSON) configurable defaults</a></li><li><a href="#url-query-options">URL query options</a></li></ul></nav></div><footer class="nav-footer" id="footer"><section class="sitemap"><div><a href="/auspice/"><img style="padding-left:20px" src="/auspice/img/logo-light.svg" alt="Auspice" width="66" height="58"/></a></div><div><h5>External Links</h5><a href="https://github.com/nextstrain/auspice">GitHub repo</a><a href="https://www.npmjs.com/package/auspice">NPM package</a><a href="https://nextstrain.org">Nextstrain</a></div><div><h5>Contact Us</h5><a href="mailto:hello@nextstrain.org">email</a><a href="https://twitter.com/hamesjadfield">twitter</a></div></section><section class="copyright">Website built by <a href="https://twitter.com/hamesjadfield">James Hadfield</a> using <a href="https://docusaurus.io">Docusaurus</a></section><section class="copyright">If you use auspice, please cite <a href="https://doi.org/10.1093/bioinformatics/bty407">Hadfield et al., 2018</a></section><section class="copyright">Copyright © 2014-2020 Richard Neher &amp; Trevor Bedford</section></footer></div></body></html>

8
docs/contributing/overview.html

@ -61,8 +61,12 @@
<p>This project strictly adheres to the <a href="https://github.com/nextstrain/.github/blob/master/CODE_OF_CONDUCT.md">Contributor Covenant Code of Conduct</a>.</p>
<p>Please see the <a href="https://github.com/orgs/nextstrain/projects">project boards</a> for currently available issues.</p>
<h2><a class="anchor" aria-hidden="true" id="contributing-code"></a><a href="#contributing-code" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Contributing code</h2>
<p><a href="https://nextstrain.github.io/auspice/introduction/install">Please see the main auspice docs</a> for details on how to install and run auspice locally.</p>
<p>For pull requests, please use <a href="https://eslint.org/">eslint</a> as much as possible (via <code>npm run lint</code>).</p>
<p>Code contributions are welcomed! <a href="https://nextstrain.github.io/auspice/introduction/install">Please see the main auspice docs</a> for details on how to install and run auspice from source.</p>
<p>Please comment on an open issue if you are working on it.
For changes unrelated to an open issue, please make an issue outlining what you would like to change/add.</p>
<p>Please ensure there are no <strong>linting</strong> errors by running <code>npm run lint</code> (which uses <a href="https://eslint.org/">eslint</a>).
In the future we will make this a requirement for PRs or commits.</p>
<p>Where possible, <strong>please rebase</strong> your work onto master rather than merging changes from master into your PR.</p>
<h2><a class="anchor" aria-hidden="true" id="contributing-to-documentation"></a><a href="#contributing-to-documentation" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Contributing to Documentation</h2>
<p>Nextstrain documentation is available at <a href="https://nextstrain.github.io/auspice/">nextstrain.github.io/auspice/</a>.</p>
<p>This documentation is built from files contained within the Auspice GitHub repo -- see the <a href="https://github.com/nextstrain/auspice/tree/master/docs-src">docs-src/README</a> within the <code>docs-src</code> directory for more details and instructions on how to contribute.</p>

8
docs/contributing/overview/index.html

@ -61,8 +61,12 @@
<p>This project strictly adheres to the <a href="https://github.com/nextstrain/.github/blob/master/CODE_OF_CONDUCT.md">Contributor Covenant Code of Conduct</a>.</p>
<p>Please see the <a href="https://github.com/orgs/nextstrain/projects">project boards</a> for currently available issues.</p>
<h2><a class="anchor" aria-hidden="true" id="contributing-code"></a><a href="#contributing-code" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Contributing code</h2>
<p><a href="https://nextstrain.github.io/auspice/introduction/install">Please see the main auspice docs</a> for details on how to install and run auspice locally.</p>
<p>For pull requests, please use <a href="https://eslint.org/">eslint</a> as much as possible (via <code>npm run lint</code>).</p>
<p>Code contributions are welcomed! <a href="https://nextstrain.github.io/auspice/introduction/install">Please see the main auspice docs</a> for details on how to install and run auspice from source.</p>
<p>Please comment on an open issue if you are working on it.
For changes unrelated to an open issue, please make an issue outlining what you would like to change/add.</p>
<p>Please ensure there are no <strong>linting</strong> errors by running <code>npm run lint</code> (which uses <a href="https://eslint.org/">eslint</a>).
In the future we will make this a requirement for PRs or commits.</p>
<p>Where possible, <strong>please rebase</strong> your work onto master rather than merging changes from master into your PR.</p>
<h2><a class="anchor" aria-hidden="true" id="contributing-to-documentation"></a><a href="#contributing-to-documentation" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Contributing to Documentation</h2>
<p>Nextstrain documentation is available at <a href="https://nextstrain.github.io/auspice/">nextstrain.github.io/auspice/</a>.</p>
<p>This documentation is built from files contained within the Auspice GitHub repo -- see the <a href="https://github.com/nextstrain/auspice/tree/master/docs-src">docs-src/README</a> within the <code>docs-src</code> directory for more details and instructions on how to contribute.</p>

36
docs/releases/changelog.html

File diff suppressed because one or more lines are too long

36
docs/releases/changelog/index.html

File diff suppressed because one or more lines are too long

4
docs/server/introduction.html

@ -78,7 +78,7 @@ Alternatively, you can build your own server -- it just needs to satisfy the abo
Similarly, nextstrain.org is a server which has handlers for these three API endpoints, so if you visit <a href="https://nextstrain.org/charon/getAvailable">nextstrain.org/charon/getAvailable</a> you'll see Nextstrain's available datasets.</p>
<p>See <a href="/auspice/server/api">the server API</a> for details about each of these requests.</p>
<blockquote>
<p>Note that &quot;/charon&quot; can be changed to any address you wich by customising the client at build time.
<p>Note that &quot;/charon&quot; can be changed to any address you wish by customising the client at build time.
See <a href="../customise-client/api">the client-cusomisation API</a> for more details.</p>
</blockquote>
<h2><a class="anchor" aria-hidden="true" id="the-default-auspice-server"></a><a href="#the-default-auspice-server" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The &quot;Default&quot; Auspice Server</h2>
@ -105,4 +105,4 @@ Deploying to Heroku is straightforward, but there are a few points to note:</p>
<li>Make sure the datasets to be served are either (a) included in your git repo or (b) downloaded by the heroku build pipeline.
<a href="https://github.com/nextstrain/auspice/blob/master/package.json">We use option (b)</a> by specifing a npm script called <code>heroku-postbuild</code>.</li>
</ol>
</span></div></article></div><div class="docLastUpdate"><em>Last updated on 1/2/2020</em></div><div class="docs-prevnext"><a class="docs-prev button" href="/auspice/customise-client/requests"><span class="arrow-prev"></span><span>Requests Made from the Client</span></a><a class="docs-next button" href="/auspice/server/api"><span>Server API</span><span class="arrow-next"></span></a></div></div></div><nav class="onPageNav"><ul class="toc-headings"><li><a href="#get-requests">GET Requests</a></li><li><a href="#the-quot-default-quot-auspice-server">The &quot;Default&quot; Auspice Server</a></li><li><a href="#customising-the-default-auspice-server">Customising the Default Auspice Server</a></li><li><a href="#writing-your-own-custom-server">Writing Your Own Custom Server</a></li><li><a href="#deploying-via-heroku">Deploying via Heroku</a></li></ul></nav></div><footer class="nav-footer" id="footer"><section class="sitemap"><div><a href="/auspice/"><img style="padding-left:20px" src="/auspice/img/logo-light.svg" alt="Auspice" width="66" height="58"/></a></div><div><h5>External Links</h5><a href="https://github.com/nextstrain/auspice">GitHub repo</a><a href="https://www.npmjs.com/package/auspice">NPM package</a><a href="https://nextstrain.org">Nextstrain</a></div><div><h5>Contact Us</h5><a href="mailto:hello@nextstrain.org">email</a><a href="https://twitter.com/hamesjadfield">twitter</a></div></section><section class="copyright">Website built by <a href="https://twitter.com/hamesjadfield">James Hadfield</a> using <a href="https://docusaurus.io">Docusaurus</a></section><section class="copyright">If you use auspice, please cite <a href="https://doi.org/10.1093/bioinformatics/bty407">Hadfield et al., 2018</a></section><section class="copyright">Copyright © 2014-2020 Richard Neher &amp; Trevor Bedford</section></footer></div></body></html>
</span></div></article></div><div class="docLastUpdate"><em>Last updated on 3/31/2020</em></div><div class="docs-prevnext"><a class="docs-prev button" href="/auspice/customise-client/requests"><span class="arrow-prev"></span><span>Requests Made from the Client</span></a><a class="docs-next button" href="/auspice/server/api"><span>Server API</span><span class="arrow-next"></span></a></div></div></div><nav class="onPageNav"><ul class="toc-headings"><li><a href="#get-requests">GET Requests</a></li><li><a href="#the-quot-default-quot-auspice-server">The &quot;Default&quot; Auspice Server</a></li><li><a href="#customising-the-default-auspice-server">Customising the Default Auspice Server</a></li><li><a href="#writing-your-own-custom-server">Writing Your Own Custom Server</a></li><li><a href="#deploying-via-heroku">Deploying via Heroku</a></li></ul></nav></div><footer class="nav-footer" id="footer"><section class="sitemap"><div><a href="/auspice/"><img style="padding-left:20px" src="/auspice/img/logo-light.svg" alt="Auspice" width="66" height="58"/></a></div><div><h5>External Links</h5><a href="https://github.com/nextstrain/auspice">GitHub repo</a><a href="https://www.npmjs.com/package/auspice">NPM package</a><a href="https://nextstrain.org">Nextstrain</a></div><div><h5>Contact Us</h5><a href="mailto:hello@nextstrain.org">email</a><a href="https://twitter.com/hamesjadfield">twitter</a></div></section><section class="copyright">Website built by <a href="https://twitter.com/hamesjadfield">James Hadfield</a> using <a href="https://docusaurus.io">Docusaurus</a></section><section class="copyright">If you use auspice, please cite <a href="https://doi.org/10.1093/bioinformatics/bty407">Hadfield et al., 2018</a></section><section class="copyright">Copyright © 2014-2020 Richard Neher &amp; Trevor Bedford</section></footer></div></body></html>

4
docs/server/introduction/index.html

@ -78,7 +78,7 @@ Alternatively, you can build your own server -- it just needs to satisfy the abo
Similarly, nextstrain.org is a server which has handlers for these three API endpoints, so if you visit <a href="https://nextstrain.org/charon/getAvailable">nextstrain.org/charon/getAvailable</a> you'll see Nextstrain's available datasets.</p>
<p>See <a href="/auspice/server/api">the server API</a> for details about each of these requests.</p>
<blockquote>
<p>Note that &quot;/charon&quot; can be changed to any address you wich by customising the client at build time.
<p>Note that &quot;/charon&quot; can be changed to any address you wish by customising the client at build time.
See <a href="../customise-client/api">the client-cusomisation API</a> for more details.</p>
</blockquote>
<h2><a class="anchor" aria-hidden="true" id="the-default-auspice-server"></a><a href="#the-default-auspice-server" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The &quot;Default&quot; Auspice Server</h2>
@ -105,4 +105,4 @@ Deploying to Heroku is straightforward, but there are a few points to note:</p>
<li>Make sure the datasets to be served are either (a) included in your git repo or (b) downloaded by the heroku build pipeline.
<a href="https://github.com/nextstrain/auspice/blob/master/package.json">We use option (b)</a> by specifing a npm script called <code>heroku-postbuild</code>.</li>
</ol>
</span></div></article></div><div class="docLastUpdate"><em>Last updated on 1/2/2020</em></div><div class="docs-prevnext"><a class="docs-prev button" href="/auspice/customise-client/requests"><span class="arrow-prev"></span><span>Requests Made from the Client</span></a><a class="docs-next button" href="/auspice/server/api"><span>Server API</span><span class="arrow-next"></span></a></div></div></div><nav class="onPageNav"><ul class="toc-headings"><li><a href="#get-requests">GET Requests</a></li><li><a href="#the-quot-default-quot-auspice-server">The &quot;Default&quot; Auspice Server</a></li><li><a href="#customising-the-default-auspice-server">Customising the Default Auspice Server</a></li><li><a href="#writing-your-own-custom-server">Writing Your Own Custom Server</a></li><li><a href="#deploying-via-heroku">Deploying via Heroku</a></li></ul></nav></div><footer class="nav-footer" id="footer"><section class="sitemap"><div><a href="/auspice/"><img style="padding-left:20px" src="/auspice/img/logo-light.svg" alt="Auspice" width="66" height="58"/></a></div><div><h5>External Links</h5><a href="https://github.com/nextstrain/auspice">GitHub repo</a><a href="https://www.npmjs.com/package/auspice">NPM package</a><a href="https://nextstrain.org">Nextstrain</a></div><div><h5>Contact Us</h5><a href="mailto:hello@nextstrain.org">email</a><a href="https://twitter.com/hamesjadfield">twitter</a></div></section><section class="copyright">Website built by <a href="https://twitter.com/hamesjadfield">James Hadfield</a> using <a href="https://docusaurus.io">Docusaurus</a></section><section class="copyright">If you use auspice, please cite <a href="https://doi.org/10.1093/bioinformatics/bty407">Hadfield et al., 2018</a></section><section class="copyright">Copyright © 2014-2020 Richard Neher &amp; Trevor Bedford</section></footer></div></body></html>
</span></div></article></div><div class="docLastUpdate"><em>Last updated on 3/31/2020</em></div><div class="docs-prevnext"><a class="docs-prev button" href="/auspice/customise-client/requests"><span class="arrow-prev"></span><span>Requests Made from the Client</span></a><a class="docs-next button" href="/auspice/server/api"><span>Server API</span><span class="arrow-next"></span></a></div></div></div><nav class="onPageNav"><ul class="toc-headings"><li><a href="#get-requests">GET Requests</a></li><li><a href="#the-quot-default-quot-auspice-server">The &quot;Default&quot; Auspice Server</a></li><li><a href="#customising-the-default-auspice-server">Customising the Default Auspice Server</a></li><li><a href="#writing-your-own-custom-server">Writing Your Own Custom Server</a></li><li><a href="#deploying-via-heroku">Deploying via Heroku</a></li></ul></nav></div><footer class="nav-footer" id="footer"><section class="sitemap"><div><a href="/auspice/"><img style="padding-left:20px" src="/auspice/img/logo-light.svg" alt="Auspice" width="66" height="58"/></a></div><div><h5>External Links</h5><a href="https://github.com/nextstrain/auspice">GitHub repo</a><a href="https://www.npmjs.com/package/auspice">NPM package</a><a href="https://nextstrain.org">Nextstrain</a></div><div><h5>Contact Us</h5><a href="mailto:hello@nextstrain.org">email</a><a href="https://twitter.com/hamesjadfield">twitter</a></div></section><section class="copyright">Website built by <a href="https://twitter.com/hamesjadfield">James Hadfield</a> using <a href="https://docusaurus.io">Docusaurus</a></section><section class="copyright">If you use auspice, please cite <a href="https://doi.org/10.1093/bioinformatics/bty407">Hadfield et al., 2018</a></section><section class="copyright">Copyright © 2014-2020 Richard Neher &amp; Trevor Bedford</section></footer></div></body></html>

2746
package-lock.json

File diff suppressed because it is too large

3
package.json

@ -1,6 +1,6 @@
{
"name": "auspice",
"version": "2.11.4",
"version": "2.12.0",
"description": "Web app for visualizing pathogen evolution",
"author": "James Hadfield, Trevor Bedford and Richard Neher",
"license": "AGPL-3.0-only",
@ -46,6 +46,7 @@
"awesomplete": "^1.1.2",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.4",
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-strip-function-call": "^1.0.2",
"babel-plugin-styled-components": "^1.10.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",

3
src/components/controls/language.js

@ -29,7 +29,8 @@ class Language extends React.Component {
{value: "ru", label: "Русский"},
{value: "lt", label: "Lietuvių"},
{value: "pt", label: "Português"},
{value: "fr", label: "Français"}
{value: "fr", label: "Français"},
{value: "ja", label: "日本語"},
];
return languages;
}

10
src/components/main/index.js

@ -77,6 +77,14 @@ class Main extends React.Component {
toggleSidebar() {
this.props.dispatch({type: TOGGLE_SIDEBAR, value: !this.props.sidebarOpen});
}
shouldShowMapLegend() {
const showingTree = this.props.panelsToDisplay.includes("tree");
const inGrid = this.props.panelLayout !== "grid";
return !showingTree || inGrid;
}
render() {
if (this.state.showSpinner) {
return (<Spinner/>);
@ -133,7 +141,7 @@ class Main extends React.Component {
}
{this.props.displayNarrative || this.props.showOnlyPanels ? null : <Info width={calcUsableWidth(availableWidth, 1)} />}
{this.props.panelsToDisplay.includes("tree") ? <Tree width={big.width} height={big.height} /> : null}
{this.props.panelsToDisplay.includes("map") ? <Map width={big.width} height={big.height} justGotNewDatasetRenderNewMap={false} /> : null}
{this.props.panelsToDisplay.includes("map") ? <Map width={big.width} height={big.height} justGotNewDatasetRenderNewMap={false} legend={this.shouldShowMapLegend()} /> : null}
{this.props.panelsToDisplay.includes("entropy") ?
(<Suspense fallback={null}>
<Entropy width={chart.width} height={chart.height} />

13
src/components/map/map.js

@ -22,6 +22,8 @@ import { MAP_ANIMATION_PLAY_PAUSE_BUTTON } from "../../actions/types";
// import { incommingMapPNG } from "../download/helperFunctions";
import { timerStart, timerEnd } from "../../util/perf";
import { tabSingle, darkGrey, lightGrey, goColor, pauseColor } from "../../globalStyles";
import ErrorBoundary from "../../util/errorBoundry";
import Legend from "../tree/legend/legend";
import "../../css/mapbox.css";
/* global L */
@ -524,20 +526,20 @@ class Map extends React.Component {
padding: 12,
border: "none",
zIndex: 900,
position: "absolute",
position: "relative",
textTransform: "uppercase"
};
if (this.props.branchLengthsToDisplay !== "divOnly") {
return (
<div>
<div style={{position: "absolute"}}>
<button
style={{...buttonBaseStyle, top: 20, left: 20, width: 60, backgroundColor: this.props.animationPlayPauseButton === "Pause" ? pauseColor : goColor}}
style={{...buttonBaseStyle, top: 20, left: 20, backgroundColor: this.props.animationPlayPauseButton === "Pause" ? pauseColor : goColor}}
onClick={this.playPauseButtonClicked}
>
{this.props.t(this.props.animationPlayPauseButton)}
</button>
<button
style={{...buttonBaseStyle, top: 20, left: 88, width: 60, backgroundColor: lightGrey}}
style={{...buttonBaseStyle, top: 20, left: 30, backgroundColor: lightGrey}}
onClick={this.resetButtonClicked}
>
{this.props.t("Reset")}
@ -651,6 +653,9 @@ class Map extends React.Component {
// clear layers - store all markers in map state https://github.com/Leaflet/Leaflet/issues/3238#issuecomment-77061011
return (
<Card center title={transmissionsExist ? t("Transmissions") : t("Geography")}>
{this.props.legend && <ErrorBoundary>
<Legend right width={this.props.width} />
</ErrorBoundary>}
{this.maybeCreateMapDiv()}
{this.props.narrativeMode ? null : (
<button

83
src/components/narrative/ReactPageScroller.js

@ -20,6 +20,7 @@ const scrollWindowDown = Symbol();
const ANIMATION_TIMER = 200;
const KEY_UP = 38;
const KEY_DOWN = 40;
const SCROLL_THRESHOLD = 25;
export default class ReactPageScroller extends React.Component {
static propTypes = {
@ -47,9 +48,9 @@ export default class ReactPageScroller extends React.Component {
this[wheelScroll] = (event) => {
if (event.deltaY < 0) {
this[scrollWindowUp]();
this[scrollWindowUp](-event.deltaY);
} else {
this[scrollWindowDown]();
this[scrollWindowDown](event.deltaY);
}
};
@ -57,9 +58,9 @@ export default class ReactPageScroller extends React.Component {
this[touchMove] = (event) => {
if (!_.isNull(this[previousTouchMove])) {
if (event.touches[0].clientY > this[previousTouchMove]) {
this[scrollWindowUp]();
this[scrollWindowUp](SCROLL_THRESHOLD);
} else {
this[scrollWindowDown]();
this[scrollWindowDown](SCROLL_THRESHOLD);
}
} else {
this[previousTouchMove] = event.touches[0].clientY;
@ -68,10 +69,10 @@ export default class ReactPageScroller extends React.Component {
this[keyPress] = (event) => {
if (_.isEqual(event.keyCode, KEY_UP)) {
this[scrollWindowUp]();
this[scrollWindowUp](SCROLL_THRESHOLD);
}
if (_.isEqual(event.keyCode, KEY_DOWN)) {
this[scrollWindowDown]();
this[scrollWindowDown](SCROLL_THRESHOLD);
}
};
@ -102,46 +103,52 @@ export default class ReactPageScroller extends React.Component {
this.setState({componentsToRender: [...componentsToRender]});
};
this[scrollWindowUp] = () => {
this[scrollWindowUp] = (amount) => {
if (!_.isNil(this["container_" + (this.state.componentIndex - 1)]) && !this[scrolling]) {
this[scrolling] = true;
this._pageContainer.style.transform = `translate3d(0, ${(this.state.componentIndex - 1) * -100}%, 0)`;
if (this.props.pageOnChange) {
this.props.pageOnChange(this.state.componentIndex);
var element = this["container_" + this.state.componentIndex].getElementsByTagName('div')[0];
if (element.scrollTop === 0 && amount >= SCROLL_THRESHOLD) {
this[scrolling] = true;
this._pageContainer.style.transform = `translate3d(0, ${(this.state.componentIndex - 1) * -100}%, 0)`;
if (this.props.pageOnChange) {
this.props.pageOnChange(this.state.componentIndex);
}
setTimeout(() => {
this.setState((prevState) => ({componentIndex: prevState.componentIndex - 1}), () => {
this[scrolling] = false;
this[previousTouchMove] = null;
});
}, this.props.animationTimer + ANIMATION_TIMER)
}
setTimeout(() => {
this.setState((prevState) => ({componentIndex: prevState.componentIndex - 1}), () => {
this[scrolling] = false;
this[previousTouchMove] = null;
});
}, this.props.animationTimer + ANIMATION_TIMER)
} else if (this.props.scrollUnavailable) {
this.props.scrollUnavailable();
}
};
this[scrollWindowDown] = () => {
this[scrollWindowDown] = (amount) => {
if (!_.isNil(this["container_" + (this.state.componentIndex + 1)]) && !this[scrolling]) {
this[scrolling] = true;
this._pageContainer.style.transform = `translate3d(0, ${(this.state.componentIndex + 1) * -100}%, 0)`;
if (this.props.pageOnChange) {
this.props.pageOnChange(this.state.componentIndex + 2);
var element = this["container_" + this.state.componentIndex].getElementsByTagName('div')[0];
if (element.scrollTop === element.scrollHeight - element.clientHeight && amount >= SCROLL_THRESHOLD) {
this[scrolling] = true;
this._pageContainer.style.transform = `translate3d(0, ${(this.state.componentIndex + 1) * -100}%, 0)`;
if (this.props.pageOnChange) {
this.props.pageOnChange(this.state.componentIndex + 2);
}
setTimeout(() => {
this.setState((prevState) => ({componentIndex: prevState.componentIndex + 1}), () => {
this[scrolling] = false;
this[previousTouchMove] = null;
this[addNextComponent]();
});
}, this.props.animationTimer + ANIMATION_TIMER)
}
setTimeout(() => {
this.setState((prevState) => ({componentIndex: prevState.componentIndex + 1}), () => {
this[scrolling] = false;
this[previousTouchMove] = null;
this[addNextComponent]();
});
}, this.props.animationTimer + ANIMATION_TIMER)
} else if (this.props.scrollUnavailable) {
this.props.scrollUnavailable();
}
@ -165,7 +172,7 @@ export default class ReactPageScroller extends React.Component {
<div
key={this.state.componentIndex}
ref={c => this["container_" + this.state.componentIndex] = c}
style={{height: "100%", width: "100%"}}
style={{height: "calc(100% - 20px)", width: "100%", "padding-bottom": "20px"}}
>
{this.props.children[this.state.componentIndex]}
</div>
@ -174,7 +181,7 @@ export default class ReactPageScroller extends React.Component {
componentsToRender.push(
<div
ref={c => this["container_" + this.state.componentIndex] = c}
style={{height: "100%", width: "100%"}}
style={{height: "calc(100% - 20px)", width: "100%", "padding-bottom": "20px"}}
>
{this.props.children}
</div>
@ -217,7 +224,7 @@ export default class ReactPageScroller extends React.Component {
componentsToRender.push(
<div key={number + 1}
ref={c => this["container_" + (number + 1)] = c}
style={{height: "100%", width: "100%"}}>
style={{height: "calc(100% - 20px)", width: "100%", "padding-bottom": "20px"}}>
{children[number + 1]}
</div>
);
@ -237,7 +244,7 @@ export default class ReactPageScroller extends React.Component {
componentsToRender.push(
<div key={i}
ref={c => this["container_" + i] = c}
style={{height: "100%", width: "100%"}}>
style={{height: "calc(100% - 20px)", width: "100%", "padding-bottom": "20px"}}>
{children[i]}
</div>
);
@ -247,7 +254,7 @@ export default class ReactPageScroller extends React.Component {
componentsToRender.push(
<div key={number + 1}
ref={c => this["container_" + (number + 1)] = c}
style={{height: "100%", width: "100%"}}>
style={{height: "calc(100% - 20px)", width: "100%", "padding-bottom": "20px"}}>
{children[number + 1]}
</div>
);

6
src/components/narrative/index.js

@ -22,8 +22,7 @@ const progressHeight = 25;
const explanationParagraph=`
<p class="explanation">
Explore the content by scrolling the left hand side (or click on the arrows), and the data visualizations will change accordingly.
Clicking "explore the data yourself" (top right of page) will replace this narrative with a set of controls so that you may interact with the data.
Explore the narrative by scrolling on the left panel, or click "explore the data yourself" in the top right to interact with the data.
</p>
`;
@ -152,7 +151,8 @@ class Narrative extends React.Component {
style={{
padding: "10px 20px",
height: "inherit",
overflow: "hidden"
overflowX: "hidden",
overflowY: "auto"
}}
dangerouslySetInnerHTML={{__html}}
/>

6
src/components/tree/infoPanels/hover.js

@ -160,7 +160,9 @@ const Mutations = ({node, t}) => {
const nucLen = nucs.length; // number of mutations that exist without N/-
let m = nucs.slice(0, Math.min(nDisplay, nucLen)).join(", ");
m += nucLen > nDisplay ? " + " + (nucLen - nDisplay) + " " + t("more") : "";
if (nucLen > nDisplay) {
m += " + " + t("{{x}} more", {x: nucLen - nDisplay});
}
if (nucLen !== 0) {
elements.push(<InfoLine name={t("Nucleotide mutations")+":"} value={m} key="nuc"/>);
@ -201,7 +203,7 @@ const Mutations = ({node, t}) => {
if (idx < nProtsToDisplay) {
let x = prot + ":\u00A0\u00A0" + mutationsToDisplay[prot].slice(0, Math.min(nDisplay, mutationsToDisplay[prot].length)).join(", ");
if (mutationsToDisplay[prot].length > nDisplay) {
x += " + " + (mutationsToDisplay[prot].length - nDisplay) + " " + t("more");
x += " + " + t("{{x}} more", {x: mutationsToDisplay[prot].length - nDisplay});
}
mutationsToRender.push(x);
} else if (idx === nProtsToDisplay) {

61
src/components/tree/legend/legend.js

@ -8,6 +8,13 @@ import { numericToCalendar } from "../../../util/dateHelpers";
import { isColorByGenotype, decodeColorByGenotype } from "../../../util/getGenotype";
import { TOGGLE_LEGEND } from "../../../actions/types";
const svg = {
position: "absolute",
top: 26,
borderRadius: 4,
zIndex: 1000,
userSelect: "none"
};
@connect((state) => {
return {
@ -69,9 +76,14 @@ class Legend extends React.Component {
return this.props.colorings[this.props.colorBy] === undefined ?
"" : this.props.colorings[this.props.colorBy].title;
}
getTitleWidth() {
// This is a hack because we can't use getBBox in React.
// Lots of work to get measured width of DOM element.
// Works fine, but will need adjusting if title font is changed.
return 15 + 5.3 * this.getTitleString().length;
}
toggleLegend() {
this.props.dispatch({type: TOGGLE_LEGEND, value: !this.props.legendOpen});
}
@ -85,7 +97,7 @@ class Legend extends React.Component {
<g id="Title">
<rect width={this.getTitleWidth()} height="12" fill="rgba(255,255,255,.85)"/>
<text
x={5}
x={this.getTitleOffset()}
y={10}
style={{
fontSize: 12,
@ -106,10 +118,8 @@ class Legend extends React.Component {
*/
legendChevron() {
const degrees = this.showLegend() ? -180 : 0;
// This is a hack because we can't use getBBox in React.
// Lots of work to get measured width of DOM element.
// Works fine, but will need adjusting if title font is changed.
const offset = this.getTitleWidth();
const offset = this.getArrowOffset();
return (
<g id="Chevron" transform={`translate(${offset},0)`}>
<svg width="12" height="12" viewBox="0 0 1792 1792">
@ -187,32 +197,53 @@ class Legend extends React.Component {
getStyles() {
return {
svg: {
position: "absolute",
left: 5,
top: 26,
borderRadius: 4,
zIndex: 1000,
userSelect: "none"
svgLeft: {
...svg,
left: 5
},
svgRight: {
...svg,
right: 5
}
};
}
getSVGStyle() {
const styles = this.getStyles();
if (this.props.right) { return styles.svgRight; }
return styles.svgLeft;
}
getArrowOffset() {
if (this.props.right) {
return this.getSVGWidth() - 20;
}
return this.getTitleWidth();
}
getTitleOffset() {
if (this.props.right) {
return this.getSVGWidth() - this.getTitleWidth() - 15;
}
return 5;
}
render() {
// catch the case where we try to render before anythings ready
if (!this.props.colorScale) return null;
const styles = this.getStyles();
return (
<svg
id="TreeLegendContainer"
width={this.getSVGWidth()}
height={this.getSVGHeight()}
style={styles.svg}
style={this.getSVGStyle()}
>
{this.legendItems()}
<g
id="TitleAndChevron"
onClick={() => this.toggleLegend()}
style={{cursor: "pointer"}}
style={{cursor: "pointer", textAlign: "right" }}
>
{this.legendTitle()}
{this.legendChevron()}

6
src/components/tree/phyloTree/change.js

@ -193,7 +193,7 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra
/* background temporal time slice */
if (extras.timeSliceHasPotentiallyChanged) {
this.addTemporalSlice();
this.showTemporalSlice();
}
/* branch labels */
@ -222,7 +222,7 @@ export const modifySVGInStages = function modifySVGInStages(elemsToUpdate, svgPr
this.drawTips();
this.updateTipLabels();
if (this.vaccines) this.drawVaccines();
this.addTemporalSlice();
this.showTemporalSlice();
if (this.layout === "clock" && this.distance === "num_date") this.drawRegression();
if (elemsToUpdate.has(".branchLabel")) this.drawBranchLabels(this.params.branchLabelKey);
};
@ -244,7 +244,7 @@ export const modifySVGInStages = function modifySVGInStages(elemsToUpdate, svgPr
.remove()
.on("start", () => inProgress++)
.on("end", step2);
this.removeTemporalSlice();
this.hideTemporalSlice();
if (!transitionTimeFadeOut) timerFlush();
};

104
src/components/tree/phyloTree/grid.js

@ -1,7 +1,9 @@
/* eslint-disable space-infix-ops */
import { min, max } from "d3-array";
import { transition } from "d3-transition";
import { easeLinear } from "d3-ease";
import { timerStart, timerEnd } from "../../../util/perf";
import { months } from "../../../util/globals";
import { months, animationInterpolationDuration } from "../../../util/globals";
import { numericToCalendar } from "../../../util/dateHelpers";
export const hideGrid = function hideGrid() {
@ -22,6 +24,12 @@ export const hideGrid = function hideGrid() {
const addSVGGroupsIfNeeded = (groups, svg) => {
if (!("temporalWindow" in groups)) {
groups.temporalWindow = svg.append("g").attr("id", "temporalWindow");
// Technically rects aren't groups, but store them to avoid searching for them on each "showTemporalSlice" render.
groups.temporalWindowStart = groups.temporalWindow.append('rect')
.attr('class', 'temporalWindowStart');
groups.temporalWindowEnd = groups.temporalWindow.append('rect')
.attr('class', 'temporalWindowEnd');
}
if (!("majorGrid" in groups)) {
groups.majorGrid = svg.append("g").attr("id", "majorGrid");
@ -380,17 +388,24 @@ export const addGrid = function addGrid() {
timerEnd("addGrid");
};
export const removeTemporalSlice = function removeTemporalSlice() {
this.groups.temporalWindow.selectAll("*").remove();
export const hideTemporalSlice = function hideTemporalSlice() {
this.groups.temporalWindowStart.attr('opacity', 0);
this.groups.temporalWindowEnd.attr('opacity', 0);
};
// d3-transition to ensure both rectangles move at the same rate
export const temporalWindowTransition = transition('temporalWindowTransition')
.duration(animationInterpolationDuration)
.ease(easeLinear); // the underlying animation uses linear interpolation, let's override the default easeCubic
/**
* add background grey rectangles to demarcate the temporal slice
*/
export const addTemporalSlice = function addTemporalSlice() {
this.removeTemporalSlice();
if (this.layout !== "rect" || this.distance !== "num_date") return;
export const showTemporalSlice = function showTemporalSlice() {
if (this.layout !== "rect" || this.distance !== "num_date") {
this.hideTemporalSlice();
return;
}
const xWindow = [this.xScale(this.dateRange[0]), this.xScale(this.dateRange[1])];
const height = this.yScale.range()[1];
@ -403,25 +418,72 @@ export const addTemporalSlice = function addTemporalSlice() {
/* the gray region between the root (ish) and the minimum date */
if (Math.abs(xWindow[0]-rootXPos) > minPxThreshold) { /* don't render anything less than this num of px */
this.groups.temporalWindow.append("rect")
.attr("x", rightHandTree ? xWindow[0] : 0)
.attr("width", rightHandTree ? totalWidth-xWindow[0]: xWindow[0])
.attr("y", 0)
let width_startRegion = xWindow[0];
let translateX_startRegion = 0;
// With right hand tree, the coordinate system flips (right to left)
if (rightHandTree) {
width_startRegion = totalWidth - xWindow[0];
translateX_startRegion = xWindow[0];
}
const wasStartRegionVisible = this.groups.temporalWindowStart.attr('opacity') === '1';
this.groups.temporalWindowStart
.attr('opacity', 1)
.attr("height", height)
.attr("transform", `translate(${translateX_startRegion},0)`)
.attr("fill", fill);
// Only apply animation if rectangle was already visible in the previous frame.
if (wasStartRegionVisible) {
this.groups.temporalWindowStart.transition('temporalWindowTransition')
.attr("width", width_startRegion);
} else {
this.groups.temporalWindowStart
.attr("width", width_startRegion);
}
} else {
this.groups.temporalWindowStart.attr('opacity', 0);
}
/* the gray region between the maximum selected date and the last tip */
const startingX = rightHandTree ? this.params.margins.right : xWindow[1];
const rectWidth = rightHandTree ?
xWindow[1]-this.params.margins.right :
totalWidth-this.params.margins.right-xWindow[1];
if (rectWidth > minPxThreshold) {
this.groups.temporalWindow.append("rect")
.attr("x", startingX)
.attr("width", rectWidth)
.attr("y", 0)
let xStart_endRegion = xWindow[1]; // starting X coordinate of the "end" rectangle
let width_endRegion = totalWidth - this.params.margins.right - xWindow[1];
let transform_endRegion = `translate(${totalWidth - this.params.margins.right},0) scale(-1,1)`;
// With a right hand tree, the coordinate system flips (right to left)
if (rightHandTree) {
xStart_endRegion = this.params.margins.right;
width_endRegion = xWindow[1] - this.params.margins.right;
transform_endRegion = `translate(${xStart_endRegion},0)`;
}
if (width_endRegion > minPxThreshold) {
const wasEndRegionVisible = this.groups.temporalWindowEnd.attr('opacity') === '1';
this.groups.temporalWindowEnd
.attr('opacity', 1)
.attr("height", height)
.attr("fill", fill);
.attr("fill", fill)
.attr("transform", transform_endRegion);
// Only apply animation if rectangle was already visible in the previous frame.
// Unlike the startingRegion, this panel cannot depend
// on letting the SVG boundaries clip part of the rectangle.
// As a result, we'll have to animate width instead of position
// If performance becomes an issue, try add a custom clip-path with
// a fixed-width region instead.
if (wasEndRegionVisible) {
this.groups.temporalWindowEnd
.transition('temporalWindowTransition')
.attr("width", width_endRegion);
} else {
this.groups.temporalWindowEnd
.attr("width", width_endRegion);
}
} else {
this.groups.temporalWindowEnd.attr('opacity', 0);
}
};

20
src/components/tree/phyloTree/labels.js

@ -11,24 +11,28 @@ export const updateTipLabels = function updateTipLabels(dt) {
const tLFunc = this.callbacks.tipLabel;
const xPad = this.params.tipLabelPadX;
const yPad = this.params.tipLabelPadY;
const inViewTerminalNodes = this.nodes
const inViewTips = this.nodes
.filter((d) => d.terminal)
.filter((d) => d.inView);
// console.log(`there are ${inViewTerminalNodes.length} nodes in view`)
if (inViewTerminalNodes.length < this.params.tipLabelBreakL1) {
const inViewVisibleTips = inViewTips.filter((d) => d.visibility === NODE_VISIBLE);
/* We show tip labels by checking the number of "inView & visible" tips */
if (inViewVisibleTips.length < this.params.tipLabelBreakL1) {
/* We calculate font size based on the total number of in view tips (both visible & non-visible) */
let fontSize = this.params.tipLabelFontSizeL1;
if (inViewTerminalNodes.length < this.params.tipLabelBreakL2) {
fontSize = this.params.tipLabelFontSizeL2;
}
if (inViewTerminalNodes.length < this.params.tipLabelBreakL3) {
if (inViewTips.length < this.params.tipLabelBreakL3) {
fontSize = this.params.tipLabelFontSizeL3;
} else if (inViewTips.length < this.params.tipLabelBreakL2) {
fontSize = this.params.tipLabelFontSizeL2;
}
window.setTimeout(() => {
this.groups.tipLabels
.selectAll('.tipLabel')
.data(inViewTerminalNodes)
.data(inViewVisibleTips)
.enter()
.append("text")
.attr("x", (d) => d.xTip + xPad)

4
src/components/tree/phyloTree/phyloTree.js

@ -90,7 +90,7 @@ PhyloTree.prototype.removeTipLabels = labels.removeTipLabels;
/* G R I D */
PhyloTree.prototype.hideGrid = grid.hideGrid;
PhyloTree.prototype.addGrid = grid.addGrid;
PhyloTree.prototype.addTemporalSlice = grid.addTemporalSlice;
PhyloTree.prototype.removeTemporalSlice = grid.removeTemporalSlice;
PhyloTree.prototype.showTemporalSlice = grid.showTemporalSlice;
PhyloTree.prototype.hideTemporalSlice = grid.hideTemporalSlice;
export default PhyloTree;

2
src/components/tree/phyloTree/renderers.js

@ -44,7 +44,7 @@ export const render = function render(svg, layout, distance, parameters, callbac
/* draw functions */
if (this.params.showGrid) {
this.addGrid();
this.addTemporalSlice();
this.showTemporalSlice();
}
this.drawBranches();
this.drawTips();

2
src/index.js

@ -9,6 +9,7 @@ import { Provider } from "react-redux";
/* A U S P I C E I M P O R T S */
import configureStore from "./store";
import { initialiseGoogleAnalyticsIfRequired } from "./util/googleAnalytics";
import Root from "./root";
/* S T Y L E S H E E T S */
import "font-awesome/css/font-awesome.css";
import "leaflet/dist/leaflet.css";
@ -35,7 +36,6 @@ initialiseGoogleAnalyticsIfRequired();
/* Using React Hot Loader 4 https://github.com/gaearon/react-hot-loader */
const renderApp = () => {
const Root = require("./root").default; // eslint-disable-line global-require
ReactDOM.render(
<Provider store={store}>
<Root />

2
src/locales/de/translation.json

@ -61,7 +61,7 @@
"AA mutations": "AA-Mutationen",
"protein mutations truncated": "Protein-Mutationen entfernt",
"Gaps": "Lücken",
"more": "mehr",
"{{x}} more": "{{x}} mehr",
"No nucleotide mutations": "Keine Nukleotid-Mutationen",
"No amino acid mutations": "Keine Aminosäure-Mutationen",
"Divergence": "Divergenz",

2
src/locales/en/translation.json

@ -62,7 +62,7 @@
"AA mutations": "AA mutations",
"protein mutations truncated": "protein mutations truncated",
"Gaps": "Gaps",
"more": "more",
"{{x}} more": "{{x}} more",
"No nucleotide mutations": "No nucleotide mutations",
"No amino acid mutations": "No amino acid mutations",
"Divergence": "Divergence",

2
src/locales/es/translation.json

@ -59,7 +59,7 @@
"AA mutations": "Mutaciones de AA",
"protein mutations truncated": "mutaciones proteicas truncadas",
"Gaps": "Huecos",
"more": "mas",
"{{x}} more": "{{x}} mas",
"No nucleotide mutations": "No mutaciones de nucleótidos",
"No amino acid mutations": "No mutaciones de aminoácidos",
"Divergence": "Divergencia",

2
src/locales/fr/translation.json

@ -62,7 +62,7 @@
"AA mutations": "Mutations de AA",
"protein mutations truncated": "mutations protéiques tronquées",
"Gaps": "Lacunes",
"more": "plus",
"{{x}} more": "{{x}} plus",
"No nucleotide mutations": "Aucune mutation nucléotidique",
"No amino acid mutations": "Aucune mutation d'acides aminés",
"Divergence": "Divergence",

3
src/locales/ja/language.json

@ -0,0 +1,3 @@
{
"name": "日本語"
}

33
src/locales/ja/sidebar.json

@ -0,0 +1,33 @@
{
"Dataset": "データセット",
"Date Range": "データ範囲",
"Color By": "色分け",
"Tree Options": "ツリーのオプション",
"Layout": "レイアウト",
"rectangular": "矩形",
"radial": "放射状",
"unrooted": "無根",
"clock": "時系列",
"Branch Length": "枝の長さ",
"time": "時間",
"divergence": "分岐",
"Show confidence intervals": "信頼区間を表示",
"Branch Labels": "枝のラベル",
"Search Strains": "系統を検索",
"Second Tree": "第二ツリー",
"Map Options": "地図のオプション",
"Geographic resolution": "地域分けの単位",
"Animation Speed": "アニメーション速度",
"Loop animation": "アニメーションを繰り返し",
"Animate cumulative history": "累積の履歴をアニメーション",
"Panel Options": "パネルのオプション",
"Show tree": "ツリーを表示",
"Show map": "地図を表示",
"Show entropy": "エントロピーを表示",
"Language": "言語",
"Slow": "遅い",
"Medium": "普通",
"Fast": "速い",
"full": "完全",
"grid": "グリッド"
}

91
src/locales/ja/translation.json

@ -0,0 +1,91 @@
{
"__Header//Byline__": "########################################",
"Maintained by": "管理者",
"using data from": "使用データ",
"Built with": "使用ビルド",
"__Header//Info__": "########################################",
"Showing {{x}} of {{y}} genomes": "{{y}} 中 {{x}} のゲノムを表示中",
"Showing {{x}} of {{y}} genomes sampled between {{from}} and {{to}}": "{{y}} 中 {{x}} の {{from}} から {{to}} にサンプルされたゲノムを表示中",
"Comprising": "次を含む",
"Animation in progress": "アニメーション中",
"Filtered to": "フィルタ条件",
"__Download Modal__": "########################################",
"click outside this box to return to the app": "元の画面に戻るにはこのポップアップの外側をクリック",
"last updated": "最終更新",
"A full list of sequence authors is available via the TSV files below": "シーケンス作成者の完全な一覧は、TSVファイルとしてもダウンロードできます",
"Data usage policy": "データ利用ポリシー",
"Please cite the authors who contributed genomic data (where relevant), as well as": "次の貢献者と、(該当する場合)遺伝子データを貢献した作者を明記すること",
"Download data": "データをダウンロード",
"Data usage part 1": "このデータは、重要な病原体の分析結果を迅速に広めるために提供されている。出版されていないデータが生成者の許可のもと含まれているが、このことは生成者の出版権利に影響しない。",
"Data usage part 2": "もしダウンロードしたデータをもとにさらに研究を行う予定であれば、(TSVファイルに含まれる)該当する作者に連絡のこと。系統発生などの派生したデータは下記からダウンロード可能 - 必要であれば該当する作者に連絡のこと。",
"__Entropy Panel__": "########################################",
"Diversity": "多様性",
"entropy": "エントロピー",
"events": "イベント",
"Codon {{codon}} in protein {{protein}}": "タンパク質 {{protein}} に含まれるコドン {{codon}}",
"Nucleotide {{nuc}}": "ヌクレオチド {{nuc}}",
"Nuc positions {{a}} to {{b}}": "ヌクレオチド位置 {{a}} から {{b}}",
"Num mutations": "変異数",
"Negative strand": "負のらせん",
"Positive strand": "正のらせん",
"Click to color tree & map": "クリックしてツリーと地図を色分けする",
"__Footer__": "########################################",
"Filter by {{filterTitle}}": "{{filterTitle}} でフィルタ",
"Data updated": "データ更新",
"__Map panel__": "########################################",
"Transmissions": "伝搬",
"Geography": "地理",
"reset zoom": "標準倍率",
"Reset": "リセット",
"Play": "再生",
"Pause": "一時停止",
"__Tree (Phylogeny) panel__": "########################################",
"Phylogeny": "発生系統",
"Reset Layout": "レイアウトをリセット",
"Click on tip to display more info": "詳細を表示するには吹き出しをクリック",
"Click to zoom into clade": "クリックして分岐群へズーム",
"Click to zoom out to parent clade": "親の分岐群にズームアウトするにはクリック",
"Branch leading to": "枝分かれ先",
"Number of descendants": "子孫数",
"Nucleotide mutations": "ヌクレオチド変異",
"AA mutations": "AA 変異",
"protein mutations truncated": "タンパク質変異は省略された",
"Gaps": "間隔",
"{{x}} more": "他 {{x}}",
"No nucleotide mutations": "ヌクレオチド変異なし",
"No amino acid mutations": "アミノ酸変異なし",
"Divergence": "分岐",
"Date": "日付",
"Collection date": "収集日",
"Inferred collection date": "推定収集日",
"Inferred Date": "推定日",
"Date Confidence Interval": "日付の信頼区間",
"Vaccine selected": "選択されたワクチン",
"Vaccine start date": "ワクチン開始日",
"Vaccine end date": "ワクチン終了日",
"Serum strain": "血清株",
"Click outside this box to go back to the tree": "ツリーに戻るにはこのボックスの外側をクリック",
"Authors": "作成者",
"Title": "タイトル",
"Journal": "ジャーナル",
"__Frequencies panel__": "########################################",
"Frequencies": "頻度",
"colored by": "色分け条件",
"Projection": "射影",
"Time point": "基準時間",
"Frequency": "頻度",
"Projected frequency": "射影された頻度"
}

2
src/locales/lt/translation.json

@ -62,7 +62,7 @@
"AA mutations": "Amino rūgščių mutacijos",
"protein mutations truncated": "baltymo mutacijos sutrumpintos",
"Gaps": "Tarpai",
"more": "daugiau",
"{{x}} more": "{{x}} daugiau",
"No nucleotide mutations": "Nėra nukelotidų mutacijų",
"No amino acid mutations": "Nėra amino rūgščių mutacijų",
"Divergence": "Skirtumas",

2
src/locales/pt/translation.json

@ -62,7 +62,7 @@
"AA mutations": "Mutações de AA",