npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

archaeopteryx

v3.28.0

Published

Archaeopteryx.js is a software tool for the visualization and analysis of highly annotated phylogenetic trees.

Readme

Archaeopteryx.js

Archaeopteryx.js is a software tool for the visualization and analysis of highly annotated phylogenetic trees.

npm

https://www.npmjs.com/package/archaeopteryx

GitHub

https://github.com/cmzmasek/archaeopteryx-js

Live demos

Self-contained demos, served from this repository (no external dependencies) — they run entirely in your browser:

https://cmzmasek.github.io/archaeopteryx-js/

Try your own tree: cmzmasek.github.io/archaeopteryx-js/open.html — paste or open a Newick / NHX / Nexus / phyloXML / Auspice JSON file and Archaeopteryx.js will visualize it. The tree is read locally in your browser; nothing is uploaded. Its Expert options panel exercises every launch config key live and shows the exact config JSON to copy into your own launch() call.

Detailed developer documentation

To be written. For now, the For Developers section below covers the entry points, configuration, and the visualization system.

Dependencies

Archaeopteryx.js has the following dependencies:

  • forester.js: https://www.npmjs.com/package/archaeopteryx
  • phyloxml.js: https://www.npmjs.com/package/phyloxml
  • d3.js (version 7): https://www.npmjs.com/package/d3
  • sax.js (1.6.1): https://www.npmjs.com/package/sax/v/1.6.1

For raster PNG export (optional — the PNG entry, the Copy PNG button and the PNG-scale row appear only when window.Canvg is present):

  • canvg (4.x): https://www.npmjs.com/package/canvg. Load it before launch() is called: the Download menu looks for it at click time, but the Copy PNG button and the PNG-scale row are built with the control panel at launch, so a page that provides it later gets a PNG entry and nothing else. canvg publishes ES modules only (no classic-script global build); this repository ships two ready-made files:
    • docs/lib/canvg.global.js — a classic-script build for a plain <script src="canvg.global.js"></script>, or for one more file in a site's concatenated bundle; it defines window.Canvg itself. This is what the demo pages use. (Generated from canvg.js by test/make_canvg_global.js; a unit test keeps the two in step.)
    • docs/lib/canvg.js — the self-contained ES module build (npm's [email protected] bundled into one file via esm.sh/canvg@4?bundle, with its one unnecessary Node-environment shim import removed — see the file's own header), for a page that prefers a module bridge of its own:
      <script type="module">
          import {Canvg} from 'http://path/to/canvg.js';
          window.Canvg = Canvg;
      </script>
      A module script runs after the document is parsed, so a page that calls launch() from an inline script during parsing must use the classic build instead.

For vector PDF export (optional — the PDF entry appears in the Download menu only when both are loaded before archaeopteryx.js):

  • jspdf (4.x): https://www.npmjs.com/package/jspdf
  • svg2pdf.js (2.8.x): https://www.npmjs.com/package/svg2pdf.js

File (Newick/New Hampshire, Nexus, phyloXML, FASTA) and SVG download, as well as saving the exported PNG, use native browser APIs (Blob, canvas.toBlob(), and an <a download> link), so Blob.js, canvas-toBlob.js and FileSaver.js are no longer required.

The user interface (control panel, sliders, dialogs) is built with native DOM elements, so jQuery and jQuery UI are no longer required — neither their JavaScript nor jQuery UI's CSS.

Basic Example of HTML for launching Archaeopteryx.js

Example of HTML page to launch a basic Archaeopteryx.js instance:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8">
   <title>Archaeopteryx.js Basic Demo</title>

   <!-- D3.js (version 7): -->
   <script src="https://d3js.org/d3.v7.min.js"></script>

   <!-- SAX XML parser (needed by phyloxml.js): -->
   <script src="https://path/to/sax.js"></script>

   <!-- Archaeopteryx.js requires forester.js and phyloxml.js: -->
   <script src="https://path/to/phyloxml.js"></script>
   <script src="https://path/to/forester.js"></script>
   <script src="https://path/to/archaeopteryx.js"></script>
</head>

<body>
<div>
   <h2>Archaeopteryx.js Basic Demo</h2>
   <div id="phylogram1"></div>
</div>

<script>
    window.addEventListener('load', async () => {
        const loc = 'https://path/to/apaf.xml';
        const config = {};  // see "For Developers" below for what can go here

        try {
            const response = await fetch(loc);
            if (!response.ok) {
                throw new Error('HTTP ' + response.status + ' loading ' + loc);
            }
            const data = await response.text();

            // launchArchaeopteryx returns a viewer handle:
            //   viewer.getSelectedNodes()  -- the user's node selection
            //   viewer.destroy()           -- unmount the viewer completely
            //                                 (call it when your view goes away;
            //                                  a later launch works normally)
            const viewer = archaeopteryx.launchArchaeopteryx('#phylogram1', loc, data, config);
        } catch (e) {
            document.getElementById('phylogram1').textContent = 'Error: ' + e.message;
        }
    });
</script>
</body>
</html>

Archaeopteryx.js reports problems by throwing — a failed download, an unparsable tree, a container that does not resolve, and an unknown or removed config key all land in the catch above with a message naming the problem. See For Developers below for what goes into config. Working copies of every file referenced above are vendored in this repository's docs/lib/ (they are what the live demos load), and docs/demo.html is a real, running version of this page — including using viewer.destroy() to switch trees in place.

Using the visualizations

A tree opens already coloured by its most informative field — the viewer inspects what the tree carries (taxonomy, sequence fields, metadata) and decides by itself what is worth showing. There is nothing to configure.

  • The Color and Shape menus (top of the control panel) list every field worth visualizing, best first. Colouring paints the node label and the node itself; shapes draw a symbol per value. Fields with many values (hosts, species) sit at the end of the Color list — they are offered, but never chosen for you.
  • Numeric fields with few values are treated as codes (H5N1 vs H5N2) and get individual colours; with many values they become a gradient (years). The legend's [colors] / [gradient] chip switches between the two where both make sense.
  • The legend is a card you can drag anywhere. It shows a colour and a count per value, [by count] / [A-Z] toggles the order, a dashed no value row counts the nodes the field does not cover (a value that is nothing but underscores or a ;/: qualifier counts as no value), and very long legends show the top 20 with a [+N more] chip. Legends are part of PNG, PDF and SVG exports (exports always come out light).
  • Switch into a subtree and the legend re-describes what is on screen — rows, counts, the no value row, and a numeric field's colours-or-gradient band — while the menus and your Color / Shape choice stand exactly as they were: colouring by Genus and entering a one-genus clade shows a one-row legend, not a grey tree. A category value keeps its colour for the whole session; a gradient re-spans the values on screen. Deleting part of the tree re-derives the menus from what is left, and keeps your choice as long as its field still has a value somewhere.
  • The Visualizations checkbox hides the chosen colours/shapes; the Visual Styles checkbox controls colours embedded in the tree file itself (and phyloXML branch colours). Search hits and selections always outrank visualization colours. Esc returns to the opening state.

