archaeopteryx 3.23.0 → 3.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ config key live and shows the exact config JSON to copy into your own
25
25
  `launch()` call.
26
26
 
27
27
  * [Auspice / Nextstrain JSON (Time | Div, Clock plot)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=auspice)
28
- * [Swine H1 HA1 + alignment (Nexus)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=swh1)
28
+ * [Swine H1 HA1 + alignment (Nexus; Clock plot from the years in the names)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=swh1)
29
29
  * [BEAST annotations (Nexus)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=beast)
30
30
  * [Flavivirus mature peptides (10 trees)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=flavivirus)
31
31
  * [SARS-CoV-2 time tree (calendar)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=sarscov2)
@@ -933,11 +933,28 @@ ancestors' points, each at its own date and divergence; the panel opens
933
933
  without them, since the line is the tips' and a deep root would crowd them
934
934
  into a corner.
935
935
 
936
- The button is offered on a tree that has **Time | Div** and whose line can
937
- be drawn: three tips or more, not all on one date. A tree sampled at one
938
- moment has no slope to estimate. (A tree whose branch lengths are divergence
939
- and whose tips alone are dated — the tree a root-to-tip plot is run on
940
- *before* dating — has no plot yet.)
936
+ The button is offered on two kinds of tree, whenever the line can be drawn
937
+ (three tips or more, not all on one date; a tree sampled at one moment has
938
+ no slope to estimate):
939
+
940
+ - a tree that has **Time | Div**: every node is dated and has a divergence,
941
+ so the ancestors can be shown too;
942
+ - a tree whose **branch lengths are its divergence and whose tips alone are
943
+ dated** — the tree a root-to-tip plot is run on *before* dating, TempEst's
944
+ case. A tip's divergence is the sum of the branch lengths from the root;
945
+ the plot has tips only (**Internal nodes** is not offered, since no
946
+ ancestor is dated); **Root date, in the tree** says `none`, since the tree
947
+ states no root date, and **Rate** is in the tree's own branch length unit,
948
+ where it states one. A tip's date is what the file states for it (a
949
+ phyloXML `<date>`, a `num_date` annotation) or, failing that, the date in
950
+ its **name**, as TempEst reads it: the rightmost date the label carries
951
+ (`A/swine/Iowa/A02524571/2020|1A.3.3.3` is 2020; a year alone is taken as
952
+ mid-year, 2020.5; a day/month-ambiguous `03/04/2020` is read day first),
953
+ and the panel's note says how many names it read and how many were
954
+ ambiguous. An ancestor is never dated by its name. Every tip must be
955
+ dated, by the file or by its name, and every branch must have a length: a
956
+ tree with a gap is not offered the plot, rather than plotted with tips
957
+ missing.
941
958
 
942
959
  # forester.js
943
960
  forester.js is a general suite for dealing with phylogenetic trees.
@@ -2599,15 +2616,25 @@ deliberate difference.)
2599
2616
  ### The clock plot
2600
2617
 
2601
2618
  **Model** (`forester.js`, pure). `forester.clockPlotKind(phy)` answers
