archaeopteryx 3.18.0 → 3.19.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 +29 -15
- package/archaeopteryx.d.ts +12 -0
- package/archaeopteryx.js +253 -71
- package/forester.js +14 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -247,9 +247,20 @@ A phylogram carries a **scale bar** at the bottom left: a round number of
|
|
|
247
247
|
branch-length units (1, 2 or 5 × 10ᵏ, whichever makes the bar about 100 px)
|
|
248
248
|
with its length written above it. It is drawn with the tree, so it zooms and
|
|
249
249
|
exports with it and its label always holds. A cladogram has nothing to
|
|
250
|
-
measure and shows none, and a tree under a
|
|
250
|
+
measure and shows none, and a tree under a scale axis leaves the measuring to
|
|
251
251
|
the axis.
|
|
252
252
|
|
|
253
|
+
**Scale Axis** under Display Data (off by default, as on the desktop) draws a
|
|
254
|
+
labelled ruler under a phylogram, from 0 at the root out to the deepest tip, in
|
|
255
|
+
the tree's branch-length units, with the unit at its end when the tree states
|
|
256
|
+
one. **Scale Grid** adds a faint line at every tick, behind the tree. In the
|
|
257
|
+
circular layout the ruler runs out from the root along the gap between the last
|
|
258
|
+
tip and the first, and the grid lines become rings; the unrooted layout has no
|
|
259
|
+
axis. The ruler floats at the bottom of the window as the tree scrolls, and it
|
|
260
|
+
starts at a subtree view's own root. On a time tree the same switch draws the
|
|
261
|
+
time axis instead (below): a tree flipped to **Div** gets the distance ruler,
|
|
262
|
+
and one flipped back gets its time axis.
|
|
263
|
+
|
|
253
264
|
In the two radial layouts the zoom row changes meaning, exactly as on the
|
|
254
265
|
desktop: **Y+ / Y− become the plain + / − zoom** (a circle has one diameter;
|
|
255
266
|
the mouse wheel zooms too, and never rotates), **X− / X+ become rotate** (a
|
|
@@ -479,7 +490,7 @@ too.
|
|
|
479
490
|
| ⌘⇧E | Ctrl+Shift+E | Expand vertically until the labels fit |
|
|
480
491
|
| ⌘⇧L | Ctrl+Shift+L | Next layout: rectangular, circular, unrooted |
|
|
481
492
|
| ⌘⇧D | Ctrl+Shift+D | Next display type: phylogram, aligned, cladogram |
|
|
482
|
-
| ⌘⇧X | Ctrl+Shift+X | Time
|
|
493
|
+
| ⌘⇧X | Ctrl+Shift+X | Time \| Div, where the tree offers it; the Scale Axis on / off otherwise |
|
|
483
494
|
| ⌘⇧O | Ctrl+Shift+O | Ladderize |
|
|
484
495
|
| ⌘⇧U | Ctrl+Shift+U | Uncollapse every clade |
|
|
485
496
|
| ⌘F | Ctrl+F | Go to the search box |
|
|
@@ -504,9 +515,10 @@ desktop's Unicode entry: it stays with the text field there, as it should.
|
|
|
504
515
|
A view is what you made of a tree with the panel: the layout and display
|
|
505
516
|
type, which labels show, the colour and shape fields, both searches, the
|
|
506
517
|
clade you switched to, the clades you collapsed, the font, node and branch
|
|
507
|
-
sizes, the rotation,
|
|
508
|
-
|
|
509
|
-
|
|
518
|
+
sizes, the rotation, whether the branches show time or divergence, and the
|
|
519
|
+
tracks — which track is shown, the heat map's column order (including one
|
|
520
|
+
you arranged by hand) and whether the alignment is summarised as a logo. On
|
|
521
|
+
the demo pages it rides in the URL's
|
|
510
522
|
`#` hash and follows every change, so the address bar is always a link to
|
|
511
523
|
what is on screen: copy it (**Copy link to this view** in the toolbar) and
|
|
512
524
|
the recipient opens the same tree in the same view. Opening your own file
|
|
@@ -733,12 +745,12 @@ nodes, and on tips whatever the axis says the range means — on geologic
|
|
|
733
745
|
time a fossil's observed range, sepia **FAD/LAD bars with end caps**; on
|
|
734
746
|
calendar time the uncertainty of a **sampling date**, drawn like an age bar,
|
|
735
747
|
slimmer, and only when it has a width. Node tooltips show the date. The
|
|
736
|
-
**
|
|
748
|
+
**Scale Axis** checkbox under Display Data toggles everything; the axis needs
|
|
737
749
|
a phylogram and the rectangular or circular layout. In **circular** the bars
|
|
738
750
|
run along each node's spoke — on through the centre where a range reaches
|
|
739
751
|
back past the root's own age — the ruler runs out from the root along the
|
|
740
752
|
gap between the last tip and the first, turning with the tree, and a
|
|
741
|
-
geologic axis lays its intervals down as coloured rings. **
|
|
753
|
+
geologic axis lays its intervals down as coloured rings. **Scale Grid** (off
|
|
742
754
|
by default) adds faint lines, or rings, at the fine interval boundaries or
|
|
743
755
|
the calendar years.
|
|
744
756
|
|
|
@@ -767,7 +779,7 @@ builds state — keeps its sign, and is drawn at length 0 like any negative
|
|
|
767
779
|
branch length. The switch acts on the whole tree, also from a subtree view,
|
|
768
780
|
and a node deleted meanwhile leaves the branch that takes its place spanning
|
|
769
781
|
what both spanned, in either layout. The time axis is hidden while the
|
|
770
|
-
branches show divergence. Each layout stamps the tree's `branch_length_unit`
|
|
782
|
+
branches show divergence, and the Scale Axis measures distance instead. Each layout stamps the tree's `branch_length_unit`
|
|
771
783
|
(`subs/site`; for time the dates' unit, or `time`), so a tree saved while
|
|
772
784
|
Div is on screen says so and opens again showing divergence. All of this is
|
|
773
785
|
shared with the desktop.
|
|
@@ -1164,7 +1176,8 @@ developer spec below). The checkboxes are still there to override it.
|
|
|
1164
1176
|
|
|
1165
1177
|
The same data-driven rule applies to the two big overlays: a tree whose tips
|
|
1166
1178
|
carry an aligned `mol_seq` opens with its **sequence alignment** showing, and
|
|
1167
|
-
a tree with phyloXML `<date>` elements opens
|
|
1179
|
+
a tree with phyloXML `<date>` elements that opens shown in time has its
|
|
1180
|
+
**time axis** drawn —
|
|
1168
1181
|
each with a checkbox under Display Data to turn it off.
|
|
1169
1182
|
|
|
1170
1183
|
Support and branch-length values draw **2 px smaller than the label font**
|
|
@@ -1348,8 +1361,8 @@ copy-pastable JSON.
|
|
|
1348
1361
|
| `domainLabels` | `'domains'` | Where domain names go: `'domains'` (on the boxes), `'legend'` (a card), or `'none'`. |
|
|
1349
1362
|
| `domainGlow` | `false` | Open with the glow around each domain box on. |
|
|
1350
1363
|
| `domainEvalueExponent` | `-3` | The E-value threshold's exponent at launch, an integer from `-20` to `3`: domains with an E-value at or under `10^exponent` are drawn. |
|
|
1351
|
-
| `
|
|
1352
|
-
| `
|
|
1364
|
+
| `showScaleAxis` | tree-derived | Open with the scale axis shown: the time axis on a time tree whose branches show time, a distance axis on any other phylogram. Default: on when the tree carries `<date>` elements and opens shown in time, off otherwise (a dated tree that opens in divergence is a divergence tree on screen) — an explicit `true`/`false` overrides that. |
|
|
1365
|
+
| `showScaleGrid` | `false` | Open with the Scale Grid lines on (only meaningful — and only offered as a checkbox — while the scale axis itself is shown). |
|
|
1353
1366
|
| `showSupportDots` | `false` | Open with the Support Dots marks on (the checkbox appears whenever the tree has confidences). |
|
|
1354
1367
|
| `supportDotMinimum` | `95` | Support Dots threshold, as a percentage. On a tree whose confidences top out at 1 (posterior probabilities) it is read on the 0–1 scale, so the default means ≥ 0.95 there and ≥ 95 on a bootstrap tree. |
|
|
1355
1368
|
| `searchAinitialValue` | `null` | Prefill search box A. |
|
|
@@ -1372,7 +1385,7 @@ copy-pastable JSON.
|
|
|
1372
1385
|
### Anything else throws
|
|
1373
1386
|
|
|
1374
1387
|
An unrecognised key is an error, whether it was removed in this modernization
|
|
1375
|
-
or simply mistyped (the
|
|
1388
|
+
or simply mistyped (the deprecated keys below are the exception — they
|
|
1376
1389
|
warn rather than throw):
|
|
1377
1390
|
|
|
1378
1391
|
```
|
|
@@ -1387,14 +1400,15 @@ whatever it names.
|
|
|
1387
1400
|
|
|
1388
1401
|
### Accepted, with a warning
|
|
1389
1402
|
|
|
1390
|
-
|
|
1391
|
-
embed keeps working, and warn on the console.
|
|
1392
|
-
values.
|
|
1403
|
+
Four keys are neither current nor removed: they are accepted so an existing
|
|
1404
|
+
embed keeps working, and warn on the console.
|
|
1393
1405
|
|
|
1394
1406
|
| Key | What happens |
|
|
1395
1407
|
|---|---|
|
|
1396
1408
|
| `nhConfidenceValuesAsInternalNames` | Translated to `internalNumericLabels`. **`true` becomes `'confidence'`, not `'auto'`** — `'auto'` is all-or-nothing and promotes nothing in a tree that mixes clade names with support, so a caller moved to it silently would lose promotions they had. An explicit `internalNumericLabels` always wins. |
|
|
1397
1409
|
| `nhConfidenceValuesInBrackets` | Retired: ignored. It gated whether `[95]` is read as a confidence, but setting it `false` never reinterpreted the bracket — it *discarded* it, so the option's only effect was to throw support values away. A bracket that is not a number is a Newick comment and was ignored either way, and NHX / BEAST blobs go through a different path. Bracketed values are now always read as confidences. |
|
|
1410
|
+
| `showTimeAxis` | Translated to `showScaleAxis`, value unchanged: the switch now also draws a distance axis on a tree that is not in time. An explicit `showScaleAxis` always wins. |
|
|
1411
|
+
| `timeAxisGrid` | Translated to `showScaleGrid`, value unchanged. |
|
|
1398
1412
|
|
|
1399
1413
|
### What replaced the rest
|
|
1400
1414
|
|
package/archaeopteryx.d.ts
CHANGED
|
@@ -104,9 +104,17 @@ export interface ArchaeopteryxConfig {
|
|
|
104
104
|
* bar: each column a stack of letters as tall as its information content,
|
|
105
105
|
* over the tips currently on screen. Only drawn with the track shown. */
|
|
106
106
|
showMsaLogo?: boolean;
|
|
107
|
+
/** The scale axis under a phylogram: the time axis on a time tree whose
|
|
108
|
+
* branches show time, a distance axis on any other. Default: on for a
|
|
109
|
+
* time tree, off otherwise. */
|
|
110
|
+
showScaleAxis?: boolean;
|
|
111
|
+
/** Grid lines (rings, in circular) at the scale axis's ticks. */
|
|
112
|
+
showScaleGrid?: boolean;
|
|
107
113
|
showSupportDots?: boolean;
|
|
114
|
+
/** @deprecated renamed showScaleAxis; still read, with a warning. */
|
|
108
115
|
showTimeAxis?: boolean;
|
|
109
116
|
supportDotMinimum?: number;
|
|
117
|
+
/** @deprecated renamed showScaleGrid; still read, with a warning. */
|
|
110
118
|
timeAxisGrid?: boolean;
|
|
111
119
|
/** Open straight into a view (getViewState / decodeViewState). */
|
|
112
120
|
view?: ViewState | null;
|
|
@@ -165,7 +173,11 @@ export interface ViewState {
|
|
|
165
173
|
domainLabels?: 'none' | 'domains' | 'legend';
|
|
166
174
|
domainGlow?: boolean;
|
|
167
175
|
domainEvalue?: number;
|
|
176
|
+
scaleAxis?: boolean;
|
|
177
|
+
scaleGrid?: boolean;
|
|
178
|
+
/** @deprecated the name of scaleAxis in older links; still read. */
|
|
168
179
|
timeAxis?: boolean;
|
|
180
|
+
/** @deprecated the name of scaleGrid in older links; still read. */
|
|
169
181
|
timeGrid?: boolean;
|
|
170
182
|
searchA?: ViewSearch;
|
|
171
183
|
searchB?: ViewSearch;
|
package/archaeopteryx.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
// v 3.
|
|
23
|
+
// v 3.19.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.
|
|
106
|
+
const VERSION = '3.19.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';
|
|
@@ -380,8 +380,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
380
380
|
const DOMAIN_EVALUE_READOUT = 'domain_evalue';
|
|
381
381
|
const DOMAIN_LABELS_SELECT = 'domain_labels';
|
|
382
382
|
const DOMAIN_GLOW_CB = 'domain_glow_cb';
|
|
383
|
-
const
|
|
384
|
-
const
|
|
383
|
+
const SCALE_AXIS_CB = 'scaleaxis_cb';
|
|
384
|
+
const SCALE_GRID_CB = 'scalegrid_cb';
|
|
385
385
|
const MSA_SCROLL_ID = 'aptxmsascroll';
|
|
386
386
|
const FONT_SIZE_SLIDER = 'fs_sl';
|
|
387
387
|
const EXTERNAL_LABEL_CB = 'extl_cb';
|
|
@@ -641,6 +641,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
641
641
|
// ------ time axis (the desktop's geologic / calendar overlays) ------
|
|
642
642
|
const TIME_GEO_RESERVE = 52; // two ICS band rows + the Ma ruler
|
|
643
643
|
const TIME_CAL_RESERVE = 26; // the calendar year ruler
|
|
644
|
+
const DISTANCE_AXIS_RESERVE = 26; // the distance ruler, one label row
|
|
644
645
|
const TIME_BAND_ROW_H = 13;
|
|
645
646
|
const HPD_BAR_COLOR = 'rgba(70,130,220,0.35)'; // translucent blue, the usual HPD tint
|
|
646
647
|
const FOSSIL_BAR_COLOR = 'rgba(150,100,55,0.86)'; // opaque-ish sepia
|
|
@@ -5440,6 +5441,23 @@ function (root, d3, forester, phyloXml) {
|
|
|
5440
5441
|
return v === true ? 'confidence' : PARSE_DEFAULTS.internalNumericLabels;
|
|
5441
5442
|
},
|
|
5442
5443
|
note: 'use \'confidence\' for what this flag did (\'auto\' is the new default, and does not promote mixed trees)'
|
|
5444
|
+
},
|
|
5445
|
+
// Renamed in 3.19.0, when the axis stopped being time-only: a tree
|
|
5446
|
+
// whose branches measure divergence gets a distance axis from the
|
|
5447
|
+
// same switch. The meaning on a time tree is unchanged.
|
|
5448
|
+
showTimeAxis: {
|
|
5449
|
+
to: 'showScaleAxis',
|
|
5450
|
+
map: function (v) {
|
|
5451
|
+
return v;
|
|
5452
|
+
},
|
|
5453
|
+
note: 'the one switch now draws the time axis on a time tree and a distance axis on any other phylogram'
|
|
5454
|
+
},
|
|
5455
|
+
timeAxisGrid: {
|
|
5456
|
+
to: 'showScaleGrid',
|
|
5457
|
+
map: function (v) {
|
|
5458
|
+
return v;
|
|
5459
|
+
},
|
|
5460
|
+
note: 'the grid follows the scale axis, time or distance'
|
|
5443
5461
|
}
|
|
5444
5462
|
};
|
|
5445
5463
|
|
|
@@ -5610,8 +5628,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
5610
5628
|
'domainLabels',
|
|
5611
5629
|
'domainGlow',
|
|
5612
5630
|
'domainEvalueExponent',
|
|
5613
|
-
'
|
|
5614
|
-
'
|
|
5631
|
+
'showScaleAxis',
|
|
5632
|
+
'showScaleGrid',
|
|
5615
5633
|
'showSupportDots',
|
|
5616
5634
|
'searchAinitialValue',
|
|
5617
5635
|
'searchBinitialValue',
|
|
@@ -5910,10 +5928,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
5910
5928
|
+ ' with a missing or impossible from / to / E-value ignored');
|
|
5911
5929
|
}
|
|
5912
5930
|
}
|
|
5913
|
-
// Likewise a dated tree draws its
|
|
5914
|
-
// geologic ICS bands or calendar years, decided from the
|
|
5915
|
-
// elements by forester.timeAxisInfo -- again unless the caller
|
|
5916
|
-
//
|
|
5931
|
+
// Likewise a dated tree draws its scale axis from the start -- a
|
|
5932
|
+
// time axis of geologic ICS bands or calendar years, decided from the
|
|
5933
|
+
// <date> elements by forester.timeAxisInfo -- again unless the caller
|
|
5934
|
+
// set showScaleAxis explicitly. Any other tree opens without one, as
|
|
5935
|
+
// on the desktop: its distance axis is asked for.
|
|
5917
5936
|
_timeInfo = _treeData ? forester.timeAxisInfo(forester.getTreeRoot(_treeData)) : null;
|
|
5918
5937
|
_timeTree = _treeData ? forester.isTimeTree(_treeData) : false;
|
|
5919
5938
|
// Whether this tree says two different things -- when its nodes sit in
|
|
@@ -5922,10 +5941,13 @@ function (root, d3, forester, phyloXml) {
|
|
|
5922
5941
|
// BEAST or Newick file arrives stating its own branch lengths.
|
|
5923
5942
|
_branchScaleAvailable = _treeData ? forester.hasTimeAndDivergence(_treeData) : false;
|
|
5924
5943
|
_state.branchScale = _treeData ? forester.branchLengthScale(_treeData) : 'divergence';
|
|
5925
|
-
if (_state.
|
|
5926
|
-
|
|
5944
|
+
if (_state.showScaleAxis === undefined) {
|
|
5945
|
+
// on for a tree SHOWN in time; a dated tree that opens in
|
|
5946
|
+
// divergence (a build refused the switch) is a divergence tree
|
|
5947
|
+
// on screen, and opens without its distance axis like any other
|
|
5948
|
+
_state.showScaleAxis = !!(_timeInfo && _timeInfo.type) && !branchesShowDivergence();
|
|
5927
5949
|
}
|
|
5928
|
-
_state.
|
|
5950
|
+
_state.showScaleGrid = _state.showScaleGrid === true; // desktop default: off
|
|
5929
5951
|
_state.showSupportDots = _state.showSupportDots === true;
|
|
5930
5952
|
_state.showNodeEvents = _basicTreeProperties.nodeEvents === true;
|
|
5931
5953
|
_state.showBranchEvents = _basicTreeProperties.branchEvents === true;
|
|
@@ -9737,9 +9759,12 @@ function (root, d3, forester, phyloXml) {
|
|
|
9737
9759
|
s.domainGlow = _state.domainGlow === true;
|
|
9738
9760
|
s.domainEvalue = _state.domainEvalueExponent;
|
|
9739
9761
|
}
|
|
9740
|
-
if (
|
|
9741
|
-
s.
|
|
9742
|
-
s.
|
|
9762
|
+
if (_basicTreeProperties.branchLengths === true) {
|
|
9763
|
+
s.scaleAxis = _state.showScaleAxis === true;
|
|
9764
|
+
s.scaleGrid = _state.showScaleGrid === true;
|
|
9765
|
+
}
|
|
9766
|
+
if (_branchScaleAvailable) {
|
|
9767
|
+
s.scale = _state.branchScale; // time or divergence: which layout the branches show
|
|
9743
9768
|
}
|
|
9744
9769
|
let a = searchStateOf(0);
|
|
9745
9770
|
let b = searchStateOf(1);
|
|
@@ -9810,6 +9835,16 @@ function (root, d3, forester, phyloXml) {
|
|
|
9810
9835
|
}
|
|
9811
9836
|
});
|
|
9812
9837
|
}
|
|
9838
|
+
// the branches' layout, time or divergence, on a tree that has both:
|
|
9839
|
+
// laid out before the properties are measured, since the lengths are
|
|
9840
|
+
// the layout. A tree with one layout ignores the key.
|
|
9841
|
+
if (_branchScaleAvailable && (s.scale === 'time' || s.scale === 'divergence') && s.scale !== _state.branchScale) {
|
|
9842
|
+
let laidOut = (s.scale === 'time') ? forester.applyTimeBranchLengths(_treeData)
|
|
9843
|
+
: forester.applyDivergenceBranchLengths(_treeData);
|
|
9844
|
+
if (laidOut) {
|
|
9845
|
+
_state.branchScale = s.scale;
|
|
9846
|
+
}
|
|
9847
|
+
}
|
|
9813
9848
|
_basicTreeProperties = forester.collectBasicTreeProperties(_root);
|
|
9814
9849
|
|
|
9815
9850
|
if (s.layout === 'rectangular' || s.layout === 'circular' || s.layout === 'unrooted') {
|
|
@@ -9881,11 +9916,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
9881
9916
|
_domain.palette = null; // the drawn set changed: the palette is dealt again
|
|
9882
9917
|
}
|
|
9883
9918
|
}
|
|
9884
|
-
|
|
9885
|
-
|
|
9919
|
+
// timeAxis / timeGrid: the names before the axis also measured
|
|
9920
|
+
// distance, still read so that older links keep their axis
|
|
9921
|
+
let axis = typeof s.scaleAxis === 'boolean' ? s.scaleAxis : s.timeAxis;
|
|
9922
|
+
if (typeof axis === 'boolean') {
|
|
9923
|
+
_state.showScaleAxis = axis;
|
|
9886
9924
|
}
|
|
9887
|
-
|
|
9888
|
-
|
|
9925
|
+
let grid = typeof s.scaleGrid === 'boolean' ? s.scaleGrid : s.timeGrid;
|
|
9926
|
+
if (typeof grid === 'boolean') {
|
|
9927
|
+
_state.showScaleGrid = grid;
|
|
9889
9928
|
}
|
|
9890
9929
|
if (!radialDisplay()) {
|
|
9891
9930
|
_radialLabelsHorizontal = false;
|
|
@@ -9952,6 +9991,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9952
9991
|
function syncViewControls() {
|
|
9953
9992
|
setDisplayTypeButtons();
|
|
9954
9993
|
syncZoomRowButtons();
|
|
9994
|
+
syncBranchScaleControls();
|
|
9955
9995
|
VIEW_SHOW_FLAGS.forEach(function (f) {
|
|
9956
9996
|
setCheckboxValue(f[1], _state[f[2]] === true);
|
|
9957
9997
|
});
|
|
@@ -9964,8 +10004,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
9964
10004
|
setCheckboxValue(MSA_LOGO_CB, _state.showMsaLogo === true);
|
|
9965
10005
|
setCheckboxValue(HEATMAP_CB, _state.showHeatmap === true);
|
|
9966
10006
|
syncHeatmapControls();
|
|
9967
|
-
setCheckboxValue(
|
|
9968
|
-
setCheckboxValue(
|
|
10007
|
+
setCheckboxValue(SCALE_AXIS_CB, _state.showScaleAxis === true);
|
|
10008
|
+
setCheckboxValue(SCALE_GRID_CB, _state.showScaleGrid === true);
|
|
9969
10009
|
setCheckboxValue(DOMAIN_GLOW_CB, _state.domainGlow === true);
|
|
9970
10010
|
setValue(DOMAIN_LABELS_SELECT, _state.domainLabels);
|
|
9971
10011
|
syncDomainControls();
|
|
@@ -10041,11 +10081,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
10041
10081
|
// The heat map's four keys and the alignment logo went in exactly that
|
|
10042
10082
|
// way, while the README promised a link reproduced the figure.
|
|
10043
10083
|
const VIEW_TEXT_KEYS = ['layout', 'display', 'order', 'root', 'colorBy', 'shapeBy', 'domainLabels', 'combine',
|
|
10044
|
-
'heatmapOrder'];
|
|
10084
|
+
'heatmapOrder', 'scale'];
|
|
10045
10085
|
const VIEW_INT_KEYS = ['tree', 'subtree', 'rotation', 'domainEvalue'];
|
|
10046
10086
|
const VIEW_NUMBER_KEYS = ['font', 'node', 'branch'];
|
|
10047
10087
|
const VIEW_BOOL_KEYS = ['horizontalLabels', 'msa', 'msaLogo', 'heatmap', 'domains', 'domainGlow',
|
|
10048
|
-
'timeAxis', 'timeGrid', 'matchCase', 'inverse'];
|
|
10088
|
+
'scaleAxis', 'scaleGrid', 'timeAxis', 'timeGrid', 'matchCase', 'inverse'];
|
|
10049
10089
|
// Lists of strings, comma-joined. A property ref carries ':' but never a
|
|
10050
10090
|
// comma, and encodeViewValue leaves both readable in a hash.
|
|
10051
10091
|
const VIEW_LIST_KEYS = ['heatmapManual'];
|
|
@@ -10212,7 +10252,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
10212
10252
|
{key: 'e', label: 'E', shift: true, what: 'Expand vertically until the labels fit', run: function () { zoomToExpandY(); }},
|
|
10213
10253
|
{key: 'l', label: 'L', shift: true, what: 'Next layout: rectangular, circular, unrooted', run: cycleLayout},
|
|
10214
10254
|
{key: 'd', label: 'D', shift: true, what: 'Next display type: phylogram, aligned, cladogram', run: cycleDisplayType},
|
|
10215
|
-
{key: 'x', label: 'X', shift: true, what: 'Time / divergence scale, or the
|
|
10255
|
+
{key: 'x', label: 'X', shift: true, what: 'Time / divergence scale, or the scale axis', run: toggleScaleAxis},
|
|
10216
10256
|
{key: 'o', label: 'O', shift: true, what: 'Ladderize (order the tree)', run: function () { ladderizeButtonPressed(); }},
|
|
10217
10257
|
{key: 'u', label: 'U', shift: true, what: 'Uncollapse every clade', run: function () { uncollapseAll(); }},
|
|
10218
10258
|
{key: 'f', label: 'F', what: 'Go to the search box', run: focusSearch, typing: true},
|
|
@@ -10359,19 +10399,19 @@ function (root, d3, forester, phyloXml) {
|
|
|
10359
10399
|
}
|
|
10360
10400
|
|
|
10361
10401
|
// Shift+X. The switch takes this key over where the tree offers it, as was
|
|
10362
|
-
// always intended; on every other tree it
|
|
10402
|
+
// always intended; on every other tree it works the Scale Axis
|
|
10363
10403
|
// checkbox, so the key never does nothing.
|
|
10364
|
-
function
|
|
10404
|
+
function toggleScaleAxis() {
|
|
10365
10405
|
if (_branchScaleAvailable) {
|
|
10366
10406
|
setBranchScale(_state.branchScale === 'time' ? 'divergence' : 'time');
|
|
10367
10407
|
return;
|
|
10368
10408
|
}
|
|
10369
|
-
let cb = byId(
|
|
10409
|
+
let cb = byId(SCALE_AXIS_CB);
|
|
10370
10410
|
if (!cb || cb.disabled) {
|
|
10371
10411
|
return;
|
|
10372
10412
|
}
|
|
10373
10413
|
cb.checked = !cb.checked;
|
|
10374
|
-
|
|
10414
|
+
scaleAxisCbClicked();
|
|
10375
10415
|
}
|
|
10376
10416
|
|
|
10377
10417
|
function focusSearch() {
|
|
@@ -11577,11 +11617,24 @@ function (root, d3, forester, phyloXml) {
|
|
|
11577
11617
|
&& _treeData.branch_length_unit.trim() === 'subs/site';
|
|
11578
11618
|
}
|
|
11579
11619
|
|
|
11580
|
-
|
|
11581
|
-
|
|
11620
|
+
// The Scale Axis switch, as on the desktop: an axis under a rectangular
|
|
11621
|
+
// or circular phylogram. What it measures is the tree's to say -- time
|
|
11622
|
+
// on a time tree whose branches show time, distance from the root on
|
|
11623
|
+
// every other -- so flipping Time | Div changes what the axis reads,
|
|
11624
|
+
// never whether there is one.
|
|
11625
|
+
function scaleAxisShown() {
|
|
11626
|
+
return _state.showScaleAxis === true && !_state.unrootedDisplay
|
|
11582
11627
|
&& _state.phylogram === true
|
|
11628
|
+
&& _basicTreeProperties.branchLengths === true;
|
|
11629
|
+
}
|
|
11630
|
+
|
|
11631
|
+
function distanceAxisShown() {
|
|
11632
|
+
return scaleAxisShown() && !timeAxisShown();
|
|
11633
|
+
}
|
|
11634
|
+
|
|
11635
|
+
function timeAxisShown() {
|
|
11636
|
+
return scaleAxisShown()
|
|
11583
11637
|
&& _timeInfo !== null && _timeInfo.type !== null
|
|
11584
|
-
&& _basicTreeProperties.branchLengths === true
|
|
11585
11638
|
// The axis is an overlay calibrated to the tree's own branch scale,
|
|
11586
11639
|
// so it must be hidden when the branches are showing DIVERGENCE --
|
|
11587
11640
|
// calendar years against substitutions reads as a confident lie.
|
|
@@ -11594,11 +11647,14 @@ function (root, d3, forester, phyloXml) {
|
|
|
11594
11647
|
&& !branchesShowDivergence();
|
|
11595
11648
|
}
|
|
11596
11649
|
|
|
11597
|
-
function
|
|
11650
|
+
function scaleAxisBottomReserve() {
|
|
11598
11651
|
// circular draws its ruler in the tree's own gap, not under it
|
|
11599
|
-
if (!
|
|
11652
|
+
if (!scaleAxisShown() || _state.circularDisplay) {
|
|
11600
11653
|
return 0;
|
|
11601
11654
|
}
|
|
11655
|
+
if (!timeAxisShown()) {
|
|
11656
|
+
return DISTANCE_AXIS_RESERVE;
|
|
11657
|
+
}
|
|
11602
11658
|
return _timeInfo.type === 'geologic' ? TIME_GEO_RESERVE : TIME_CAL_RESERVE;
|
|
11603
11659
|
}
|
|
11604
11660
|
|
|
@@ -11610,7 +11666,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
11610
11666
|
// own bottom rows (conservation, consensus, ruler) must end above it
|
|
11611
11667
|
// or the bar covers them -- the old bare slider did exactly that.
|
|
11612
11668
|
// The scale bar sits in the same band, under the tree's last row.
|
|
11613
|
-
return Math.max(msaShown() ? msaBottomReserve() + MSA_NAV_RESERVE : 0,
|
|
11669
|
+
return Math.max(msaShown() ? msaBottomReserve() + MSA_NAV_RESERVE : 0, scaleAxisBottomReserve(),
|
|
11614
11670
|
heatmapBottomReserve(),
|
|
11615
11671
|
scaleBarShown() && !radialDisplay() ? SCALE_BAR_RESERVE : 0);
|
|
11616
11672
|
}
|
|
@@ -11620,14 +11676,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
11620
11676
|
// round number of branch-length units (forester.scaleBarLength) with
|
|
11621
11677
|
// end ticks and its length written above, drawn in the tree's own
|
|
11622
11678
|
// coordinates so it zooms and exports with the tree and its label stays
|
|
11623
|
-
// true. Not in a cladogram (nothing to measure) and not under
|
|
11624
|
-
// axis (which
|
|
11625
|
-
// the fan at its left edge.
|
|
11679
|
+
// true. Not in a cladogram (nothing to measure) and not under the scale
|
|
11680
|
+
// axis (which says the same, and more); in the radial layouts it sits
|
|
11681
|
+
// below the fan at its left edge. The desktop keeps its bar beside its
|
|
11682
|
+
// axis; ours gives way (Christian, 2026-09-30).
|
|
11626
11683
|
const SCALE_BAR_TARGET_PX = 100;
|
|
11627
11684
|
const SCALE_BAR_RESERVE = 30;
|
|
11628
11685
|
|
|
11629
11686
|
function scaleBarShown() {
|
|
11630
|
-
return _state.phylogram === true && _basicTreeProperties.branchLengths === true && !
|
|
11687
|
+
return _state.phylogram === true && _basicTreeProperties.branchLengths === true && !scaleAxisShown();
|
|
11631
11688
|
}
|
|
11632
11689
|
|
|
11633
11690
|
// pixels per branch-length unit in the current layout's own coordinates
|
|
@@ -11703,6 +11760,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
11703
11760
|
delete _floatStrips['aptx-time-axis'];
|
|
11704
11761
|
}
|
|
11705
11762
|
_svgGroup.selectAll('g.aptx-timegrid').remove();
|
|
11763
|
+
_svgGroup.selectAll('g.aptx-distance, g.aptx-distance-grid').remove();
|
|
11764
|
+
if (_floatGroup) {
|
|
11765
|
+
_floatGroup.selectAll('g.aptx-distance-axis').remove();
|
|
11766
|
+
delete _floatStrips['aptx-distance-axis'];
|
|
11767
|
+
}
|
|
11768
|
+
if (distanceAxisShown()) {
|
|
11769
|
+
drawDistanceAxis();
|
|
11770
|
+
return;
|
|
11771
|
+
}
|
|
11706
11772
|
if (!timeAxisShown() || !_root || !_yScale) {
|
|
11707
11773
|
return;
|
|
11708
11774
|
}
|
|
@@ -11744,7 +11810,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
11744
11810
|
// BEHIND the tree (inserted first in the group) at the fine geologic
|
|
11745
11811
|
// boundaries / calendar year ticks; each axis branch below fills it
|
|
11746
11812
|
let grid = null;
|
|
11747
|
-
if (_state.
|
|
11813
|
+
if (_state.showScaleGrid) {
|
|
11748
11814
|
grid = _svgGroup.insert('g', 'g').attr('class', 'aptx-timegrid')
|
|
11749
11815
|
.style('pointer-events', 'none');
|
|
11750
11816
|
}
|
|
@@ -11858,17 +11924,16 @@ function (root, d3, forester, phyloXml) {
|
|
|
11858
11924
|
}
|
|
11859
11925
|
|
|
11860
11926
|
// ---- the axis itself: a FLOATING strip (see floatStripGroup) ----
|
|
11861
|
-
|
|
11927
|
+
// the viewer's sans-serif: an SVG text inherits the browser's serif
|
|
11928
|
+
let ax = floatStripGroup('aptx-time-axis', axisTop - 4, scaleAxisBottomReserve() + 4)
|
|
11929
|
+
.style('font-family', FONT_DEFAULTS);
|
|
11862
11930
|
function axisBackdrop(x0, x1) {
|
|
11863
11931
|
// opaque, in the background colour, so tips panned under the
|
|
11864
11932
|
// strip do not show through between the bands and the ruler
|
|
11865
11933
|
let left = Math.min(x0, x1);
|
|
11866
11934
|
ax.append('rect').attr('x', left - 2).attr('y', axisTop - 4)
|
|
11867
|
-
.attr('width', Math.abs(x1 - x0) + 4).attr('height',
|
|
11935
|
+
.attr('width', Math.abs(x1 - x0) + 4).attr('height', scaleAxisBottomReserve() + 4)
|
|
11868
11936
|
.attr('fill', _state.backgroundColorDefault);
|
|
11869
|
-
ax.append('line').attr('x1', left - 2).attr('x2', left - 2 + Math.abs(x1 - x0) + 4)
|
|
11870
|
-
.attr('y1', axisTop - 4).attr('y2', axisTop - 4)
|
|
11871
|
-
.attr('stroke', ink).attr('stroke-opacity', 0.25).attr('stroke-width', 1);
|
|
11872
11937
|
}
|
|
11873
11938
|
if (info.type === 'geologic') {
|
|
11874
11939
|
let rootAge = info.rootAge;
|
|
@@ -12024,7 +12089,19 @@ function (root, d3, forester, phyloXml) {
|
|
|
12024
12089
|
let from; // the root end and the tip end, as dates
|
|
12025
12090
|
let to;
|
|
12026
12091
|
let bands = [];
|
|
12027
|
-
|
|
12092
|
+
let unit = null; // written once past the tip end
|
|
12093
|
+
if (info.type === 'distance') {
|
|
12094
|
+
// not a date: a distance from the displayed root, 0 at its centre
|
|
12095
|
+
depthOf = function (v) {
|
|
12096
|
+
return info.rootDepth + (v * corr);
|
|
12097
|
+
};
|
|
12098
|
+
from = 0;
|
|
12099
|
+
to = info.maxDist;
|
|
12100
|
+
ticks = info.ticks.map(function (v) {
|
|
12101
|
+
return {value: v, text: String(v)};
|
|
12102
|
+
});
|
|
12103
|
+
unit = info.unit;
|
|
12104
|
+
} else if (info.type === 'geologic') {
|
|
12028
12105
|
let rootAge = info.rootAge;
|
|
12029
12106
|
if (!(rootAge > 0)) {
|
|
12030
12107
|
return;
|
|
@@ -12045,6 +12122,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
12045
12122
|
ticks.push({value: to, text: String(Math.round(to * 100) / 100), first: true});
|
|
12046
12123
|
}
|
|
12047
12124
|
bands = forester.geoOverlapping(forester.geoBandRanks(to, rootAge)[1], to, rootAge);
|
|
12125
|
+
unit = 'Ma';
|
|
12048
12126
|
} else {
|
|
12049
12127
|
let present = info.presentDate;
|
|
12050
12128
|
if (!(present > 0)) {
|
|
@@ -12107,7 +12185,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
12107
12185
|
let at = function (r, off) {
|
|
12108
12186
|
return [(along[0] * r) + (across[0] * off), (along[1] * r) + (across[1] * off)];
|
|
12109
12187
|
};
|
|
12110
|
-
let ax = g.append('g').attr('class', 'aptx-time-ruler');
|
|
12188
|
+
let ax = g.append('g').attr('class', 'aptx-time-ruler').style('font-family', FONT_DEFAULTS);
|
|
12111
12189
|
let e0 = at(r0, 0);
|
|
12112
12190
|
let e1 = at(r1, 0);
|
|
12113
12191
|
ax.append('line').attr('x1', e0[0]).attr('y1', e0[1]).attr('x2', e1[0]).attr('y2', e1[1])
|
|
@@ -12148,8 +12226,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
12148
12226
|
label(r, side * 11, t.text, ink, true);
|
|
12149
12227
|
placed.push(r);
|
|
12150
12228
|
});
|
|
12229
|
+
if (unit) {
|
|
12230
|
+
// along the ruler past its end, so a long unit reads outward
|
|
12231
|
+
label(r1 + 8 + (unit.length * 2.8), 0, unit, ink, false);
|
|
12232
|
+
}
|
|
12151
12233
|
if (info.type === 'geologic') {
|
|
12152
|
-
label(r1 + 14, 0, 'Ma', ink, false);
|
|
12153
12234
|
named.forEach(function (n) {
|
|
12154
12235
|
if ((n.text.length * 5.5) + 4 <= n.span) {
|
|
12155
12236
|
label(n.r, -side * 11, n.text, ink, true);
|
|
@@ -12158,13 +12239,108 @@ function (root, d3, forester, phyloXml) {
|
|
|
12158
12239
|
}
|
|
12159
12240
|
}
|
|
12160
12241
|
|
|
12161
|
-
|
|
12162
|
-
|
|
12242
|
+
// The distance axis, the desktop's "Scale Axis": a ruler from 0 at the
|
|
12243
|
+
// displayed root out to the deepest tip, in branch-length units, with
|
|
12244
|
+
// round ticks and the tree's unit at its end; "Scale Grid" draws each
|
|
12245
|
+
// tick as a faint line (a ring, in circular) behind the tree. Measured
|
|
12246
|
+
// per draw, so a subtree view's axis starts at ITS root.
|
|
12247
|
+
function drawDistanceAxis() {
|
|
12248
|
+
if (!_root || !_yScale) {
|
|
12249
|
+
return;
|
|
12250
|
+
}
|
|
12251
|
+
let corr = Math.abs(_yScale(1) - _yScale(0));
|
|
12252
|
+
if (!isFinite(corr) || corr <= 0) {
|
|
12253
|
+
return;
|
|
12254
|
+
}
|
|
12255
|
+
let rootDepth = (topNode() || _root).y;
|
|
12256
|
+
let maxTipX = rootDepth;
|
|
12257
|
+
forEachDisplayed(function (n) {
|
|
12258
|
+
if (!n.children && n.y > maxTipX) {
|
|
12259
|
+
maxTipX = n.y;
|
|
12260
|
+
}
|
|
12261
|
+
});
|
|
12262
|
+
let maxDist = (maxTipX - rootDepth) / corr;
|
|
12263
|
+
let ticks = forester.distanceAxisTickValues(maxDist);
|
|
12264
|
+
if (ticks.length === 0) {
|
|
12265
|
+
return;
|
|
12266
|
+
}
|
|
12267
|
+
let unit = _treeData && typeof _treeData.branch_length_unit === 'string'
|
|
12268
|
+
? _treeData.branch_length_unit.trim() : '';
|
|
12269
|
+
let ink = _state.branchColorDefault;
|
|
12270
|
+
let grid = null;
|
|
12271
|
+
if (_state.showScaleGrid) {
|
|
12272
|
+
grid = _svgGroup.insert('g', 'g').attr('class', 'aptx-distance-grid')
|
|
12273
|
+
.style('pointer-events', 'none');
|
|
12274
|
+
}
|
|
12275
|
+
if (_state.circularDisplay) {
|
|
12276
|
+
let g = _svgGroup.append('g').attr('class', 'aptx-distance').style('pointer-events', 'none');
|
|
12277
|
+
drawCircularTimeRuler({type: 'distance', rootDepth: rootDepth, maxDist: maxDist, ticks: ticks,
|
|
12278
|
+
unit: unit || null}, null, maxTipX, corr, g, grid, ink);
|
|
12279
|
+
return;
|
|
12280
|
+
}
|
|
12281
|
+
let xOf = function (v) {
|
|
12282
|
+
return rootDepth + (v * corr);
|
|
12283
|
+
};
|
|
12284
|
+
let axisTop = _clusterH + 6;
|
|
12285
|
+
if (grid) {
|
|
12286
|
+
// the root and tip ends excluded: they would retrace the tree
|
|
12287
|
+
ticks.forEach(function (v) {
|
|
12288
|
+
if (v > 0 && v < maxDist - 1e-9) {
|
|
12289
|
+
grid.append('line').attr('x1', xOf(v)).attr('x2', xOf(v))
|
|
12290
|
+
.attr('y1', 0).attr('y2', axisTop)
|
|
12291
|
+
.attr('stroke', ink).attr('stroke-opacity', 0.18)
|
|
12292
|
+
.attr('stroke-width', 1);
|
|
12293
|
+
}
|
|
12294
|
+
});
|
|
12295
|
+
}
|
|
12296
|
+
let reserve = scaleAxisBottomReserve();
|
|
12297
|
+
let ax = floatStripGroup('aptx-distance-axis', axisTop - 4, reserve + 4)
|
|
12298
|
+
.style('font-family', FONT_DEFAULTS);
|
|
12299
|
+
let x0 = xOf(0);
|
|
12300
|
+
let x1 = xOf(maxDist);
|
|
12301
|
+
// opaque, as the time axis's, so tips panned under it do not show
|
|
12302
|
+
ax.append('rect').attr('x', x0 - 2).attr('y', axisTop - 4)
|
|
12303
|
+
.attr('width', (x1 - x0) + 4).attr('height', reserve + 4)
|
|
12304
|
+
.attr('fill', _state.backgroundColorDefault);
|
|
12305
|
+
let rulerY = axisTop + 4;
|
|
12306
|
+
ax.append('line').attr('x1', x0).attr('x2', x1)
|
|
12307
|
+
.attr('y1', rulerY).attr('y2', rulerY)
|
|
12308
|
+
.attr('stroke', ink).attr('stroke-width', 1);
|
|
12309
|
+
// every tick drawn, its number only where it clears the last one
|
|
12310
|
+
let lastRight = -Infinity;
|
|
12311
|
+
ticks.forEach(function (v) {
|
|
12312
|
+
let x = xOf(v);
|
|
12313
|
+
ax.append('line').attr('x1', x).attr('x2', x)
|
|
12314
|
+
.attr('y1', rulerY).attr('y2', rulerY + 4)
|
|
12315
|
+
.attr('stroke', ink).attr('stroke-width', 1);
|
|
12316
|
+
let text = String(v);
|
|
12317
|
+
let half = text.length * 2.8;
|
|
12318
|
+
if ((x - half) >= lastRight + 4) {
|
|
12319
|
+
ax.append('text').attr('x', x).attr('y', rulerY + 14)
|
|
12320
|
+
.attr('text-anchor', 'middle')
|
|
12321
|
+
.style('font-size', '9px').style('fill', ink)
|
|
12322
|
+
.text(text);
|
|
12323
|
+
lastRight = x + half;
|
|
12324
|
+
}
|
|
12325
|
+
});
|
|
12326
|
+
if (unit) {
|
|
12327
|
+
// past the ruler's end, and past the last number, which is
|
|
12328
|
+
// centred on that end
|
|
12329
|
+
ax.append('text').attr('x', Math.max(x1 + 8, lastRight + 6)).attr('y', rulerY + 14)
|
|
12330
|
+
.attr('text-anchor', 'start')
|
|
12331
|
+
.style('font-size', '9px').style('fill', ink)
|
|
12332
|
+
.text(unit);
|
|
12333
|
+
}
|
|
12334
|
+
placeFloatingOverlays();
|
|
12335
|
+
}
|
|
12336
|
+
|
|
12337
|
+
function scaleAxisCbClicked() {
|
|
12338
|
+
_state.showScaleAxis = getCheckboxValue(SCALE_AXIS_CB);
|
|
12163
12339
|
scheduleUpdate(null, 0);
|
|
12164
12340
|
}
|
|
12165
12341
|
|
|
12166
|
-
function
|
|
12167
|
-
_state.
|
|
12342
|
+
function scaleGridCbClicked() {
|
|
12343
|
+
_state.showScaleGrid = getCheckboxValue(SCALE_GRID_CB);
|
|
12168
12344
|
scheduleUpdate(null, 0);
|
|
12169
12345
|
}
|
|
12170
12346
|
|
|
@@ -13312,15 +13488,17 @@ function (root, d3, forester, phyloXml) {
|
|
|
13312
13488
|
heatCb.disabled = _state.unrootedDisplay === true;
|
|
13313
13489
|
}
|
|
13314
13490
|
syncHeatmapControls();
|
|
13315
|
-
let
|
|
13316
|
-
if (
|
|
13317
|
-
// circular draws it along the gap;
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13491
|
+
let axisCb = byId(SCALE_AXIS_CB);
|
|
13492
|
+
if (axisCb) {
|
|
13493
|
+
// circular draws it along the gap; unrooted cannot, and a
|
|
13494
|
+
// cladogram has nothing to measure
|
|
13495
|
+
axisCb.disabled = _state.unrootedDisplay === true || _state.phylogram !== true;
|
|
13496
|
+
}
|
|
13497
|
+
let gridCb = byId(SCALE_GRID_CB);
|
|
13498
|
+
if (gridCb) {
|
|
13499
|
+
// grid lines hang off the scale axis: no axis, nothing to grid
|
|
13500
|
+
gridCb.disabled = _state.unrootedDisplay === true || _state.phylogram !== true
|
|
13501
|
+
|| _state.showScaleAxis !== true;
|
|
13324
13502
|
}
|
|
13325
13503
|
let minus = byId(ZOOM_OUT_X);
|
|
13326
13504
|
let plus = byId(ZOOM_IN_X);
|
|
@@ -16175,8 +16353,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
16175
16353
|
on(DOMAIN_EVALUE_INC, 'click', function () { domainEvalueStep(1); });
|
|
16176
16354
|
on(DOMAIN_LABELS_SELECT, 'change', domainLabelsChanged);
|
|
16177
16355
|
on(DOMAIN_GLOW_CB, 'click', domainGlowCbClicked);
|
|
16178
|
-
on(
|
|
16179
|
-
on(
|
|
16356
|
+
on(SCALE_AXIS_CB, 'click', scaleAxisCbClicked);
|
|
16357
|
+
on(SCALE_GRID_CB, 'click', scaleGridCbClicked);
|
|
16180
16358
|
|
|
16181
16359
|
on(LAYOUT_RECT_BUTTON, 'click', layoutButtonClicked);
|
|
16182
16360
|
on(BRANCH_SCALE_TIME_BUTTON, 'click', branchScaleButtonClicked);
|
|
@@ -16721,12 +16899,16 @@ function (root, d3, forester, phyloXml) {
|
|
|
16721
16899
|
if (heatmapAvailable()) {
|
|
16722
16900
|
opts.push(makeCheckboxItem('Heat Map', HEATMAP_CB, 'to show/hide a cell per tip and numeric field beside the tree, every column on one color scale; concentric rings in the circular layout (not in the unrooted one, where a column has no ring to be)'));
|
|
16723
16901
|
}
|
|
16724
|
-
if (
|
|
16725
|
-
|
|
16726
|
-
|
|
16727
|
-
|
|
16728
|
-
opts.push(makeCheckboxItem('
|
|
16729
|
-
+ (
|
|
16902
|
+
if (_basicTreeProperties.branchLengths === true) {
|
|
16903
|
+
let timeAxis = _timeInfo && _timeInfo.type
|
|
16904
|
+
? (_timeInfo.type === 'geologic' ? 'the geologic (ICS) time axis' : 'the calendar time axis')
|
|
16905
|
+
: null;
|
|
16906
|
+
opts.push(makeCheckboxItem('Scale Axis', SCALE_AXIS_CB, 'to show/hide '
|
|
16907
|
+
+ (timeAxis ? timeAxis + ' and node-age bars (a distance axis while the branches show divergence)'
|
|
16908
|
+
: 'a labeled distance axis from the root to the deepest tip')
|
|
16909
|
+
+ ' (phylogram; rectangular and circular layouts)'));
|
|
16910
|
+
opts.push(makeCheckboxItem('Scale Grid', SCALE_GRID_CB, 'to show/hide grid lines (rings, in circular) at the '
|
|
16911
|
+
+ (timeAxis ? 'time axis\'s intervals or ticks' : 'scale axis ticks')));
|
|
16730
16912
|
}
|
|
16731
16913
|
|
|
16732
16914
|
let h = '<fieldset><legend>Display Data</legend>';
|
|
@@ -17092,8 +17274,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
17092
17274
|
setCheckboxValue(HEATMAP_CB, _state.showHeatmap);
|
|
17093
17275
|
syncDomainControls();
|
|
17094
17276
|
syncHeatmapControls();
|
|
17095
|
-
setCheckboxValue(
|
|
17096
|
-
setCheckboxValue(
|
|
17277
|
+
setCheckboxValue(SCALE_AXIS_CB, _state.showScaleAxis);
|
|
17278
|
+
setCheckboxValue(SCALE_GRID_CB, _state.showScaleGrid);
|
|
17097
17279
|
setCheckboxValue(SHORTEN_NODE_NAME_CB, _state.shortenNodeNames);
|
|
17098
17280
|
populateVisualizationMenus();
|
|
17099
17281
|
initializeSearchOptions();
|
package/forester.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
// v 3.
|
|
23
|
+
// v 3.19.0
|
|
24
24
|
// 2026-09-17
|
|
25
25
|
//
|
|
26
26
|
// forester.js is a general suite for dealing with phylogenetic trees.
|
|
@@ -9461,13 +9461,22 @@
|
|
|
9461
9461
|
// Tick ages for a "Ma before present" ruler: ~8 nice steps from 0 back
|
|
9462
9462
|
// to the root age.
|
|
9463
9463
|
forester.maAxisTickValues = function (rootAge) {
|
|
9464
|
-
|
|
9464
|
+
return forester.distanceAxisTickValues(rootAge);
|
|
9465
|
+
};
|
|
9466
|
+
|
|
9467
|
+
// Tick values for a distance axis: ~8 nice steps from 0 (the root) out
|
|
9468
|
+
// to maxDist (the deepest tip), in the tree's branch-length units.
|
|
9469
|
+
forester.distanceAxisTickValues = function (maxDist) {
|
|
9470
|
+
if (!(maxDist > 0) || !isFinite(maxDist)) {
|
|
9471
|
+
return [];
|
|
9472
|
+
}
|
|
9473
|
+
let step = forester.niceAxisStep(maxDist / 8);
|
|
9474
|
+
if (!(step > 0)) {
|
|
9465
9475
|
return [];
|
|
9466
9476
|
}
|
|
9467
|
-
let step = forester.niceAxisStep(rootAge / 8);
|
|
9468
9477
|
let vals = [];
|
|
9469
|
-
for (let
|
|
9470
|
-
vals.push(
|
|
9478
|
+
for (let i = 0; (i * step) <= maxDist + (step * 1e-9); ++i) {
|
|
9479
|
+
vals.push(Number((i * step).toPrecision(12)));
|
|
9471
9480
|
}
|
|
9472
9481
|
return vals;
|
|
9473
9482
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archaeopteryx",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.19.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",
|