archaeopteryx 3.27.0 → 3.28.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 +86 -1
- package/archaeopteryx.js +460 -153
- package/forester.js +2 -2
- package/package.json +1 -1
package/archaeopteryx.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
// v 3.
|
|
23
|
+
// v 3.28.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.28.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';
|
|
@@ -272,6 +272,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
272
272
|
const PHYLOGRAM_MIN_BRANCH_FRACTION = 0.5;
|
|
273
273
|
const SHORTEN_NAME_MAX_LENGTH = 18;
|
|
274
274
|
const PANEL_STYLE_ID = 'aptx-panel-styles';
|
|
275
|
+
// The tree's own <svg>: named so that the stylesheet can say what a host
|
|
276
|
+
// page's rules for a bare "svg" would otherwise decide (hostProofCss).
|
|
277
|
+
const TREE_SVG_CLASS = 'aptx-tree';
|
|
275
278
|
// How wide a legend row is treated as, for grabbing it with the mouse.
|
|
276
279
|
const PANEL_WIDTH = 214; // fixed control-panel width; shared by the .aptx-panel CSS and leftPanelClearance() so the two can't drift
|
|
277
280
|
const PANEL_WIDTH_COMPACT = 196; // the same, at panelDensity 'compact'; panelWidth() picks between them
|
|
@@ -454,6 +457,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
454
457
|
// archaeopteryx-anime.svg), inlined so the library stays a single file.
|
|
455
458
|
// Gradient ids are prefixed: they were generic enough to collide with an
|
|
456
459
|
// embedding page's own defs.
|
|
460
|
+
// (drawn with attributes, as any svg file is; paintInline moves its paint into styles where it is used)
|
|
457
461
|
const ARCHAEOPTERYX_LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-label="Archaeopteryx"> <defs> <linearGradient id="aptxlogo-wg" x1="0" y1="1" x2="1" y2="0"> <stop offset="0" stop-color="#4f46e5"/><stop offset="0.55" stop-color="#38bdf8"/><stop offset="1" stop-color="#a7f3d0"/> </linearGradient> <linearGradient id="aptxlogo-wg2" x1="0" y1="1" x2="1" y2="0"> <stop offset="0" stop-color="#3730a3"/><stop offset="1" stop-color="#4f79e0"/> </linearGradient> <linearGradient id="aptxlogo-tg" x1="0" y1="1" x2="1" y2="0"> <stop offset="0" stop-color="#f43f5e"/><stop offset="0.5" stop-color="#fb923c"/><stop offset="1" stop-color="#fde047"/> </linearGradient> <linearGradient id="aptxlogo-bg" x1="0" y1="0" x2="0" y2="1"> <stop offset="0" stop-color="#fff7e0"/><stop offset="1" stop-color="#fcd34d"/> </linearGradient> </defs> <g transform="translate(28.9,18.0) scale(0.9091)"> <g stroke="#25304f" stroke-width="6" stroke-linejoin="round" stroke-linecap="round"> <path d="M 78.0,152.0 C 49.3,150.6 12.5,173.3 -4.4,194.3 C 25.1,192.0 66.1,175.5 78.0,152.0 Z" fill="url(#aptxlogo-tg)"/> <path d="M 78.0,152.0 C 48.1,143.8 3.5,158.8 -19.7,176.6 C 12.1,181.2 59.5,173.7 78.0,152.0 Z" fill="url(#aptxlogo-tg)"/> <path d="M 78.0,152.0 C 51.8,138.2 6.9,142.5 -18.7,154.0 C 10.0,164.8 56.0,167.9 78.0,152.0 Z" fill="url(#aptxlogo-tg)"/> <path d="M 78.0,152.0 C 58.9,135.3 20.6,130.3 -2.8,135.1 C 18.9,149.7 56.6,161.2 78.0,152.0 Z" fill="url(#aptxlogo-tg)"/> <path d="M 118.0,126.0 C 110.4,106.2 84.4,93.0 65.5,92.1 C 75.9,110.5 99.0,129.6 118.0,126.0 Z" fill="url(#aptxlogo-wg2)"/> <path d="M 118.0,126.0 C 115.6,103.3 92.6,79.7 73.5,71.3 C 79.1,93.8 97.5,122.0 118.0,126.0 Z" fill="url(#aptxlogo-wg2)"/> <path d="M 118.0,126.0 C 123.0,106.1 110.4,79.8 96.5,67.4 C 94.4,87.9 101.6,117.0 118.0,126.0 Z" fill="url(#aptxlogo-wg2)"/> <path d="M 106,172 L 101,202 M 132,172 L 129,202" fill="none" stroke-width="9"/> <path d="M 101,202 L 88,209 M 101,202 L 112,210 M 101,202 L 99,213 M 129,202 L 116,209 M 129,202 L 140,210 M 129,202 L 128,213" fill="none" stroke-width="6"/> <path d="M 96,176 C 74,168 66,146 76,128 C 86,110 110,102 132,106 C 152,110 164,126 162,146 C 160,166 142,180 120,180 C 112,180 103,179 96,176 Z" fill="url(#aptxlogo-bg)"/> <path d="M 100,174 C 86,166 82,150 90,138 C 96,146 108,152 122,152 C 136,152 148,148 156,140 C 160,156 150,172 128,177 C 118,179 108,178 100,174 Z" fill="#fff6dd" stroke="none"/> <path d="M 96,176 C 74,168 66,146 76,128 C 86,110 110,102 132,106 C 152,110 164,126 162,146 C 160,166 142,180 120,180 C 112,180 103,179 96,176 Z" fill="none"/> <path d="M 150.0,60.0 C 159.5,51.2 158.7,35.2 152.9,25.9 C 145.3,35.7 141.5,51.8 150.0,60.0 Z" fill="url(#aptxlogo-wg)"/> <path d="M 164.0,56.0 C 174.4,51.0 177.8,37.3 175.2,28.0 C 166.2,34.2 158.9,46.9 164.0,56.0 Z" fill="url(#aptxlogo-wg)"/> <path d="M 136.0,66.0 C 141.8,56.5 137.0,44.2 129.6,38.5 C 125.7,48.3 126.7,61.9 136.0,66.0 Z" fill="url(#aptxlogo-wg)"/> <path d="M 158,58 C 186,54 208,72 208,96 C 208,120 188,136 164,134 C 140,132 126,114 128,92 C 130,72 142,60 158,58 Z" fill="url(#aptxlogo-bg)"/> <path d="M 203,82 C 216,84 230,92 238,100 C 230,108 216,113 202,112 C 206,104 206,90 203,82 Z" fill="#fb923c"/> <path d="M 204,101 C 214,102 224,103 234,101" fill="none" stroke-width="4"/> <path d="M 128.0,116.0 C 113.1,91.9 75.3,80.3 50.0,83.2 C 68.3,104.8 103.7,124.6 128.0,116.0 Z" fill="url(#aptxlogo-wg)"/> <path d="M 128.0,116.0 C 118.0,85.8 79.7,60.2 51.1,54.1 C 65.4,83.1 98.9,116.3 128.0,116.0 Z" fill="url(#aptxlogo-wg)"/> <path d="M 128.0,116.0 C 127.0,83.7 97.3,47.3 71.5,32.7 C 76.9,65.0 99.8,107.4 128.0,116.0 Z" fill="url(#aptxlogo-wg)"/> <path d="M 128.0,116.0 C 136.1,88.7 121.1,50.1 103.3,30.9 C 99.0,59.5 106.6,101.2 128.0,116.0 Z" fill="url(#aptxlogo-wg)"/> </g> <g> <ellipse cx="172" cy="94" rx="15" ry="17" fill="#25304f"/> <ellipse cx="172" cy="97" rx="10" ry="11" fill="#38bdf8"/> <circle cx="167" cy="88" r="6" fill="#ffffff"/> <circle cx="178" cy="103" r="3" fill="#ffffff" opacity="0.9"/> <ellipse cx="146" cy="112" rx="9" ry="5" fill="#fb7185" opacity="0.75"/> <path d="M 52,53 C 53.62,59.75 54.25,60.38 61,62 C 54.25,63.62 53.62,64.25 52,71 C 50.38,64.25 49.75,63.62 43,62 C 49.75,60.38 50.38,59.75 52,53 Z" fill="#fde68a"/> <path d="M 222,161 C 223.26,166.25 223.75,166.74 229,168 C 223.75,169.26 223.26,169.75 222,175 C 220.74,169.75 220.25,169.26 215,168 C 220.25,166.74 220.74,166.25 222,161 Z" fill="#fde68a"/> <path d="M 86,208 C 87.08,212.5 87.5,212.92 92,214 C 87.5,215.08 87.08,215.5 86,220 C 84.92,215.5 84.5,215.08 80,214 C 84.5,212.92 84.92,212.5 86,208 Z" fill="#fde68a"/> </g> </g> </svg>';
|
|
458
462
|
const NODE_DATA = 'node_data_dialog';
|
|
459
463
|
const COLOR_PICKER = 'color_picker_dialog';
|
|
@@ -1538,9 +1542,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
1538
1542
|
let fill = hoverGlowColor(d);
|
|
1539
1543
|
HOVER_GLOW_RADII.forEach(function (radius, i) {
|
|
1540
1544
|
g.append('circle')
|
|
1541
|
-
.
|
|
1542
|
-
.
|
|
1543
|
-
.
|
|
1545
|
+
.style('fill-opacity', HOVER_GLOW_ALPHAS[i])
|
|
1546
|
+
.style('stroke', 'none')
|
|
1547
|
+
.style('fill', fill)
|
|
1544
1548
|
.attr('r', (dia * radius) / 2);
|
|
1545
1549
|
});
|
|
1546
1550
|
}
|
|
@@ -2758,7 +2762,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
2758
2762
|
chips.forEach(function (chip) {
|
|
2759
2763
|
let t = g.append('text')
|
|
2760
2764
|
.attr('x', chipRight).attr('y', baseline)
|
|
2761
|
-
.
|
|
2765
|
+
.style('text-anchor', 'end')
|
|
2762
2766
|
.style('font', rowFont)
|
|
2763
2767
|
.style('fill', ink)
|
|
2764
2768
|
.style('fill-opacity', 0.75)
|
|
@@ -2786,10 +2790,10 @@ function (root, d3, forester, phyloXml) {
|
|
|
2786
2790
|
let frac = max > min ? (mean - min) / (max - min) : 0.5;
|
|
2787
2791
|
let gradId = 'aptx_legend_ramp_' + id;
|
|
2788
2792
|
let grad = g.append('defs').append('linearGradient').attr('id', gradId);
|
|
2789
|
-
grad.append('stop').attr('offset', '0%').
|
|
2793
|
+
grad.append('stop').attr('offset', '0%').style('stop-color', VIS_COLOR_RAMP[0]);
|
|
2790
2794
|
grad.append('stop').attr('offset', (100 * Math.max(0.01, Math.min(0.99, frac))) + '%')
|
|
2791
|
-
.
|
|
2792
|
-
grad.append('stop').attr('offset', '100%').
|
|
2795
|
+
.style('stop-color', VIS_COLOR_RAMP[1]);
|
|
2796
|
+
grad.append('stop').attr('offset', '100%').style('stop-color', VIS_COLOR_RAMP[2]);
|
|
2793
2797
|
let barY = baseline + 6;
|
|
2794
2798
|
let barW = width - 2 * PAD;
|
|
2795
2799
|
g.append('rect')
|
|
@@ -2805,7 +2809,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
2805
2809
|
.text(legendNumberLabel(min));
|
|
2806
2810
|
g.append('text')
|
|
2807
2811
|
.attr('x', x + PAD + barW).attr('y', lblY)
|
|
2808
|
-
.
|
|
2812
|
+
.style('text-anchor', 'end')
|
|
2809
2813
|
.style('font', rowFont).style('fill', ink)
|
|
2810
2814
|
.text(legendNumberLabel(max));
|
|
2811
2815
|
baseline = lblY;
|
|
@@ -2870,7 +2874,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
2870
2874
|
.text(r.text);
|
|
2871
2875
|
g.append('text')
|
|
2872
2876
|
.attr('x', x + width - PAD).attr('y', baseline)
|
|
2873
|
-
.
|
|
2877
|
+
.style('text-anchor', 'end')
|
|
2874
2878
|
.style('font', rowFont)
|
|
2875
2879
|
.style('fill', ink)
|
|
2876
2880
|
.style('fill-opacity', 0.55)
|
|
@@ -3380,7 +3384,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
3380
3384
|
.style('paint-order', 'stroke')
|
|
3381
3385
|
// anchor, dy and x all come from nodeLabelPlacement, which the
|
|
3382
3386
|
// occupancy box is built from too
|
|
3383
|
-
.
|
|
3387
|
+
.style('text-anchor', function (d) {
|
|
3384
3388
|
return nodeLabelPlacement(d).anchor;
|
|
3385
3389
|
})
|
|
3386
3390
|
.attr('transform', function (d) {
|
|
@@ -3418,7 +3422,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
3418
3422
|
node.select('text.bllabel')
|
|
3419
3423
|
.style('font-size', _state.branchDataFontSize + 'px')
|
|
3420
3424
|
.style('fill', _state.branchColorDefault)
|
|
3421
|
-
.
|
|
3425
|
+
.style('text-anchor', function () {
|
|
3422
3426
|
return radialDisplay() ? 'middle' : null;
|
|
3423
3427
|
})
|
|
3424
3428
|
.attr('transform', function (d) {
|
|
@@ -3585,9 +3589,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
3585
3589
|
if (tipGuides || collapsedGuides.length > 0) {
|
|
3586
3590
|
let ext = _svgGroup.insert('g', 'g').attr('class', 'aptx-align-ext');
|
|
3587
3591
|
let guideStyle = function (sel) {
|
|
3588
|
-
return sel.
|
|
3589
|
-
.
|
|
3590
|
-
.
|
|
3592
|
+
return sel.style('fill', 'none')
|
|
3593
|
+
.style('stroke-width', 1)
|
|
3594
|
+
.style('stroke', _state.branchColorDefault)
|
|
3591
3595
|
.style('stroke-opacity', 0.25);
|
|
3592
3596
|
};
|
|
3593
3597
|
if (tipGuides) {
|
|
@@ -3632,10 +3636,10 @@ function (root, d3, forester, phyloXml) {
|
|
|
3632
3636
|
conn.selectAll('line')
|
|
3633
3637
|
.data(connected)
|
|
3634
3638
|
.enter().append('line')
|
|
3635
|
-
.
|
|
3639
|
+
.style('stroke', _state.branchColorDefault)
|
|
3636
3640
|
// width/opacity match the rectangular aligned extensions; the
|
|
3637
3641
|
// dash alone marks these as connectors (0.5px at 0.3 vanished)
|
|
3638
|
-
.
|
|
3642
|
+
.style('stroke-width', 1)
|
|
3639
3643
|
.style('stroke-opacity', 0.4)
|
|
3640
3644
|
.style('stroke-dasharray', '2,3')
|
|
3641
3645
|
.attr('x1', function (d) { return radialXY(d.x, innerY(d))[0]; })
|
|
@@ -4729,7 +4733,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
4729
4733
|
sync(g, 'text.extlabel', d._extLabelText !== '', function (sel) {
|
|
4730
4734
|
sel.append('text')
|
|
4731
4735
|
.attr('class', 'extlabel')
|
|
4732
|
-
.
|
|
4736
|
+
.style('text-anchor', d.children ? 'end' : 'start')
|
|
4733
4737
|
.style('font-family', font)
|
|
4734
4738
|
.style('fill-opacity', 0.5);
|
|
4735
4739
|
});
|
|
@@ -4745,7 +4749,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
4745
4749
|
sync(g, 'text.conflabel', d._confText !== '', function (sel) {
|
|
4746
4750
|
sel.append('text')
|
|
4747
4751
|
.attr('class', 'conflabel')
|
|
4748
|
-
.
|
|
4752
|
+
.style('text-anchor', 'middle')
|
|
4749
4753
|
.style('font-family', font);
|
|
4750
4754
|
});
|
|
4751
4755
|
}
|
|
@@ -4753,7 +4757,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
4753
4757
|
sync(g, 'text.brancheventlabel', d._eventText !== '', function (sel) {
|
|
4754
4758
|
sel.append('text')
|
|
4755
4759
|
.attr('class', 'brancheventlabel')
|
|
4756
|
-
.
|
|
4760
|
+
.style('text-anchor', 'middle')
|
|
4757
4761
|
.style('font-family', font);
|
|
4758
4762
|
});
|
|
4759
4763
|
}
|
|
@@ -5393,9 +5397,16 @@ function (root, d3, forester, phyloXml) {
|
|
|
5393
5397
|
let els = sizeLayer(_geomLayers.branches, 'path', branchList.length);
|
|
5394
5398
|
branchList.forEach(function (b, k) {
|
|
5395
5399
|
b.el = els[k];
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5400
|
+
// These paths are kept from one drawing to the next, and a style
|
|
5401
|
+
// is not an attribute: a value that is no colour or no width (a
|
|
5402
|
+
// file's <width>-1</width>) is IGNORED, and the path would keep
|
|
5403
|
+
// what another batch gave it the last time. So the old is taken
|
|
5404
|
+
// off first, and such a batch is drawn as the svg has it.
|
|
5405
|
+
b.el.style.setProperty('fill', 'none');
|
|
5406
|
+
b.el.style.removeProperty('stroke');
|
|
5407
|
+
b.el.style.setProperty('stroke', b.color);
|
|
5408
|
+
b.el.style.removeProperty('stroke-width');
|
|
5409
|
+
b.el.style.setProperty('stroke-width', b.width);
|
|
5399
5410
|
});
|
|
5400
5411
|
// `last`: batches painted after the sorted ones, whatever their count
|
|
5401
5412
|
let paintLayer = function (batches, layer, last) {
|
|
@@ -6892,8 +6903,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
6892
6903
|
createGui();
|
|
6893
6904
|
|
|
6894
6905
|
_baseSvg = d3.select(containerEl).append('svg')
|
|
6895
|
-
.attr('
|
|
6896
|
-
.
|
|
6906
|
+
.attr('class', TREE_SVG_CLASS)
|
|
6907
|
+
.call(sizeTreeSvg, _displayWidth, _displayHeight)
|
|
6908
|
+
// The tree's font, for the texts that state only a size (the
|
|
6909
|
+
// alignment strip's ruler and caption, the heat map strip's
|
|
6910
|
+
// numbers and caption): they were drawn in the PAGE's font,
|
|
6911
|
+
// in boxes measured for this one, and in an exported file in
|
|
6912
|
+
// whatever the program that opened it thought best. Inline,
|
|
6913
|
+
// so that the file has it too.
|
|
6914
|
+
.style('font-family', FONT_DEFAULTS)
|
|
6897
6915
|
// an inline svg sits on a text baseline, and the room left
|
|
6898
6916
|
// under it for descenders makes a container sized to the
|
|
6899
6917
|
// svg a few pixels taller than the svg itself -- enough to
|
|
@@ -6916,8 +6934,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
6916
6934
|
}
|
|
6917
6935
|
_displayWidth = size.w;
|
|
6918
6936
|
_displayHeight = size.h;
|
|
6919
|
-
_baseSvg.
|
|
6920
|
-
_baseSvg.attr('height', size.h);
|
|
6937
|
+
sizeTreeSvg(_baseSvg, size.w, size.h);
|
|
6921
6938
|
rebuildOverview();
|
|
6922
6939
|
if ((_settings.zoomToFitUponWindowResize === true) && (_zoomed_x_or_y === false) && (Math.abs(currentZoomScale() - 1.0) < 0.001)) {
|
|
6923
6940
|
zoomToFit();
|
|
@@ -7989,7 +8006,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
7989
8006
|
let x1 = Math.round(originX + (endI * cw));
|
|
7990
8007
|
g.append('rect').attr('x', x0).attr('y', cy)
|
|
7991
8008
|
.attr('width', Math.max(1, x1 - x0)).attr('height', rh)
|
|
7992
|
-
.
|
|
8009
|
+
.style('fill', 'rgb(' + runRgb.join(',') + ')');
|
|
7993
8010
|
runStart = -1;
|
|
7994
8011
|
runRgb = null;
|
|
7995
8012
|
};
|
|
@@ -8004,7 +8021,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8004
8021
|
g.append('line')
|
|
8005
8022
|
.attr('x1', Math.round(originX + (gapStart * cw))).attr('y1', mid)
|
|
8006
8023
|
.attr('x2', Math.round(originX + (endI * cw))).attr('y2', mid)
|
|
8007
|
-
.
|
|
8024
|
+
.style('stroke', ink).style('stroke-opacity', 0.35).style('stroke-width', 1);
|
|
8008
8025
|
gapStart = -1;
|
|
8009
8026
|
};
|
|
8010
8027
|
for (let i = 0; i < visible; ++i) {
|
|
@@ -8028,9 +8045,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
8028
8045
|
let x1 = Math.round(originX + ((i + 1) * cw));
|
|
8029
8046
|
g.append('rect').attr('x', x0).attr('y', cy)
|
|
8030
8047
|
.attr('width', Math.max(1, x1 - x0)).attr('height', rh)
|
|
8031
|
-
.
|
|
8048
|
+
.style('fill', 'rgb(' + rgb.join(',') + ')');
|
|
8032
8049
|
g.append('text').attr('x', x0 + ((x1 - x0) / 2)).attr('y', cy + (rh / 2))
|
|
8033
|
-
.
|
|
8050
|
+
.style('text-anchor', 'middle').attr('dy', '0.35em')
|
|
8034
8051
|
.style('font-family', 'monospace').style('font-size', fontPx + 'px')
|
|
8035
8052
|
.style('fill', forester.msaLetterInk(rgb))
|
|
8036
8053
|
.text(ch.toUpperCase());
|
|
@@ -8054,13 +8071,13 @@ function (root, d3, forester, phyloXml) {
|
|
|
8054
8071
|
if (offset <= 0) {
|
|
8055
8072
|
g.append('line').attr('x1', Math.round(originX)).attr('x2', Math.round(originX))
|
|
8056
8073
|
.attr('y1', rowsTop).attr('y2', rowsBottom)
|
|
8057
|
-
.
|
|
8074
|
+
.style('stroke', ink).style('stroke-opacity', 0.7).style('stroke-width', 1);
|
|
8058
8075
|
}
|
|
8059
8076
|
if ((offset + visible) >= total) {
|
|
8060
8077
|
let xe = Math.round(originX + (visible * cw));
|
|
8061
8078
|
g.append('line').attr('x1', xe).attr('x2', xe)
|
|
8062
8079
|
.attr('y1', rowsTop).attr('y2', rowsBottom)
|
|
8063
|
-
.
|
|
8080
|
+
.style('stroke', ink).style('stroke-opacity', 0.7).style('stroke-width', 1);
|
|
8064
8081
|
}
|
|
8065
8082
|
|
|
8066
8083
|
// Scored over the visible WINDOW only -- the whole alignment would
|
|
@@ -8099,9 +8116,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
8099
8116
|
g.append('line')
|
|
8100
8117
|
.attr('x1', from).attr('x2', guideEnd)
|
|
8101
8118
|
.attr('y1', d.x).attr('y2', d.x)
|
|
8102
|
-
.
|
|
8103
|
-
.
|
|
8104
|
-
.
|
|
8119
|
+
.style('stroke', ink)
|
|
8120
|
+
.style('stroke-width', 1)
|
|
8121
|
+
.style('stroke-dasharray', '2,3')
|
|
8105
8122
|
.style('stroke-opacity', 0.35)
|
|
8106
8123
|
.style('pointer-events', 'none');
|
|
8107
8124
|
}
|
|
@@ -8111,7 +8128,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8111
8128
|
let strip = floatStripGroup('aptx-msa-strip', _clusterH, stripH);
|
|
8112
8129
|
strip.append('rect').attr('x', Math.round(originX) - 2).attr('y', _clusterH)
|
|
8113
8130
|
.attr('width', trackW + 4).attr('height', stripH)
|
|
8114
|
-
.
|
|
8131
|
+
.style('fill', _state.backgroundColorDefault);
|
|
8115
8132
|
let consTop = _clusterH + MSA_CONS_TOP_GAP;
|
|
8116
8133
|
// A caption on the panel's own background, so it stays readable over
|
|
8117
8134
|
// whatever the band happens to draw underneath it.
|
|
@@ -8124,9 +8141,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
8124
8141
|
let xe = Math.round(originX) + trackW - 3;
|
|
8125
8142
|
strip.append('rect').attr('x', xe - w).attr('y', y - 8)
|
|
8126
8143
|
.attr('width', w).attr('height', 11)
|
|
8127
|
-
.
|
|
8144
|
+
.style('fill', _state.backgroundColorDefault).style('fill-opacity', 0.85);
|
|
8128
8145
|
strip.append('text').attr('x', xe - 3).attr('y', y)
|
|
8129
|
-
.
|
|
8146
|
+
.style('text-anchor', 'end')
|
|
8130
8147
|
.style('font-size', '8px').style('fill', ink).style('fill-opacity', 0.9)
|
|
8131
8148
|
.text(txt);
|
|
8132
8149
|
};
|
|
@@ -8143,7 +8160,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8143
8160
|
strip.append('line')
|
|
8144
8161
|
.attr('x1', Math.round(originX)).attr('x2', Math.round(originX) + trackW)
|
|
8145
8162
|
.attr('y1', floor).attr('y2', floor)
|
|
8146
|
-
.
|
|
8163
|
+
.style('stroke', ink).style('stroke-opacity', 0.5).style('stroke-width', 1);
|
|
8147
8164
|
for (let i = 0; i < visible; ++i) {
|
|
8148
8165
|
let col = logo.columns[i];
|
|
8149
8166
|
if (!col || !(col.height > 0)) {
|
|
@@ -8165,7 +8182,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8165
8182
|
.attr('transform', 'translate(' + (x0 + ((x1 - x0) / 2)) + ','
|
|
8166
8183
|
+ (y - (glyph.descent * sy)) + ')'
|
|
8167
8184
|
+ ' scale(' + (Math.max(1, x1 - x0) / glyph.width) + ',' + sy + ')')
|
|
8168
|
-
.
|
|
8185
|
+
.style('text-anchor', 'middle')
|
|
8169
8186
|
.style('font-family', 'monospace')
|
|
8170
8187
|
.style('font-size', MSA_LOGO_FONT_PX + 'px')
|
|
8171
8188
|
.style('fill', rgb === null ? ink : 'rgb(' + rgb.join(',') + ')')
|
|
@@ -8178,7 +8195,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8178
8195
|
} else {
|
|
8179
8196
|
strip.append('rect').attr('x', Math.round(originX)).attr('y', consTop)
|
|
8180
8197
|
.attr('width', trackW).attr('height', MSA_CONS_BAR_H)
|
|
8181
|
-
.
|
|
8198
|
+
.style('fill', ink).style('fill-opacity', 0.08);
|
|
8182
8199
|
for (let i = 0; i < visible; ++i) {
|
|
8183
8200
|
let score = cons.scores[i] || 0;
|
|
8184
8201
|
if (score <= 0) {
|
|
@@ -8189,7 +8206,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8189
8206
|
let x1 = Math.round(originX + ((i + 1) * cw));
|
|
8190
8207
|
strip.append('rect').attr('x', x0).attr('y', (consTop + MSA_CONS_BAR_H) - bh)
|
|
8191
8208
|
.attr('width', Math.max(1, x1 - x0)).attr('height', bh)
|
|
8192
|
-
.
|
|
8209
|
+
.style('fill', ink).style('fill-opacity', 0.7);
|
|
8193
8210
|
}
|
|
8194
8211
|
caption('Consensus identity (n = ' + n + ')', consTop + 9);
|
|
8195
8212
|
for (let i = 0; i < visible; ++i) {
|
|
@@ -8199,7 +8216,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8199
8216
|
}
|
|
8200
8217
|
strip.append('text').attr('x', Math.round(originX + (i * cw)) + (cw / 2))
|
|
8201
8218
|
.attr('y', consTop + MSA_CONS_BAR_H + 10)
|
|
8202
|
-
.
|
|
8219
|
+
.style('text-anchor', 'middle')
|
|
8203
8220
|
.style('font-family', 'monospace').style('font-size', '8px')
|
|
8204
8221
|
.style('fill', ink)
|
|
8205
8222
|
.text(cc);
|
|
@@ -8211,7 +8228,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8211
8228
|
let rulerY = consTop + (logo ? MSA_LOGO_CAP_H + MSA_LOGO_H : MSA_CONS_BAR_H + MSA_CONS_ROW_H);
|
|
8212
8229
|
strip.append('line').attr('x1', Math.round(originX)).attr('x2', Math.round(originX) + trackW)
|
|
8213
8230
|
.attr('y1', rulerY).attr('y2', rulerY)
|
|
8214
|
-
.
|
|
8231
|
+
.style('stroke', ink).style('stroke-opacity', 0.8).style('stroke-width', 1);
|
|
8215
8232
|
let lastRight = -Infinity;
|
|
8216
8233
|
let drawTick = function (c, force) {
|
|
8217
8234
|
let i = c - 1 - offset;
|
|
@@ -8220,11 +8237,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
8220
8237
|
}
|
|
8221
8238
|
let x = Math.round(originX + (i * cw)) + (cw / 2);
|
|
8222
8239
|
strip.append('line').attr('x1', x).attr('x2', x).attr('y1', rulerY).attr('y2', rulerY + 4)
|
|
8223
|
-
.
|
|
8240
|
+
.style('stroke', ink).style('stroke-opacity', 0.8).style('stroke-width', 1);
|
|
8224
8241
|
let label = String(c);
|
|
8225
8242
|
let half = label.length * 2.8;
|
|
8226
8243
|
if (force || (x - half) >= (lastRight + 4)) {
|
|
8227
|
-
strip.append('text').attr('x', x).attr('y', rulerY + 14).
|
|
8244
|
+
strip.append('text').attr('x', x).attr('y', rulerY + 14).style('text-anchor', 'middle')
|
|
8228
8245
|
.style('font-size', '9px').style('fill', ink)
|
|
8229
8246
|
.text(label);
|
|
8230
8247
|
lastRight = x + half;
|
|
@@ -8241,7 +8258,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
8241
8258
|
g.append('rect').attr('class', 'aptx-msa-hover')
|
|
8242
8259
|
.attr('x', Math.round(originX)).attr('y', rowsTop)
|
|
8243
8260
|
.attr('width', trackW).attr('height', Math.max(1, rowsBottom - rowsTop))
|
|
8244
|
-
.
|
|
8261
|
+
.style('fill', 'transparent')
|
|
8245
8262
|
.on('mousemove', msaHoverMove)
|
|
8246
8263
|
.on('mouseout', msaHoverOut)
|
|
8247
8264
|
.on('wheel', function (event) {
|
|
@@ -8743,10 +8760,14 @@ function (root, d3, forester, phyloXml) {
|
|
|
8743
8760
|
// that came and went with the zoom level. Both strokes are a constant
|
|
8744
8761
|
// screen width, so a zoom neither fattens nor loses them.
|
|
8745
8762
|
function paintHeatmapCell(el, fill, bordered) {
|
|
8746
|
-
el.
|
|
8747
|
-
.
|
|
8748
|
-
.
|
|
8749
|
-
|
|
8763
|
+
el.style('fill', fill)
|
|
8764
|
+
.style('stroke', bordered ? heatmapBorderColor(fill) : fill)
|
|
8765
|
+
.style('stroke-width', bordered ? HEATMAP_BORDER_W : HEATMAP_SEAL_W)
|
|
8766
|
+
// ... said twice: as a style for the page, as everything here is,
|
|
8767
|
+
// and as an attribute for canvg, which draws the PNG and reads this
|
|
8768
|
+
// from the attribute alone (as a style only, every border came out
|
|
8769
|
+
// at the export's scale, four times as wide)
|
|
8770
|
+
.style('vector-effect', 'non-scaling-stroke').attr('vector-effect', 'non-scaling-stroke');
|
|
8750
8771
|
return el;
|
|
8751
8772
|
}
|
|
8752
8773
|
|
|
@@ -8876,8 +8897,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
8876
8897
|
let rect = g.append('rect').attr('x', x0).attr('y', cy)
|
|
8877
8898
|
.attr('width', Math.max(1, x1 - x0)).attr('height', rh);
|
|
8878
8899
|
if (runFill === HEATMAP_BLANK) {
|
|
8879
|
-
rect.
|
|
8880
|
-
.
|
|
8900
|
+
rect.style('fill', _state.backgroundColorDefault)
|
|
8901
|
+
.style('stroke', ink).style('stroke-opacity', 0.45).style('stroke-width', 1);
|
|
8881
8902
|
} else {
|
|
8882
8903
|
paintHeatmapCell(rect, runFill, bordered);
|
|
8883
8904
|
}
|
|
@@ -8909,8 +8930,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
8909
8930
|
g.append('rect')
|
|
8910
8931
|
.attr('x', Math.round(originX)).attr('y', rowsTop)
|
|
8911
8932
|
.attr('width', trackW).attr('height', Math.max(1, rowsBottom - rowsTop))
|
|
8912
|
-
.
|
|
8913
|
-
.
|
|
8933
|
+
.style('fill', 'none')
|
|
8934
|
+
.style('stroke', ink).style('stroke-opacity', 0.55).style('stroke-width', 1);
|
|
8914
8935
|
|
|
8915
8936
|
// ---- the clustering behind the column order, above the grid ----
|
|
8916
8937
|
// Drawn with the cells, in the tree's own group, so it zooms, pans and
|
|
@@ -8963,9 +8984,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
8963
8984
|
}
|
|
8964
8985
|
g.append('path').attr('class', 'aptx-heatmap-dendro')
|
|
8965
8986
|
.attr('d', path)
|
|
8966
|
-
.
|
|
8967
|
-
.
|
|
8968
|
-
.
|
|
8987
|
+
.style('fill', 'none')
|
|
8988
|
+
.style('stroke', ink)
|
|
8989
|
+
.style('stroke-width', Math.max(1, _state.branchWidthDefault))
|
|
8969
8990
|
.style('pointer-events', 'none');
|
|
8970
8991
|
}
|
|
8971
8992
|
|
|
@@ -8994,9 +9015,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
8994
9015
|
g.append('line')
|
|
8995
9016
|
.attr('x1', from).attr('x2', guideEnd)
|
|
8996
9017
|
.attr('y1', d.x).attr('y2', d.x)
|
|
8997
|
-
.
|
|
8998
|
-
.
|
|
8999
|
-
.
|
|
9018
|
+
.style('stroke', ink)
|
|
9019
|
+
.style('stroke-width', 1)
|
|
9020
|
+
.style('stroke-dasharray', '2,3')
|
|
9000
9021
|
.style('stroke-opacity', 0.35)
|
|
9001
9022
|
.style('pointer-events', 'none');
|
|
9002
9023
|
}
|
|
@@ -9008,7 +9029,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9008
9029
|
let strip = floatStripGroup('aptx-heatmap-strip', _clusterH, heatmapBottomReserve());
|
|
9009
9030
|
strip.append('rect').attr('x', Math.round(originX) - 2).attr('y', _clusterH)
|
|
9010
9031
|
.attr('width', trackW + 4).attr('height', heatmapStripHeight())
|
|
9011
|
-
.
|
|
9032
|
+
.style('fill', _state.backgroundColorDefault);
|
|
9012
9033
|
let labelTop = _clusterH + HEATMAP_HEAD_GAP;
|
|
9013
9034
|
let headH = heatmapHeadHeight();
|
|
9014
9035
|
if (headH > 0) {
|
|
@@ -9019,7 +9040,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9019
9040
|
.attr('transform', 'translate(' + (originX + (i * cw) + (cw / 2)) + ','
|
|
9020
9041
|
+ labelTop + ') rotate(-90)')
|
|
9021
9042
|
.attr('dy', '0.32em')
|
|
9022
|
-
.
|
|
9043
|
+
.style('text-anchor', 'end')
|
|
9023
9044
|
.style('font', font)
|
|
9024
9045
|
.style('fill', _state.labelColorDefault)
|
|
9025
9046
|
.text(clipTextToWidth(col.label, font, HEATMAP_LABEL_MAX_PX));
|
|
@@ -9036,20 +9057,20 @@ function (root, d3, forester, phyloXml) {
|
|
|
9036
9057
|
VIS_COLOR_RAMP.forEach(function (c, i) {
|
|
9037
9058
|
grad.append('stop')
|
|
9038
9059
|
.attr('offset', ((i * 100) / (VIS_COLOR_RAMP.length - 1)) + '%')
|
|
9039
|
-
.
|
|
9060
|
+
.style('stop-color', c);
|
|
9040
9061
|
});
|
|
9041
9062
|
strip.append('rect')
|
|
9042
9063
|
.attr('x', Math.round(originX)).attr('y', scaleTop)
|
|
9043
9064
|
.attr('width', barW).attr('height', HEATMAP_SCALE_BAR_H)
|
|
9044
|
-
.
|
|
9045
|
-
.
|
|
9065
|
+
.style('fill', 'url(#' + gradId + ')')
|
|
9066
|
+
.style('stroke', ink).style('stroke-opacity', 0.5).style('stroke-width', 1);
|
|
9046
9067
|
let numY = scaleTop + HEATMAP_SCALE_BAR_H + 10;
|
|
9047
9068
|
strip.append('text').attr('x', Math.round(originX)).attr('y', numY)
|
|
9048
|
-
.
|
|
9069
|
+
.style('text-anchor', 'start')
|
|
9049
9070
|
.style('font-size', '9px').style('fill', ink)
|
|
9050
9071
|
.text(heatmapNum(model.min));
|
|
9051
9072
|
strip.append('text').attr('x', Math.round(originX) + barW).attr('y', numY)
|
|
9052
|
-
.
|
|
9073
|
+
.style('text-anchor', 'end')
|
|
9053
9074
|
.style('font-size', '9px').style('fill', ink)
|
|
9054
9075
|
.text(heatmapNum(model.max));
|
|
9055
9076
|
// the caption: what the matrix is showing, and -- when it is windowed
|
|
@@ -9067,11 +9088,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
9067
9088
|
if ((rightEdge - cursor) >= keyW) {
|
|
9068
9089
|
strip.append('rect').attr('x', cursor).attr('y', scaleTop)
|
|
9069
9090
|
.attr('width', HEATMAP_SCALE_BAR_H).attr('height', HEATMAP_SCALE_BAR_H)
|
|
9070
|
-
.
|
|
9071
|
-
.
|
|
9091
|
+
.style('fill', _state.backgroundColorDefault)
|
|
9092
|
+
.style('stroke', ink).style('stroke-opacity', 0.45).style('stroke-width', 1);
|
|
9072
9093
|
strip.append('text').attr('x', cursor + HEATMAP_SCALE_BAR_H + 4)
|
|
9073
9094
|
.attr('y', scaleTop + HEATMAP_SCALE_BAR_H)
|
|
9074
|
-
.
|
|
9095
|
+
.style('text-anchor', 'start')
|
|
9075
9096
|
.style('font', capFont).style('fill', ink).style('fill-opacity', 0.85)
|
|
9076
9097
|
.text(HEATMAP_NOT_ASSESSED);
|
|
9077
9098
|
cursor += keyW + 10;
|
|
@@ -9079,7 +9100,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9079
9100
|
}
|
|
9080
9101
|
if ((rightEdge - cursor) > 60) {
|
|
9081
9102
|
strip.append('text').attr('x', cursor).attr('y', scaleTop + HEATMAP_SCALE_BAR_H)
|
|
9082
|
-
.
|
|
9103
|
+
.style('text-anchor', 'start')
|
|
9083
9104
|
.style('font', capFont).style('fill', ink).style('fill-opacity', 0.85)
|
|
9084
9105
|
.text(clipTextToWidth(caption, capFont, rightEdge - cursor));
|
|
9085
9106
|
}
|
|
@@ -9088,7 +9109,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9088
9109
|
g.append('rect').attr('class', 'aptx-heatmap-hover')
|
|
9089
9110
|
.attr('x', Math.round(originX)).attr('y', rowsTop)
|
|
9090
9111
|
.attr('width', trackW).attr('height', Math.max(1, rowsBottom - rowsTop))
|
|
9091
|
-
.
|
|
9112
|
+
.style('fill', 'transparent')
|
|
9092
9113
|
.on('mousemove', heatmapHoverMove)
|
|
9093
9114
|
.on('mouseout', heatmapHoverOut)
|
|
9094
9115
|
.on('wheel', function (event) {
|
|
@@ -9230,12 +9251,12 @@ function (root, d3, forester, phyloXml) {
|
|
|
9230
9251
|
let path = g.append('path')
|
|
9231
9252
|
.attr('d', heatmapSector(edge[runStart], a1, r0, r1 + bleed));
|
|
9232
9253
|
if (runFill === HEATMAP_BLANK) {
|
|
9233
|
-
path.
|
|
9234
|
-
.
|
|
9254
|
+
path.style('fill', _state.backgroundColorDefault)
|
|
9255
|
+
.style('stroke', ink).style('stroke-opacity', 0.45).style('stroke-width', 1);
|
|
9235
9256
|
} else if (bordered) {
|
|
9236
9257
|
paintHeatmapCell(path, runFill, true);
|
|
9237
9258
|
} else {
|
|
9238
|
-
path.
|
|
9259
|
+
path.style('fill', runFill); // the bleed already closes the seams here
|
|
9239
9260
|
}
|
|
9240
9261
|
runStart = -1;
|
|
9241
9262
|
runFill = null;
|
|
@@ -9261,7 +9282,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9261
9282
|
[start, start + (columns.length * ringW)].forEach(function (r) {
|
|
9262
9283
|
g.append('path')
|
|
9263
9284
|
.attr('d', heatmapSector(edge[0], edge[n], r - 0.5, r + 0.5))
|
|
9264
|
-
.
|
|
9285
|
+
.style('fill', ink).style('fill-opacity', 0.5);
|
|
9265
9286
|
});
|
|
9266
9287
|
|
|
9267
9288
|
// Each ring's name, laid along the ring at the fan's seam -- the gap
|
|
@@ -9285,7 +9306,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9285
9306
|
g.append('text')
|
|
9286
9307
|
.attr('transform', 'translate(' + xy[0].toFixed(1) + ',' + xy[1].toFixed(1) + ') '
|
|
9287
9308
|
+ 'rotate(' + (deg + (flip ? 180 : 0)).toFixed(1) + ')')
|
|
9288
|
-
.
|
|
9309
|
+
.style('text-anchor', 'middle')
|
|
9289
9310
|
.attr('dy', '0.32em')
|
|
9290
9311
|
.style('font', font)
|
|
9291
9312
|
.style('fill', _state.labelColorDefault)
|
|
@@ -9297,7 +9318,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9297
9318
|
g.append('path')
|
|
9298
9319
|
.attr('class', 'aptx-heatmap-hover')
|
|
9299
9320
|
.attr('d', heatmapSector(edge[0], edge[n], start, start + (columns.length * ringW)))
|
|
9300
|
-
.
|
|
9321
|
+
.style('fill', 'transparent')
|
|
9301
9322
|
.on('mousemove', heatmapHoverMove)
|
|
9302
9323
|
.on('mouseout', heatmapHoverOut);
|
|
9303
9324
|
|
|
@@ -9351,31 +9372,31 @@ function (root, d3, forester, phyloXml) {
|
|
|
9351
9372
|
VIS_COLOR_RAMP.forEach(function (c, i) {
|
|
9352
9373
|
grad.append('stop')
|
|
9353
9374
|
.attr('offset', ((i * 100) / (VIS_COLOR_RAMP.length - 1)) + '%')
|
|
9354
|
-
.
|
|
9375
|
+
.style('stop-color', c);
|
|
9355
9376
|
});
|
|
9356
9377
|
let barY = y + PAD;
|
|
9357
9378
|
g.append('rect').attr('x', x + PAD).attr('y', barY)
|
|
9358
9379
|
.attr('width', BAR_W).attr('height', HEATMAP_SCALE_BAR_H)
|
|
9359
|
-
.
|
|
9360
|
-
.
|
|
9380
|
+
.style('fill', 'url(#' + gradId + ')')
|
|
9381
|
+
.style('stroke', frame).style('stroke-opacity', 0.5).style('stroke-width', 1);
|
|
9361
9382
|
let line = barY + HEATMAP_SCALE_BAR_H + FS + 2;
|
|
9362
|
-
g.append('text').attr('x', x + PAD).attr('y', line).
|
|
9383
|
+
g.append('text').attr('x', x + PAD).attr('y', line).style('text-anchor', 'start')
|
|
9363
9384
|
.style('font', capFont).style('fill', ink).text(heatmapNum(m.min));
|
|
9364
|
-
g.append('text').attr('x', x + PAD + BAR_W).attr('y', line).
|
|
9385
|
+
g.append('text').attr('x', x + PAD + BAR_W).attr('y', line).style('text-anchor', 'end')
|
|
9365
9386
|
.style('font', capFont).style('fill', ink).text(heatmapNum(m.max));
|
|
9366
9387
|
if (blanks) {
|
|
9367
9388
|
line += FS + 4;
|
|
9368
9389
|
g.append('rect').attr('x', x + PAD).attr('y', line - HEATMAP_SCALE_BAR_H)
|
|
9369
9390
|
.attr('width', HEATMAP_SCALE_BAR_H).attr('height', HEATMAP_SCALE_BAR_H)
|
|
9370
|
-
.
|
|
9371
|
-
.
|
|
9391
|
+
.style('fill', _state.backgroundColorDefault)
|
|
9392
|
+
.style('stroke', frame).style('stroke-opacity', 0.45).style('stroke-width', 1);
|
|
9372
9393
|
g.append('text').attr('x', x + PAD + HEATMAP_SCALE_BAR_H + 4).attr('y', line)
|
|
9373
|
-
.
|
|
9394
|
+
.style('text-anchor', 'start')
|
|
9374
9395
|
.style('font', capFont).style('fill', ink).style('fill-opacity', 0.85)
|
|
9375
9396
|
.text(HEATMAP_NOT_ASSESSED);
|
|
9376
9397
|
}
|
|
9377
9398
|
line += FS + 4;
|
|
9378
|
-
g.append('text').attr('x', x + PAD).attr('y', line).
|
|
9399
|
+
g.append('text').attr('x', x + PAD).attr('y', line).style('text-anchor', 'start')
|
|
9379
9400
|
.style('font', capFont).style('fill', ink).style('fill-opacity', 0.85)
|
|
9380
9401
|
.text(caption);
|
|
9381
9402
|
}
|
|
@@ -9970,7 +9991,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
9970
9991
|
let gap = _state.nodeLabelGap;
|
|
9971
9992
|
let flip = radialDisplay() && labelFlip(d);
|
|
9972
9993
|
if (_state.circularDisplay && _radial) {
|
|
9973
|
-
t.
|
|
9994
|
+
t.style('text-anchor', flip ? 'end' : 'start')
|
|
9974
9995
|
.attr('x', flip ? -gap : gap).attr('dy', '0.32em');
|
|
9975
9996
|
if (_radialLabelsHorizontal) {
|
|
9976
9997
|
let p = radialXY(d.x, d.y);
|
|
@@ -9983,12 +10004,12 @@ function (root, d3, forester, phyloXml) {
|
|
|
9983
10004
|
} else if (radialDisplay()) {
|
|
9984
10005
|
let r = radialRadius(far) - radialRadius(d.y) + gap;
|
|
9985
10006
|
t.attr('transform', 'rotate(' + labelAngleDeg(d) + ') translate(' + r + ',0)' + (flip ? ' rotate(180)' : ''))
|
|
9986
|
-
.
|
|
10007
|
+
.style('text-anchor', flip ? 'end' : 'start')
|
|
9987
10008
|
.attr('x', 0).attr('dy', '0.32em');
|
|
9988
10009
|
} else {
|
|
9989
10010
|
let column = (_state.phylogram && _state.alignPhylogram) ? _w : far;
|
|
9990
10011
|
t.attr('transform', null)
|
|
9991
|
-
.
|
|
10012
|
+
.style('text-anchor', 'start')
|
|
9992
10013
|
.attr('x', column - d.y + gap)
|
|
9993
10014
|
.attr('dy', (0.3 * fs) + 'px');
|
|
9994
10015
|
}
|
|
@@ -11255,6 +11276,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
11255
11276
|
let svg = clockSvg(body, 'svg', 'aptx-clock-svg');
|
|
11256
11277
|
svg.setAttribute('width', CLOCK_PLOT_W);
|
|
11257
11278
|
svg.setAttribute('height', CLOCK_PLOT_H);
|
|
11279
|
+
// inline as well: an attribute gives way to a page's rule for a bare "svg" (sizeTreeSvg)
|
|
11280
|
+
svg.style.width = CLOCK_PLOT_W + 'px';
|
|
11281
|
+
svg.style.height = CLOCK_PLOT_H + 'px';
|
|
11258
11282
|
svg.setAttribute('viewBox', '0 0 ' + CLOCK_PLOT_W + ' ' + CLOCK_PLOT_H);
|
|
11259
11283
|
svg.setAttribute('role', 'img');
|
|
11260
11284
|
svg.setAttribute('aria-label', 'Clock plot: each tip\'s date against its divergence from the root');
|
|
@@ -11581,7 +11605,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
11581
11605
|
let t = clockSvg(cp.axes, 'text', cls);
|
|
11582
11606
|
t.setAttribute('x', tx);
|
|
11583
11607
|
t.setAttribute('y', ty);
|
|
11584
|
-
t.
|
|
11608
|
+
t.style.setProperty('text-anchor', anchor);
|
|
11585
11609
|
t.textContent = s;
|
|
11586
11610
|
return t;
|
|
11587
11611
|
};
|
|
@@ -12374,8 +12398,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
12374
12398
|
made[id] = true;
|
|
12375
12399
|
let grad = d3.select(defs).append('linearGradient').attr('id', id)
|
|
12376
12400
|
.attr('x1', 0).attr('y1', 0).attr('x2', 0).attr('y2', 1);
|
|
12377
|
-
grad.append('stop').attr('offset', '0%').
|
|
12378
|
-
grad.append('stop').attr('offset', '100%').
|
|
12401
|
+
grad.append('stop').attr('offset', '0%').style('stop-color', forester.domainLighten(base, 0.12));
|
|
12402
|
+
grad.append('stop').attr('offset', '100%').style('stop-color', forester.domainDarken(base, 0.10));
|
|
12379
12403
|
}
|
|
12380
12404
|
return 'url(#' + id + ')';
|
|
12381
12405
|
}
|
|
@@ -12556,7 +12580,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
12556
12580
|
el.setAttribute('x', t.x);
|
|
12557
12581
|
el.setAttribute('y', t.y);
|
|
12558
12582
|
el.setAttribute('dy', '0.35em');
|
|
12559
|
-
el.
|
|
12583
|
+
el.style.setProperty('text-anchor', 'middle');
|
|
12560
12584
|
el.style.font = t.font;
|
|
12561
12585
|
el.style.fill = t.fill;
|
|
12562
12586
|
el.textContent = t.text;
|
|
@@ -13269,7 +13293,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
13269
13293
|
el.setAttribute('x', t.x);
|
|
13270
13294
|
el.setAttribute('y', t.y);
|
|
13271
13295
|
el.setAttribute('dy', '0.35em');
|
|
13272
|
-
el.
|
|
13296
|
+
el.style.setProperty('text-anchor', 'middle');
|
|
13273
13297
|
el.style.font = t.font;
|
|
13274
13298
|
el.style.fill = t.fill;
|
|
13275
13299
|
el.textContent = t.text;
|
|
@@ -13684,7 +13708,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
13684
13708
|
});
|
|
13685
13709
|
g.append('text')
|
|
13686
13710
|
.attr('x', x + (bar.px / 2)).attr('y', y - 6)
|
|
13687
|
-
.
|
|
13711
|
+
.style('text-anchor', 'middle')
|
|
13688
13712
|
.style('font', fs + 'px ' + FONT_DEFAULTS)
|
|
13689
13713
|
.style('fill', _state.labelColorDefault)
|
|
13690
13714
|
.text(bar.label);
|
|
@@ -13763,8 +13787,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
13763
13787
|
function gridLineAt(x) {
|
|
13764
13788
|
grid.append('line').attr('x1', x).attr('x2', x)
|
|
13765
13789
|
.attr('y1', 0).attr('y2', axisTop)
|
|
13766
|
-
.
|
|
13767
|
-
.
|
|
13790
|
+
.style('stroke', ink).style('stroke-opacity', 0.18)
|
|
13791
|
+
.style('stroke-width', 1);
|
|
13768
13792
|
}
|
|
13769
13793
|
|
|
13770
13794
|
let sc = info.type === 'calendar' ? -corr : corr;
|
|
@@ -13784,11 +13808,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
13784
13808
|
let p1 = polarXY(a, radialRadius(x1));
|
|
13785
13809
|
el = g.append('line').attr('x1', p0[0]).attr('y1', p0[1])
|
|
13786
13810
|
.attr('x2', p1[0]).attr('y2', p1[1])
|
|
13787
|
-
.
|
|
13811
|
+
.style('stroke', fill).style('stroke-width', thick);
|
|
13788
13812
|
} else {
|
|
13789
13813
|
el = g.append('rect').attr('x', Math.min(x0, x1)).attr('y', d.x - (thick / 2))
|
|
13790
13814
|
.attr('width', Math.max(1, Math.abs(x1 - x0))).attr('height', thick)
|
|
13791
|
-
.
|
|
13815
|
+
.style('fill', fill);
|
|
13792
13816
|
}
|
|
13793
13817
|
el.datum(d).attr('class', cls);
|
|
13794
13818
|
}
|
|
@@ -13807,7 +13831,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
13807
13831
|
q = [x, d.x + 4];
|
|
13808
13832
|
}
|
|
13809
13833
|
g.append('line').attr('x1', p[0]).attr('y1', p[1]).attr('x2', q[0]).attr('y2', q[1])
|
|
13810
|
-
.
|
|
13834
|
+
.style('stroke', FOSSIL_BAR_COLOR).style('stroke-width', 1)
|
|
13811
13835
|
.datum(d).attr('class', 'aptx-fossil-cap');
|
|
13812
13836
|
}
|
|
13813
13837
|
|
|
@@ -13879,7 +13903,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
13879
13903
|
let left = Math.min(x0, x1);
|
|
13880
13904
|
ax.append('rect').attr('x', left - 2).attr('y', axisTop - 4)
|
|
13881
13905
|
.attr('width', Math.abs(x1 - x0) + 4).attr('height', scaleAxisBottomReserve() + 4)
|
|
13882
|
-
.
|
|
13906
|
+
.style('fill', _state.backgroundColorDefault);
|
|
13883
13907
|
}
|
|
13884
13908
|
if (info.type === 'geologic') {
|
|
13885
13909
|
let rootAge = info.rootAge;
|
|
@@ -13920,11 +13944,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
13920
13944
|
}
|
|
13921
13945
|
ax.append('rect').attr('x', left).attr('y', rowY)
|
|
13922
13946
|
.attr('width', w).attr('height', TIME_BAND_ROW_H)
|
|
13923
|
-
.
|
|
13924
|
-
.
|
|
13947
|
+
.style('fill', iv.color)
|
|
13948
|
+
.style('stroke', ink).style('stroke-opacity', 0.5).style('stroke-width', 0.5);
|
|
13925
13949
|
if ((iv.name.length * 5.5) + 4 <= w) {
|
|
13926
13950
|
ax.append('text').attr('x', left + (w / 2)).attr('y', rowY + TIME_BAND_ROW_H - 3.5)
|
|
13927
|
-
.
|
|
13951
|
+
.style('text-anchor', 'middle')
|
|
13928
13952
|
.style('font-size', '9px')
|
|
13929
13953
|
.style('fill', forester.msaLetterInk(hexToRgbTriple(iv.color)))
|
|
13930
13954
|
.text(iv.name);
|
|
@@ -13935,7 +13959,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
13935
13959
|
let rulerY = axisTop + (2 * TIME_BAND_ROW_H) + 4;
|
|
13936
13960
|
ax.append('line').attr('x1', xOfAge(rootAge)).attr('x2', xOfAge(youngBound))
|
|
13937
13961
|
.attr('y1', rulerY).attr('y2', rulerY)
|
|
13938
|
-
.
|
|
13962
|
+
.style('stroke', ink).style('stroke-width', 1);
|
|
13939
13963
|
let labels = [];
|
|
13940
13964
|
forester.maAxisTickValues(rootAge).forEach(function (v) {
|
|
13941
13965
|
if (v >= youngBound - 1e-9 && v <= rootAge + 1e-9) {
|
|
@@ -13955,7 +13979,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
13955
13979
|
let x = xOfAge(l.age);
|
|
13956
13980
|
ax.append('line').attr('x1', x).attr('x2', x)
|
|
13957
13981
|
.attr('y1', rulerY).attr('y2', rulerY + 4)
|
|
13958
|
-
.
|
|
13982
|
+
.style('stroke', ink).style('stroke-width', 1);
|
|
13959
13983
|
let text = String(l.age);
|
|
13960
13984
|
let half = text.length * 2.8;
|
|
13961
13985
|
let ok = true;
|
|
@@ -13967,14 +13991,14 @@ function (root, d3, forester, phyloXml) {
|
|
|
13967
13991
|
}
|
|
13968
13992
|
if (ok || l.priority > 0) {
|
|
13969
13993
|
ax.append('text').attr('x', x).attr('y', rulerY + 14)
|
|
13970
|
-
.
|
|
13994
|
+
.style('text-anchor', 'middle')
|
|
13971
13995
|
.style('font-size', '9px').style('fill', ink)
|
|
13972
13996
|
.text(text);
|
|
13973
13997
|
placed.push(x);
|
|
13974
13998
|
}
|
|
13975
13999
|
});
|
|
13976
14000
|
ax.append('text').attr('x', xOfAge(youngBound) + 8).attr('y', rulerY + 14)
|
|
13977
|
-
.
|
|
14001
|
+
.style('text-anchor', 'start')
|
|
13978
14002
|
.style('font-size', '9px').style('fill', ink)
|
|
13979
14003
|
.text('Ma');
|
|
13980
14004
|
} else {
|
|
@@ -14003,18 +14027,18 @@ function (root, d3, forester, phyloXml) {
|
|
|
14003
14027
|
let rulerY = axisTop + 4;
|
|
14004
14028
|
ax.append('line').attr('x1', xOfYear(rootYear)).attr('x2', xOfYear(present))
|
|
14005
14029
|
.attr('y1', rulerY).attr('y2', rulerY)
|
|
14006
|
-
.
|
|
14030
|
+
.style('stroke', ink).style('stroke-width', 1);
|
|
14007
14031
|
let lastRight = -Infinity;
|
|
14008
14032
|
forester.calendarTickYears(rootYear, present).forEach(function (yv) {
|
|
14009
14033
|
let x = xOfYear(yv);
|
|
14010
14034
|
ax.append('line').attr('x1', x).attr('x2', x)
|
|
14011
14035
|
.attr('y1', rulerY).attr('y2', rulerY + 4)
|
|
14012
|
-
.
|
|
14036
|
+
.style('stroke', ink).style('stroke-width', 1);
|
|
14013
14037
|
let text = String(Math.round(yv));
|
|
14014
14038
|
let half = text.length * 2.8;
|
|
14015
14039
|
if ((x - half) >= lastRight + 4) {
|
|
14016
14040
|
ax.append('text').attr('x', x).attr('y', rulerY + 14)
|
|
14017
|
-
.
|
|
14041
|
+
.style('text-anchor', 'middle')
|
|
14018
14042
|
.style('font-size', '9px').style('fill', ink)
|
|
14019
14043
|
.text(text);
|
|
14020
14044
|
lastRight = x + half;
|
|
@@ -14108,15 +14132,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
14108
14132
|
return;
|
|
14109
14133
|
}
|
|
14110
14134
|
behind.append('path').attr('d', ring.innerRadius(a).outerRadius(b)())
|
|
14111
|
-
.
|
|
14135
|
+
.style('fill', iv.color).style('fill-opacity', 0.35);
|
|
14112
14136
|
named.push({r: (a + b) / 2, span: b - a, text: iv.name, color: iv.color});
|
|
14113
14137
|
});
|
|
14114
14138
|
if (grid) {
|
|
14115
14139
|
ticks.forEach(function (t) {
|
|
14116
14140
|
let r = radiusOf(t.value);
|
|
14117
14141
|
if (r > r0 + 0.5 && r < r1 - 0.5) {
|
|
14118
|
-
grid.append('circle').attr('r', r).
|
|
14119
|
-
.
|
|
14142
|
+
grid.append('circle').attr('r', r).style('fill', 'none')
|
|
14143
|
+
.style('stroke', ink).style('stroke-opacity', 0.18).style('stroke-width', 1);
|
|
14120
14144
|
}
|
|
14121
14145
|
});
|
|
14122
14146
|
}
|
|
@@ -14135,7 +14159,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
14135
14159
|
let e0 = at(r0, 0);
|
|
14136
14160
|
let e1 = at(r1, 0);
|
|
14137
14161
|
ax.append('line').attr('x1', e0[0]).attr('y1', e0[1]).attr('x2', e1[0]).attr('y2', e1[1])
|
|
14138
|
-
.
|
|
14162
|
+
.style('stroke', ink).style('stroke-width', 1);
|
|
14139
14163
|
// numbers on one side of the ruler, interval names on the other; the
|
|
14140
14164
|
// headline value first, then the round ticks outward, none overlapping
|
|
14141
14165
|
function label(r, off, text, fill, backdrop) {
|
|
@@ -14145,9 +14169,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
14145
14169
|
let w = text.length * 5.5;
|
|
14146
14170
|
if (backdrop) {
|
|
14147
14171
|
t.append('rect').attr('x', -(w / 2) - 2).attr('y', -6).attr('width', w + 4).attr('height', 12)
|
|
14148
|
-
.attr('rx', 2).
|
|
14172
|
+
.attr('rx', 2).style('fill', _state.backgroundColorDefault).style('fill-opacity', 0.85);
|
|
14149
14173
|
}
|
|
14150
|
-
t.append('text').
|
|
14174
|
+
t.append('text').style('text-anchor', 'middle').style('dominant-baseline', 'central')
|
|
14151
14175
|
.style('font-size', '9px').style('fill', fill).text(text);
|
|
14152
14176
|
}
|
|
14153
14177
|
let side = flip ? -1 : 1;
|
|
@@ -14160,7 +14184,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
14160
14184
|
let c0 = at(r, -4);
|
|
14161
14185
|
let c1 = at(r, 4);
|
|
14162
14186
|
ax.append('line').attr('x1', c0[0]).attr('y1', c0[1]).attr('x2', c1[0]).attr('y2', c1[1])
|
|
14163
|
-
.
|
|
14187
|
+
.style('stroke', ink).style('stroke-width', 1);
|
|
14164
14188
|
let half = t.text.length * 2.8;
|
|
14165
14189
|
for (let i = 0; i < placed.length; ++i) {
|
|
14166
14190
|
if (Math.abs(placed[i] - r) < (half * 2) + 6) {
|
|
@@ -14234,8 +14258,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
14234
14258
|
if (v > 0 && v < maxDist - 1e-9) {
|
|
14235
14259
|
grid.append('line').attr('x1', xOf(v)).attr('x2', xOf(v))
|
|
14236
14260
|
.attr('y1', 0).attr('y2', axisTop)
|
|
14237
|
-
.
|
|
14238
|
-
.
|
|
14261
|
+
.style('stroke', ink).style('stroke-opacity', 0.18)
|
|
14262
|
+
.style('stroke-width', 1);
|
|
14239
14263
|
}
|
|
14240
14264
|
});
|
|
14241
14265
|
}
|
|
@@ -14247,23 +14271,23 @@ function (root, d3, forester, phyloXml) {
|
|
|
14247
14271
|
// opaque, as the time axis's, so tips panned under it do not show
|
|
14248
14272
|
ax.append('rect').attr('x', x0 - 2).attr('y', axisTop - 4)
|
|
14249
14273
|
.attr('width', (x1 - x0) + 4).attr('height', reserve + 4)
|
|
14250
|
-
.
|
|
14274
|
+
.style('fill', _state.backgroundColorDefault);
|
|
14251
14275
|
let rulerY = axisTop + 4;
|
|
14252
14276
|
ax.append('line').attr('x1', x0).attr('x2', x1)
|
|
14253
14277
|
.attr('y1', rulerY).attr('y2', rulerY)
|
|
14254
|
-
.
|
|
14278
|
+
.style('stroke', ink).style('stroke-width', 1);
|
|
14255
14279
|
// every tick drawn, its number only where it clears the last one
|
|
14256
14280
|
let lastRight = -Infinity;
|
|
14257
14281
|
ticks.forEach(function (v) {
|
|
14258
14282
|
let x = xOf(v);
|
|
14259
14283
|
ax.append('line').attr('x1', x).attr('x2', x)
|
|
14260
14284
|
.attr('y1', rulerY).attr('y2', rulerY + 4)
|
|
14261
|
-
.
|
|
14285
|
+
.style('stroke', ink).style('stroke-width', 1);
|
|
14262
14286
|
let text = String(v);
|
|
14263
14287
|
let half = text.length * 2.8;
|
|
14264
14288
|
if ((x - half) >= lastRight + 4) {
|
|
14265
14289
|
ax.append('text').attr('x', x).attr('y', rulerY + 14)
|
|
14266
|
-
.
|
|
14290
|
+
.style('text-anchor', 'middle')
|
|
14267
14291
|
.style('font-size', '9px').style('fill', ink)
|
|
14268
14292
|
.text(text);
|
|
14269
14293
|
lastRight = x + half;
|
|
@@ -14273,7 +14297,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
14273
14297
|
// past the ruler's end, and past the last number, which is
|
|
14274
14298
|
// centred on that end
|
|
14275
14299
|
ax.append('text').attr('x', Math.max(x1 + 8, lastRight + 6)).attr('y', rulerY + 14)
|
|
14276
|
-
.
|
|
14300
|
+
.style('text-anchor', 'start')
|
|
14277
14301
|
.style('font-size', '9px').style('fill', ink)
|
|
14278
14302
|
.text(unit);
|
|
14279
14303
|
}
|
|
@@ -14855,8 +14879,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
14855
14879
|
_displayHeight = size.h;
|
|
14856
14880
|
// the canvas has to follow, or the layout is computed for one
|
|
14857
14881
|
// size and drawn on another
|
|
14858
|
-
_baseSvg.
|
|
14859
|
-
_baseSvg.attr('height', size.h);
|
|
14882
|
+
sizeTreeSvg(_baseSvg, size.w, size.h);
|
|
14860
14883
|
}
|
|
14861
14884
|
}
|
|
14862
14885
|
// Where the user dragged the legend to is their choice; a resize is no
|
|
@@ -16745,9 +16768,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
16745
16768
|
case 'moon': body = glyphMoon(); break;
|
|
16746
16769
|
default: throw new Error('unknown control-panel glyph: ' + kind);
|
|
16747
16770
|
}
|
|
16748
|
-
|
|
16771
|
+
// written as an svg file would be, its paint in attributes; paintInline
|
|
16772
|
+
// makes inline styles of it, which no rule of a page outranks
|
|
16773
|
+
return paintInline('<svg class="aptx-glyph" viewBox="0 0 ' + w + ' 100" aria-hidden="true" focusable="false"'
|
|
16749
16774
|
+ ' fill="none" stroke="currentColor" stroke-width="' + sw + '"'
|
|
16750
|
-
+ ' stroke-linecap="' + cap + '" stroke-linejoin="' + join + '">' + body + '</svg>';
|
|
16775
|
+
+ ' stroke-linecap="' + cap + '" stroke-linejoin="' + join + '">' + body + '</svg>');
|
|
16751
16776
|
}
|
|
16752
16777
|
|
|
16753
16778
|
// The desktop's UNCOLLAPSE_ALL glyph (ControlButtonIcon.paintUncollapse),
|
|
@@ -16764,6 +16789,214 @@ function (root, d3, forester, phyloXml) {
|
|
|
16764
16789
|
+ '<path d="M56,20 H94 M56,50 H94 M56,80 H94"/>';
|
|
16765
16790
|
}
|
|
16766
16791
|
|
|
16792
|
+
// ===================== A host page's stylesheet =====================
|
|
16793
|
+
// The viewer is drawn inside somebody else's page, under that page's
|
|
16794
|
+
// stylesheet, and a rule of the host's wins wherever this sheet says
|
|
16795
|
+
// nothing. BV-BRC, 2026-10-09: ".Phylogeny svg { background:#FFFFFF }",
|
|
16796
|
+
// written when the only svg in that widget was the tree, put a white box
|
|
16797
|
+
// behind every icon of the panel. Measured after that, on pages nobody
|
|
16798
|
+
// had tried: a page set in capitals, or centred, set the dialogs and the
|
|
16799
|
+
// tooltip that way too; and under "* { box-sizing: border-box }", which
|
|
16800
|
+
// most pages have, the tooltip came out 22 px narrower.
|
|
16801
|
+
//
|
|
16802
|
+
// So the sheet opens by saying what it used to leave unsaid. These rules
|
|
16803
|
+
// come FIRST, and the general ones carry as little weight as will do:
|
|
16804
|
+
// every rule after them still has the last word. What they cannot do is
|
|
16805
|
+
// outrank a rule of the host's that is heavier still: one scoped by an
|
|
16806
|
+
// id, or by more classes than the rule here has, or marked !important.
|
|
16807
|
+
//
|
|
16808
|
+
// test/browser/host_css.html launches the viewer under real host sheets
|
|
16809
|
+
// and fails on any difference from a clean page. Something new that the
|
|
16810
|
+
// viewer hangs on the page goes into HOST_PROOF_ROOTS, and through it.
|
|
16811
|
+
const HOST_PROOF_ROOTS = ['.aptx-panel', '.aptx-dialog', '.aptx-node-menu', '.aptx-suggest', '.aptx-tip',
|
|
16812
|
+
'.aptx-busy', '.aptx-msa-nav'];
|
|
16813
|
+
// What paints a shape or sets a text, what shows or hides it, and what
|
|
16814
|
+
// would animate it: the properties a page's rule for a bare "path", "rect"
|
|
16815
|
+
// or "text" is likely to set. An svg ATTRIBUTE for one of these gives way
|
|
16816
|
+
// to any such rule, so the viewer states them as inline styles, and its
|
|
16817
|
+
// sheet says, for every shape of its svgs, "as a page that says nothing
|
|
16818
|
+
// has it" -- which for most of them is what the shape's parent has, and
|
|
16819
|
+
// so, in the end, what the svg itself has: the value beside each name,
|
|
16820
|
+
// where a parent hands it down. (Without one: not handed down; or stated
|
|
16821
|
+
// by the svg's own rule; or left to the page on purpose, as the cursor
|
|
16822
|
+
// and whether the thing is shown at all.)
|
|
16823
|
+
//
|
|
16824
|
+
// It also means that an ATTRIBUTE for one of these, on a shape of the
|
|
16825
|
+
// viewer's, no longer does anything, in any page: test/
|
|
16826
|
+
// visualization_test.js holds the drawing code to that, and test/browser/
|
|
16827
|
+
// host_css.html the page (with these rules taken out, nothing changes).
|
|
16828
|
+
// Markup with its paint in attributes goes through paintInline.
|
|
16829
|
+
//
|
|
16830
|
+
// Not here, and still attributes: where a shape is and how large (x, y,
|
|
16831
|
+
// d, r, transform), clip-path and mask.
|
|
16832
|
+
const SHAPE_PAINT = {
|
|
16833
|
+
'fill': '#000', 'fill-opacity': '1', 'fill-rule': 'nonzero',
|
|
16834
|
+
'stroke': 'none', 'stroke-width': '1', 'stroke-opacity': '1', 'stroke-dasharray': 'none',
|
|
16835
|
+
'stroke-dashoffset': '0', 'stroke-linecap': 'butt', 'stroke-linejoin': 'miter', 'stroke-miterlimit': '4',
|
|
16836
|
+
'paint-order': 'normal', 'text-anchor': 'start', 'dominant-baseline': 'auto', 'shape-rendering': 'auto',
|
|
16837
|
+
'vector-effect': null, 'stop-color': null, 'stop-opacity': null, 'opacity': null, 'filter': null,
|
|
16838
|
+
'mix-blend-mode': null, 'display': null, 'visibility': null, 'cursor': null, 'pointer-events': null,
|
|
16839
|
+
'color': null, 'font': null, 'letter-spacing': null, 'word-spacing': null, 'text-transform': null,
|
|
16840
|
+
'text-decoration': null, 'text-shadow': null, 'text-rendering': null, 'white-space': null,
|
|
16841
|
+
'transition': null, 'animation': null
|
|
16842
|
+
};
|
|
16843
|
+
// What of that paint a FILE states as attributes again (paintAsAttributes,
|
|
16844
|
+
// for the SVG, PNG and PDF downloads). A file has no page to hold its
|
|
16845
|
+
// ground against, and not every program that opens one reads a style as
|
|
16846
|
+
// well as it reads an attribute: MuPDF takes a line's colour from a style
|
|
16847
|
+
// and not its opacity, and drew the faint lines of a time axis at full
|
|
16848
|
+
// strength. These are the paint properties SVG 1.1 has as attributes, and
|
|
16849
|
+
// vector-effect, which every release wrote as one. (paint-order is SVG
|
|
16850
|
+
// 2's: it stays the style it always was. So do the fonts.)
|
|
16851
|
+
const FILE_PAINT = ['fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-width', 'stroke-opacity',
|
|
16852
|
+
'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit',
|
|
16853
|
+
'text-anchor', 'dominant-baseline', 'shape-rendering', 'vector-effect', 'stop-color', 'stop-opacity',
|
|
16854
|
+
'opacity'];
|
|
16855
|
+
// The paints that are a colour, each with the attribute a colour's alpha
|
|
16856
|
+
// goes to in a file. SVG 1.1 has no colour with an alpha and no
|
|
16857
|
+
// "transparent", and a program that knows neither draws black: MuPDF put
|
|
16858
|
+
// a black slab over a heat map (the surface it listens to the pointer
|
|
16859
|
+
// with) and over every row of a legend, and a solid bar where a node's
|
|
16860
|
+
// age interval is a translucent one.
|
|
16861
|
+
const FILE_ALPHA = {'fill': 'fill-opacity', 'stroke': 'stroke-opacity', 'stop-color': 'stop-opacity'};
|
|
16862
|
+
// every kind of element an svg of the viewer's is made of: the rule names
|
|
16863
|
+
// them one by one, so that a browser never tries it on anything else
|
|
16864
|
+
const SHAPE_ELEMENTS = ['g', 'path', 'line', 'rect', 'circle', 'ellipse', 'polygon', 'polyline', 'text', 'tspan',
|
|
16865
|
+
'textPath', 'use', 'image', 'a', 'defs', 'mask', 'clipPath', 'linearGradient', 'radialGradient', 'stop',
|
|
16866
|
+
'symbol', 'marker', 'pattern', 'title', 'desc'];
|
|
16867
|
+
// Weight without rank. A selector made of element names alone weighs less
|
|
16868
|
+
// than one class however many names it has, so eight names that are no
|
|
16869
|
+
// element's, inside a ":not", make a rule outweigh every rule a page
|
|
16870
|
+
// writes with element names only ("label", "fieldset > legend", "*"),
|
|
16871
|
+
// whichever sheet came first, and still give way to any rule for a class.
|
|
16872
|
+
const ELEMENT_WEIGHT = ':not(' + new Array(8).fill('aptx-none').join(' ') + ')';
|
|
16873
|
+
// Where the browser's own focus ring is what shows the focus (the menus
|
|
16874
|
+
// light the entry instead), and the controls there that draw their own.
|
|
16875
|
+
const FOCUS_RING_ROOTS = ['.aptx-panel', '.aptx-dialog', '.aptx-msa-nav'];
|
|
16876
|
+
// (Every rule of this sheet that sets an outline has its control here: the
|
|
16877
|
+
// rule that gives the ring back outweighs them all, so one left out gets
|
|
16878
|
+
// the browser's ring on top of its own. A colour swatch draws an outline
|
|
16879
|
+
// under the pointer, and keeps the browser's ring otherwise.)
|
|
16880
|
+
const OWN_FOCUS = ['.aptx-cp-hex', '.aptx-reps-input', '.aptx-reorder-row', '.aptx-reps-button',
|
|
16881
|
+
'.aptx-msa-nav-jump', '.aptx-clampable', '.aptx-cp-sw:hover', 'input[type=text]', 'select'];
|
|
16882
|
+
|
|
16883
|
+
// Svg markup as a file has it, its paint in attributes, with that paint
|
|
16884
|
+
// moved into inline styles: an attribute gives way to any rule of the page
|
|
16885
|
+
// the markup is put in, and to the viewer's own "as a page that says
|
|
16886
|
+
// nothing has it" (SHAPE_PAINT). The icons and the logo go through here.
|
|
16887
|
+
// An attribute may say what a style may not: a font given piece by piece,
|
|
16888
|
+
// a size without its unit.
|
|
16889
|
+
function paintInline(markup) {
|
|
16890
|
+
let names = Object.keys(SHAPE_PAINT).concat(['font-family', 'font-size', 'font-style', 'font-weight']);
|
|
16891
|
+
let paint = new RegExp('\\s(' + names.join('|') + ')="([^"]*)"', 'g');
|
|
16892
|
+
return markup.replace(/<([a-zA-Z]+)(\s[^<>]*?)(\/?)>/g, function (all, tag, attributes, closed) {
|
|
16893
|
+
let styles = [];
|
|
16894
|
+
let rest = attributes.replace(paint, function (attribute, name, value) {
|
|
16895
|
+
let unitless = (name === 'font-size' || name === 'letter-spacing' || name === 'word-spacing')
|
|
16896
|
+
&& /^-?[0-9.]+$/.test(value);
|
|
16897
|
+
styles.push(name + ':' + value + (unitless ? 'px' : ''));
|
|
16898
|
+
return '';
|
|
16899
|
+
});
|
|
16900
|
+
if (styles.length === 0) {
|
|
16901
|
+
return all;
|
|
16902
|
+
}
|
|
16903
|
+
// a style the element already has is said after, and so still has the last word
|
|
16904
|
+
let own = '';
|
|
16905
|
+
rest = rest.replace(/\sstyle="([^"]*)"/, function (attribute, value) {
|
|
16906
|
+
own = ';' + value;
|
|
16907
|
+
return '';
|
|
16908
|
+
});
|
|
16909
|
+
return '<' + tag + rest + ' style="' + styles.join(';') + own + '"' + closed + '>';
|
|
16910
|
+
});
|
|
16911
|
+
}
|
|
16912
|
+
|
|
16913
|
+
function hostProofCss() {
|
|
16914
|
+
let roots = HOST_PROOF_ROOTS.join(', ');
|
|
16915
|
+
// (the weight: BV-BRC's rule is ".Phylogeny svg", exactly as heavy as "svg.aptx-tree")
|
|
16916
|
+
let tree = 'svg.' + TREE_SVG_CLASS + ELEMENT_WEIGHT;
|
|
16917
|
+
// Every root and all that is in it (":where" adds no weight), svgs
|
|
16918
|
+
// apart: their shapes are drawn with attributes, which a rule saying
|
|
16919
|
+
// "revert" would undo. The part about the element itself stands last,
|
|
16920
|
+
// where a browser tries it first: a shape of the tree is turned away
|
|
16921
|
+
// at "svg *". (Measured, a whole page's styles worked out again: 8 ms
|
|
16922
|
+
// became 10 on a page of 20,000 elements around the viewer.)
|
|
16923
|
+
let everything = ':where(' + roots + ')' + ELEMENT_WEIGHT
|
|
16924
|
+
+ ', :where(' + roots + ') :not(svg, svg *)' + ELEMENT_WEIGHT;
|
|
16925
|
+
// Every shape of every svg of the viewer's: the tree's, the icons',
|
|
16926
|
+
// the logo's, the clock plot's. With the weight of a class, as the
|
|
16927
|
+
// svgs themselves: "rect:hover", "text:first-child" and ".chart text"
|
|
16928
|
+
// all weigh one class and an element. So a rule of this sheet for a
|
|
16929
|
+
// shape names two classes (".aptx-dialog .aptx-clock-grid"), or it
|
|
16930
|
+
// loses to this one.
|
|
16931
|
+
let svgs = ':is(svg.' + TREE_SVG_CLASS + ', ' + HOST_PROOF_ROOTS.map(function (r) {
|
|
16932
|
+
return r + ' svg';
|
|
16933
|
+
}).join(', ') + ')';
|
|
16934
|
+
let shapes = SHAPE_ELEMENTS.map(function (element) {
|
|
16935
|
+
return svgs + ' ' + element + ELEMENT_WEIGHT;
|
|
16936
|
+
}).join(', ');
|
|
16937
|
+
let properties = Object.keys(SHAPE_PAINT);
|
|
16938
|
+
// where a shape's paint starts from, for what it does not state: said on the svg
|
|
16939
|
+
let start = properties.filter(function (name) {
|
|
16940
|
+
return SHAPE_PAINT[name] !== null;
|
|
16941
|
+
}).map(function (name) {
|
|
16942
|
+
return name + ':' + SHAPE_PAINT[name] + ';';
|
|
16943
|
+
}).join(' ');
|
|
16944
|
+
return ''
|
|
16945
|
+
// 1. Everything starts from the browser's own defaults, as in a
|
|
16946
|
+
// page that says nothing: a reset's "* { box-sizing:border-box }"
|
|
16947
|
+
// or "button { font: inherit }", a framework's "label { font-
|
|
16948
|
+
// weight: 700 }". In a page that does say nothing this rule
|
|
16949
|
+
// changes nothing; and every rule of this sheet for a class,
|
|
16950
|
+
// here or below, still wins (ELEMENT_WEIGHT).
|
|
16951
|
+
+ everything + ' { all:revert; }'
|
|
16952
|
+
// An svg's own element likewise (its shapes are left alone),
|
|
16953
|
+
// and with the weight of a class: BV-BRC's rule was a class of
|
|
16954
|
+
// the page's and "svg". So an svg of the viewer's states its
|
|
16955
|
+
// size in the sheet or inline, never as attributes alone, and
|
|
16956
|
+
// its own rule names two classes (or it loses to this one).
|
|
16957
|
+
+ ':is(' + roots + ') svg' + ELEMENT_WEIGHT + ' { all:revert; ' + start + ' }'
|
|
16958
|
+
// And a shape of an svg, in what paints it (SHAPE_PAINT): it
|
|
16959
|
+
// states that inline, so what a page says of a "path" or a
|
|
16960
|
+
// "text" is set aside for what the browser has, which for most
|
|
16961
|
+
// of these is what the shape's parent has.
|
|
16962
|
+
+ shapes + ' { ' + properties.join(':revert; ') + ':revert; }'
|
|
16963
|
+
// Bootstrap 3 names tick boxes by their type, which outweighs
|
|
16964
|
+
// the rule above: the one tick box here that states no margin
|
|
16965
|
+
// of its own keeps the browser's, and a disabled one its cursor.
|
|
16966
|
+
+ '.aptx-dialog .aptx-reorder-check { margin:revert; }'
|
|
16967
|
+
+ '.aptx-dialog input[type=checkbox]:disabled, .aptx-dialog input[type=radio]:disabled { cursor:default; }'
|
|
16968
|
+
// 2. What a page hands down by inheritance stops at each root. The
|
|
16969
|
+
// values are the ones a page that says nothing has; a root's
|
|
16970
|
+
// own rule, further down, states its font, size and colour.
|
|
16971
|
+
// (Left to the page on purpose: direction, cursor, visibility,
|
|
16972
|
+
// pointer-events.)
|
|
16973
|
+
+ roots + ', ' + tree + ' {'
|
|
16974
|
+
+ ' color-scheme:light; accent-color:auto; caret-color:auto;'
|
|
16975
|
+
+ ' font:400 11px system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;'
|
|
16976
|
+
+ ' letter-spacing:normal; word-spacing:normal; text-align:start; text-align-last:auto; text-indent:0;'
|
|
16977
|
+
+ ' text-transform:none; text-shadow:none; text-rendering:auto;'
|
|
16978
|
+
+ ' -webkit-text-size-adjust:auto; text-size-adjust:auto;'
|
|
16979
|
+
+ ' white-space:normal; word-break:normal; overflow-wrap:normal; hyphens:manual; tab-size:8;'
|
|
16980
|
+
+ ' quotes:auto; list-style:disc outside none; }'
|
|
16981
|
+
// 3. The tree's own svg. Its shapes carry their paint inline; what
|
|
16982
|
+
// they inherit, and what a rule for a bare "svg" would set, is
|
|
16983
|
+
// said here. (Its width, height and font are inline: they are
|
|
16984
|
+
// the file's as well as the page's, see sizeTreeSvg and where
|
|
16985
|
+
// the svg is made. The background is the viewer's own rect.)
|
|
16986
|
+
// border-box, so that a border a page gives it on purpose fits
|
|
16987
|
+
// inside the size stated, in a page with a reset and without.
|
|
16988
|
+
// Nor does it move as a page's "* { transition: all .3s }" would
|
|
16989
|
+
// have it: the tree is redrawn frame by frame.
|
|
16990
|
+
+ tree + ' { color:#000; background:none; max-width:none; box-sizing:border-box;'
|
|
16991
|
+
+ ' vertical-align:baseline; transition:none; animation:none; ' + start + ' }'
|
|
16992
|
+
// "node" is every d3 example's class as well: ".node { font: 10px sans-serif; cursor: pointer }"
|
|
16993
|
+
+ tree + ' g.node { font:inherit; cursor:inherit; }'
|
|
16994
|
+
// 4. The browser's own focus ring, back on whatever does not draw
|
|
16995
|
+
// one of its own: ":focus { outline:0 }" is in many a sheet.
|
|
16996
|
+
+ ':is(' + FOCUS_RING_ROOTS.join(', ') + ') :focus-visible:not(' + OWN_FOCUS.join(', ') + ')'
|
|
16997
|
+
+ ' { outline:revert; outline-offset:revert; }';
|
|
16998
|
+
}
|
|
16999
|
+
|
|
16767
17000
|
function injectPanelStyles() {
|
|
16768
17001
|
loadPanelTheme();
|
|
16769
17002
|
if (document.getElementById(PANEL_STYLE_ID)) {
|
|
@@ -16775,7 +17008,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
16775
17008
|
+ ' --p-line:#27343f; --p-line-strong:#35434f; --p-surface2:#202d38;'
|
|
16776
17009
|
+ ' --p-accent:#57a6ff; --p-accent-ink:#9cc7ff; --p-accent-weak:rgba(87,166,255,0.18);'
|
|
16777
17010
|
+ ' --p-shadow-sm:0 1px 2px rgba(0,0,0,0.4);';
|
|
16778
|
-
let css =
|
|
17011
|
+
let css = hostProofCss()
|
|
16779
17012
|
+ '.aptx-panel {'
|
|
16780
17013
|
+ ' --p-bg: rgba(255,255,255,0.86); --p-ink:#1e2a35; --p-muted:#6b7a89; --p-faint:#93a3b2; --p-warn:#b15a0b;'
|
|
16781
17014
|
+ ' --p-line:#e3e9f0; --p-line-strong:#cad6e1; --p-surface2:#f3f6fa;'
|
|
@@ -16971,7 +17204,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
16971
17204
|
+ '.aptx-reps { line-height:1.35; }'
|
|
16972
17205
|
+ '.aptx-reps-lead { margin:0 0 4px; }'
|
|
16973
17206
|
+ '.aptx-reps-choice { display:flex; align-items:center; gap:6px; padding:2px 0; cursor:pointer; }'
|
|
16974
|
-
|
|
17207
|
+
// (".aptx-dialog": Bootstrap 3's rule for a tick box weighs as much as ".aptx-reps-choice input")
|
|
17208
|
+
+ '.aptx-dialog .aptx-reps-choice input { accent-color:var(--p-accent); width:13px; height:13px; margin:0; flex:none; }'
|
|
16975
17209
|
+ '.aptx-reps-disabled { color:var(--p-faint); cursor:default; }'
|
|
16976
17210
|
+ '.aptx-reps-input { display:block; box-sizing:border-box; width:100%; font:inherit; color:var(--p-ink);'
|
|
16977
17211
|
+ ' background:var(--p-surface2); border:1px solid var(--p-line-strong); border-radius:6px; padding:4px 7px; }'
|
|
@@ -17020,18 +17254,18 @@ function (root, d3, forester, phyloXml) {
|
|
|
17020
17254
|
// node's in the tree and is set on the point itself
|
|
17021
17255
|
+ '.aptx-dialog-drag { cursor:move; touch-action:none; user-select:none; -webkit-user-select:none; }'
|
|
17022
17256
|
+ '.aptx-clock.aptx-dialog-body { overflow:visible; }'
|
|
17023
|
-
+ '.aptx-clock-svg { display:block; touch-action:none; user-select:none; -webkit-user-select:none; }'
|
|
17024
|
-
+ '.aptx-clock-svg text { font-size:10px; fill:var(--p-muted); font-variant-numeric:tabular-nums; }'
|
|
17025
|
-
+ '.aptx-clock-svg text.aptx-clock-title { font-size:10.5px; fill:var(--p-ink); }'
|
|
17026
|
-
+ '.aptx-clock-ground { fill:var(--p-surface2); }'
|
|
17027
|
-
+ '.aptx-clock-grid { stroke:var(--p-line); stroke-width:1; shape-rendering:crispEdges; }'
|
|
17028
|
-
+ '.aptx-clock-frame { stroke:var(--p-line-strong); stroke-width:1; shape-rendering:crispEdges; }'
|
|
17029
|
-
+ '.aptx-clock-line { stroke:var(--p-accent); stroke-width:1.6; }'
|
|
17030
|
-
+ '.aptx-clock-internal circle { fill:var(--p-faint); fill-opacity:0.55; }'
|
|
17031
|
-
+ '.aptx-clock-tips circle { fill:var(--p-ink); fill-opacity:0.8; }'
|
|
17032
|
-
+ '.aptx-clock-svg circle.aptx-clock-hit { fill-opacity:1; stroke-width:1.2; }'
|
|
17033
|
-
+ '.aptx-clock-ring { fill:none; stroke:var(--p-accent); stroke-width:1.8; pointer-events:none; }'
|
|
17034
|
-
+ '.aptx-clock-box { fill:var(--p-accent-weak); stroke:var(--p-accent); stroke-width:1;'
|
|
17257
|
+
+ '.aptx-dialog .aptx-clock-svg { display:block; touch-action:none; user-select:none; -webkit-user-select:none; }'
|
|
17258
|
+
+ '.aptx-dialog .aptx-clock-svg text { font-size:10px; fill:var(--p-muted); font-variant-numeric:tabular-nums; }'
|
|
17259
|
+
+ '.aptx-dialog .aptx-clock-svg text.aptx-clock-title { font-size:10.5px; fill:var(--p-ink); }'
|
|
17260
|
+
+ '.aptx-dialog .aptx-clock-ground { fill:var(--p-surface2); }'
|
|
17261
|
+
+ '.aptx-dialog .aptx-clock-grid { stroke:var(--p-line); stroke-width:1; shape-rendering:crispEdges; }'
|
|
17262
|
+
+ '.aptx-dialog .aptx-clock-frame { stroke:var(--p-line-strong); stroke-width:1; shape-rendering:crispEdges; }'
|
|
17263
|
+
+ '.aptx-dialog .aptx-clock-line { stroke:var(--p-accent); stroke-width:1.6; }'
|
|
17264
|
+
+ '.aptx-dialog .aptx-clock-internal circle { fill:var(--p-faint); fill-opacity:0.55; }'
|
|
17265
|
+
+ '.aptx-dialog .aptx-clock-tips circle { fill:var(--p-ink); fill-opacity:0.8; }'
|
|
17266
|
+
+ '.aptx-dialog .aptx-clock-svg circle.aptx-clock-hit { fill-opacity:1; stroke-width:1.2; }'
|
|
17267
|
+
+ '.aptx-dialog .aptx-clock-ring { fill:none; stroke:var(--p-accent); stroke-width:1.8; pointer-events:none; }'
|
|
17268
|
+
+ '.aptx-dialog .aptx-clock-box { fill:var(--p-accent-weak); stroke:var(--p-accent); stroke-width:1;'
|
|
17035
17269
|
+ ' shape-rendering:crispEdges; pointer-events:none; }'
|
|
17036
17270
|
+ '.aptx-clock-options { display:flex; align-items:center; gap:14px; margin:6px 0 8px; }'
|
|
17037
17271
|
+ '.aptx-clock-options .aptx-clock-deselect { margin-left:auto; height:22px; padding:0 9px; }'
|
|
@@ -17097,7 +17331,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
17097
17331
|
+ '.aptx-about .aptx-dialog-key { flex-basis:31%; }'
|
|
17098
17332
|
+ '.aptx-about-head { display:flex; align-items:center; gap:12px; margin-bottom:10px; }'
|
|
17099
17333
|
+ '.aptx-about-logo { flex:none; width:56px; height:56px; }'
|
|
17100
|
-
+ '.aptx-about-logo svg { width:100%; height:100%; display:block; }'
|
|
17334
|
+
+ '.aptx-dialog .aptx-about-logo svg { width:100%; height:100%; display:block; }'
|
|
17101
17335
|
+ '.aptx-about-name { font-size:15px; font-weight:650; letter-spacing:-0.01em; }'
|
|
17102
17336
|
+ '.aptx-about-version { color:var(--p-muted); }'
|
|
17103
17337
|
+ '.aptx-about-blurb { margin:0 0 11px; color:var(--p-muted); }'
|
|
@@ -17123,6 +17357,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
17123
17357
|
+ '.aptx-panel legend { float:none; width:auto; padding:0; margin:0 0 5px; font-size:9px; font-weight:700; letter-spacing:0.09em; text-transform:uppercase; color:var(--p-faint); }'
|
|
17124
17358
|
+ '.aptx-panel label { cursor:pointer; }'
|
|
17125
17359
|
+ '.aptx-panel input[type=checkbox],.aptx-panel input[type=radio] { accent-color:var(--p-accent); width:13px; height:13px; vertical-align:-2px; margin:0 4px 0 0; }'
|
|
17360
|
+
+ '.aptx-panel input[type=checkbox]:disabled,.aptx-panel input[type=radio]:disabled { cursor:default; }'
|
|
17126
17361
|
// checkbox/radio + label as one item (used by the Display Data grid and the inline P/A/C and search-option rows)
|
|
17127
17362
|
// every item carries an invisible box, so the one that lights up
|
|
17128
17363
|
// (Auto-hide Labels while it is hiding) does not shift its neighbours
|
|
@@ -17210,9 +17445,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
17210
17445
|
+ '.aptx-panel .aptx-searchnav { align-items:center; gap:4px; margin:2px 0 4px; }'
|
|
17211
17446
|
+ '.aptx-panel .aptx-searchnav span { flex:1 1 auto; text-align:center; font-weight:600; font-size:11px; color:var(--p-ink); }'
|
|
17212
17447
|
+ '.aptx-panel .aptx-searchnav .aptx-gbtn:last-child { margin-right:0; }'
|
|
17213
|
-
+ '.aptx-found-halo { opacity:' + HALO_REST_OPACITY + '; transform-box:fill-box; transform-origin:center; animation:aptx-halo-pulse 1.3s ease-in-out infinite; }'
|
|
17448
|
+
+ 'svg.' + TREE_SVG_CLASS + ' .aptx-found-halo { opacity:' + HALO_REST_OPACITY + '; transform-box:fill-box; transform-origin:center; animation:aptx-halo-pulse 1.3s ease-in-out infinite; }'
|
|
17214
17449
|
+ '@keyframes aptx-halo-pulse { 0%,100% { transform:scale(1); opacity:' + HALO_REST_OPACITY + '; } 50% { transform:scale(2.5); opacity:0.12; } }'
|
|
17215
|
-
+ '@media (prefers-reduced-motion: reduce) { .aptx-found-halo { animation:none; } }'
|
|
17450
|
+
+ '@media (prefers-reduced-motion: reduce) { svg.' + TREE_SVG_CLASS + ' .aptx-found-halo { animation:none; } }'
|
|
17216
17451
|
+ '.aptx-panel .aptx-zoomgrid { display:flex; flex-direction:column; align-items:stretch; }'
|
|
17217
17452
|
+ '.aptx-panel .aptx-zoomgrid > input[type=button] { width:100%; margin-right:0; }'
|
|
17218
17453
|
+ '.aptx-panel .aptx-zoomrow { display:flex; }'
|
|
@@ -18909,7 +19144,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
18909
19144
|
head.className = 'aptx-about-head';
|
|
18910
19145
|
let logo = document.createElement('div');
|
|
18911
19146
|
logo.className = 'aptx-about-logo';
|
|
18912
|
-
logo.innerHTML = ARCHAEOPTERYX_LOGO_SVG;
|
|
19147
|
+
logo.innerHTML = paintInline(ARCHAEOPTERYX_LOGO_SVG);
|
|
18913
19148
|
head.appendChild(logo);
|
|
18914
19149
|
|
|
18915
19150
|
let titles = document.createElement('div');
|
|
@@ -20244,7 +20479,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
20244
20479
|
.attr('height', '100%')
|
|
20245
20480
|
.style('opacity', 1)
|
|
20246
20481
|
.attr('class', BASE_BACKGROUND)
|
|
20247
|
-
.
|
|
20482
|
+
.style('fill', _state.backgroundColorDefault);
|
|
20248
20483
|
}
|
|
20249
20484
|
|
|
20250
20485
|
|
|
@@ -20450,6 +20685,19 @@ function (root, d3, forester, phyloXml) {
|
|
|
20450
20685
|
return _baseSvg ? _baseSvg.node() : null;
|
|
20451
20686
|
}
|
|
20452
20687
|
|
|
20688
|
+
// The tree svg's size, said twice. The attributes are what an export and
|
|
20689
|
+
// the layout read; but an attribute gives way to any rule of the page the
|
|
20690
|
+
// viewer is drawn in: under "svg { width: 24px; height: 24px }", a page
|
|
20691
|
+
// whose only svgs were icons, the tree was 24 px square. (A rule that says
|
|
20692
|
+
// "height: auto" changes nothing: the attributes still give the svg its
|
|
20693
|
+
// own size.) The inline style does not give way.
|
|
20694
|
+
function sizeTreeSvg(svg, w, h) {
|
|
20695
|
+
svg.attr('width', w)
|
|
20696
|
+
.attr('height', h)
|
|
20697
|
+
.style('width', w + 'px')
|
|
20698
|
+
.style('height', h + 'px');
|
|
20699
|
+
}
|
|
20700
|
+
|
|
20453
20701
|
// An export is always light, whatever the screen is set to: a dark PNG or
|
|
20454
20702
|
// SVG is wrong on paper, in a slide and in a paper figure, and it is the
|
|
20455
20703
|
// file that outlives the session. Only the four theme colours are swapped,
|
|
@@ -20493,6 +20741,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
20493
20741
|
}
|
|
20494
20742
|
|
|
20495
20743
|
function cleanExportCopy(copy) {
|
|
20744
|
+
// What the live svg carries only for the page it is drawn in (its
|
|
20745
|
+
// class, its size said a second time): a file has no page, and an
|
|
20746
|
+
// inline size would hold a full-size drawing to the window's.
|
|
20747
|
+
copy.classList.remove(TREE_SVG_CLASS);
|
|
20748
|
+
if (!copy.getAttribute('class')) {
|
|
20749
|
+
copy.removeAttribute('class');
|
|
20750
|
+
}
|
|
20751
|
+
copy.style.removeProperty('width');
|
|
20752
|
+
copy.style.removeProperty('height');
|
|
20496
20753
|
// Serialize a COPY with the overview taken out of it: the overview is
|
|
20497
20754
|
// an on-screen navigation aid, not part of the tree, and working on a
|
|
20498
20755
|
// copy leaves the live display untouched.
|
|
@@ -20509,7 +20766,57 @@ function (root, d3, forester, phyloXml) {
|
|
|
20509
20766
|
if (glow) {
|
|
20510
20767
|
glow.remove();
|
|
20511
20768
|
}
|
|
20512
|
-
return copy;
|
|
20769
|
+
return paintAsAttributes(copy);
|
|
20770
|
+
}
|
|
20771
|
+
|
|
20772
|
+
// The opposite of paintInline, for a file: a shape's paint, inline styles
|
|
20773
|
+
// on the page, is attributes again (FILE_PAINT). Where an element says a
|
|
20774
|
+
// thing both ways, the style is what painted it and has the last word.
|
|
20775
|
+
function paintAsAttributes(svg) {
|
|
20776
|
+
[svg].concat(Array.prototype.slice.call(svg.querySelectorAll('[style]'))).forEach(function (el) {
|
|
20777
|
+
let style = el.style;
|
|
20778
|
+
if (!style) {
|
|
20779
|
+
return;
|
|
20780
|
+
}
|
|
20781
|
+
FILE_PAINT.forEach(function (name) {
|
|
20782
|
+
let value = style.getPropertyValue(name);
|
|
20783
|
+
if (value !== '') {
|
|
20784
|
+
// (the browser writes url("#id"); an attribute has no quotes there)
|
|
20785
|
+
el.setAttribute(name, value.replace(/^url\("([^"]*)"\)$/, 'url($1)'));
|
|
20786
|
+
style.removeProperty(name);
|
|
20787
|
+
}
|
|
20788
|
+
});
|
|
20789
|
+
if (style.length === 0) {
|
|
20790
|
+
el.removeAttribute('style');
|
|
20791
|
+
}
|
|
20792
|
+
});
|
|
20793
|
+
// A colour with an alpha is the colour and, in the paint's own
|
|
20794
|
+
// opacity, the alpha times what the shape has of it already, stated
|
|
20795
|
+
// or handed down; a paint that is no paint at all is "none". Only on
|
|
20796
|
+
// a shape: an opacity stated on a group would reach the shapes in it
|
|
20797
|
+
// that have a colour of their own.
|
|
20798
|
+
Array.prototype.slice.call(svg.querySelectorAll('[fill], [stroke], [stop-color]')).forEach(function (el) {
|
|
20799
|
+
let holdsShapes = Array.prototype.some.call(el.children, function (child) {
|
|
20800
|
+
return child.localName !== 'title' && child.localName !== 'desc';
|
|
20801
|
+
});
|
|
20802
|
+
Object.keys(FILE_ALPHA).forEach(function (paint) {
|
|
20803
|
+
let c = el.hasAttribute(paint) ? d3.color(el.getAttribute(paint)) : null;
|
|
20804
|
+
if (!c || !(c.opacity < 1) || holdsShapes) {
|
|
20805
|
+
return;
|
|
20806
|
+
}
|
|
20807
|
+
let opacity = FILE_ALPHA[paint];
|
|
20808
|
+
if (c.opacity === 0 && paint !== 'stop-color') {
|
|
20809
|
+
el.setAttribute(paint, 'none');
|
|
20810
|
+
return;
|
|
20811
|
+
}
|
|
20812
|
+
// (a gradient's stop hands nothing down, and is handed nothing)
|
|
20813
|
+
let from = paint === 'stop-color' ? (el.hasAttribute(opacity) ? el : null) : el.closest('[' + opacity + ']');
|
|
20814
|
+
let has = from ? parseFloat(from.getAttribute(opacity)) : 1;
|
|
20815
|
+
el.setAttribute(paint, c.copy({opacity: 1}).formatRgb());
|
|
20816
|
+
el.setAttribute(opacity, String(+(c.opacity * (isNaN(has) ? 1 : has)).toFixed(4)));
|
|
20817
|
+
});
|
|
20818
|
+
});
|
|
20819
|
+
return svg;
|
|
20513
20820
|
}
|
|
20514
20821
|
|
|
20515
20822
|
// The same element of the copy as `live` is of the svg: children are
|