Layouts

Three layouts, switched with the second row of buttons at the top of the panel: rectangular (root at left), circular, and unrooted — the desktop's equal-angle fan, where each subtree opens a wedge proportional to how many tips it holds.

In the rectangular layouts a rooted tree shows a short stub branch into its root; a tree that declares itself unrooted (phyloXML rooted="false", a Nexus [&U] tree) shows none, and the circular layout never draws one. A subtree view always shows the stub, since a clade has a definite root, whatever the branch above it was. When the whole tree is a phylogram and the file gives the root a branch length, that branch is drawn to scale instead of the stub — except in the unrooted display, which draws no root branch of any kind, real or stub: there the root is a point of the fan, not the end of a branch from nowhere. The root itself wears a circle only for the reasons any node does — an event, a search hit, a selection, a visualization.

Collapsing a clade. The node menu's Collapse/Uncollapse folds an internal node's whole clade into a wedge and opens it again; Uncollapse Subtree opens everything below a node; the tool row's uncollapse-all button (the desktop's glyph, lit only while something is collapsed) opens the whole tree, and so does Esc. The wedge has its apex at the node, one edge reaching the clade's nearest tip and the other its farthest, so the shape shows how uneven the clade's branch lengths are (one depth step in a cladogram). Its label stands where a tip's would: on the label column in the aligned phylogram and on the outer ring in circular, with the same guide line; it is filled in the colour most of its tips wear under the current Color-by (the colour they wore, even when no tip on screen shares their value), grows gently taller with its tip count, and is named — the node's own name if it has one; else the one Color-by value nearly all its tips share, so a clade reads "Bovine · 12 tips" while you look at hosts; else the tips' common name prefix; always with the tip count, and with [found/total] while a search hits inside it. Legends, alignment rows and domain tracks describe the tips on screen, so a collapsed clade's tips leave them, and the counts follow every clade you fold or open. A clade holding search hits is one dot in the overview and one stop for the hit navigator; one holding selected tips is outlined in the selection colour, and filled when all its tips are selected. Re-rooting opens any collapsed clade whose tips it would change, such as one the new midpoint falls inside. Collapsing is display state only: nothing is removed, exports and downloads carry every tip, and the unrooted layout, which has no rows to fold, shows every clade open. The controls are the desktop's; the drawing and naming are this program's.

A phylogram carries a scale bar at the bottom left: a round number of branch-length units (1, 2 or 5 × 10ᵏ, whichever makes the bar about 100 px) with its length written above it, followed by the unit the tree states (phyloXML's branch_length_unit, e.g. 0.05 subs/site) and by nothing when it states none: a tree that does not say what its lengths measure gets no guess. It is drawn with the tree, so it zooms and exports with it and its label always holds. A cladogram has nothing to measure and shows none, and a tree under a scale axis leaves the measuring to the axis. Scale under Display Data > Options hides it (on by default; config showScaleBar, view key scaleBar; the desktop's switch has the same name and defaults to off).

Scale Axis under Display Data (off by default, as on the desktop) draws a labelled ruler under a phylogram, from 0 at the root out to the deepest tip, in the tree's branch-length units, with the unit at its end when the tree states one. Scale Grid adds a faint line at every tick, behind the tree. In the circular layout the ruler runs out from the root along the gap between the last tip and the first, and the grid lines become rings; the unrooted layout has no axis. The ruler floats at the bottom of the window as the tree scrolls, and it starts at a subtree view's own root. On a time tree the same switch draws the time axis instead (below): a tree flipped to Div gets the distance ruler, and one flipped back gets its time axis.

In the two radial layouts the zoom row changes meaning, exactly as on the desktop: Y+ / Y− become the plain + / − zoom (a circle has one diameter; the mouse wheel zooms too, and never rotates), X− / X+ become rotate (a 32nd of a turn per press), and the fit-width slot becomes the node label direction flip — labels riding their spokes or standing upright — while vertical expansion greys out. Fit centres and scales the fan; Esc also resets rotation and label direction. Each radial layout greys out the display type it cannot show. Circular always aligns — its external labels are pulled out to a shared ring, with dashed connectors to match, whichever type is chosen — so the circular phylogram is the aligned one, and the greyed button there is the unaligned phylogram. Unrooted is the other way round: it has no common edge to align to, so the aligned type is the one greyed. The button shown as chosen describes the picture, and choosing the phylogram in circular leaves your aligned-or-not preference for the other layouts untouched. Auto-hide Labels stays live in every layout.

The control-panel cheat sheet

The card button in the panel header opens Control panel: one row for every control the panel is currently showing, in the order it shows them, each with the control's own glyph or name and the sentence that explains it — the layout and display-type buttons included, drawn with the very glyph they carry in the panel. The About box has a row for it too, and one for Diagnostics.

The display types say what they do and when they are greyed, whether or not they are greyed in the view you are looking at: "phylogram: branch lengths drawn to scale, so the tips end ragged. Greyed in the circular layout, which always carries its labels to the outer ring, so there the aligned phylogram is the one drawn."

It is modeless and sits beside the panel, so a row can be read while the control it names is used. It describes what is on the screen: fold a section and its controls leave the sheet, open a tree that offers no alignment and the alignment controls are not listed. Nothing on it is written twice — a row's words are the tooltip the control already carries, so a control added later needs no edit here, only a tooltip. And no row shows a live value, so a sheet that is saved or printed does not go wrong for every reader but the one who made it.

Rooting

The tool row's re-root button asks how: MAD re-root (Tria et al., 2017) or Midpoint re-root (hover over the MAD entry for the full citation). A node's menu offers Reroot on the branch above that node. None of them is offered for a tree whose phyloXML says rerootable="false", nor for a time tree, one whose internal nodes are mostly dated (BEAST node heights, Nextstrain dates, phyloXML <date>s): a new root would contradict the dates. For such a tree the re-root button and the node menu's Reroot are greyed, their tooltip saying why, and a shared view's root is ignored.

