archaeopteryx 3.18.0 → 3.20.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 +96 -22
- package/archaeopteryx.d.ts +18 -0
- package/archaeopteryx.js +598 -84
- package/forester.js +204 -10
- package/package.json +2 -2
package/archaeopteryx.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
// v 3.
|
|
23
|
+
// v 3.20.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.20.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';
|
|
@@ -456,6 +456,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
456
456
|
const SEARCH_COMBINE_SELECT = 'scmb';
|
|
457
457
|
const SEARCH_COMBINE_ROW = 'scmb_row';
|
|
458
458
|
const SEQUENCE_CB = 'seq_cb';
|
|
459
|
+
const PROPERTIES_CB = 'props_cb';
|
|
460
|
+
const LABEL_FIELDS_BUTTON = 'label_fields_b';
|
|
461
|
+
const LABEL_FIELDS_DIALOG = 'label_fields_dialog';
|
|
459
462
|
const SHORTEN_NODE_NAME_CB = 'shortennodename_cb';
|
|
460
463
|
const TAXONOMY_CB = 'tax_cb';
|
|
461
464
|
const ZOOM_IN_X = 'zoomin_x';
|
|
@@ -630,17 +633,31 @@ function (root, d3, forester, phyloXml) {
|
|
|
630
633
|
// within 1.2px of a ring boundary came out background-coloured.
|
|
631
634
|
const HEATMAP_RING_BLEED = 1.2;
|
|
632
635
|
const HEATMAP_BLANK = 'blank'; // the run-merger's stand-in for a cell nobody filled in
|
|
636
|
+
// Cell borders (Christian, 2026-09-30): each cell outlined in its own hue,
|
|
637
|
+
// darker, so a block of equal values still reads as a block while each
|
|
638
|
+
// cell can be counted. Only where a cell is big enough on SCREEN to carry
|
|
639
|
+
// one, and not on a matrix so large that a node per cell would drag.
|
|
640
|
+
const HEATMAP_BORDER_MIN_PX = 5; // the cell's smaller side on screen
|
|
641
|
+
const HEATMAP_BORDER_MAX_CELLS = 60000;
|
|
642
|
+
const HEATMAP_BORDER_W = 0.75; // screen px at any zoom (non-scaling stroke)
|
|
643
|
+
const HEATMAP_BORDER_DARKEN = 0.8; // d3's darker(k): each step x0.7
|
|
644
|
+
const HEATMAP_SEAL_W = 0.6; // a merged run's stroke in its own colour, over the seams
|
|
633
645
|
const HEATMAP_DENDRO_GAP = 5; // px between the dendrogram's leaves and the first row
|
|
634
646
|
const HEATMAP_DENDRO_MIN_BAND = 24; // the band it is drawn in, from the label font
|
|
635
647
|
const HEATMAP_DENDRO_MAX_BAND = 72;
|
|
636
648
|
let _heatmapReserve = 0; // horizontal px reserved for the matrix, set with _w
|
|
637
649
|
let _heatmapModel = null; // forester.heatmapColumns of the WHOLE tree, cached per launch
|
|
650
|
+
let _heatmapCells = null; // what was drawn: {cellMin (tree units), count, bordered}
|
|
651
|
+
let _heatmapBorderTimer = null;
|
|
652
|
+
let _figureMatrix = null; // the MATRIX refs the file's aptx:figure names, when it draws a matrix
|
|
653
|
+
let _labelFields = null; // forester.labelPropertyRefs of the tree, cached until an edit
|
|
638
654
|
let _heatmapColor = null; // the one scale every column is painted on
|
|
639
655
|
let _heatmapColOffset = 0; // first shown column, while the matrix is windowed
|
|
640
656
|
let _heatmapGeom = null; // the last draw's geometry, for the hover readout
|
|
641
657
|
// ------ time axis (the desktop's geologic / calendar overlays) ------
|
|
642
658
|
const TIME_GEO_RESERVE = 52; // two ICS band rows + the Ma ruler
|
|
643
659
|
const TIME_CAL_RESERVE = 26; // the calendar year ruler
|
|
660
|
+
const DISTANCE_AXIS_RESERVE = 26; // the distance ruler, one label row
|
|
644
661
|
const TIME_BAND_ROW_H = 13;
|
|
645
662
|
const HPD_BAR_COLOR = 'rgba(70,130,220,0.35)'; // translucent blue, the usual HPD tint
|
|
646
663
|
const FOSSIL_BAR_COLOR = 'rgba(150,100,55,0.86)'; // opaque-ish sepia
|
|
@@ -1330,6 +1347,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
1330
1347
|
_svgGroup.attr('transform', event.transform);
|
|
1331
1348
|
placeFloatingOverlays();
|
|
1332
1349
|
updateOverviewViewport();
|
|
1350
|
+
heatmapBordersFollowZoom();
|
|
1333
1351
|
}
|
|
1334
1352
|
|
|
1335
1353
|
// A strip in the floating layer. Drawn in TREE coordinates like everything
|
|
@@ -3390,7 +3408,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
3390
3408
|
_svgGroup.selectAll('g.aptx-align-ext').remove();
|
|
3391
3409
|
let alignedRect = !radialDisplay() && _state.phylogram && _state.alignPhylogram;
|
|
3392
3410
|
let tipGuides = alignedRect && _state.showExternalLabels
|
|
3393
|
-
&& (_state.showNodeName || _state.showTaxonomy || _state.showSequence);
|
|
3411
|
+
&& (_state.showNodeName || _state.showTaxonomy || _state.showSequence || _state.showProperties);
|
|
3394
3412
|
// a collapsed clade's label always shows, so its guide does too: from
|
|
3395
3413
|
// past the wedge's farthest tip to the label column
|
|
3396
3414
|
let collapsedGuides = alignedRect ? nodes.filter(function (d) {
|
|
@@ -4660,6 +4678,10 @@ function (root, d3, forester, phyloXml) {
|
|
|
4660
4678
|
}
|
|
4661
4679
|
|
|
4662
4680
|
|
|
4681
|
+
if (_state.showProperties && phynode.properties) {
|
|
4682
|
+
l = append(l, labelPropertiesOf(phynode));
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4663
4685
|
if (_nodeLabels && phynode.properties) {
|
|
4664
4686
|
const props_length = phynode.properties.length;
|
|
4665
4687
|
if (props_length > 0) {
|
|
@@ -5440,6 +5462,23 @@ function (root, d3, forester, phyloXml) {
|
|
|
5440
5462
|
return v === true ? 'confidence' : PARSE_DEFAULTS.internalNumericLabels;
|
|
5441
5463
|
},
|
|
5442
5464
|
note: 'use \'confidence\' for what this flag did (\'auto\' is the new default, and does not promote mixed trees)'
|
|
5465
|
+
},
|
|
5466
|
+
// Renamed in 3.19.0, when the axis stopped being time-only: a tree
|
|
5467
|
+
// whose branches measure divergence gets a distance axis from the
|
|
5468
|
+
// same switch. The meaning on a time tree is unchanged.
|
|
5469
|
+
showTimeAxis: {
|
|
5470
|
+
to: 'showScaleAxis',
|
|
5471
|
+
map: function (v) {
|
|
5472
|
+
return v;
|
|
5473
|
+
},
|
|
5474
|
+
note: 'the one switch now draws the time axis on a time tree and a distance axis on any other phylogram'
|
|
5475
|
+
},
|
|
5476
|
+
timeAxisGrid: {
|
|
5477
|
+
to: 'showScaleGrid',
|
|
5478
|
+
map: function (v) {
|
|
5479
|
+
return v;
|
|
5480
|
+
},
|
|
5481
|
+
note: 'the grid follows the scale axis, time or distance'
|
|
5443
5482
|
}
|
|
5444
5483
|
};
|
|
5445
5484
|
|
|
@@ -5603,6 +5642,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
5603
5642
|
'layout',
|
|
5604
5643
|
'showMsa',
|
|
5605
5644
|
'showMsaLogo',
|
|
5645
|
+
'showProperties',
|
|
5646
|
+
'labelProperties',
|
|
5606
5647
|
'showHeatmap',
|
|
5607
5648
|
'heatmapColumnOrder',
|
|
5608
5649
|
'heatmapManualOrder',
|
|
@@ -5610,8 +5651,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
5610
5651
|
'domainLabels',
|
|
5611
5652
|
'domainGlow',
|
|
5612
5653
|
'domainEvalueExponent',
|
|
5613
|
-
'
|
|
5614
|
-
'
|
|
5654
|
+
'showScaleAxis',
|
|
5655
|
+
'showScaleGrid',
|
|
5615
5656
|
'showSupportDots',
|
|
5616
5657
|
'searchAinitialValue',
|
|
5617
5658
|
'searchBinitialValue',
|
|
@@ -5842,8 +5883,18 @@ function (root, d3, forester, phyloXml) {
|
|
|
5842
5883
|
// numeric per-tip fields, which is common enough that turning it on
|
|
5843
5884
|
// by itself would be an opinion about the tree rather than a service
|
|
5844
5885
|
// -- the alignment track, by contrast, is on when there IS one.
|
|
5886
|
+
// The desktop's figure setting: its MATRIX columns open as the heat
|
|
5887
|
+
// map, only those, in the file's order. Only the phylogeny's own
|
|
5888
|
+
// aptx:figure counts; a copy on a clade (the old place) is dropped,
|
|
5889
|
+
// so a tree saved from here carries none. Here and not in launchInto:
|
|
5890
|
+
// telling whether the columns exist is a scan of every tip, which on
|
|
5891
|
+
// a big tree belongs behind the working card, not in front of it.
|
|
5892
|
+
if (_treeData) {
|
|
5893
|
+
forester.dropCladeFigures(_treeData);
|
|
5894
|
+
}
|
|
5895
|
+
_figureMatrix = _treeData ? figureMatrixOf(_treeData) : null;
|
|
5845
5896
|
if (_state.showHeatmap === undefined) {
|
|
5846
|
-
_state.showHeatmap =
|
|
5897
|
+
_state.showHeatmap = _figureMatrix !== null;
|
|
5847
5898
|
}
|
|
5848
5899
|
// The logo is a summary of the alignment, not a second view of the
|
|
5849
5900
|
// tree, so it waits to be asked for: an alignment opens showing its
|
|
@@ -5858,7 +5909,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
5858
5909
|
// value always wins and is never re-derived -- including 'document',
|
|
5859
5910
|
// which keeps the order the producer wrote the fields in.
|
|
5860
5911
|
if (_state.heatmapColumnOrder === undefined) {
|
|
5861
|
-
_state.heatmapColumnOrder = null;
|
|
5912
|
+
_state.heatmapColumnOrder = _figureMatrix ? 'manual' : null;
|
|
5862
5913
|
} else if (_state.heatmapColumnOrder !== null
|
|
5863
5914
|
&& forester.HEATMAP_ORDER_MODES.indexOf(_state.heatmapColumnOrder) < 0) {
|
|
5864
5915
|
throw new Error(ERROR + '"heatmapColumnOrder" must be one of '
|
|
@@ -5869,7 +5920,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
5869
5920
|
// not got is ignored rather than fatal -- an order outliving one of
|
|
5870
5921
|
// its columns is ordinary (see forester.heatmapManualOrder).
|
|
5871
5922
|
if (_state.heatmapManualOrder === undefined) {
|
|
5872
|
-
_state.heatmapManualOrder = null;
|
|
5923
|
+
_state.heatmapManualOrder = _figureMatrix ? _figureMatrix.slice() : null;
|
|
5873
5924
|
} else if (_state.heatmapManualOrder !== null
|
|
5874
5925
|
&& !(Array.isArray(_state.heatmapManualOrder)
|
|
5875
5926
|
&& _state.heatmapManualOrder.every(function (r) {
|
|
@@ -5910,10 +5961,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
5910
5961
|
+ ' with a missing or impossible from / to / E-value ignored');
|
|
5911
5962
|
}
|
|
5912
5963
|
}
|
|
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
|
-
//
|
|
5964
|
+
// Likewise a dated tree draws its scale axis from the start -- a
|
|
5965
|
+
// time axis of geologic ICS bands or calendar years, decided from the
|
|
5966
|
+
// <date> elements by forester.timeAxisInfo -- again unless the caller
|
|
5967
|
+
// set showScaleAxis explicitly. Any other tree opens without one, as
|
|
5968
|
+
// on the desktop: its distance axis is asked for.
|
|
5917
5969
|
_timeInfo = _treeData ? forester.timeAxisInfo(forester.getTreeRoot(_treeData)) : null;
|
|
5918
5970
|
_timeTree = _treeData ? forester.isTimeTree(_treeData) : false;
|
|
5919
5971
|
// Whether this tree says two different things -- when its nodes sit in
|
|
@@ -5922,10 +5974,13 @@ function (root, d3, forester, phyloXml) {
|
|
|
5922
5974
|
// BEAST or Newick file arrives stating its own branch lengths.
|
|
5923
5975
|
_branchScaleAvailable = _treeData ? forester.hasTimeAndDivergence(_treeData) : false;
|
|
5924
5976
|
_state.branchScale = _treeData ? forester.branchLengthScale(_treeData) : 'divergence';
|
|
5925
|
-
if (_state.
|
|
5926
|
-
|
|
5977
|
+
if (_state.showScaleAxis === undefined) {
|
|
5978
|
+
// on for a tree SHOWN in time; a dated tree that opens in
|
|
5979
|
+
// divergence (a build refused the switch) is a divergence tree
|
|
5980
|
+
// on screen, and opens without its distance axis like any other
|
|
5981
|
+
_state.showScaleAxis = !!(_timeInfo && _timeInfo.type) && !branchesShowDivergence();
|
|
5927
5982
|
}
|
|
5928
|
-
_state.
|
|
5983
|
+
_state.showScaleGrid = _state.showScaleGrid === true; // desktop default: off
|
|
5929
5984
|
_state.showSupportDots = _state.showSupportDots === true;
|
|
5930
5985
|
_state.showNodeEvents = _basicTreeProperties.nodeEvents === true;
|
|
5931
5986
|
_state.showBranchEvents = _basicTreeProperties.branchEvents === true;
|
|
@@ -6038,6 +6093,27 @@ function (root, d3, forester, phyloXml) {
|
|
|
6038
6093
|
+ ' characters)');
|
|
6039
6094
|
}
|
|
6040
6095
|
|
|
6096
|
+
// Properties in the tip label: off unless asked for, as on the desktop,
|
|
6097
|
+
// in every user-visible property's own order unless a list says which.
|
|
6098
|
+
// The file's figure setting (aptx:figure) can say both, and whether
|
|
6099
|
+
// the names show: labelprops, show.SHOW_PROPERTIES and
|
|
6100
|
+
// show.SHOW_NODE_NAMES, the desktop's own keys. The config wins.
|
|
6101
|
+
let figure = _treeData ? forester.readFigure(_treeData) : null;
|
|
6102
|
+
if (_state.showProperties === undefined) {
|
|
6103
|
+
_state.showProperties = !!(figure && figure.show.SHOW_PROPERTIES === true);
|
|
6104
|
+
}
|
|
6105
|
+
if (_state.labelProperties === undefined) {
|
|
6106
|
+
_state.labelProperties = (figure && figure.labelProps) ? figure.labelProps.slice() : null;
|
|
6107
|
+
} else if (_state.labelProperties !== null && !(Array.isArray(_state.labelProperties)
|
|
6108
|
+
&& _state.labelProperties.every(function (r) {
|
|
6109
|
+
return typeof r === 'string';
|
|
6110
|
+
}))) {
|
|
6111
|
+
throw new Error(ERROR + '"labelProperties" must be an array of property refs');
|
|
6112
|
+
}
|
|
6113
|
+
if (figure && typeof figure.show.SHOW_NODE_NAMES === 'boolean' && _basicTreeProperties.nodeNames) {
|
|
6114
|
+
_state.showNodeName = figure.show.SHOW_NODE_NAMES;
|
|
6115
|
+
}
|
|
6116
|
+
|
|
6041
6117
|
// A small tree is drawn with a heavier stroke; hairlines are for trees
|
|
6042
6118
|
// dense enough to need them.
|
|
6043
6119
|
_state.branchWidthDefault = _basicTreeProperties.externalNodesCount <= SMALL_TREE_MAX_EXT_NODES
|
|
@@ -6496,6 +6572,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
6496
6572
|
_treeData = phylo;
|
|
6497
6573
|
_trees = trees;
|
|
6498
6574
|
_treeIndex = index;
|
|
6575
|
+
_figureMatrix = null; // read by initializeState, behind the working card
|
|
6576
|
+
_labelFields = null;
|
|
6499
6577
|
_launchConfig = config;
|
|
6500
6578
|
_container = containerEl;
|
|
6501
6579
|
assignViewIds(phylo);
|
|
@@ -7253,6 +7331,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
7253
7331
|
_domain.palette = null; // the tree changed: the domain names are dealt their colours again
|
|
7254
7332
|
}
|
|
7255
7333
|
if (edited) {
|
|
7334
|
+
_labelFields = null; // the fields the labels can show, too
|
|
7256
7335
|
_heatmapModel = null; // ... and the heat map's columns and scale are derived from it again
|
|
7257
7336
|
_heatmapColor = null;
|
|
7258
7337
|
}
|
|
@@ -8170,11 +8249,28 @@ function (root, d3, forester, phyloXml) {
|
|
|
8170
8249
|
|
|
8171
8250
|
function heatmapModel() {
|
|
8172
8251
|
if (!_heatmapModel && _treeData) {
|
|
8173
|
-
_heatmapModel = forester.heatmapColumns(_treeData);
|
|
8252
|
+
_heatmapModel = forester.heatmapColumns(_treeData, _figureMatrix);
|
|
8174
8253
|
}
|
|
8175
8254
|
return _heatmapModel;
|
|
8176
8255
|
}
|
|
8177
8256
|
|
|
8257
|
+
// The file's MATRIX columns, when at least a heat map's worth of them are
|
|
8258
|
+
// numeric fields this tree has; otherwise null, and the heat map offers
|
|
8259
|
+
// every numeric field as it would without a figure.
|
|
8260
|
+
// The model it builds is kept as the heat map's own, so the scan runs once.
|
|
8261
|
+
function figureMatrixOf(tree) {
|
|
8262
|
+
let refs = forester.figureMatrixRefs(tree);
|
|
8263
|
+
if (refs.length < HEATMAP_MIN_COLUMNS) {
|
|
8264
|
+
return null;
|
|
8265
|
+
}
|
|
8266
|
+
let model = forester.heatmapColumns(tree, refs);
|
|
8267
|
+
if (model.refs.length < HEATMAP_MIN_COLUMNS) {
|
|
8268
|
+
return null;
|
|
8269
|
+
}
|
|
8270
|
+
_heatmapModel = model;
|
|
8271
|
+
return refs;
|
|
8272
|
+
}
|
|
8273
|
+
|
|
8178
8274
|
// Offered when the tree holds at least two numeric per-tip refs that
|
|
8179
8275
|
// actually carry values: one column is a stripe rather than a matrix,
|
|
8180
8276
|
// and a scale needs something to span.
|
|
@@ -8382,7 +8478,53 @@ function (root, d3, forester, phyloXml) {
|
|
|
8382
8478
|
return heatmapTopReserve();
|
|
8383
8479
|
}
|
|
8384
8480
|
|
|
8481
|
+
// Whether cells of this size (tree units) and number are drawn one by one
|
|
8482
|
+
// with borders at zoom scale k, or merged into runs.
|
|
8483
|
+
function heatmapBordered(cellMin, count, k) {
|
|
8484
|
+
return count <= HEATMAP_BORDER_MAX_CELLS && (cellMin * k) >= HEATMAP_BORDER_MIN_PX;
|
|
8485
|
+
}
|
|
8486
|
+
|
|
8487
|
+
function heatmapBorderColor(fill) {
|
|
8488
|
+
let c = d3.color(fill);
|
|
8489
|
+
return c ? c.darker(HEATMAP_BORDER_DARKEN).formatHex() : fill;
|
|
8490
|
+
}
|
|
8491
|
+
|
|
8492
|
+
// A cell (or a merged run) of colour `fill`: bordered, its edge a darker
|
|
8493
|
+
// shade of itself; or sealed, stroked in its own colour so that no seam of
|
|
8494
|
+
// background shows between two runs at a fractional zoom -- the faint lines
|
|
8495
|
+
// that came and went with the zoom level. Both strokes are a constant
|
|
8496
|
+
// screen width, so a zoom neither fattens nor loses them.
|
|
8497
|
+
function paintHeatmapCell(el, fill, bordered) {
|
|
8498
|
+
el.attr('fill', fill)
|
|
8499
|
+
.attr('stroke', bordered ? heatmapBorderColor(fill) : fill)
|
|
8500
|
+
.attr('stroke-width', bordered ? HEATMAP_BORDER_W : HEATMAP_SEAL_W)
|
|
8501
|
+
.attr('vector-effect', 'non-scaling-stroke');
|
|
8502
|
+
return el;
|
|
8503
|
+
}
|
|
8504
|
+
|
|
8505
|
+
// Called on every zoom event: when the zoom has carried the cells across
|
|
8506
|
+
// the border threshold, the heat map alone is drawn again once the zoom
|
|
8507
|
+
// settles.
|
|
8508
|
+
function heatmapBordersFollowZoom() {
|
|
8509
|
+
if (!_heatmapCells || !_baseSvg) {
|
|
8510
|
+
return;
|
|
8511
|
+
}
|
|
8512
|
+
let want = heatmapBordered(_heatmapCells.cellMin, _heatmapCells.count, currentZoomScale());
|
|
8513
|
+
if (want === _heatmapCells.bordered) {
|
|
8514
|
+
return;
|
|
8515
|
+
}
|
|
8516
|
+
if (_heatmapBorderTimer) {
|
|
8517
|
+
clearTimeout(_heatmapBorderTimer);
|
|
8518
|
+
}
|
|
8519
|
+
_heatmapBorderTimer = setTimeout(function () {
|
|
8520
|
+
_heatmapBorderTimer = null;
|
|
8521
|
+
drawHeatmapTrack();
|
|
8522
|
+
placeFloatingOverlays();
|
|
8523
|
+
}, 120);
|
|
8524
|
+
}
|
|
8525
|
+
|
|
8385
8526
|
function drawHeatmapTrack() {
|
|
8527
|
+
_heatmapCells = null;
|
|
8386
8528
|
if (!_svgGroup) {
|
|
8387
8529
|
return;
|
|
8388
8530
|
}
|
|
@@ -8465,6 +8607,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
8465
8607
|
// a run of CELLS, or its top and bottom edges read as a drawn rule
|
|
8466
8608
|
// across the matrix rather than as missing data.
|
|
8467
8609
|
let mergeBlanks = cw < HEATMAP_LABEL_MIN_COL_W;
|
|
8610
|
+
let cellMin = Math.min(cw, n > 1 ? 2 * pad : cw);
|
|
8611
|
+
let bordered = heatmapBordered(cellMin, n * visible, currentZoomScale());
|
|
8612
|
+
_heatmapCells = {cellMin: cellMin, count: n * visible, bordered: bordered};
|
|
8468
8613
|
for (let r = 0; r < n; ++r) {
|
|
8469
8614
|
let d = tips[r];
|
|
8470
8615
|
let cy = Math.round(bounds[r]);
|
|
@@ -8486,7 +8631,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8486
8631
|
rect.attr('fill', _state.backgroundColorDefault)
|
|
8487
8632
|
.attr('stroke', ink).attr('stroke-opacity', 0.45).attr('stroke-width', 1);
|
|
8488
8633
|
} else {
|
|
8489
|
-
rect
|
|
8634
|
+
paintHeatmapCell(rect, runFill, bordered);
|
|
8490
8635
|
}
|
|
8491
8636
|
runStart = -1;
|
|
8492
8637
|
runFill = null;
|
|
@@ -8497,7 +8642,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
8497
8642
|
if (fill === HEATMAP_BLANK) {
|
|
8498
8643
|
blanks++;
|
|
8499
8644
|
}
|
|
8500
|
-
|
|
8645
|
+
// bordered, every cell is its own: a run would hide the cells in it
|
|
8646
|
+
if (!bordered && runFill === fill && (mergeBlanks || fill !== HEATMAP_BLANK)) {
|
|
8501
8647
|
continue; // the run extends
|
|
8502
8648
|
}
|
|
8503
8649
|
flush(i);
|
|
@@ -8807,6 +8953,10 @@ function (root, d3, forester, phyloXml) {
|
|
|
8807
8953
|
edge[n] = ang[n - 1] + pad;
|
|
8808
8954
|
|
|
8809
8955
|
let blanks = 0;
|
|
8956
|
+
// a cell's smaller side: its ring width, or its arc at the ring's inside
|
|
8957
|
+
let cellMin = Math.min(ringW, n > 1 ? 2 * pad * start : ringW);
|
|
8958
|
+
let bordered = heatmapBordered(cellMin, n * columns.length, currentZoomScale());
|
|
8959
|
+
_heatmapCells = {cellMin: cellMin, count: n * columns.length, bordered: bordered};
|
|
8810
8960
|
for (let c = 0; c < columns.length; ++c) {
|
|
8811
8961
|
let r0 = start + (c * ringW);
|
|
8812
8962
|
let r1 = r0 + ringW;
|
|
@@ -8825,15 +8975,19 @@ function (root, d3, forester, phyloXml) {
|
|
|
8825
8975
|
// A BLANK is not bled -- it is background-coloured, so a seam
|
|
8826
8976
|
// beside it shows nothing, and bleeding it would push its
|
|
8827
8977
|
// outward OUTLINE under the next ring and cost it an edge.
|
|
8828
|
-
|
|
8978
|
+
// (bordered, the border covers the seam and a bleed would push
|
|
8979
|
+
// one cell's edge under its neighbour)
|
|
8980
|
+
let bleed = (runFill === HEATMAP_BLANK || bordered) ? 0 : HEATMAP_RING_BLEED;
|
|
8829
8981
|
let a1 = edge[endI] + (bleed / Math.max(1, r1));
|
|
8830
8982
|
let path = g.append('path')
|
|
8831
8983
|
.attr('d', heatmapSector(edge[runStart], a1, r0, r1 + bleed));
|
|
8832
8984
|
if (runFill === HEATMAP_BLANK) {
|
|
8833
8985
|
path.attr('fill', _state.backgroundColorDefault)
|
|
8834
8986
|
.attr('stroke', ink).attr('stroke-opacity', 0.45).attr('stroke-width', 1);
|
|
8987
|
+
} else if (bordered) {
|
|
8988
|
+
paintHeatmapCell(path, runFill, true);
|
|
8835
8989
|
} else {
|
|
8836
|
-
path.attr('fill', runFill);
|
|
8990
|
+
path.attr('fill', runFill); // the bleed already closes the seams here
|
|
8837
8991
|
}
|
|
8838
8992
|
runStart = -1;
|
|
8839
8993
|
runFill = null;
|
|
@@ -8844,7 +8998,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8844
8998
|
if (fill === HEATMAP_BLANK) {
|
|
8845
8999
|
blanks++;
|
|
8846
9000
|
}
|
|
8847
|
-
if (runFill === fill && (ringW < HEATMAP_LABEL_MIN_COL_W || fill !== HEATMAP_BLANK)) {
|
|
9001
|
+
if (!bordered && runFill === fill && (ringW < HEATMAP_LABEL_MIN_COL_W || fill !== HEATMAP_BLANK)) {
|
|
8848
9002
|
continue;
|
|
8849
9003
|
}
|
|
8850
9004
|
flush(i);
|
|
@@ -9605,6 +9759,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9605
9759
|
['name', NODE_NAME_CB, 'showNodeName'],
|
|
9606
9760
|
['taxonomy', TAXONOMY_CB, 'showTaxonomy'],
|
|
9607
9761
|
['sequence', SEQUENCE_CB, 'showSequence'],
|
|
9762
|
+
['properties', PROPERTIES_CB, 'showProperties'],
|
|
9608
9763
|
['confidence', CONFIDENCE_VALUES_CB, 'showConfidenceValues'],
|
|
9609
9764
|
['madValues', MAD_VALUES_CB, 'showMadValues'],
|
|
9610
9765
|
['branchLength', BRANCH_LENGTH_VALUES_CB, 'showBranchLengthValues'],
|
|
@@ -9727,6 +9882,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
9727
9882
|
if (heatmapAvailable()) {
|
|
9728
9883
|
s.heatmap = _state.showHeatmap === true;
|
|
9729
9884
|
s.heatmapOrder = heatmapMode();
|
|
9885
|
+
if (_state.labelProperties) {
|
|
9886
|
+
s.labelFields = _state.labelProperties.slice();
|
|
9887
|
+
}
|
|
9730
9888
|
if (_state.heatmapManualOrder) {
|
|
9731
9889
|
s.heatmapManual = _state.heatmapManualOrder.slice();
|
|
9732
9890
|
}
|
|
@@ -9737,9 +9895,12 @@ function (root, d3, forester, phyloXml) {
|
|
|
9737
9895
|
s.domainGlow = _state.domainGlow === true;
|
|
9738
9896
|
s.domainEvalue = _state.domainEvalueExponent;
|
|
9739
9897
|
}
|
|
9740
|
-
if (
|
|
9741
|
-
s.
|
|
9742
|
-
s.
|
|
9898
|
+
if (_basicTreeProperties.branchLengths === true) {
|
|
9899
|
+
s.scaleAxis = _state.showScaleAxis === true;
|
|
9900
|
+
s.scaleGrid = _state.showScaleGrid === true;
|
|
9901
|
+
}
|
|
9902
|
+
if (_branchScaleAvailable) {
|
|
9903
|
+
s.scale = _state.branchScale; // time or divergence: which layout the branches show
|
|
9743
9904
|
}
|
|
9744
9905
|
let a = searchStateOf(0);
|
|
9745
9906
|
let b = searchStateOf(1);
|
|
@@ -9810,6 +9971,16 @@ function (root, d3, forester, phyloXml) {
|
|
|
9810
9971
|
}
|
|
9811
9972
|
});
|
|
9812
9973
|
}
|
|
9974
|
+
// the branches' layout, time or divergence, on a tree that has both:
|
|
9975
|
+
// laid out before the properties are measured, since the lengths are
|
|
9976
|
+
// the layout. A tree with one layout ignores the key.
|
|
9977
|
+
if (_branchScaleAvailable && (s.scale === 'time' || s.scale === 'divergence') && s.scale !== _state.branchScale) {
|
|
9978
|
+
let laidOut = (s.scale === 'time') ? forester.applyTimeBranchLengths(_treeData)
|
|
9979
|
+
: forester.applyDivergenceBranchLengths(_treeData);
|
|
9980
|
+
if (laidOut) {
|
|
9981
|
+
_state.branchScale = s.scale;
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9813
9984
|
_basicTreeProperties = forester.collectBasicTreeProperties(_root);
|
|
9814
9985
|
|
|
9815
9986
|
if (s.layout === 'rectangular' || s.layout === 'circular' || s.layout === 'unrooted') {
|
|
@@ -9857,6 +10028,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
9857
10028
|
if (typeof s.heatmap === 'boolean') {
|
|
9858
10029
|
_state.showHeatmap = s.heatmap;
|
|
9859
10030
|
}
|
|
10031
|
+
if (Array.isArray(s.labelFields) && s.labelFields.every(function (r) {
|
|
10032
|
+
return typeof r === 'string';
|
|
10033
|
+
})) {
|
|
10034
|
+
_state.labelProperties = s.labelFields.slice();
|
|
10035
|
+
}
|
|
9860
10036
|
if (Array.isArray(s.heatmapManual) && s.heatmapManual.every(function (r) {
|
|
9861
10037
|
return typeof r === 'string';
|
|
9862
10038
|
})) {
|
|
@@ -9881,11 +10057,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
9881
10057
|
_domain.palette = null; // the drawn set changed: the palette is dealt again
|
|
9882
10058
|
}
|
|
9883
10059
|
}
|
|
9884
|
-
|
|
9885
|
-
|
|
10060
|
+
// timeAxis / timeGrid: the names before the axis also measured
|
|
10061
|
+
// distance, still read so that older links keep their axis
|
|
10062
|
+
let axis = typeof s.scaleAxis === 'boolean' ? s.scaleAxis : s.timeAxis;
|
|
10063
|
+
if (typeof axis === 'boolean') {
|
|
10064
|
+
_state.showScaleAxis = axis;
|
|
9886
10065
|
}
|
|
9887
|
-
|
|
9888
|
-
|
|
10066
|
+
let grid = typeof s.scaleGrid === 'boolean' ? s.scaleGrid : s.timeGrid;
|
|
10067
|
+
if (typeof grid === 'boolean') {
|
|
10068
|
+
_state.showScaleGrid = grid;
|
|
9889
10069
|
}
|
|
9890
10070
|
if (!radialDisplay()) {
|
|
9891
10071
|
_radialLabelsHorizontal = false;
|
|
@@ -9952,6 +10132,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9952
10132
|
function syncViewControls() {
|
|
9953
10133
|
setDisplayTypeButtons();
|
|
9954
10134
|
syncZoomRowButtons();
|
|
10135
|
+
syncBranchScaleControls();
|
|
9955
10136
|
VIEW_SHOW_FLAGS.forEach(function (f) {
|
|
9956
10137
|
setCheckboxValue(f[1], _state[f[2]] === true);
|
|
9957
10138
|
});
|
|
@@ -9964,8 +10145,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
9964
10145
|
setCheckboxValue(MSA_LOGO_CB, _state.showMsaLogo === true);
|
|
9965
10146
|
setCheckboxValue(HEATMAP_CB, _state.showHeatmap === true);
|
|
9966
10147
|
syncHeatmapControls();
|
|
9967
|
-
setCheckboxValue(
|
|
9968
|
-
setCheckboxValue(
|
|
10148
|
+
setCheckboxValue(SCALE_AXIS_CB, _state.showScaleAxis === true);
|
|
10149
|
+
setCheckboxValue(SCALE_GRID_CB, _state.showScaleGrid === true);
|
|
9969
10150
|
setCheckboxValue(DOMAIN_GLOW_CB, _state.domainGlow === true);
|
|
9970
10151
|
setValue(DOMAIN_LABELS_SELECT, _state.domainLabels);
|
|
9971
10152
|
syncDomainControls();
|
|
@@ -10041,14 +10222,14 @@ function (root, d3, forester, phyloXml) {
|
|
|
10041
10222
|
// The heat map's four keys and the alignment logo went in exactly that
|
|
10042
10223
|
// way, while the README promised a link reproduced the figure.
|
|
10043
10224
|
const VIEW_TEXT_KEYS = ['layout', 'display', 'order', 'root', 'colorBy', 'shapeBy', 'domainLabels', 'combine',
|
|
10044
|
-
'heatmapOrder'];
|
|
10225
|
+
'heatmapOrder', 'scale'];
|
|
10045
10226
|
const VIEW_INT_KEYS = ['tree', 'subtree', 'rotation', 'domainEvalue'];
|
|
10046
10227
|
const VIEW_NUMBER_KEYS = ['font', 'node', 'branch'];
|
|
10047
10228
|
const VIEW_BOOL_KEYS = ['horizontalLabels', 'msa', 'msaLogo', 'heatmap', 'domains', 'domainGlow',
|
|
10048
|
-
'timeAxis', 'timeGrid', 'matchCase', 'inverse'];
|
|
10229
|
+
'scaleAxis', 'scaleGrid', 'timeAxis', 'timeGrid', 'matchCase', 'inverse'];
|
|
10049
10230
|
// Lists of strings, comma-joined. A property ref carries ':' but never a
|
|
10050
10231
|
// comma, and encodeViewValue leaves both readable in a hash.
|
|
10051
|
-
const VIEW_LIST_KEYS = ['heatmapManual'];
|
|
10232
|
+
const VIEW_LIST_KEYS = ['heatmapManual', 'labelFields'];
|
|
10052
10233
|
const VIEW_SEARCH_KEYS = [['searchA', 'a'], ['searchB', 'b']];
|
|
10053
10234
|
|
|
10054
10235
|
function encodeViewValue(v) {
|
|
@@ -10212,7 +10393,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
10212
10393
|
{key: 'e', label: 'E', shift: true, what: 'Expand vertically until the labels fit', run: function () { zoomToExpandY(); }},
|
|
10213
10394
|
{key: 'l', label: 'L', shift: true, what: 'Next layout: rectangular, circular, unrooted', run: cycleLayout},
|
|
10214
10395
|
{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
|
|
10396
|
+
{key: 'x', label: 'X', shift: true, what: 'Time / divergence scale, or the scale axis', run: toggleScaleAxis},
|
|
10216
10397
|
{key: 'o', label: 'O', shift: true, what: 'Ladderize (order the tree)', run: function () { ladderizeButtonPressed(); }},
|
|
10217
10398
|
{key: 'u', label: 'U', shift: true, what: 'Uncollapse every clade', run: function () { uncollapseAll(); }},
|
|
10218
10399
|
{key: 'f', label: 'F', what: 'Go to the search box', run: focusSearch, typing: true},
|
|
@@ -10359,19 +10540,19 @@ function (root, d3, forester, phyloXml) {
|
|
|
10359
10540
|
}
|
|
10360
10541
|
|
|
10361
10542
|
// Shift+X. The switch takes this key over where the tree offers it, as was
|
|
10362
|
-
// always intended; on every other tree it
|
|
10543
|
+
// always intended; on every other tree it works the Scale Axis
|
|
10363
10544
|
// checkbox, so the key never does nothing.
|
|
10364
|
-
function
|
|
10545
|
+
function toggleScaleAxis() {
|
|
10365
10546
|
if (_branchScaleAvailable) {
|
|
10366
10547
|
setBranchScale(_state.branchScale === 'time' ? 'divergence' : 'time');
|
|
10367
10548
|
return;
|
|
10368
10549
|
}
|
|
10369
|
-
let cb = byId(
|
|
10550
|
+
let cb = byId(SCALE_AXIS_CB);
|
|
10370
10551
|
if (!cb || cb.disabled) {
|
|
10371
10552
|
return;
|
|
10372
10553
|
}
|
|
10373
10554
|
cb.checked = !cb.checked;
|
|
10374
|
-
|
|
10555
|
+
scaleAxisCbClicked();
|
|
10375
10556
|
}
|
|
10376
10557
|
|
|
10377
10558
|
function focusSearch() {
|
|
@@ -11577,11 +11758,24 @@ function (root, d3, forester, phyloXml) {
|
|
|
11577
11758
|
&& _treeData.branch_length_unit.trim() === 'subs/site';
|
|
11578
11759
|
}
|
|
11579
11760
|
|
|
11580
|
-
|
|
11581
|
-
|
|
11761
|
+
// The Scale Axis switch, as on the desktop: an axis under a rectangular
|
|
11762
|
+
// or circular phylogram. What it measures is the tree's to say -- time
|
|
11763
|
+
// on a time tree whose branches show time, distance from the root on
|
|
11764
|
+
// every other -- so flipping Time | Div changes what the axis reads,
|
|
11765
|
+
// never whether there is one.
|
|
11766
|
+
function scaleAxisShown() {
|
|
11767
|
+
return _state.showScaleAxis === true && !_state.unrootedDisplay
|
|
11582
11768
|
&& _state.phylogram === true
|
|
11769
|
+
&& _basicTreeProperties.branchLengths === true;
|
|
11770
|
+
}
|
|
11771
|
+
|
|
11772
|
+
function distanceAxisShown() {
|
|
11773
|
+
return scaleAxisShown() && !timeAxisShown();
|
|
11774
|
+
}
|
|
11775
|
+
|
|
11776
|
+
function timeAxisShown() {
|
|
11777
|
+
return scaleAxisShown()
|
|
11583
11778
|
&& _timeInfo !== null && _timeInfo.type !== null
|
|
11584
|
-
&& _basicTreeProperties.branchLengths === true
|
|
11585
11779
|
// The axis is an overlay calibrated to the tree's own branch scale,
|
|
11586
11780
|
// so it must be hidden when the branches are showing DIVERGENCE --
|
|
11587
11781
|
// calendar years against substitutions reads as a confident lie.
|
|
@@ -11594,11 +11788,14 @@ function (root, d3, forester, phyloXml) {
|
|
|
11594
11788
|
&& !branchesShowDivergence();
|
|
11595
11789
|
}
|
|
11596
11790
|
|
|
11597
|
-
function
|
|
11791
|
+
function scaleAxisBottomReserve() {
|
|
11598
11792
|
// circular draws its ruler in the tree's own gap, not under it
|
|
11599
|
-
if (!
|
|
11793
|
+
if (!scaleAxisShown() || _state.circularDisplay) {
|
|
11600
11794
|
return 0;
|
|
11601
11795
|
}
|
|
11796
|
+
if (!timeAxisShown()) {
|
|
11797
|
+
return DISTANCE_AXIS_RESERVE;
|
|
11798
|
+
}
|
|
11602
11799
|
return _timeInfo.type === 'geologic' ? TIME_GEO_RESERVE : TIME_CAL_RESERVE;
|
|
11603
11800
|
}
|
|
11604
11801
|
|
|
@@ -11610,7 +11807,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
11610
11807
|
// own bottom rows (conservation, consensus, ruler) must end above it
|
|
11611
11808
|
// or the bar covers them -- the old bare slider did exactly that.
|
|
11612
11809
|
// The scale bar sits in the same band, under the tree's last row.
|
|
11613
|
-
return Math.max(msaShown() ? msaBottomReserve() + MSA_NAV_RESERVE : 0,
|
|
11810
|
+
return Math.max(msaShown() ? msaBottomReserve() + MSA_NAV_RESERVE : 0, scaleAxisBottomReserve(),
|
|
11614
11811
|
heatmapBottomReserve(),
|
|
11615
11812
|
scaleBarShown() && !radialDisplay() ? SCALE_BAR_RESERVE : 0);
|
|
11616
11813
|
}
|
|
@@ -11620,14 +11817,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
11620
11817
|
// round number of branch-length units (forester.scaleBarLength) with
|
|
11621
11818
|
// end ticks and its length written above, drawn in the tree's own
|
|
11622
11819
|
// 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.
|
|
11820
|
+
// true. Not in a cladogram (nothing to measure) and not under the scale
|
|
11821
|
+
// axis (which says the same, and more); in the radial layouts it sits
|
|
11822
|
+
// below the fan at its left edge. The desktop keeps its bar beside its
|
|
11823
|
+
// axis; ours gives way (Christian, 2026-09-30).
|
|
11626
11824
|
const SCALE_BAR_TARGET_PX = 100;
|
|
11627
11825
|
const SCALE_BAR_RESERVE = 30;
|
|
11628
11826
|
|
|
11629
11827
|
function scaleBarShown() {
|
|
11630
|
-
return _state.phylogram === true && _basicTreeProperties.branchLengths === true && !
|
|
11828
|
+
return _state.phylogram === true && _basicTreeProperties.branchLengths === true && !scaleAxisShown();
|
|
11631
11829
|
}
|
|
11632
11830
|
|
|
11633
11831
|
// pixels per branch-length unit in the current layout's own coordinates
|
|
@@ -11703,6 +11901,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
11703
11901
|
delete _floatStrips['aptx-time-axis'];
|
|
11704
11902
|
}
|
|
11705
11903
|
_svgGroup.selectAll('g.aptx-timegrid').remove();
|
|
11904
|
+
_svgGroup.selectAll('g.aptx-distance, g.aptx-distance-grid').remove();
|
|
11905
|
+
if (_floatGroup) {
|
|
11906
|
+
_floatGroup.selectAll('g.aptx-distance-axis').remove();
|
|
11907
|
+
delete _floatStrips['aptx-distance-axis'];
|
|
11908
|
+
}
|
|
11909
|
+
if (distanceAxisShown()) {
|
|
11910
|
+
drawDistanceAxis();
|
|
11911
|
+
return;
|
|
11912
|
+
}
|
|
11706
11913
|
if (!timeAxisShown() || !_root || !_yScale) {
|
|
11707
11914
|
return;
|
|
11708
11915
|
}
|
|
@@ -11744,7 +11951,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
11744
11951
|
// BEHIND the tree (inserted first in the group) at the fine geologic
|
|
11745
11952
|
// boundaries / calendar year ticks; each axis branch below fills it
|
|
11746
11953
|
let grid = null;
|
|
11747
|
-
if (_state.
|
|
11954
|
+
if (_state.showScaleGrid) {
|
|
11748
11955
|
grid = _svgGroup.insert('g', 'g').attr('class', 'aptx-timegrid')
|
|
11749
11956
|
.style('pointer-events', 'none');
|
|
11750
11957
|
}
|
|
@@ -11858,17 +12065,16 @@ function (root, d3, forester, phyloXml) {
|
|
|
11858
12065
|
}
|
|
11859
12066
|
|
|
11860
12067
|
// ---- the axis itself: a FLOATING strip (see floatStripGroup) ----
|
|
11861
|
-
|
|
12068
|
+
// the viewer's sans-serif: an SVG text inherits the browser's serif
|
|
12069
|
+
let ax = floatStripGroup('aptx-time-axis', axisTop - 4, scaleAxisBottomReserve() + 4)
|
|
12070
|
+
.style('font-family', FONT_DEFAULTS);
|
|
11862
12071
|
function axisBackdrop(x0, x1) {
|
|
11863
12072
|
// opaque, in the background colour, so tips panned under the
|
|
11864
12073
|
// strip do not show through between the bands and the ruler
|
|
11865
12074
|
let left = Math.min(x0, x1);
|
|
11866
12075
|
ax.append('rect').attr('x', left - 2).attr('y', axisTop - 4)
|
|
11867
|
-
.attr('width', Math.abs(x1 - x0) + 4).attr('height',
|
|
12076
|
+
.attr('width', Math.abs(x1 - x0) + 4).attr('height', scaleAxisBottomReserve() + 4)
|
|
11868
12077
|
.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
12078
|
}
|
|
11873
12079
|
if (info.type === 'geologic') {
|
|
11874
12080
|
let rootAge = info.rootAge;
|
|
@@ -12024,7 +12230,19 @@ function (root, d3, forester, phyloXml) {
|
|
|
12024
12230
|
let from; // the root end and the tip end, as dates
|
|
12025
12231
|
let to;
|
|
12026
12232
|
let bands = [];
|
|
12027
|
-
|
|
12233
|
+
let unit = null; // written once past the tip end
|
|
12234
|
+
if (info.type === 'distance') {
|
|
12235
|
+
// not a date: a distance from the displayed root, 0 at its centre
|
|
12236
|
+
depthOf = function (v) {
|
|
12237
|
+
return info.rootDepth + (v * corr);
|
|
12238
|
+
};
|
|
12239
|
+
from = 0;
|
|
12240
|
+
to = info.maxDist;
|
|
12241
|
+
ticks = info.ticks.map(function (v) {
|
|
12242
|
+
return {value: v, text: String(v)};
|
|
12243
|
+
});
|
|
12244
|
+
unit = info.unit;
|
|
12245
|
+
} else if (info.type === 'geologic') {
|
|
12028
12246
|
let rootAge = info.rootAge;
|
|
12029
12247
|
if (!(rootAge > 0)) {
|
|
12030
12248
|
return;
|
|
@@ -12045,6 +12263,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
12045
12263
|
ticks.push({value: to, text: String(Math.round(to * 100) / 100), first: true});
|
|
12046
12264
|
}
|
|
12047
12265
|
bands = forester.geoOverlapping(forester.geoBandRanks(to, rootAge)[1], to, rootAge);
|
|
12266
|
+
unit = 'Ma';
|
|
12048
12267
|
} else {
|
|
12049
12268
|
let present = info.presentDate;
|
|
12050
12269
|
if (!(present > 0)) {
|
|
@@ -12107,7 +12326,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
12107
12326
|
let at = function (r, off) {
|
|
12108
12327
|
return [(along[0] * r) + (across[0] * off), (along[1] * r) + (across[1] * off)];
|
|
12109
12328
|
};
|
|
12110
|
-
let ax = g.append('g').attr('class', 'aptx-time-ruler');
|
|
12329
|
+
let ax = g.append('g').attr('class', 'aptx-time-ruler').style('font-family', FONT_DEFAULTS);
|
|
12111
12330
|
let e0 = at(r0, 0);
|
|
12112
12331
|
let e1 = at(r1, 0);
|
|
12113
12332
|
ax.append('line').attr('x1', e0[0]).attr('y1', e0[1]).attr('x2', e1[0]).attr('y2', e1[1])
|
|
@@ -12148,8 +12367,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
12148
12367
|
label(r, side * 11, t.text, ink, true);
|
|
12149
12368
|
placed.push(r);
|
|
12150
12369
|
});
|
|
12370
|
+
if (unit) {
|
|
12371
|
+
// along the ruler past its end, so a long unit reads outward
|
|
12372
|
+
label(r1 + 8 + (unit.length * 2.8), 0, unit, ink, false);
|
|
12373
|
+
}
|
|
12151
12374
|
if (info.type === 'geologic') {
|
|
12152
|
-
label(r1 + 14, 0, 'Ma', ink, false);
|
|
12153
12375
|
named.forEach(function (n) {
|
|
12154
12376
|
if ((n.text.length * 5.5) + 4 <= n.span) {
|
|
12155
12377
|
label(n.r, -side * 11, n.text, ink, true);
|
|
@@ -12158,13 +12380,108 @@ function (root, d3, forester, phyloXml) {
|
|
|
12158
12380
|
}
|
|
12159
12381
|
}
|
|
12160
12382
|
|
|
12161
|
-
|
|
12162
|
-
|
|
12383
|
+
// The distance axis, the desktop's "Scale Axis": a ruler from 0 at the
|
|
12384
|
+
// displayed root out to the deepest tip, in branch-length units, with
|
|
12385
|
+
// round ticks and the tree's unit at its end; "Scale Grid" draws each
|
|
12386
|
+
// tick as a faint line (a ring, in circular) behind the tree. Measured
|
|
12387
|
+
// per draw, so a subtree view's axis starts at ITS root.
|
|
12388
|
+
function drawDistanceAxis() {
|
|
12389
|
+
if (!_root || !_yScale) {
|
|
12390
|
+
return;
|
|
12391
|
+
}
|
|
12392
|
+
let corr = Math.abs(_yScale(1) - _yScale(0));
|
|
12393
|
+
if (!isFinite(corr) || corr <= 0) {
|
|
12394
|
+
return;
|
|
12395
|
+
}
|
|
12396
|
+
let rootDepth = (topNode() || _root).y;
|
|
12397
|
+
let maxTipX = rootDepth;
|
|
12398
|
+
forEachDisplayed(function (n) {
|
|
12399
|
+
if (!n.children && n.y > maxTipX) {
|
|
12400
|
+
maxTipX = n.y;
|
|
12401
|
+
}
|
|
12402
|
+
});
|
|
12403
|
+
let maxDist = (maxTipX - rootDepth) / corr;
|
|
12404
|
+
let ticks = forester.distanceAxisTickValues(maxDist);
|
|
12405
|
+
if (ticks.length === 0) {
|
|
12406
|
+
return;
|
|
12407
|
+
}
|
|
12408
|
+
let unit = _treeData && typeof _treeData.branch_length_unit === 'string'
|
|
12409
|
+
? _treeData.branch_length_unit.trim() : '';
|
|
12410
|
+
let ink = _state.branchColorDefault;
|
|
12411
|
+
let grid = null;
|
|
12412
|
+
if (_state.showScaleGrid) {
|
|
12413
|
+
grid = _svgGroup.insert('g', 'g').attr('class', 'aptx-distance-grid')
|
|
12414
|
+
.style('pointer-events', 'none');
|
|
12415
|
+
}
|
|
12416
|
+
if (_state.circularDisplay) {
|
|
12417
|
+
let g = _svgGroup.append('g').attr('class', 'aptx-distance').style('pointer-events', 'none');
|
|
12418
|
+
drawCircularTimeRuler({type: 'distance', rootDepth: rootDepth, maxDist: maxDist, ticks: ticks,
|
|
12419
|
+
unit: unit || null}, null, maxTipX, corr, g, grid, ink);
|
|
12420
|
+
return;
|
|
12421
|
+
}
|
|
12422
|
+
let xOf = function (v) {
|
|
12423
|
+
return rootDepth + (v * corr);
|
|
12424
|
+
};
|
|
12425
|
+
let axisTop = _clusterH + 6;
|
|
12426
|
+
if (grid) {
|
|
12427
|
+
// the root and tip ends excluded: they would retrace the tree
|
|
12428
|
+
ticks.forEach(function (v) {
|
|
12429
|
+
if (v > 0 && v < maxDist - 1e-9) {
|
|
12430
|
+
grid.append('line').attr('x1', xOf(v)).attr('x2', xOf(v))
|
|
12431
|
+
.attr('y1', 0).attr('y2', axisTop)
|
|
12432
|
+
.attr('stroke', ink).attr('stroke-opacity', 0.18)
|
|
12433
|
+
.attr('stroke-width', 1);
|
|
12434
|
+
}
|
|
12435
|
+
});
|
|
12436
|
+
}
|
|
12437
|
+
let reserve = scaleAxisBottomReserve();
|
|
12438
|
+
let ax = floatStripGroup('aptx-distance-axis', axisTop - 4, reserve + 4)
|
|
12439
|
+
.style('font-family', FONT_DEFAULTS);
|
|
12440
|
+
let x0 = xOf(0);
|
|
12441
|
+
let x1 = xOf(maxDist);
|
|
12442
|
+
// opaque, as the time axis's, so tips panned under it do not show
|
|
12443
|
+
ax.append('rect').attr('x', x0 - 2).attr('y', axisTop - 4)
|
|
12444
|
+
.attr('width', (x1 - x0) + 4).attr('height', reserve + 4)
|
|
12445
|
+
.attr('fill', _state.backgroundColorDefault);
|
|
12446
|
+
let rulerY = axisTop + 4;
|
|
12447
|
+
ax.append('line').attr('x1', x0).attr('x2', x1)
|
|
12448
|
+
.attr('y1', rulerY).attr('y2', rulerY)
|
|
12449
|
+
.attr('stroke', ink).attr('stroke-width', 1);
|
|
12450
|
+
// every tick drawn, its number only where it clears the last one
|
|
12451
|
+
let lastRight = -Infinity;
|
|
12452
|
+
ticks.forEach(function (v) {
|
|
12453
|
+
let x = xOf(v);
|
|
12454
|
+
ax.append('line').attr('x1', x).attr('x2', x)
|
|
12455
|
+
.attr('y1', rulerY).attr('y2', rulerY + 4)
|
|
12456
|
+
.attr('stroke', ink).attr('stroke-width', 1);
|
|
12457
|
+
let text = String(v);
|
|
12458
|
+
let half = text.length * 2.8;
|
|
12459
|
+
if ((x - half) >= lastRight + 4) {
|
|
12460
|
+
ax.append('text').attr('x', x).attr('y', rulerY + 14)
|
|
12461
|
+
.attr('text-anchor', 'middle')
|
|
12462
|
+
.style('font-size', '9px').style('fill', ink)
|
|
12463
|
+
.text(text);
|
|
12464
|
+
lastRight = x + half;
|
|
12465
|
+
}
|
|
12466
|
+
});
|
|
12467
|
+
if (unit) {
|
|
12468
|
+
// past the ruler's end, and past the last number, which is
|
|
12469
|
+
// centred on that end
|
|
12470
|
+
ax.append('text').attr('x', Math.max(x1 + 8, lastRight + 6)).attr('y', rulerY + 14)
|
|
12471
|
+
.attr('text-anchor', 'start')
|
|
12472
|
+
.style('font-size', '9px').style('fill', ink)
|
|
12473
|
+
.text(unit);
|
|
12474
|
+
}
|
|
12475
|
+
placeFloatingOverlays();
|
|
12476
|
+
}
|
|
12477
|
+
|
|
12478
|
+
function scaleAxisCbClicked() {
|
|
12479
|
+
_state.showScaleAxis = getCheckboxValue(SCALE_AXIS_CB);
|
|
12163
12480
|
scheduleUpdate(null, 0);
|
|
12164
12481
|
}
|
|
12165
12482
|
|
|
12166
|
-
function
|
|
12167
|
-
_state.
|
|
12483
|
+
function scaleGridCbClicked() {
|
|
12484
|
+
_state.showScaleGrid = getCheckboxValue(SCALE_GRID_CB);
|
|
12168
12485
|
scheduleUpdate(null, 0);
|
|
12169
12486
|
}
|
|
12170
12487
|
|
|
@@ -13312,15 +13629,17 @@ function (root, d3, forester, phyloXml) {
|
|
|
13312
13629
|
heatCb.disabled = _state.unrootedDisplay === true;
|
|
13313
13630
|
}
|
|
13314
13631
|
syncHeatmapControls();
|
|
13315
|
-
let
|
|
13316
|
-
if (
|
|
13317
|
-
// circular draws it along the gap;
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13632
|
+
let axisCb = byId(SCALE_AXIS_CB);
|
|
13633
|
+
if (axisCb) {
|
|
13634
|
+
// circular draws it along the gap; unrooted cannot, and a
|
|
13635
|
+
// cladogram has nothing to measure
|
|
13636
|
+
axisCb.disabled = _state.unrootedDisplay === true || _state.phylogram !== true;
|
|
13637
|
+
}
|
|
13638
|
+
let gridCb = byId(SCALE_GRID_CB);
|
|
13639
|
+
if (gridCb) {
|
|
13640
|
+
// grid lines hang off the scale axis: no axis, nothing to grid
|
|
13641
|
+
gridCb.disabled = _state.unrootedDisplay === true || _state.phylogram !== true
|
|
13642
|
+
|| _state.showScaleAxis !== true;
|
|
13324
13643
|
}
|
|
13325
13644
|
let minus = byId(ZOOM_OUT_X);
|
|
13326
13645
|
let plus = byId(ZOOM_IN_X);
|
|
@@ -13415,6 +13734,182 @@ function (root, d3, forester, phyloXml) {
|
|
|
13415
13734
|
scheduleUpdate();
|
|
13416
13735
|
}
|
|
13417
13736
|
|
|
13737
|
+
// ===================== Properties in the label =====================
|
|
13738
|
+
// The desktop's "Properties" option and its Annotation Fields chooser:
|
|
13739
|
+
// a node's property values in its label, values only, " | "-joined, in
|
|
13740
|
+
// the order chosen (forester.labelPropertiesText). Nothing chosen means
|
|
13741
|
+
// every user-visible property in the node's own order, less the heat
|
|
13742
|
+
// map's columns while it is shown -- a field has one display role.
|
|
13743
|
+
|
|
13744
|
+
function labelFields() {
|
|
13745
|
+
if (_labelFields === null && _treeData) {
|
|
13746
|
+
_labelFields = forester.labelPropertyRefs(_treeData);
|
|
13747
|
+
}
|
|
13748
|
+
return _labelFields || [];
|
|
13749
|
+
}
|
|
13750
|
+
|
|
13751
|
+
// the heat map's columns while it is shown: left out of the default label
|
|
13752
|
+
function labelFieldsExcluded() {
|
|
13753
|
+
let excluded = Object.create(null);
|
|
13754
|
+
if (heatmapShown()) {
|
|
13755
|
+
let m = heatmapModel();
|
|
13756
|
+
(m ? m.refs : []).forEach(function (c) {
|
|
13757
|
+
excluded[c.ref] = true;
|
|
13758
|
+
});
|
|
13759
|
+
}
|
|
13760
|
+
return excluded;
|
|
13761
|
+
}
|
|
13762
|
+
|
|
13763
|
+
function labelPropertiesOf(node) {
|
|
13764
|
+
return forester.labelPropertiesText(node, _state.labelProperties,
|
|
13765
|
+
_state.labelProperties ? null : labelFieldsExcluded());
|
|
13766
|
+
}
|
|
13767
|
+
|
|
13768
|
+
function propertiesCbClicked() {
|
|
13769
|
+
_state.showProperties = getCheckboxValue(PROPERTIES_CB);
|
|
13770
|
+
if (_state.showProperties) {
|
|
13771
|
+
_state.showExternalLabels = true;
|
|
13772
|
+
setCheckboxValue(EXTERNAL_LABEL_CB, true);
|
|
13773
|
+
}
|
|
13774
|
+
search0();
|
|
13775
|
+
search1();
|
|
13776
|
+
scheduleUpdate();
|
|
13777
|
+
}
|
|
13778
|
+
|
|
13779
|
+
// Which properties the label shows, and in what order: each field ticked
|
|
13780
|
+
// or not, and moved with the arrows (or the arrow keys). Apply keeps the ticked
|
|
13781
|
+
// ones in the order shown; picking a field that was not shown turns the
|
|
13782
|
+
// Properties label on, as the desktop does, since otherwise choosing it
|
|
13783
|
+
// would visibly do nothing. "All" goes back to every field.
|
|
13784
|
+
function showLabelFieldsDialog() {
|
|
13785
|
+
let all = labelFields();
|
|
13786
|
+
if (all.length < 1) {
|
|
13787
|
+
return;
|
|
13788
|
+
}
|
|
13789
|
+
let chosen = _state.labelProperties ? _state.labelProperties.filter(function (r) {
|
|
13790
|
+
return all.indexOf(r) >= 0;
|
|
13791
|
+
}) : null;
|
|
13792
|
+
// the chosen fields first, in their order, then the rest as offered;
|
|
13793
|
+
// with none chosen, ticked is what the label shows by default
|
|
13794
|
+
let excluded = chosen ? null : labelFieldsExcluded();
|
|
13795
|
+
let working = (chosen || []).concat(all.filter(function (r) {
|
|
13796
|
+
return !chosen || chosen.indexOf(r) < 0;
|
|
13797
|
+
})).map(function (ref) {
|
|
13798
|
+
return {ref: ref, on: chosen ? chosen.indexOf(ref) >= 0 : !excluded[ref]};
|
|
13799
|
+
});
|
|
13800
|
+
if (!chosen) {
|
|
13801
|
+
// the ticked ones first, so the list reads as the label does
|
|
13802
|
+
working = working.filter(function (w) {
|
|
13803
|
+
return w.on;
|
|
13804
|
+
}).concat(working.filter(function (w) {
|
|
13805
|
+
return !w.on;
|
|
13806
|
+
}));
|
|
13807
|
+
}
|
|
13808
|
+
let before = working.filter(function (w) {
|
|
13809
|
+
return w.on && (chosen !== null || _state.showProperties);
|
|
13810
|
+
}).map(function (w) {
|
|
13811
|
+
return w.ref;
|
|
13812
|
+
});
|
|
13813
|
+
let shell = makeDialogShell(LABEL_FIELDS_DIALOG, 'Label fields', 340);
|
|
13814
|
+
let body = shell.body;
|
|
13815
|
+
repsElement(body, 'div', 'aptx-reps-lead',
|
|
13816
|
+
'Tick the properties the labels show, and set their order with the arrows.');
|
|
13817
|
+
let list = repsElement(body, 'div', 'aptx-reorder');
|
|
13818
|
+
|
|
13819
|
+
function move(from, to) {
|
|
13820
|
+
if (to < 0 || to >= working.length || from === to) {
|
|
13821
|
+
return false;
|
|
13822
|
+
}
|
|
13823
|
+
working.splice(to, 0, working.splice(from, 1)[0]);
|
|
13824
|
+
draw();
|
|
13825
|
+
return true;
|
|
13826
|
+
}
|
|
13827
|
+
|
|
13828
|
+
function draw() {
|
|
13829
|
+
list.textContent = '';
|
|
13830
|
+
working.forEach(function (w, i) {
|
|
13831
|
+
let row = repsElement(list, 'div', 'aptx-reorder-row');
|
|
13832
|
+
row.tabIndex = 0;
|
|
13833
|
+
let box = repsElement(row, 'input', 'aptx-reorder-check');
|
|
13834
|
+
box.type = 'checkbox';
|
|
13835
|
+
box.checked = w.on;
|
|
13836
|
+
box.addEventListener('change', function () {
|
|
13837
|
+
w.on = box.checked;
|
|
13838
|
+
});
|
|
13839
|
+
repsElement(row, 'span', 'aptx-reorder-name', forester.propertyDisplayName(w.ref)).title = w.ref;
|
|
13840
|
+
let up = repsElement(row, 'button', 'aptx-reorder-move', '↑');
|
|
13841
|
+
let down = repsElement(row, 'button', 'aptx-reorder-move', '↓');
|
|
13842
|
+
up.type = 'button';
|
|
13843
|
+
down.type = 'button';
|
|
13844
|
+
up.title = 'Move up';
|
|
13845
|
+
down.title = 'Move down';
|
|
13846
|
+
up.disabled = (i === 0);
|
|
13847
|
+
down.disabled = (i === working.length - 1);
|
|
13848
|
+
up.addEventListener('click', function () {
|
|
13849
|
+
if (move(i, i - 1)) {
|
|
13850
|
+
list.children[i - 1].focus();
|
|
13851
|
+
}
|
|
13852
|
+
});
|
|
13853
|
+
down.addEventListener('click', function () {
|
|
13854
|
+
if (move(i, i + 1)) {
|
|
13855
|
+
list.children[i + 1].focus();
|
|
13856
|
+
}
|
|
13857
|
+
});
|
|
13858
|
+
row.addEventListener('keydown', function (event) {
|
|
13859
|
+
if (event.key === ' ' && event.target === row) {
|
|
13860
|
+
event.preventDefault();
|
|
13861
|
+
box.checked = !box.checked;
|
|
13862
|
+
w.on = box.checked;
|
|
13863
|
+
return;
|
|
13864
|
+
}
|
|
13865
|
+
let d = (event.key === 'ArrowUp') ? -1 : ((event.key === 'ArrowDown') ? 1 : 0);
|
|
13866
|
+
if (d === 0) {
|
|
13867
|
+
return;
|
|
13868
|
+
}
|
|
13869
|
+
event.preventDefault();
|
|
13870
|
+
if (move(i, i + d)) {
|
|
13871
|
+
list.children[i + d].focus();
|
|
13872
|
+
}
|
|
13873
|
+
});
|
|
13874
|
+
});
|
|
13875
|
+
}
|
|
13876
|
+
draw();
|
|
13877
|
+
|
|
13878
|
+
let actions = repsElement(body, 'div', 'aptx-reps-actions');
|
|
13879
|
+
repsButton(actions, 'All', false, function () {
|
|
13880
|
+
_state.labelProperties = null;
|
|
13881
|
+
shell.dialog.close();
|
|
13882
|
+
scheduleUpdate();
|
|
13883
|
+
});
|
|
13884
|
+
repsButton(actions, 'Cancel', false, function () {
|
|
13885
|
+
shell.dialog.close();
|
|
13886
|
+
});
|
|
13887
|
+
repsButton(actions, 'Apply', true, function () {
|
|
13888
|
+
_state.labelProperties = working.filter(function (w) {
|
|
13889
|
+
return w.on;
|
|
13890
|
+
}).map(function (w) {
|
|
13891
|
+
return w.ref;
|
|
13892
|
+
});
|
|
13893
|
+
let added = _state.labelProperties.some(function (r) {
|
|
13894
|
+
return before.indexOf(r) < 0;
|
|
13895
|
+
});
|
|
13896
|
+
if (added && !_state.showProperties) {
|
|
13897
|
+
_state.showProperties = true;
|
|
13898
|
+
_state.showExternalLabels = true;
|
|
13899
|
+
setCheckboxValue(PROPERTIES_CB, true);
|
|
13900
|
+
setCheckboxValue(EXTERNAL_LABEL_CB, true);
|
|
13901
|
+
}
|
|
13902
|
+
shell.dialog.close();
|
|
13903
|
+
search0();
|
|
13904
|
+
search1();
|
|
13905
|
+
scheduleUpdate();
|
|
13906
|
+
});
|
|
13907
|
+
shell.dialog.showModal();
|
|
13908
|
+
if (list.firstChild) {
|
|
13909
|
+
list.firstChild.focus();
|
|
13910
|
+
}
|
|
13911
|
+
}
|
|
13912
|
+
|
|
13418
13913
|
function sequenceCbClicked() {
|
|
13419
13914
|
_state.showSequence = getCheckboxValue(SEQUENCE_CB);
|
|
13420
13915
|
if (_state.showSequence) {
|
|
@@ -16126,6 +16621,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
16126
16621
|
on(TAXONOMY_CB, 'click', taxonomyCbClicked);
|
|
16127
16622
|
|
|
16128
16623
|
on(SEQUENCE_CB, 'click', sequenceCbClicked);
|
|
16624
|
+
on(PROPERTIES_CB, 'click', propertiesCbClicked);
|
|
16625
|
+
on(LABEL_FIELDS_BUTTON, 'click', showLabelFieldsDialog);
|
|
16129
16626
|
|
|
16130
16627
|
on(CONFIDENCE_VALUES_CB, 'click', confidenceValuesCbClicked);
|
|
16131
16628
|
on(SUPPORT_DOTS_CB, 'click', supportDotsCbClicked);
|
|
@@ -16175,8 +16672,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
16175
16672
|
on(DOMAIN_EVALUE_INC, 'click', function () { domainEvalueStep(1); });
|
|
16176
16673
|
on(DOMAIN_LABELS_SELECT, 'change', domainLabelsChanged);
|
|
16177
16674
|
on(DOMAIN_GLOW_CB, 'click', domainGlowCbClicked);
|
|
16178
|
-
on(
|
|
16179
|
-
on(
|
|
16675
|
+
on(SCALE_AXIS_CB, 'click', scaleAxisCbClicked);
|
|
16676
|
+
on(SCALE_GRID_CB, 'click', scaleGridCbClicked);
|
|
16180
16677
|
|
|
16181
16678
|
on(LAYOUT_RECT_BUTTON, 'click', layoutButtonClicked);
|
|
16182
16679
|
on(BRANCH_SCALE_TIME_BUTTON, 'click', branchScaleButtonClicked);
|
|
@@ -16654,6 +17151,10 @@ function (root, d3, forester, phyloXml) {
|
|
|
16654
17151
|
if (_basicTreeProperties.sequences) {
|
|
16655
17152
|
labels.push(makeCheckboxItem('Sequence', SEQUENCE_CB, 'to show/hide node sequence information'));
|
|
16656
17153
|
}
|
|
17154
|
+
if (labelFields().length > 0) {
|
|
17155
|
+
labels.push(makeCheckboxItem('Properties', PROPERTIES_CB, 'to show/hide the nodes\' property values'
|
|
17156
|
+
+ ' (Label fields… chooses which, and in what order)'));
|
|
17157
|
+
}
|
|
16657
17158
|
if (_nodeLabels) {
|
|
16658
17159
|
for (const [key, value] of Object.entries(_nodeLabels)) {
|
|
16659
17160
|
if (value.label && value.propertyRef && value.description) {
|
|
@@ -16721,16 +17222,26 @@ function (root, d3, forester, phyloXml) {
|
|
|
16721
17222
|
if (heatmapAvailable()) {
|
|
16722
17223
|
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
17224
|
}
|
|
16724
|
-
if (
|
|
16725
|
-
|
|
16726
|
-
|
|
16727
|
-
|
|
16728
|
-
opts.push(makeCheckboxItem('
|
|
16729
|
-
+ (
|
|
17225
|
+
if (_basicTreeProperties.branchLengths === true) {
|
|
17226
|
+
let timeAxis = _timeInfo && _timeInfo.type
|
|
17227
|
+
? (_timeInfo.type === 'geologic' ? 'the geologic (ICS) time axis' : 'the calendar time axis')
|
|
17228
|
+
: null;
|
|
17229
|
+
opts.push(makeCheckboxItem('Scale Axis', SCALE_AXIS_CB, 'to show/hide '
|
|
17230
|
+
+ (timeAxis ? timeAxis + ' and node-age bars (a distance axis while the branches show divergence)'
|
|
17231
|
+
: 'a labeled distance axis from the root to the deepest tip')
|
|
17232
|
+
+ ' (phylogram; rectangular and circular layouts)'));
|
|
17233
|
+
opts.push(makeCheckboxItem('Scale Grid', SCALE_GRID_CB, 'to show/hide grid lines (rings, in circular) at the '
|
|
17234
|
+
+ (timeAxis ? 'time axis\'s intervals or ticks' : 'scale axis ticks')));
|
|
16730
17235
|
}
|
|
16731
17236
|
|
|
16732
17237
|
let h = '<fieldset><legend>Display Data</legend>';
|
|
16733
17238
|
h = h.concat(makeCheckboxGroup('Labels', labels));
|
|
17239
|
+
if (labelFields().length > 0) {
|
|
17240
|
+
h = h.concat('<div class="aptx-domrow"><span class="aptx-domlabel"></span>'
|
|
17241
|
+
+ '<input type="button" class="aptx-widebtn" value="Label fields…" name="' + LABEL_FIELDS_BUTTON
|
|
17242
|
+
+ '" id="' + LABEL_FIELDS_BUTTON + '" title="choose which properties the labels show, and in what'
|
|
17243
|
+
+ ' order"></div>');
|
|
17244
|
+
}
|
|
16734
17245
|
h = h.concat(makeCheckboxGroup('Nodes', nodes));
|
|
16735
17246
|
h = h.concat(makeCheckboxGroup('Options', opts));
|
|
16736
17247
|
h = h.concat('</fieldset>');
|
|
@@ -17055,6 +17566,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
17055
17566
|
setCheckboxValue(NODE_NAME_CB, _state.showNodeName);
|
|
17056
17567
|
setCheckboxValue(TAXONOMY_CB, _state.showTaxonomy);
|
|
17057
17568
|
setCheckboxValue(SEQUENCE_CB, _state.showSequence)
|
|
17569
|
+
setCheckboxValue(PROPERTIES_CB, _state.showProperties);
|
|
17058
17570
|
setCheckboxValue(CONFIDENCE_VALUES_CB, _state.showConfidenceValues);
|
|
17059
17571
|
setCheckboxValue(SUPPORT_DOTS_CB, _state.showSupportDots);
|
|
17060
17572
|
syncMadValuesCheckbox();
|
|
@@ -17092,8 +17604,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
17092
17604
|
setCheckboxValue(HEATMAP_CB, _state.showHeatmap);
|
|
17093
17605
|
syncDomainControls();
|
|
17094
17606
|
syncHeatmapControls();
|
|
17095
|
-
setCheckboxValue(
|
|
17096
|
-
setCheckboxValue(
|
|
17607
|
+
setCheckboxValue(SCALE_AXIS_CB, _state.showScaleAxis);
|
|
17608
|
+
setCheckboxValue(SCALE_GRID_CB, _state.showScaleGrid);
|
|
17097
17609
|
setCheckboxValue(SHORTEN_NODE_NAME_CB, _state.shortenNodeNames);
|
|
17098
17610
|
populateVisualizationMenus();
|
|
17099
17611
|
initializeSearchOptions();
|
|
@@ -17729,9 +18241,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
17729
18241
|
}
|
|
17730
18242
|
let name = _root_const.name ? String(_root_const.name).trim() : '';
|
|
17731
18243
|
// what the phylogeny itself states goes with the part: without its
|
|
17732
|
-
// branch_length_unit a subtree saved from Div opened again as time
|
|
18244
|
+
// branch_length_unit a subtree saved from Div opened again as time,
|
|
18245
|
+
// and without its properties (the figure setting) the part opened
|
|
18246
|
+
// without the heat map the whole tree opens with
|
|
17733
18247
|
let part = Object.assign({}, _root, {name: name ? name + ' (subtree)' : undefined});
|
|
17734
|
-
['rooted', 'rerootable', 'type', 'branch_length_unit'].forEach(function (k) {
|
|
18248
|
+
['rooted', 'rerootable', 'type', 'branch_length_unit', 'properties'].forEach(function (k) {
|
|
17735
18249
|
if (_root_const[k] !== undefined && _root_const[k] !== null) {
|
|
17736
18250
|
part[k] = _root_const[k];
|
|
17737
18251
|
}
|