2602
- `'divergence'` or `null`: the tree has Time and Div
2603
- (`forester.hasTimeAndDivergence`) and three tips or more, not all on one
2604
- date. It is asked of the **tree**, never of the view. A second kind,
2605
- `'root-to-tip'` — divergence branch lengths, dated tips, no dated ancestors —
2606
- is described beside the function and not built. `forester.clockPlotData(phy,
2607
- view)` returns `{kind, forward, unit, fromRates, points, root, fit}`: a point
2619
+ `'divergence'`, `'root-to-tip'` or `null`: `'divergence'` where the tree has
2620
+ Time and Div (`forester.hasTimeAndDivergence`); `'root-to-tip'` where every
2621
+ tip is dated (by the file, or by the date in its name:
2622
+ `forester.parseTipLabelDate`), no ancestor is, every branch states a length
2623
+ and no branch states a clock rate (a clock-model tree's lengths are time) — and in
2624
+ both cases three tips or more, not all on one date. It is asked of the
2625
+ **tree**, never of the view. `forester.clockPlotData(phy, view)` returns
2626
+ `{kind, forward, unit, divUnit, fromRates, points, root, fit}`: a point
2608
2627
  `{node, date, div, tip}` per node of the view (a tip also carries its
2609
- `residual`), `forward` false where the dates are ages, `fromRates` true where
2610
- the divergence is time × clock rate, and `fit` =
2628
+ `residual`; on a root-to-tip tree the tips only, their `div` the sum of the
2629
+ branch lengths from the tree's root), `forward` false where the dates are
2630
+ ages (on a root-to-tip tree: where their unit is geologic), `divUnit` the
2631
+ divergence's unit (`subs/site` on a Time | Div tree, a root-to-tip tree's
2632
+ stated branch length unit, or `null`), `fromRates` true where the divergence
2633
+ is time × clock rate, `calendar` true where the dates are calendar years
2634
+ (their unit says so, or they were read off the tips' names), `fromNames` and
2635
+ `ambiguousNames` how many tips a root-to-tip tree dates by their names and
2636
+ how many of those are day/month-ambiguous (read day first), `root.date`
2637
+ `null` on a root-to-tip tree, and `fit` =
2611
2638
  `forester.clockRegression(dates, divergences)` over the tips — ordinary least
2612
2639
  squares about the means, `{n, slope, intercept, r2}` — plus `rate` (the slope
2613
2640
  in the direction time runs) and `rootDate` (where the line reaches the view
package/archaeopteryx.js CHANGED
@@ -20,7 +20,7 @@
20
20
  *
21
21
  */
22
22
 
23
- // v 3.23.0
23
+ // v 3.24.0
24
24
  // 2026-09-17
25
25
  //
26
26
  // Archaeopteryx.js is a software tool for the visualization and
@@ -103,7 +103,7 @@ function (root, d3, forester, phyloXml) {
103
103
  // IIFE's own function name -- a plain object says what it is.)
104
104
  let archaeopteryx = {};
105
105
 
106
- const VERSION = '3.23.0';
106
+ const VERSION = '3.24.0';
107
107
  const WEBSITE = 'https://cmzmasek.github.io/archaeopteryx-js/';
108
108
  const DESKTOP_WEBSITE = 'https://cmzmasek.github.io/archaeopteryx/';
109
109
  const SOURCE_WEBSITE = 'https://github.com/cmzmasek/archaeopteryx-js';
@@ -11171,10 +11171,12 @@ function (root, d3, forester, phyloXml) {
11171
11171
  row.appendChild(input);
11172
11172
  row.appendChild(span);
11173
11173
  options.appendChild(row);
11174
- return input;
11174
+ return row;
11175
11175
  };
11176
11176
  check('Regression line', 'line', 'to show/hide the least-squares line through the tips');
11177
- check('Internal nodes', 'internal', 'to show/hide the internal nodes: their dates were inferred,'
11177
+ // on a root-to-tip tree no ancestor is dated: nothing to show, so the
11178
+ // choice is not offered (layClockPlot hides it by the data's kind)
11179
+ let internalRow = check('Internal nodes', 'internal', 'to show/hide the internal nodes: their dates were inferred,'
11178
11180
  + ' and they take no part in the line');
11179
11181
  let deselect = document.createElement('button');
11180
11182
  deselect.type = 'button';
@@ -11197,7 +11199,7 @@ function (root, d3, forester, phyloXml) {
11197
11199
  body.appendChild(note);
11198
11200
 
11199
11201
  _clockPlot = {dialog: shell.dialog, svg: svg, axes: axes, internal: internal, line: line, tips: tips,
11200
- ring: ring, box: box, stats: stats, note: note, deselect: deselect,
11202
+ ring: ring, box: box, stats: stats, note: note, deselect: deselect, internalRow: internalRow,
11201
11203
  data: null, marks: [], seen: null, hover: null, lays: 0, x: null, y: null};
11202
11204
  // the shell's close control, and anything else that closes the dialog
11203
11205
  let closed = function () {
@@ -11408,7 +11410,9 @@ function (root, d3, forester, phyloXml) {
11408
11410
  }
11409
11411
 
11410
11412
  function layClockPlot(cp, data) {
11411
- let calendar = data.forward && !!_timeInfo && _timeInfo.type === 'calendar';
11413
+ let calendar = data.calendar;
11414
+ let rootToTip = data.kind === 'root-to-tip';
11415
+ cp.internalRow.style.display = rootToTip ? 'none' : '';
11412
11416
  let shown = data.points.filter(function (p) {
11413
11417
  return p.tip || _clockPlotOptions.internal;
11414
11418
  });
@@ -11464,7 +11468,7 @@ function (root, d3, forester, phyloXml) {
11464
11468
  seg('aptx-clock-frame', left, top, left, bottom);
11465
11469
  let unit = data.unit && !calendar ? ' (' + data.unit + ')' : '';
11466
11470
  text('aptx-clock-title', (data.forward ? 'Date' : 'Age') + unit, (left + right) / 2, CLOCK_PLOT_H - 6, 'middle');
11467
- let yt = text('aptx-clock-title', 'Divergence (subs/site)', 0, 0, 'middle');
11471
+ let yt = text('aptx-clock-title', 'Divergence' + (data.divUnit ? ' (' + data.divUnit + ')' : ''), 0, 0, 'middle');
11468
11472
  yt.setAttribute('transform', 'translate(12,' + ((top + bottom) / 2) + ') rotate(-90)');
11469
11473
 
11470
11474
  // --- the points: ancestors under the line, tips over it
@@ -11506,12 +11510,13 @@ function (root, d3, forester, phyloXml) {
11506
11510
  let tipCount = data.points.reduce(function (n, p) {
11507
11511
  return n + (p.tip ? 1 : 0);
11508
11512
  }, 0);
11509
- let per = ' subs/site per ' + (calendar ? 'year' : (data.unit || 'unit of time'));
11513
+ let per = (data.divUnit ? ' ' + data.divUnit : '') + ' per ' + (calendar ? 'year' : (data.unit || 'unit of time'));
11510
11514
  if (fit) {
11511
11515
  treePropRow(cp.stats, 'Rate', clockNumber(fit.rate) + per);
11512
11516
  treePropRow(cp.stats, 'Root date, by the line', fit.rootDate === null
11513
11517
  ? 'none: divergence does not rise with time' : clockDate(fit.rootDate, calendar));
11514
- treePropRow(cp.stats, 'Root date, in the tree', clockDate(data.root.date, calendar));
11518
+ treePropRow(cp.stats, 'Root date, in the tree', data.root.date === null
11519
+ ? 'none: only the tips are dated' : clockDate(data.root.date, calendar));
11515
11520
  treePropRow(cp.stats, 'R²', fit.r2 === null ? 'none: every tip has one divergence' : clockNumber(fit.r2));
11516
11521
  } else {
11517
11522
  treePropRow(cp.stats, 'Line', 'none: it takes three tips, not all on one date');
@@ -11520,6 +11525,15 @@ function (root, d3, forester, phyloXml) {
11520
11525
  cp.note.textContent = (data.fromRates
11521
11526
  ? 'Divergence here is each branch’s time × its clock rate: the points show the model’s rates, not a measurement of their own. '
11522
11527
  : '')
11528
+ + (rootToTip
11529
+ ? 'Divergence here is each tip’s distance from the root along the branch lengths the file states; only the tips are dated. '
11530
+ : '')
11531
+ + (data.fromNames > 0
11532
+ ? (data.fromNames === 1 ? 'One tip is dated by its name' : countNumber(data.fromNames) + ' tips are dated by their names')
11533
+ + ' (a year alone is taken as mid-year'
11534
+ + (data.ambiguousNames > 0 ? '; ' + countNumber(data.ambiguousNames) + ' with day and month ambiguous, read day first' : '')
11535
+ + '). '
11536
+ : '')
11523
11537
  + 'The line is fitted to the tips only. Tips share ancestry, so R² describes the fit and is no test.'
11524
11538
  + ' Click a point to select its node; drag to select the tips in a box.';
11525
11539
  // what was laid down, for the browser harness: read here, where it is
@@ -18225,9 +18239,12 @@ function (root, d3, forester, phyloXml) {
18225
18239
  // live. Injecting twice is a no-op.
18226
18240
  injectPanelStyles();
18227
18241
 
18242
+ // REMOVED (2026-10-07): the 2.x class "node_mouseover_tooltip" beside
18243
+ // "aptx-tip". A host's old stylesheet outranked .aptx-tip through it
18244
+ // (div.X beats .Y) and restyled the popup; see CHANGELOG, Unreleased.
18228
18245
  if (!_node_mouseover_div) {
18229
18246
  _node_mouseover_div = d3.select("body").append("div")
18230
- .attr("class", "node_mouseover_tooltip aptx-tip")
18247
+ .attr("class", "aptx-tip")
18231
18248
  .style("opacity", 1e-6);
18232
18249
  }
18233
18250
 
package/forester.js CHANGED
@@ -20,7 +20,7 @@
20
20
  *
21
21
  */
22
22
 
23
- // v 3.23.0
23
+ // v 3.24.0
24
24
  // 2026-09-17
25
25
  //
26
26
  // forester.js is a general suite for dealing with phylogenetic trees.
@@ -7254,18 +7254,28 @@
7254
7254
  // a panel linked to the tree, named "Clock plot"; JS only until the
7255
7255
  // design is final.
7256
7256
  //
7257
- // THE RULE, designed for two kinds of tree, the first of them built:
7257
+ // THE RULE, designed for two kinds of tree (both built; the second on
7258
+ // Christian's word, 2026-10-03):
7258
7259
  //
7259
7260
  // 1. 'divergence' -- a tree with Time and Div (hasTimeAndDivergence): each
7260
7261
  // node states a date and a divergence from the root, recorded on it or
7261
7262
  // summed from each branch's time x its clock rate. Every node is a
7262
7263
  // point; which of the two layouts is on screen does not matter.
7263
- // 2. 'root-to-tip' (NOT BUILT) -- a tree whose branch lengths are
7264
- // divergence and whose tips are dated, with no dates on its ancestors
7265
- // (it is not a time tree): a tip's divergence is the sum of the stated
7266
- // lengths from the root. Tips only. This is the tree a clock plot is
7267
- // wanted for most -- is there a signal worth dating? -- and the one
7268
- // whose answer depends on its root.
7264
+ // 2. 'root-to-tip' -- a tree whose branch lengths are its divergence and
7265
+ // whose tips are dated, with no date on any ancestor (it is not a time
7266
+ // tree): a tip's divergence is the sum of the stated lengths from the
7267
+ // root. Tips only. This is the tree a clock plot is wanted for most --
7268
+ // is there a signal worth dating? -- and the one whose answer depends
7269
+ // on its root. TempEst's plot. A tip is dated by what the file STATES
7270
+ // for it (a phyloXML <date>, a num_date annotation), or else by the
7271
+ // date its NAME carries, read by the label-date parser both programs
7272
+ // anchor BEAST heights with (Christian, 2026-10-03: names count, as
7273
+ // TempEst reads them; a year alone is mid-year). An ancestor is never
7274
+ // dated by its name. Every tip must be dated and every branch must
7275
+ // state a length: a tip with either missing has no point, and a plot
7276
+ // that drops tips silently answers a different question than it was
7277
+ // asked. A clock-model tree (a rate on every branch) is never this
7278
+ // kind: its lengths are time.
7269
7279
  //
7270
7280
  // In both, the plot is offered when its LINE can be drawn: three tips or
7271
7281
  // more, and not all of them on one date. A tree sampled at one moment has
@@ -7277,19 +7287,76 @@
7277
7287
  // an observation; an ancestor's was inferred, usually with a clock, and
7278
7288
  // counting it would have the estimate confirm itself. The line is not
7279
7289
  // forced through the root, so the date it reaches the root's divergence
7280
- // at can be held against the date the tree states for the root.
7290
+ // at can be held against the date the tree states for the root -- where
7291
+ // the tree states one; a root-to-tip tree does not, and there the line's
7292
+ // date is the only estimate.
7281
7293
  const CLOCK_PLOT_MIN_TIPS = 3;
7282
7294
 
7295
+ // A tip's date on a root-to-tip tree: the one the file states, else the
7296
+ // one its name carries (forester.parseTipLabelDate: the rightmost date in
7297
+ // the label; a year alone is mid-year; an ambiguous numeric date is read
7298
+ // day first). {value, fromName, ambiguous}, or null for no date at all.
7299
+ function rootToTipDate(n) {
7300
+ let d = auspiceNodeDate(n);
7301
+ if (d !== null) {
7302
+ return {value: d, fromName: false, ambiguous: false};
7303
+ }
7304
+ let m = (typeof n.name === 'string' && n.name.length > 0) ? forester.parseTipLabelDate(n.name) : null;
7305
+ return m ? {value: m.decimalYear, fromName: true, ambiguous: m.ambiguous === true} : null;
7306
+ }
7307
+
7308
+ // Kind 2 of THE RULE, before the tips are counted: dated tips (by the
7309
+ // file or by their names), undated ancestors (by the file; a name dates
7310
+ // no ancestor), a length on every branch, no clock rates.
7311
+ function isRootToTipTree(root) {
7312
+ if (!root || root._divergenceFromRates === true || !everyLengthWasStated(root)) {
7313
+ return false;
7314
+ }
7315
+ let ok = true;
7316
+ forester.preOrderTraversalAll(root, function (n) {
7317
+ if (!ok) {
7318
+ return;
7319
+ }
7320
+ ok = (n.children && n.children.length > 0) ? auspiceNodeDate(n) === null : rootToTipDate(n) !== null;
7321
+ });
7322
+ return ok;
7323
+ }
7324
+
7325
+ // Each node's distance from the root along the stated branch lengths:
7326
+ // a root-to-tip tree's divergence.
7327
+ function rootToTipDistances(root) {
7328
+ let dist = new Map();
7329
+ (function walk(n, d) {
7330
+ dist.set(n, d);
7331
+ let children = n.children;
7332
+ if (children) {
7333
+ for (let i = 0; i < children.length; ++i) {
7334
+ let len = children[i].branch_length;
7335
+ walk(children[i], d + ((typeof len === 'number' && isFinite(len)) ? len : 0));
7336
+ }
7337
+ }
7338
+ }(root, 0));
7339
+ return dist;
7340
+ }
7341
+
7283
7342
  /**
7284
7343
  * Which clock plot the tree has, or null for none (see THE RULE above).
7285
7344
  *
7286
7345
  * @param phy the tree
7287
- * @returns {string|null} 'divergence', or null
7346
+ * @returns {string|null} 'divergence', 'root-to-tip', or null
7288
7347
  */
7289
7348
  forester.clockPlotKind = function (phy) {
7290
- if (!phy || !forester.hasTimeAndDivergence(phy)) {
7349
+ if (!phy) {
7291
7350
  return null;
7292
7351
  }
7352
+ let kind = forester.hasTimeAndDivergence(phy) ? 'divergence'
7353
+ : (isRootToTipTree(forester.getTreeRoot(phy)) ? 'root-to-tip' : null);
7354
+ if (kind === null) {
7355
+ return null;
7356
+ }
7357
+ let dateOf = kind === 'root-to-tip' ? function (n) {
7358
+ return rootToTipDate(n).value; // every tip has one: the kind says so
7359
+ } : auspiceNodeDate;
7293
7360
  let tips = 0;
7294
7361
  let first = null;
7295
7362
  let differ = false;
@@ -7298,14 +7365,14 @@
7298
7365
  return;
7299
7366
  }
7300
7367
  ++tips;
7301
- let d = auspiceNodeDate(n);
7368
+ let d = dateOf(n);
7302
7369
  if (first === null) {
7303
7370
  first = d;
7304
7371
  } else if (d !== first) {
7305
7372
  differ = true;
7306
7373
  }
7307
7374
  });
7308
- return (tips >= CLOCK_PLOT_MIN_TIPS && differ) ? 'divergence' : null;
7375
+ return (tips >= CLOCK_PLOT_MIN_TIPS && differ) ? kind : null;
7309
7376
  };
7310
7377
 
7311
7378
  /**
@@ -7363,14 +7430,25 @@
7363
7430
  * @param view the tree or clade to plot (the tree, a node, or the
7364
7431
  * viewer's subtree root); the whole tree where none is given
7365
7432
  * @returns {object|null} null where the tree has no clock plot; else
7366
- * kind 'divergence'
7433
+ * kind 'divergence' or 'root-to-tip'
7367
7434
  * forward whether the dates increase toward the tips (calendar
7368
7435
  * dates); false for ages
7369
7436
  * unit the dates' unit as the tree states it, or null
7437
+ * divUnit the divergence's unit: 'subs/site' for a tree with Time
7438
+ * and Div, a root-to-tip tree's stated branch length unit,
7439
+ * or null
7370
7440
  * fromRates whether the divergence is time x clock rate
7441
+ * calendar whether the dates are calendar years: their unit says so,
7442
+ * or they were read off the tips' names
7443
+ * fromNames how many tips are dated by their names (a root-to-tip
7444
+ * tree; 0 otherwise)
7445
+ * ambiguousNames how many of those names carry a day/month-ambiguous
7446
+ * numeric date, read day first
7371
7447
  * points [{node, date, div, tip}] in preorder; the tips'
7372
- * `residual` is their divergence less the line's
7373
- * root {node, date, div}: the top of the view
7448
+ * `residual` is their divergence less the line's. On a
7449
+ * root-to-tip tree the tips only: no ancestor has a date
7450
+ * root {node, date, div}: the top of the view; `date` null on a
7451
+ * root-to-tip tree
7374
7452
  * fit forester.clockRegression over the tips, with
7375
7453
  * rate the slope in the direction time runs: divergence
7376
7454
  * per unit of time (negative where the tips'
@@ -7391,24 +7469,49 @@
7391
7469
  if (view) {
7392
7470
  top = (!view.parent && view.children && view.children.length === 1) ? view.children[0] : view;
7393
7471
  }
7472
+ // a root-to-tip tree's divergence is the path from the TREE's root,
7473
+ // in the view of a clade too: the clade's top then has one as well
7474
+ let rootToTip = kind === 'root-to-tip';
7475
+ let dist = rootToTip ? rootToTipDistances(treeRoot) : null;
7476
+ let divOf = rootToTip ? function (n) {
7477
+ return dist.get(n);
7478
+ } : auspiceNodeDiv;
7394
7479
  let points = [];
7395
7480
  let xs = [];
7396
7481
  let ys = [];
7482
+ let fromNames = 0;
7483
+ let ambiguousNames = 0;
7397
7484
  forester.preOrderTraversalAll(top, function (n) {
7485
+ let tip = !(n.children && n.children.length > 0);
7398
7486
  let date = auspiceNodeDate(n);
7399
- let div = auspiceNodeDiv(n);
7487
+ if (rootToTip && tip && date === null) {
7488
+ let named = rootToTipDate(n); // every tip has one: the kind says so
7489
+ date = named ? named.value : null;
7490
+ if (named && named.fromName) {
7491
+ ++fromNames;
7492
+ if (named.ambiguous) {
7493
+ ++ambiguousNames;
7494
+ }
7495
+ }
7496
+ }
7497
+ let div = divOf(n);
7400
7498
  if (date === null || div === null) {
7401
- return; // not on a tree with Time and Div; a guard, not a rule
7499
+ return; // an undated ancestor (root-to-tip), or a guard, not a rule
7402
7500
  }
7403
- let tip = !(n.children && n.children.length > 0);
7404
7501
  points.push({node: n, date: date, div: div, tip: tip});
7405
7502
  if (tip) {
7406
7503
  xs.push(date);
7407
7504
  ys.push(div);
7408
7505
  }
7409
7506
  });
7410
- let forward = timeIncreasesTowardTips(treeRoot);
7411
- let root = {node: top, date: auspiceNodeDate(top), div: auspiceNodeDiv(top)};
7507
+ let info = forester.timeAxisInfo(treeRoot);
7508
+ // a root-to-tip tree has no dated pair to read the direction off:
7509
+ // its dates are calendar dates unless their unit says ages
7510
+ let forward = rootToTip ? info.type !== 'geologic' : timeIncreasesTowardTips(treeRoot);
7511
+ // calendar years: the dates' unit says so, or they were read off the
7512
+ // tips' names, which carry no other kind of date
7513
+ let calendar = forward && (info.type === 'calendar' || (rootToTip && info.type === null && fromNames > 0));
7514
+ let root = {node: top, date: auspiceNodeDate(top), div: divOf(top)};
7412
7515
  let fit = forester.clockRegression(xs, ys);
7413
7516
  if (fit) {
7414
7517
  fit.rate = forward ? fit.slope : -fit.slope;
@@ -7420,11 +7523,17 @@
7420
7523
  }
7421
7524
  }
7422
7525
  }
7526
+ let lengthUnit = (phy !== treeRoot && typeof phy.branch_length_unit === 'string'
7527
+ && phy.branch_length_unit.trim().length > 0) ? phy.branch_length_unit.trim() : null;
7423
7528
  return {
7424
7529
  kind: kind,
7425
7530
  forward: forward,
7426
7531
  unit: dateUnitOf(treeRoot),
7427
- fromRates: treeRoot._divergenceFromRates === true && !recordsDivergence(treeRoot),
7532
+ divUnit: rootToTip ? lengthUnit : DIVERGENCE_LENGTH_UNIT,
7533
+ fromRates: !rootToTip && treeRoot._divergenceFromRates === true && !recordsDivergence(treeRoot),
7534
+ calendar: calendar,
7535
+ fromNames: fromNames,
7536
+ ambiguousNames: ambiguousNames,
7428
7537
  points: points,
7429
7538
  root: root,
7430
7539
  fit: fit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archaeopteryx",
3
- "version": "3.23.0",
3
+ "version": "3.24.0",
4
4
  "description": "Archaeopteryx.js is a software tool for the visualization and analysis of highly annotated phylogenetic trees.",
5
5
  "main": "archaeopteryx.js",
6
6
  "types": "archaeopteryx.d.ts",