A re-root can take the meaning away from data on internal nodes: a node's name, taxonomy, sequence, events, distribution, date, references or node properties describe its clade, and a new root changes the clade of every node between the old root and the new one. So before re-rooting (from the button's menu or the node menu), the change is worked out on a copy of the tree, and when it would change the clade of any internal node carrying data, a warning says how many (“This tree has data on 15 internal nodes. Re-rooting changes the clade of 3 of them, so their data may no longer describe them.”) with Re-root and Cancel. Branch lengths, support and MAD values, branch colours and style: properties do not count: they belong to the branch, or to the look.

A tree its file declares unrooted (phyloXML rooted="false", Nexus [&U]), shown in the unrooted layout, has no root to measure from. There the hover card and Display Node Data show an internal node's Tips around — the tips on each of its sides, smallest first, such as 2 · 3 · 5 — instead of distance to parent, depth and tips below; a tip shows its Branch length and no depth; and the Depth from Root, Distance from Root and Clade Size search fields are not offered. The same tree in the rectangular or circular layout keeps all of them, since those layouts draw a root.

MAD rooting (minimal ancestor deviation) roots the tree without assuming a clock. The common ancestor of two tips ought to lie halfway between them, so every branch and position is scored by how far the tip pairs' ancestors fall from that halfway point, and the root goes where that deviation is smallest [1]. It has been compared with other rooting methods on prokaryotic gene families [2]. It needs branch lengths and at least three tips, and is offered only then. Tip pairs closer than 1/100,000 of the tree's diameter (identical sequences, or FastTree's 5 × 10⁻⁹ stand-in for a zero branch) are left out of the deviation sums: their deviation measures noise, and summing it would swamp the precision of all the others. The algorithm is the desktop Archaeopteryx's, and gives the same roots; it runs in O(n²) time and O(n) memory (the 13,246-tip H5N1 demo tree roots in 0.4 s, measured in Node).

Every internal branch then carries its MAD value: the root-mean-square deviation the tree would have with the root on that branch. Lower is better, and the root's branch has the smallest. The MAD Values checkbox (Display Data → Labels, present while the tree carries them) writes them on the branches, ahead of any support value, as MAD/support: 0.02/95. They are not support, so the Confidence labels, Support Dots and the Confidence search field leave them out. Midpoint or manual re-rooting removes them, since they describe the MAD rooting only. A shared view remembers a MAD root. A phyloXML download keeps them as <confidence type="MAD">, as the desktop writes them; a Newick or Nexus download never puts one where a support value goes.

A tip is written under its name; where it has none, under its taxonomy (code, then scientific, then common name), then its sequence's name, symbol or gene name, then its sequence accession, and only if it has none of those under a node<N> placeholder numbering it by position among the tips. Each step is tried in turn, so a taxonomy element that is present but empty does not stop the search. An unlabeled internal node stays unlabeled — a placeholder there would invent a name for an ancestor. Newick and Nexus use the one chain, so a tree saved in both formats names its tips identically, and it is the desktop Archaeopteryx's chain, compared byte for byte.

Newick and Nexus have one support slot per branch and no place to name what kind of support it is, so a branch carrying several confidences is written with the first that is not a MAD value, exactly as the desktop writes it, and a bootstrap read back from such a file is typed unknown. phyloXML keeps every one of them, typed. Everything else survives a trip through either format unchanged — names, branch lengths (including zero-length and negative branches) and aligned sequences — in both directions, which the test suite pins as two standing round trips.

  1. Tria, F.D.K., Landan, G., Dagan, T. (2017). Phylogenetic rooting using minimal ancestor deviation. Nature Ecology & Evolution, 1, 0193. https://www.nature.com/articles/s41559-017-0193
  2. Wade, T., Rangel, L.T., Kundu, S., Fournier, G.P., Bansal, M.S. (2020). Assessing the accuracy of phylogenetic rooting methods on prokaryotic gene families. PLOS ONE, 15(5), e0232950. https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0232950

Representative tips

A large tree often holds many near-identical tips. The tool row's representative tips button thins them out the way the desktop Archaeopteryx's Tools → Select Representative Tips does: the tips are grouped into the largest clades whose tips are all close to each other, and one tip of each group is kept. Distance is patristic, the sum of the branch lengths on the path between two tips; in a tree without branch lengths it counts edges.

The dialog takes either a distance cutoff, and no two tips of a group are then farther apart than it, or a target number of representatives, for which the cutoff giving the nearest count the tree allows is used (a tie keeps more tips; the count reached is reported). A distance cutoff needs branch lengths. The tip kept is the group's most central one (the medoid, with the smallest summed distance to its group-mates) or its most divergent one (the longest terminal branch). Tips that are selected or found by a search can be kept whatever their group: they stand in for their group's representative, so more tips than the target may stay.

The tips kept show as search A's hits, with its colour, hit count and step-through, and a summary offers Create tree: a tree of only those tips, added to the tree picker and opened drawn as the tree was — the same layout and display type (phylogram, aligned or cladogram). It is named after the tree and the count, as in mammals_233reps, and its description says how it was made. An internal node left with one child is replaced by that child, whose branch gains the node's length; the new root keeps the original root's own branch length. The original tree is not changed.

The grouping and the tips kept are the desktop's: they match its own results on 129 generated trees in 5,666 settings (test/fixtures/rep-contract.tsv, made by running the desktop's code).

Metadata tables

A tree file rarely carries everything known about its tips. A metadata table beside it does: TSV or CSV, a header row, the first column naming the tip and every other column a piece of data. On the open page drop, choose or paste the table before or after the tree — a table pasted first waits for the tree; one added while a tree is showing joins it and the view relaunches — and the toolbar says how many columns joined how many tips, with the rows that matched no tip and the tips without a row a hover away.

Each column becomes a node property (meta: plus the header, so "Collection Date" comes back as Collection Date in every menu; a header that already reads as namespace:name is kept as it is). From there nothing is special: the columns are offered for Color-by and Shape by the same rules as any property, with the same legends; they are search fields, typed numeric when every filled cell is a number; the numeric ones become heat map columns; they appear in the node data; and they are written into a phyloXML export, so a saved tree keeps them. Tip names are matched exactly, then case-insensitively; empty cells add nothing; a column the tree already carries under the same ref is replaced by the table's values. Quoted cells, # comment lines and Windows line ends are fine.

The node menu's Download Ext. Node Data writes the other direction: the tips under a node as a tab-separated table, header first, with the desktop Archaeopteryx's column names (name, taxonomy_scientific_name, …, branch_length, then one column per property ref). A column no tip fills is left out, and a node_id column comes first when tip names are blank or repeated. Such a file opens again as a metadata table.

Embedders do the same in two lines, before launch():

const tree = archaeopteryx.parseTree(name, treeText);
const report = forester.joinMetadataTable(tree, tableText);  // {columns, tips, matchedTips, unmatchedTips, unmatchedRows, properties}
archaeopteryx.launch('#tree', tree, config);

Searching

Two search boxes (A and B), each with its own field menu (built from what the tree actually carries — names, taxonomy and sequence fields, every custom property, branch lengths, confidences, structural values) and match mode (contains / starts with / ends with / whole word / regex for text; = != < <= > >= range for numbers). Inside one box, , means OR and + means AND (plain-text modes only); Combine A & B intersects or unites the two. Match case and Inverse apply to both.

Hits are hard to miss: their labels take the search colour in bold, a translucent pulsing halo breathes behind each hit, and everything that is not a hit fades — the desktop's "dim non-matches", engaged only while at least one hit is on screen, so a fruitless search never washes the tree out. A collapsed clade holding a hit counts as on screen: it stays bright, its wedge outlined in the search colour and its label counting the hits, and the rest fades even when every hit is inside collapsed clades. The overview miniature marks every hit as a dot in the same colour, and a ◀ k / N ▶ navigator appears under the search boxes: each press centres the previous / next hit in the viewport, wrapping around. A collapsed clade holding hits is one dot and one stop.

Keyboard

The same actions on every platform; only the modifier differs: ⌘ on macOS, Ctrl on Windows and Linux. ⌘/ (Ctrl+/) shows this list in the viewer, drawn for the platform you are on; the About box links to it too.

| macOS | Windows / Linux | Does | |---|---|---| | ⌘0 | Ctrl+0 | Fit the tree to the window | | ⌘+ / ⌘− | Ctrl++ / Ctrl+− | Zoom in / out | | ⌘⇧↑ / ⌘⇧↓ | Ctrl+Shift+↑ / ↓ | Zoom in / out vertically | | ⌘⇧→ / ⌘⇧← | Ctrl+Shift+→ / ← | Zoom in / out horizontally (circular: rotate) | | ⌘⇧E | Ctrl+Shift+E | Expand vertically until the labels fit | | ⌘⇧L | Ctrl+Shift+L | Next layout: rectangular, circular, unrooted | | ⌘⇧D | Ctrl+Shift+D | Next display type: phylogram, aligned, cladogram | | ⌘⇧X | Ctrl+Shift+X | Time | Div, where the tree offers it; the Scale Axis on / off otherwise | | ⌘⇧O | Ctrl+Shift+O | Ladderize | | ⌘⇧U | Ctrl+Shift+U | Uncollapse every clade | | ⌘F | Ctrl+F | Go to the search box | | ⌘G / ⌘⇧G | Ctrl+G / Ctrl+Shift+G | Next / previous search hit | | ⌘⇧< / ⌘⇧> | Ctrl+Shift+< / > | Previous / next tree of a multi-tree file | | ⌘/ | Ctrl+/ | The shortcut list | | Esc or Home | Esc or Home | Back to the whole tree, the launch view | | O | O | Move the overview to the next corner | | Page Up / Page Down | PageUp / PageDown | Larger / smaller font |

Mouse wheel, also the same everywhere: plain zooms both axes, Shift vertical only, Shift+Alt (macOS: Shift+Option) horizontal only, Ctrl+Shift the font size. Inside a text box only the combos that cannot interfere with typing fire (fit, zoom, search, the list); the plain keys never do. The letters follow the desktop Archaeopteryx where it has the action (its Alt+O, Alt+U, Alt+E and ⌘0, ⌘G). There is no key for re-rooting, by decision. On Linux, Ctrl+Shift+U inside a text field is the desktop's Unicode entry: it stays with the text field there, as it should.

Sharing a view

A view is what you made of a tree with the panel: the layout and display type, which labels show, the colour and shape fields, both searches, the clade you switched to, the clades you collapsed, the font, node and branch sizes, the rotation, whether the branches show time or divergence, and the tracks — which track is shown, the heat map's column order (including one you arranged by hand) and whether the alignment is summarised as a logo. On the demo pages it rides in the URL's # hash and follows every change, so the address bar is always a link to what is on screen: copy it (Copy link to this view in the toolbar) and the recipient opens the same tree in the same view. Opening your own file keeps the view in the hash too, so the same file reopened at that address comes back as you left it. Zoom, pan, the legend's position and the node selection are not part of a view; a shared view opens fitted.

In a file holding several trees each tree keeps its own view, so moving between them with the picker brings each tree back as you had it (see Reading trees below). The hash holds the view of the tree on screen, so a link opens that tree the way it looks; the other trees' views last as long as the page does.

Embedders get the same four pieces: the handle's getViewState() and applyViewState(state), the config's view (open straight into one) and onViewChange(state, encoded) (called when it changes), and archaeopteryx.encodeViewState() / decodeViewState() for the hash form (every key the state can hold survives the hash — a test reads getViewState and fails on any key the codec does not know), which reads like layout=circular&colorBy=tax:common_name&show=name,external&font=9&collapsed=12,44&a=HUMAN&af=Any+Text&am=contains. Nodes are named by their launch-time preorder index, so a view belongs to the tree it was made on; a key a view leaves out keeps its current value, and anything that does not fit the tree is skipped.

Protein domain architectures

A tree whose tips carry <domain_architecture> elements (a protein's length and its domains, each with a position and an E-value) draws them as domain tracks beside the tips: a thin grey backbone, L residues long, with a rounded box per domain, placed at its residues on one scale shared by the whole tree so lengths compare across tips, coloured by domain name from the Tableau palette, with the name written on the box when it fits. The tracks appear from the start whenever a tree carries them; the Domain Architectures checkbox under Display Data toggles them, and their own section holds the controls:

  • Track width − / + — the longest architecture's track starts at a quarter of the window and scales by 0.8 / 1.2 per press (hold to repeat).
  • E-value ≤ − 10⁻³ + — only domains at or under the threshold are drawn; each press moves it by a factor of ten, from 10⁻²⁰ to 10³. The colours are dealt again to the names that remain, in sorted order.
  • Labels — On domains (the default), Legend (a card, at home in the bottom-right corner, draggable, double-click to send it back: one row per drawn name with its box count, in the order the names first appear down the tree), or None.
  • Glow — a soft glow in each domain's own colour around its box.

Hovering a domain reads it out: the domain's name, its E-value (small ones keep their exponent, 7.20e-117), the residues it spans with its own length and the protein's, the tree tip it belongs to, and its accession where the file carries one. Clicking the box looks the domain up: straight to the Pfam entry when there is an accession, and an InterPro search by name when there is not — phyloXML's domain id is optional, and a file with names alone (our own apaf.xml among them) cannot address an entry, since InterPro resolves accessions and not Pfam identifiers. Only the domain boxes take the mouse, so the tree behind the track stays clickable.

In the circular and unrooted layouts the tracks ride each named tip's spoke outward and carry no names of their own (the legend still works); they need radial labels, which switching layouts turns on. An architecture goes with its name: where the crowding rule has taken a tip's name away, its track is left out too, since a track with no name beside it can only be identified by which spoke it sits on. Switching the name fields off is not the same thing — nothing is hidden by the rule then, and every track is still drawn. A malformed domain — a missing or impossible position or E-value — is skipped and counted in a console warning, never fatal. The tracks ride into the SVG, PDF and PNG exports. This is the desktop's domain display, drawn to the same numbers (test/domain_test.js holds them).

Genome regions

A tree whose tips are genomes can carry (part of) each genome: its genes as <sequence> elements, one per gene, each with a <location> on a contig and its family in an <annotation ref="…">, plus one <sequence type="dna"> record per contig. The viewer draws them as a gene track beside the tips: a thin backbone per genome with an arrow per gene, pointing by strand, coloured by family, on one scale shared by the whole tree so distances compare across genomes. The rows are anchored: the anchor family's gene sits at the same place in every row, pointing right, and the neighbours follow — a genome carrying it on the other strand is mirrored — so gene order, inversions, insertions and deletions read down the tree, as in BV-BRC's Compare Region Viewer. A family met in one genome only is grey, a gene with no family lighter grey, so conserved order stands out. A bar at the end of a backbone marks a contig end; the track's own scale bar under the rows reads in kb. The track appears from the start whenever a tree carries regions; the Genes checkbox under Display Data toggles it, and the Genome regions section holds the controls:

  • Track width − / + — the widest row starts at a quarter of the window and scales by 0.8 / 1.2 per press (hold to repeat).
  • Anchor — Auto (the family single-copy in the most genomes, at least half of them; the choice is named), any family that can anchor two genomes or more, or None, which left-aligns the rows.
  • Family — which annotation namespace colours the genes and anchors the rows, offered when the genes carry more than one (a BV-BRC export carries both PGfams and PLfams).
  • Labels — On genes (the symbol, when it fits), Legend (a card of the families with their commonest product and genome counts, draggable, double-click to send it home), or None.

Hovering an arrow reads the gene out: symbol, product, family, location, length, the genome it belongs to and its accession; clicking opens its BV-BRC feature page for a BV-BRC accession (fig|…) or NCBI's protein page for a GenBank / RefSeq one. In the circular and unrooted layouts the arrows ride each named tip's spoke, as the domain tracks do, and both tracks can show at once, the genes past the domains. Whole genomes are for viruses; a bacterial genome is shown as a window of a few dozen genes around a gene of interest, chosen by whoever writes the file. The tree is labelled by node name: a genome's sequences describe the region beside the tip, not the tip.

The file needs nothing phyloXML has not got — <location> is the schema's "location of a sequence on a genome/chromosome", an annotation's ref its reference to an external database:

<clade>
  <name>Brucella melitensis bv. 1 str. 16M</name>
  <sequence type="dna">                      <!-- the contig record, FIRST -->
    <accession source="RefSeq">NC_003317.1</accession>
    <name>chromosome I</name>
    <location>NC_003317.1:1-2117144</location>
  </sequence>
  <sequence type="protein">                  <!-- one per gene, any order -->
    <accession source="BV-BRC">fig|224914.11.peg.1021</accession>
    <symbol>dnaK</symbol>
    <name>Chaperone protein DnaK</name>
    <location>NC_003317.1:1055720-1057636(-)</location>
    <annotation ref="pgfam:PGF_00019355"></annotation>
  </sequence>
</clade>

A location is contig:start-end(+), 1-based and inclusive, split on its last colon; a located sequence with a strand is a gene, one without is the contig's record, whose span says where the contig ends. The record comes first because a tip's first sequence is "the" sequence to every other reader. A tip's genes on a second contig are not drawn (one contig per genome: the one with the most genes) and a location not in the grammar is skipped; both are counted in a console warning. The demo is synthetic (Genome regions, twelve genomes with one row per rule: an inversion, a deletion, two insertions, a mirrored genome, a contig end, two contigs, overlapping genes, a doubled anchor); test/gene_test.js pins its numbers.

Sequence alignments

A tree whose tips carry <mol_seq is_aligned="true"> shows the alignment as a residue track beside the tree (rectangular layout only — an alignment is inherently horizontal). Amino acids use a physico-chemical colour scheme, nucleotides one colour per base — decided from the residues themselves — with gaps drawn as faint dashes that join into lines, so indel blocks read at a glance.

A long alignment shows a scrollable window (at most ~60% of the display): drag the slider at the window bottom, or roll the mouse wheel over the track; the tree itself never moves. Under the rows sit a conservation bar per column with the consensus residue beneath it (scored over the tips currently displayed), and a 1-based column ruler. Hover any residue for its alignment column, its position within that sequence's own ungapped residues, its full name, class, and Kyte-Doolittle hydropathy. The Alignment checkbox under Display Data toggles the whole track. To find a motif, pick Molecular Sequence in a search box: it matches the residues as written, gap characters included, as the desktop does.

Sequence Logo (the checkbox under Alignment) replaces the conservation bar with a logo: a stack of letters per column, as tall as that column's information content in bits and shared out by residue frequency, most frequent on top — the display the MEME Suite and WebLogo draw. A conserved column is one tall letter, a variable one a short pile, and the caption gives the scale (0 to 2 bits for nucleotides, 0 to 4.3 for amino acids).

It summarises the tips currently on screen, so entering a clade gives that clade's motif rather than the file's, and the caption names how many tips that is (n = 12). Two consequences worth knowing: gaps are not a letter — frequencies are taken over the residues present, and the stack is then scaled by the column's occupancy, so a column held up by two sequences out of fifty draws short rather than perfectly conserved and an all-gap column draws nothing; and there is no small-sample correction, because entering a three-tip clade is a normal thing to do and Schneider's correction would subtract more than the maximum and leave the column blank. Read n and judge.

Alignments arrive with the tree: as phyloXML <mol_seq is_aligned="true"> elements, or in a Nexus file whose characters matrix accompanies its tree. The Nexus entry in the Download menu writes the current tree and its alignment back into one Nexus file (Taxa, Characters and Trees blocks), in the same bytes the desktop Archaeopteryx writes: a row per taxon rather than per sequence, so a tip carrying no sequence gets a row of the missing symbol ? and the matrix still covers every taxon the file declares. Reading it back, such a row is absence of data and not a sequence of question marks. Residues from a Nexus matrix are normalised as the desktop normalises them — raised to upper case, . read as a gap, and anything outside the declared alphabet read as the unspecified residue, X for protein and N for nucleotides, so the missing symbol ? arrives as X or N. A phyloXML <mol_seq> is kept exactly as written, by both programs. Sequences of unequal length are not an alignment and cannot form a character matrix, so they are not written at all — the file says so in a bracketed comment rather than padding them into an alignment that does not exist, and the Nexus entry of the Download menu says so before you choose it.

Heat maps

A tree whose tips carry numeric fields shows them as a heat map beside the tree: one row per tip, one column per field, each cell coloured by its value (the columns stand on the tips' common edge; in the circular layout that edge is a ring, so each column becomes a concentric ring past the labels — the unrooted layout has no such edge, and so no heat map). Turn it on with the Heat Map checkbox under Display Data; it is offered whenever the tree has two or more numeric per-tip fields.

Every column is painted on one shared scale, so a colour means the same number wherever it appears — that is what makes a block of related columns readable as a block, and it is the point of a heat map rather than a row of independent stripes. The scale spans the whole tree, so entering a subtree narrows the rows and leaves the colours where they were. The columns that appear are the numeric fields the Color by menu offers, so the two agree about what the tree holds — plus a field with one value on every tip, which has nothing to colour by but is a column all the same (a single-copy core gene is a column of 1s); by default their left-to-right order follows the order the file lists them in, which keeps a producer's grouping (core genes, then resistance, then prophages) intact even where some tips are missing a field — see Order columns below for the alternatives.

Where a cell is at least 5 px on screen, each cell is drawn on its own with a thin border in its own hue, darker — a block of equal values still reads as a block, and each cell can be counted. The border stays 0.75 px at any zoom. Smaller cells, or a matrix of more than 60,000 cells, are drawn as merged runs of equal colour instead, and zooming across that threshold switches between the two.

A cell nobody filled in is drawn as an outlined empty box, never as the scale's low end: on a presence/absence matrix, reading a missing field as zero states the opposite of what the file says. The key beside the scale names it.

A file can open straight into its matrix. The desktop Archaeopteryx stores its figure setting as one property of the phylogeny, directly under <phylogeny>:

<property ref="aptx:figure" datatype="xsd:string" applies_to="phylogeny">v1;columns=pgfam:PGF_00019355\sMATRIX\sCIRCLE\sfalse\ppgfam:PGF_00019313\sMATRIX\sCIRCLE\sfalse</property>

When it names two or more MATRIX columns the tree has, the tree opens with the heat map on, showing only those columns, in that order (the order is then manual, and Order columns can still change it). Other column types are the desktop's and are not drawn here. Only a property directly under <phylogeny> counts: an aptx:figure on a clade — where desktops 0.11.117 to 0.11.172 wrote it — is ignored and left out of what is saved; the phylogeny's own is saved back unchanged. An explicit showHeatmap, heatmapColumnOrder or heatmapManualOrder in the config wins over the file.

Hover any cell for its tip, the column and its value — or not assessed — and the scale it was coloured against. The column names stand under the matrix, turned; a matrix with more columns than will fit shows a window and says so (Columns 1–240 of 400), which the mouse wheel over the matrix scrolls.

Order columns (in the Heat Map section of the panel) decides where the columns go. The two clustered orders put columns that behave alike side by side and draw the clustering itself as a dendrogram above the matrix — a clustergram — and one of them is what a heat map opens on, because reading block structure is what a matrix beside a tree is for. As in the input is there when you want the producer's own grouping back — and, because phyloXML gives every node its own property list, its tooltip says whether that really is the input's order or only the order most tips agree on. Both clustered orders are complete-linkage hierarchical clustering, written to give the same answer as R's hclust(dist(t(m)), method = "complete"); they differ in what "alike" means:

  • Clustered (co-occurrence) uses Euclidean distance, the default of R's pheatmap, heatmap.2 and ComplexHeatmap.
  • Clustered (ignoring shared absence) uses the Bray–Curtis dissimilarity (R vegan's vegdist default). Euclidean distance has the double-zero problem: two genes both absent from the same strains are counted as agreeing there, so on a sparse pan-genome the rare genes cluster together merely for being rare. Bray–Curtis drops a tip where both columns are 0 instead of scoring it as agreement. On 0/1 data it is exactly the Sørensen–Dice dissimilarity.

Which of the two a tree opens on is decided by its values: Bray–Curtis where the matrix has zeros to ignore and nothing negative, Euclidean otherwise. A zero is precisely the precondition for a double zero to exist, and Bray–Curtis is meant for values 0 or more — on a matrix of years or coordinates it reads magnitude instead of pattern, and negative values leave some pairs with no distance at all. Set heatmapColumnOrder to override; an explicit choice is never re-derived.

Alphabetical and Frequency (highest mean value first, over the tips that have a value) are there too, and Reorder columns… opens a list you can drag (or move with the arrow keys) into any order you like. Doing so sets Manual, and nothing re-sorts a manual order — a sorting mode that quietly undid your arrangement would make the arrangement pointless. Automatic in that dialog hands the order back to the data. A manual order rides in a shared view, so a link reproduces the figure exactly; a column it does not name — one added by an edit, or a table joined since — follows the ones it does.

Whatever the mode, a blank is never read as 0, and the dendrogram is drawn only when it describes the columns actually on screen — never over a scrolled window, never over an order that did not come from a clustering, and never over the rings, where it would have to bend.

Sørensen, T. (1948) Biol. Skr. 5, 1–34 · Bray, J.R., Curtis, J.T. (1957) Ecol. Monogr. 27, 325–349 · Eisen, M.B. et al. (1998) PNAS 95, 14863–8.

Heat-map data arrives with the tree, as phyloXML <property> elements on the tips (<property ref="meta:recA" datatype="xsd:integer" applies_to="node">2 </property>), or from a metadata table joined to it on the open page — a table's numeric columns become heat-map columns like any others.

Time trees

A tree whose nodes carry phyloXML <date> elements is drawn against time. Ages (unit="mya" and friends, or values that look like ages) get the geologic axis: two rows of ICS intervals in their official colours — Period over Epoch for most trees, coarser pairs for Precambrian-deep ones — plus a "Ma before present" ruler. Years (unit="year", or values that look like calendar years) get a labelled calendar axis. The layout itself never changes: time is an overlay calibrated by the dates, so it also works for a fossil-only tree, where the axis stops at the youngest tip and labels that age (the ammonite demo ends at the K-Pg, 66).

Nodes with a date range (minimum/maximum — minimum is the younger bound) draw uncertainty bars: translucent blue HPD age bars on internal nodes, and on tips whatever the axis says the range means — on geologic time a fossil's observed range, sepia FAD/LAD bars with end caps; on calendar time the uncertainty of a sampling date, drawn like an age bar, slimmer, and only when it has a width. Node tooltips show the date. The Scale Axis checkbox under Display Data toggles everything; the axis needs a phylogram and the rectangular or circular layout. In circular the bars run along each node's spoke — on through the centre where a range reaches back past the root's own age — the ruler runs out from the root along the gap between the last tip and the first, turning with the tree, and a geologic axis lays its intervals down as coloured rings. Scale Grid (off by default) adds faint lines, or rings, at the fine interval boundaries or the calendar years.

Time | Div

Where a tree can be laid out by time and by divergence, a Time | Div switch appears beside the phylogram buttons (⌘⇧X). Two kinds of tree state a divergence: an Auspice build, on its nodes (div), and a BEAST clock-model tree, as a clock rate on every branch — its branch lengths are time, and its divergence is each length times its rate, in substitutions per site. A tree with neither has no second layout. A rate or a recorded divergence is a plain decimal number (0.01abc is not one); a negative divergence is a value, drawn at 0.

The switch is offered only when both layouts can state every branch — every node dated, every branch with a length, and divergence on every node or a rate on every branch — and each picture has some depth. A value the file states counts, whether zero or negative; where one is missing the switch is withheld rather than a branch drawn at a length the file never gave. Whether the two pictures differ is not asked: a strict clock's divergence is its time at another scale, and it is offered like any other.

Back in Time the tree has exactly the lengths it arrived with. A span that runs backwards — a child dated before its parent, which real Nextstrain builds state — keeps its sign, and is drawn at length 0 like any negative branch length. The switch acts on the whole tree, also from a subtree view, and a node deleted meanwhile leaves the branch that takes its place spanning what both spanned, in either layout. The time axis is hidden while the branches show divergence, and the Scale Axis measures distance instead. Each layout stamps the tree's branch_length_unit (subs/site; for time the dates' unit, or time), so a tree saved while Div is on screen says so and opens again showing divergence. All of this is shared with the desktop.

Clock plot

Beside the switch, a Clock plot button opens a panel that plots each tip's date against its divergence from the root, with a straight line through the tips — the root-to-tip plot of TempEst and TreeTime, and Auspice's clock view. Under the plot:

  • Rate — the slope of the line: substitutions per site per year (per unit of the tree's dates where they are not calendar years). Negative where the tips' divergence falls with time.
  • Root date, by the line — the date at which the line comes down to the root's divergence — over Root date, in the tree, the one the tree states. How far apart they are is the point of showing both.
  • R² and the number of Tips in the fit.

The line is ordinary least squares over the tips only: a tip's date is an observation, an ancestor's was inferred (usually with a clock), and counting it would have the estimate confirm itself. It is not forced through the root. Tips share ancestry, so R² describes the fit and is no significance test. On a BEAST clock-model tree the divergence is each branch's time × its clock rate, so there the plot shows the model's rates, not a measurement of its own (a strict clock is a perfect line), and the panel says so.

The panel is linked to the tree and stays open while you work in it (drag it by its title):

  • a point wears its node's colour in the tree — the Color visualization, search hits, the selection;
  • pointing at a point lights its node in the tree (the collapsed clade holding it, if it is hidden in one), and pointing at a node in the tree rings its point;
  • clicking a point selects or deselects its node, and dragging a box selects the tips in it — the tree's own selection, so an outlier found on the plot is found in the tree. Deselect all clears it;
  • tips with one date and one divergence — identical sequences sampled on one day — are drawn on one spot, and that dot answers for all of them: the readout says how many tips are there and names the first five, all of them light up in the tree, and a click selects them all (or deselects them all, where all were selected);
  • in a subtree view the plot is the clade's, with the clade's own line.

Regression line shows and hides the line. Internal nodes adds the ancestors' points, each at its own date and divergence; the panel opens without them, since the line is the tips' and a deep root would crowd them into a corner.

The button is offered on two kinds of tree, whenever the line can be drawn (three tips or more, not all on one date; a tree sampled at one moment has no slope to estimate):

  • a tree that has Time | Div: every node is dated and has a divergence, so the ancestors can be shown too;
  • a tree whose branch lengths are its divergence and whose tips alone are dated — the tree a root-to-tip plot is run on before dating, TempEst's case. A tip's divergence is the sum of the branch lengths from the root; the plot has tips only (Internal nodes is not offered, since no ancestor is dated); Root date, in the tree says none, since the tree states no root date, and Rate is in the tree's own branch length unit, where it states one. A tip's date is what the file states for it (a phyloXML <date>, a num_date annotation) or, failing that, the date in its name, as TempEst reads it: the rightmost date the label carries (A/swine/Iowa/A02524571/2020|1A.3.3.3 is 2020; a year alone is taken as mid-year, 2020.5; a day/month-ambiguous 03/04/2020 is read day first), and the panel's note says how many names it read and how many were ambiguous. An ancestor is never dated by its name. Every tip must be dated, by the file or by its name, and every branch must have a length: a tree with a gap is not offered the plot, rather than plotted with tips missing.

forester.js

forester.js is a general suite for dealing with phylogenetic trees.

forester.js Example

This basic example shows how to parse a New Hampshire formatted String into to a object representing a phylogenetic tree. Followed by pre- and post-order traversal, and writing back to a New Hampshire formatted String.

Change './forester' to 'forester' if you use this code outside of this package

var forester = require('./forester').forester;

var newHampshireFormattedString = "(((a:1,b:1,c:1)N:2,(d:1,e:1)M:4)O:4,f:1)R:1;";
var phylogeneticTree = forester.parseNewHampshire(newHampshireFormattedString);

console.log('Pre-order traversal:');
forester.preOrderTraversalAll(forester.getTreeRoot(phylogeneticTree), function (n) {
    console.log(n.name + ':' + n.branch_length);
});

console.log('Post-order traversal:');
forester.postOrderTraversalAll(forester.getTreeRoot(phylogeneticTree), function (n) {
    console.log(n.name + ':' + n.branch_length);
});

console.log('In New Hampshire format:');
var nh = forester.toNewHampshire(phylogeneticTree);
console.log(nh);

Expected output:

Pre-order traversal:
R:1
f:1
O:4
M:4
e:1
d:1
N:2
c:1
b:1
a:1
Post-order traversal:
a:1
b:1
c:1
N:2
d:1
e:1
M:4
O:4
f:1
R:1
In New Hampshire format:
(((a:1,b:1,c:1)N:2,(d:1,e:1)M:4)O:4,f:1)R:1;

For Developers

This section describes the two configuration objects Archaeopteryx.js accepts. It is generated against the current source; if something here disagrees with the code, the code is right and this is a bug.

The entry points

// parse and launch in one step (what most callers want); fetch the file
// content yourself -- the library does no networking
const viewer = archaeopteryx.launchArchaeopteryx(container, fileName, data, config);

// or parse yourself, then launch
const tree = archaeopteryx.parseTree(fileName, data);
const viewer = archaeopteryx.launch(container, tree, config);

// every tree the file holds (a Nexus TREES block, a multi-tree Newick,
// several phylogenies): launch() takes the list, shows the first, and
// the panel gets a picker for the rest
const trees = archaeopteryx.parseTrees(fileName, data);
const viewer = archaeopteryx.launch(container, trees, config);
viewer.showTree(1);   // also viewer.getTreeIndex(), viewer.getTreeCount()

// later, e.g. when an SPA removes the view:
viewer.destroy();

Both entry points take exactly the arguments shown — a call with the old trailing arguments (the separate settings bag, nodeVisualizations, nodeLabels, specialVisualizations, or the positional Newick parse options) throws with a message saying where each one went: everything now lives in the one config object (internalNumericLabels is a config key; nodeLabels is retired, see the deprecated keys below). config itself is optional — archaeopteryx.launch('#phylogram1', tree) works.

container is a CSS selector or the DOM element itself (frameworks hand you elements). A container that cannot be resolved throws — it used to render nothing and say nothing. Both entry points return a viewer handle:

viewer.getSelectedNodes(); // the node-menu selections (enableManualNodeSelection)
viewer.ready;              // a Promise: resolved once the tree is drawn
viewer.destroy();          // unmount COMPLETELY: the container DOM, the node
                           // menu / dialogs / alignment scroller, the window
                           // resize listener and every page-level key/wheel
                           // handler; a later launch() works normally

Big trees draw on the next frame. From 5,000 nodes, launch() does all its validation, shows a "Drawing N nodes" card over the tree area, and returns within milliseconds — the label analysis, visualization candidates, control panel and the draw itself all run one frame later, so the browser can paint the card instead of appearing frozen for the seconds a large tree takes. Every error still throws synchronously from launch() exactly as before; only the draw is deferred. viewer.ready resolves when it has run (immediately for a smaller tree, which draws synchronously). Later redraws — a checkbox, a slider, a search, the mouse wheel — run on the next animation frame for a tree of any size, and every redraw requested before that frame collapses into one: a wheel flick is one redraw, not one per notch. A "Redrawing" card appears only when the tree's previous redraw took 300 ms or more on the computer it runs on. Wait on ready before reading the tree's DOM after launch():

const viewer = archaeopteryx.launch(container, tree, config);
await viewer.ready;   // the SVG exists now

The one part the library cannot defer for you is your own parse of a big file before launch(). archaeopteryx.busy() shows the same card for that, yields a frame so it paints, runs your work, and removes it:

archaeopteryx.busy(container, 'Reading ' + name, sizeMb + ' MB', function () {
    const tree = archaeopteryx.parseTree(name, text);
    viewer = archaeopteryx.launch(container, tree, config);
});

Pass document.body as the container for a whole-page card; without the work function it returns a remover and the yielding is up to you.

One viewer per page: the library keeps its display state in one place, so a second launch — into any container — replaces the first. Launching into the same container is the supported way to switch trees (the demo pages do exactly that).

Loading the library

One file, loadable every way an embedder might want it:

  • <script> tags (the classic path): load d3 (v7), sax, phyloxml, forester, then archaeopteryx; use window.archaeopteryx.
  • AMD (Dojo, RequireJS): require(['archaeopteryx'], ...) — the module reads its dependencies off the page's globals when first required (so load the dependency scripts first), and also still sets window.archaeopteryx.
  • CommonJS / bundlers / Node: const {archaeopteryx} = require('archaeopteryx') — with TypeScript definitions included (archaeopteryx.d.ts types the whole config object and the handle). In plain Node, with no d3 at all, every parser works (archaeopteryx.parseNexus(...) etc.); only launch() needs a browser and d3, and says so by name. forester is importable as the extensionless subpath require('archaeopteryx/forester') (the package's exports map defines exactly that path).

Dependencies are checked when used, never at load time, and every failure names exactly what is missing (including "the loaded d3 is not usable as d3 version 7"). The optional export libraries stay page-level globals in every loading style: window.Canvg (PNG), window.jspdf + svg2pdf.js (PDF).

Diagnostics

Checking at use rather than at load makes the viewer hard to break — and lets it run half-equipped without a word: no PDF in the Download menu, a retired config key ignored, a forced visualization overruled, each mentioned only in a console nobody has open. archaeopteryx.doctor() says what state the program is in on this page:

var report = archaeopteryx.doctor();
report.ok;          // false when something is wrong that nobody chooses
report.summary;     // "nothing to report", or "2 things to check"
report.problems;    // ['jspdf: loaded without svg2pdf.js: PDF export needs both, so it is off', ...]
report.notes;       // stated, not flagged
report.libraries;   // [{name, role, loaded, version, status, detail}, ...]
report.functions;   // [{name: 'PDF export', available: false, detail: 'jspdf and svg2pdf.js are not loaded'}, ...]
report.launch;      // {config, tips, tree, trees, container}, or null when no tree is on view
report.text;        // all of it as plain text, to paste into an issue
console.log(archaeopteryx.doctor().text);

It works before a launch, after a launch that threw, and in Node, and it never throws. The same report is in the viewer: the About box (a click on the program name) has a Diagnostics row that opens it, with a Copy report button — the copied text adds the keys passed to launch() (a string or a number as given, anything else as its kind: a tree or a callback is never copied) and the browser.

What is a problem and what is only stated:

| | | |---|---| | An optional library that is not loaded (canvg, jspdf, svg2pdf.js) | stated: "PNG export: off". Leaving one out is a site's choice. | | Half of a pair: jspdf without svg2pdf.js, or the other way round; svg2pdf.js loaded before jspdf | problem | | A library under its 2.x-era global (window.canvg, window.jsPDF) and not under the one this version reads; a window.Canvg without fromString | problem | | A d3 that is not version 7 (d3 4 to 6