react-msaview 6.0.0 → 6.1.1

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.
Files changed (122) hide show
  1. package/bundle/index.js +98 -98
  2. package/bundle/index.js.map +4 -4
  3. package/dist/columnCounts.d.ts +7 -0
  4. package/dist/columnCounts.js +18 -0
  5. package/dist/columnCounts.js.map +1 -1
  6. package/dist/components/DragHandle.d.ts +25 -0
  7. package/dist/components/DragHandle.js +49 -0
  8. package/dist/components/DragHandle.js.map +1 -0
  9. package/dist/components/ResizeHandles.d.ts +4 -2
  10. package/dist/components/ResizeHandles.js +29 -32
  11. package/dist/components/ResizeHandles.js.map +1 -1
  12. package/dist/components/VerticalScrollbar.js +2 -2
  13. package/dist/components/VerticalScrollbar.js.map +1 -1
  14. package/dist/components/dialogs/InterProScanDialog.js +52 -129
  15. package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
  16. package/dist/components/header/settingsMenuItems.js +16 -1
  17. package/dist/components/header/settingsMenuItems.js.map +1 -1
  18. package/dist/components/minimap/Minimap.js +32 -4
  19. package/dist/components/minimap/Minimap.js.map +1 -1
  20. package/dist/components/msa/MSACanvas.js +8 -2
  21. package/dist/components/msa/MSACanvas.js.map +1 -1
  22. package/dist/components/msa/MSACanvasBlock.js +12 -3
  23. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  24. package/dist/components/msa/msaRaster.d.ts +63 -0
  25. package/dist/components/msa/msaRaster.js +252 -0
  26. package/dist/components/msa/msaRaster.js.map +1 -0
  27. package/dist/components/msa/renderMSABlock.d.ts +2 -1
  28. package/dist/components/msa/renderMSABlock.js +13 -21
  29. package/dist/components/msa/renderMSABlock.js.map +1 -1
  30. package/dist/components/msa/tileColor.d.ts +6 -0
  31. package/dist/components/msa/tileColor.js +35 -0
  32. package/dist/components/msa/tileColor.js.map +1 -0
  33. package/dist/components/renderCtx.d.ts +1 -1
  34. package/dist/components/tracks/TrackBlocks.d.ts +14 -0
  35. package/dist/components/tracks/TrackBlocks.js +65 -0
  36. package/dist/components/tracks/TrackBlocks.js.map +1 -0
  37. package/dist/components/tracks/{renderTracksSvg.d.ts → drawTracks.d.ts} +22 -10
  38. package/dist/components/tracks/drawTracks.js +193 -0
  39. package/dist/components/tracks/drawTracks.js.map +1 -0
  40. package/dist/components/tree/TreeCanvas.js +8 -2
  41. package/dist/components/tree/TreeCanvas.js.map +1 -1
  42. package/dist/components/tree/TreeCanvasBlock.js +7 -4
  43. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  44. package/dist/components/tree/renderTreeCanvas.js +27 -21
  45. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  46. package/dist/constants.d.ts +3 -0
  47. package/dist/constants.js +8 -0
  48. package/dist/constants.js.map +1 -1
  49. package/dist/model/DataModel.d.ts +1 -4
  50. package/dist/model/DataModel.js +8 -10
  51. package/dist/model/DataModel.js.map +1 -1
  52. package/dist/model/treeModel.d.ts +1 -1
  53. package/dist/model/treeModel.js +1 -1
  54. package/dist/model.d.ts +36 -7
  55. package/dist/model.js +113 -58
  56. package/dist/model.js.map +1 -1
  57. package/dist/neighborJoining.js +34 -18
  58. package/dist/neighborJoining.js.map +1 -1
  59. package/dist/renderTestEnv.d.ts +43 -0
  60. package/dist/renderTestEnv.js +88 -0
  61. package/dist/renderTestEnv.js.map +1 -0
  62. package/dist/renderToSvg.js +1 -1
  63. package/dist/renderToSvg.js.map +1 -1
  64. package/dist/sequenceLogo.d.ts +28 -0
  65. package/dist/sequenceLogo.js +47 -0
  66. package/dist/sequenceLogo.js.map +1 -0
  67. package/dist/types.d.ts +2 -0
  68. package/dist/version.d.ts +1 -1
  69. package/dist/version.js +1 -1
  70. package/package.json +3 -3
  71. package/src/columnCounts.test.ts +15 -0
  72. package/src/columnCounts.ts +19 -0
  73. package/src/components/DragHandle.tsx +81 -0
  74. package/src/components/ResizeHandles.tsx +46 -54
  75. package/src/components/VerticalScrollbar.tsx +3 -2
  76. package/src/components/dialogs/InterProScanDialog.tsx +54 -131
  77. package/src/components/header/settingsMenuItems.ts +19 -1
  78. package/src/components/minimap/Minimap.tsx +47 -4
  79. package/src/components/msa/MSACanvas.tsx +30 -10
  80. package/src/components/msa/MSACanvasBlock.tsx +13 -3
  81. package/src/components/msa/msaRaster.test.ts +145 -0
  82. package/src/components/msa/msaRaster.ts +350 -0
  83. package/src/components/msa/renderMSABlock.ts +15 -20
  84. package/src/components/msa/tileColor.ts +37 -0
  85. package/src/components/renderCtx.ts +2 -0
  86. package/src/components/tracks/TrackBlocks.tsx +111 -0
  87. package/src/components/tracks/drawTracks.ts +323 -0
  88. package/src/components/tree/TreeCanvas.tsx +16 -4
  89. package/src/components/tree/TreeCanvasBlock.tsx +18 -11
  90. package/src/components/tree/renderTreeCanvas.test.ts +55 -0
  91. package/src/components/tree/renderTreeCanvas.ts +28 -19
  92. package/src/constants.ts +10 -0
  93. package/src/impgRender.test.tsx +2 -57
  94. package/src/largeTree.test.ts +16 -2
  95. package/src/model/DataModel.ts +11 -11
  96. package/src/model/treeModel.ts +1 -1
  97. package/src/model.ts +128 -60
  98. package/src/neighborJoining.ts +36 -18
  99. package/src/renderDomainsSvg.test.tsx +2 -57
  100. package/src/renderMinimapSvg.test.tsx +2 -56
  101. package/src/renderSequenceLogoSvg.test.tsx +176 -0
  102. package/src/renderTestEnv.ts +96 -0
  103. package/src/renderToSvg.tsx +1 -1
  104. package/src/sequenceLogo.test.ts +88 -0
  105. package/src/sequenceLogo.ts +63 -0
  106. package/src/types.ts +7 -1
  107. package/src/version.ts +1 -1
  108. package/dist/components/ConservationTrack.d.ts +0 -8
  109. package/dist/components/ConservationTrack.js +0 -49
  110. package/dist/components/ConservationTrack.js.map +0 -1
  111. package/dist/components/ScrollbarThumb.d.ts +0 -14
  112. package/dist/components/ScrollbarThumb.js +0 -28
  113. package/dist/components/ScrollbarThumb.js.map +0 -1
  114. package/dist/components/TextTrack.d.ts +0 -8
  115. package/dist/components/TextTrack.js +0 -58
  116. package/dist/components/TextTrack.js.map +0 -1
  117. package/dist/components/tracks/renderTracksSvg.js +0 -126
  118. package/dist/components/tracks/renderTracksSvg.js.map +0 -1
  119. package/src/components/ConservationTrack.tsx +0 -99
  120. package/src/components/ScrollbarThumb.tsx +0 -48
  121. package/src/components/TextTrack.tsx +0 -103
  122. package/src/components/tracks/renderTracksSvg.ts +0 -247
@@ -52,15 +52,13 @@ export function DataModelF() {
52
52
  self.gff = gff;
53
53
  },
54
54
  }))
55
- .postProcessSnapshot(snap => {
56
- const { tree, msa, treeMetadata, gff } = snap;
57
- const max = 50_000;
58
- return {
59
- tree: tree && tree.length > max ? undefined : tree,
60
- msa: msa && msa.length > max ? undefined : msa,
61
- treeMetadata: treeMetadata && treeMetadata.length > max ? undefined : treeMetadata,
62
- gff: gff && gff.length > max ? undefined : gff,
63
- };
64
- });
55
+ .postProcessSnapshot(snap =>
56
+ // a large document is dropped from the snapshot rather than inlined
57
+ // into a session or a shared URL, and every field here holds one, so
58
+ // the rule is the same for all of them
59
+ Object.fromEntries(Object.entries(snap).map(([key, text]) => [
60
+ key,
61
+ text && text.length > 50_000 ? undefined : text,
62
+ ])));
65
63
  }
66
64
  //# sourceMappingURL=DataModel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataModel.js","sourceRoot":"","sources":["../../src/model/DataModel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAEhD;;;;;;GAMG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,KAAK;SACT,KAAK,CAAC;QACL;;WAEG;QACH,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;QAC/B;;WAEG;QACH,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;QAC9B;;WAEG;QACH,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;QACvC;;WAEG;QACH,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;KAC/B,CAAC;SACD,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChB;;WAEG;QACH,OAAO,CAAC,IAAa;YACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAClB,CAAC;QACD;;WAEG;QACH,MAAM,CAAC,GAAY;YACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QAChB,CAAC;QACD;;WAEG;QACH,eAAe,CAAC,YAAqB;YACnC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;QAClC,CAAC;QACD;;WAEG;QACH,MAAM,CAAC,GAAY;YACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QAChB,CAAC;KACF,CAAC,CAAC;SACF,mBAAmB,CAAC,IAAI,CAAC,EAAE;QAC1B,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;QAC7C,MAAM,GAAG,GAAG,MAAM,CAAA;QAClB,OAAO;YACL,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;YAClD,GAAG,EAAE,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG;YAC9C,YAAY,EACV,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY;YACtE,GAAG,EAAE,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG;SAC/C,CAAA;IACH,CAAC,CAAC,CAAA;AACN,CAAC"}
1
+ {"version":3,"file":"DataModel.js","sourceRoot":"","sources":["../../src/model/DataModel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAEhD;;;;;;GAMG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,KAAK;SACT,KAAK,CAAC;QACL;;WAEG;QACH,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;QAC/B;;WAEG;QACH,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;QAC9B;;WAEG;QACH,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;QACvC;;WAEG;QACH,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;KAC/B,CAAC;SACD,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChB;;WAEG;QACH,OAAO,CAAC,IAAa;YACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAClB,CAAC;QACD;;WAEG;QACH,MAAM,CAAC,GAAY;YACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QAChB,CAAC;QACD;;WAEG;QACH,eAAe,CAAC,YAAqB;YACnC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;QAClC,CAAC;QACD;;WAEG;QACH,MAAM,CAAC,GAAY;YACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QAChB,CAAC;KACF,CAAC,CAAC;SACF,mBAAmB,CAAC,IAAI,CAAC,EAAE;IAC1B,oEAAoE;IACpE,qEAAqE;IACrE,uCAAuC;IACvC,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;QACxC,GAAG;QACH,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;KAChD,CAAC,CACH,CACF,CAAA;AACL,CAAC"}
@@ -36,7 +36,7 @@ export declare function TreeModelF(): import("@jbrowse/mobx-state-tree").IModelT
36
36
  drawTree: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
37
37
  /**
38
38
  * #property
39
- * draw clickable node bubbles on the tree
39
+ * draw node bubbles on the tree; the branches stay clickable either way
40
40
  */
41
41
  drawNodeBubbles: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
42
42
  /**
@@ -41,7 +41,7 @@ export function TreeModelF() {
41
41
  drawTree: stripDefault(types.boolean, defaultDrawTree),
42
42
  /**
43
43
  * #property
44
- * draw clickable node bubbles on the tree
44
+ * draw node bubbles on the tree; the branches stay clickable either way
45
45
  */
46
46
  drawNodeBubbles: stripDefault(types.boolean, defaultDrawNodeBubbles),
47
47
  /**
package/dist/model.d.ts CHANGED
@@ -518,7 +518,10 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
518
518
  showOnly: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
519
519
  /**
520
520
  * #property
521
- * turned off tracks
521
+ * the user's explicit show/hide choice per track id, keyed by id with
522
+ * the value meaning "off". A track the user has never touched is absent
523
+ * and falls back to its own default (see `defaultOffTracks`), so a
524
+ * hidden-by-default track adds nothing to the shared URL.
522
525
  */
523
526
  turnedOffTracks: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IMapType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>>, [undefined]>;
524
527
  /**
@@ -537,10 +540,7 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
537
540
  setTreeMetadata(treeMetadata?: string): void;
538
541
  setGFF(gff?: string): void;
539
542
  }, import("@jbrowse/mobx-state-tree")._NotCustomized, {
540
- tree: string | undefined;
541
- msa: string | undefined;
542
- treeMetadata: string | undefined;
543
- gff: string | undefined;
543
+ [k: string]: string | undefined;
544
544
  }>, [undefined]>;
545
545
  /**
546
546
  * #property
@@ -664,6 +664,13 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
664
664
  * #volatile
665
665
  */
666
666
  conservationTrackHeight: number;
667
+ /**
668
+ * #volatile
669
+ * taller than the conservation track by default: the logo spends its
670
+ * height on stacked glyphs, and a 40px stack of four residues leaves each
671
+ * one too short to identify
672
+ */
673
+ sequenceLogoTrackHeight: number;
667
674
  /**
668
675
  * #volatile
669
676
  */
@@ -1013,6 +1020,13 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
1013
1020
  * Returns values 0-1 where 1 = fully conserved, 0 = no conservation.
1014
1021
  */
1015
1022
  readonly conservation: number[];
1023
+ /**
1024
+ * #getter
1025
+ * The information content of a fully conserved column, in bits, which
1026
+ * depends on the alphabet. Both the entropy ceiling `conservation`
1027
+ * normalizes against and the y-axis ceiling of the sequence logo track.
1028
+ */
1029
+ readonly alphabetMaxBits: number;
1016
1030
  /**
1017
1031
  * #getter
1018
1032
  * Per-column conservation of physicochemical property class (amino acids
@@ -1070,6 +1084,12 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
1070
1084
  * #getter
1071
1085
  */
1072
1086
  readonly blocksY: number[];
1087
+ /**
1088
+ * #getter
1089
+ * height of the alignment viewport, px. The same subtraction as
1090
+ * msaAreaHeight, which is defined later in the views chain
1091
+ */
1092
+ readonly visibleMsaHeight: number;
1073
1093
  } & {
1074
1094
  /**
1075
1095
  * #getter
@@ -1086,8 +1106,7 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
1086
1106
  /**
1087
1107
  * #getter
1088
1108
  * most-negative allowed scrollY, keeping the last row in view rather than
1089
- * letting the whole alignment scroll off the top. visible MSA height is
1090
- * computed inline here because msaAreaHeight is defined later in the chain.
1109
+ * letting the whole alignment scroll off the top.
1091
1110
  */
1092
1111
  readonly maxScrollY: number;
1093
1112
  /**
@@ -1206,6 +1225,12 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
1206
1225
  * #getter
1207
1226
  */
1208
1227
  readonly labelWidthMap: Map<string, number>;
1228
+ /**
1229
+ * #getter
1230
+ * factor turning a labelWidthMap entry into its width at the current
1231
+ * font size
1232
+ */
1233
+ readonly labelWidthScale: number;
1209
1234
  readonly labelsWidth: number;
1210
1235
  /**
1211
1236
  * #getter
@@ -1446,6 +1471,10 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
1446
1471
  * #action
1447
1472
  */
1448
1473
  setConservationTrackHeight(arg: number): void;
1474
+ /**
1475
+ * #action
1476
+ */
1477
+ setSequenceLogoTrackHeight(arg: number): void;
1449
1478
  /**
1450
1479
  * #action
1451
1480
  */
package/dist/model.js CHANGED
@@ -9,9 +9,8 @@ import { calculateBlocks } from './calculateBlocks.js';
9
9
  import { clustalXColumnColors } from './clustalX.js';
10
10
  import colorSchemes from './colorSchemes.js';
11
11
  import { columnCountsFromRows, letterOfResidueSlot } from './columnCounts.js';
12
- import ConservationTrack from './components/ConservationTrack.js';
13
- import TextTrack from './components/TextTrack.js';
14
- import { defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawMsaLetters, defaultHeight, defaultHideGaps, defaultRowHeight, defaultScrollX, defaultScrollY, defaultScrollZoom, defaultShowDomainLegend, defaultShowDomains, defaultSubFeatureRows, maxCellSize, minColWidth, minRowHeight, segmentFeatureTypes, segmentShades, } from './constants.js';
12
+ import TrackBlocks from './components/tracks/TrackBlocks.js';
13
+ import { defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawMsaLetters, defaultHeight, defaultHideGaps, defaultRowHeight, defaultScrollX, defaultScrollY, defaultScrollZoom, defaultShowDomainLegend, defaultShowDomains, defaultSubFeatureRows, labelReferenceFontSize, maxCellSize, minColWidth, minLetterColWidth, minLetterRowHeight, minRowHeight, segmentFeatureTypes, segmentShades, } from './constants.js';
15
14
  import { createPaletteMap } from './createPaletteMap.js';
16
15
  import { fetchTextWithProgress, isAbortError } from './fetchUtils.js';
17
16
  import { flatToTree } from './flatToTree.js';
@@ -26,6 +25,7 @@ import { parseAsn1 } from './parseAsn1.js';
26
25
  import { calculatePropertyConservation } from './propertyConservation.js';
27
26
  import { globalColToVisibleCol, visibleColToGlobalCol, visibleColToSeqPosForRow, visibleColsBefore, } from './rowCoordinateCalculations.js';
28
27
  import { buildSeqPosIndex } from './seqPosToGlobalCol.js';
28
+ import { maxBitsFor } from './sequenceLogo.js';
29
29
  import { stripDefault } from './stripDefault.js';
30
30
  import { computeRowInsertions, len, skipBlanks, transform } from './util.js';
31
31
  import { saveAs } from './vendor/fileSaver.js';
@@ -35,6 +35,17 @@ function parseTreeText(text) {
35
35
  }
36
36
  return parseNewick(text.startsWith('SEQ') ? parseEmfTree(text).tree : text);
37
37
  }
38
+ // Tracks that start hidden. The sequence logo answers a narrower question than
39
+ // conservation does and costs three times the vertical space, so it waits to be
40
+ // asked for.
41
+ const defaultOffTracks = new Set(['sequence-logo']);
42
+ // `turnedOffTracks` records the user's explicit choices only: an id is absent
43
+ // until they touch that track, and then its value is whether the track is OFF.
44
+ // Reading the default through this is what lets a track ship hidden without
45
+ // writing an entry into every snapshot and shared URL.
46
+ function trackIsOff(turnedOffTracks, id) {
47
+ return turnedOffTracks.get(id) ?? defaultOffTracks.has(id);
48
+ }
38
49
  /**
39
50
  * #stateModel MsaView
40
51
  *
@@ -165,7 +176,10 @@ function stateModelFactory() {
165
176
  showOnly: types.maybe(types.string),
166
177
  /**
167
178
  * #property
168
- * turned off tracks
179
+ * the user's explicit show/hide choice per track id, keyed by id with
180
+ * the value meaning "off". A track the user has never touched is absent
181
+ * and falls back to its own default (see `defaultOffTracks`), so a
182
+ * hidden-by-default track adds nothing to the shared URL.
169
183
  */
170
184
  turnedOffTracks: stripDefault(types.map(types.boolean), {}),
171
185
  /**
@@ -272,6 +286,13 @@ function stateModelFactory() {
272
286
  * #volatile
273
287
  */
274
288
  conservationTrackHeight: 40,
289
+ /**
290
+ * #volatile
291
+ * taller than the conservation track by default: the logo spends its
292
+ * height on stacked glyphs, and a 40px stack of four residues leaves each
293
+ * one too short to identify
294
+ */
295
+ sequenceLogoTrackHeight: 80,
275
296
  /**
276
297
  * #volatile
277
298
  */
@@ -877,26 +898,25 @@ function stateModelFactory() {
877
898
  * Returns values 0-1 where 1 = fully conserved, 0 = no conservation.
878
899
  */
879
900
  get conservation() {
880
- const { colStats, sequenceType } = this;
881
- const alphabetSize = sequenceType === 'amino' ? 20 : 4;
882
- const maxEntropy = Math.log2(alphabetSize);
901
+ const { colStats, alphabetMaxBits } = this;
883
902
  return Array.from({ length: colStats.numColumns }, (_, col) => {
884
903
  const total = colStats.total(col);
885
904
  const gapCount = colStats.gapCount(col);
886
- const nonGapTotal = total - gapCount;
887
- let score = 0;
888
- if (nonGapTotal > 0) {
889
- let entropy = 0;
890
- colStats.forEachResidue(col, (_slot, count) => {
891
- const freq = count / nonGapTotal;
892
- entropy -= freq * Math.log2(freq);
893
- });
894
- score =
895
- Math.max(0, 1 - entropy / maxEntropy) * (1 - gapCount / total);
896
- }
897
- return score;
905
+ return total > gapCount
906
+ ? Math.max(0, 1 - colStats.entropy(col) / alphabetMaxBits) *
907
+ (1 - gapCount / total)
908
+ : 0;
898
909
  });
899
910
  },
911
+ /**
912
+ * #getter
913
+ * The information content of a fully conserved column, in bits, which
914
+ * depends on the alphabet. Both the entropy ceiling `conservation`
915
+ * normalizes against and the y-axis ceiling of the sequence logo track.
916
+ */
917
+ get alphabetMaxBits() {
918
+ return maxBitsFor(this.sequenceType);
919
+ },
900
920
  /**
901
921
  * #getter
902
922
  * Per-column conservation of physicochemical property class (amino acids
@@ -1001,12 +1021,22 @@ function stateModelFactory() {
1001
1021
  */
1002
1022
  get blocksY() {
1003
1023
  return calculateBlocks({
1004
- viewportSize: self.height,
1024
+ viewportSize: this.visibleMsaHeight,
1005
1025
  viewportPos: -self.scrollY,
1006
1026
  blockSize: self.blockSize,
1007
1027
  mapSize: self.totalHeight,
1008
1028
  });
1009
1029
  },
1030
+ /**
1031
+ * #getter
1032
+ * height of the alignment viewport, px. The same subtraction as
1033
+ * msaAreaHeight, which is defined later in the views chain
1034
+ */
1035
+ get visibleMsaHeight() {
1036
+ return (self.height -
1037
+ self.headerHeight -
1038
+ (self.msaAreaWidth < self.totalWidth ? self.minimapHeight : 0));
1039
+ },
1010
1040
  }))
1011
1041
  .views(self => ({
1012
1042
  /**
@@ -1030,28 +1060,25 @@ function stateModelFactory() {
1030
1060
  /**
1031
1061
  * #getter
1032
1062
  * most-negative allowed scrollY, keeping the last row in view rather than
1033
- * letting the whole alignment scroll off the top. visible MSA height is
1034
- * computed inline here because msaAreaHeight is defined later in the chain.
1063
+ * letting the whole alignment scroll off the top.
1035
1064
  */
1036
1065
  get maxScrollY() {
1037
- const visibleHeight = self.height -
1038
- self.headerHeight -
1039
- (self.msaAreaWidth < self.totalWidth ? self.minimapHeight : 0);
1040
- return Math.min(-self.totalHeight + visibleHeight, 0);
1066
+ return Math.min(-self.totalHeight + self.visibleMsaHeight, 0);
1041
1067
  },
1042
1068
  /**
1043
1069
  * #getter
1044
1070
  */
1045
1071
  get showMsaLetters() {
1046
1072
  return (self.drawMsaLetters &&
1047
- self.rowHeight >= 5 &&
1073
+ self.rowHeight >= minLetterRowHeight &&
1074
+ self.colWidth >= minLetterColWidth &&
1048
1075
  self.colWidth > self.rowHeight / 2);
1049
1076
  },
1050
1077
  /**
1051
1078
  * #getter
1052
1079
  */
1053
1080
  get showTreeText() {
1054
- return self.drawLabels && self.rowHeight >= 5;
1081
+ return self.drawLabels && self.rowHeight >= minLetterRowHeight;
1055
1082
  },
1056
1083
  }))
1057
1084
  .actions(self => ({
@@ -1232,12 +1259,9 @@ function stateModelFactory() {
1232
1259
  * #action
1233
1260
  */
1234
1261
  toggleTrack(id) {
1235
- if (self.turnedOffTracks.has(id)) {
1236
- self.turnedOffTracks.delete(id);
1237
- }
1238
- else {
1239
- self.turnedOffTracks.set(id, true);
1240
- }
1262
+ // the stored value is "is off", so the current shown state is exactly
1263
+ // what the flipped entry should hold
1264
+ self.turnedOffTracks.set(id, !trackIsOff(self.turnedOffTracks, id));
1241
1265
  },
1242
1266
  /**
1243
1267
  * #action
@@ -1251,10 +1275,13 @@ function stateModelFactory() {
1251
1275
  * #getter
1252
1276
  */
1253
1277
  get labelWidthMap() {
1254
- const { showTreeText, leaves, treeMetadata, fontSize } = self;
1278
+ const { showTreeText, leaves, treeMetadata } = self;
1255
1279
  // gated on the same condition the renderer draws labels under, so the
1256
1280
  // gutter labelsWidth reserves and the labels actually drawn cannot
1257
- // disagree -- and so turning labels off hands their space to the tree
1281
+ // disagree -- and so turning labels off hands their space to the tree.
1282
+ // Measured at a fixed reference size and scaled by labelWidthScale:
1283
+ // re-measuring every leaf on every vertical-zoom frame cost ~200ms on a
1284
+ // 50k-leaf tree
1258
1285
  return showTreeText
1259
1286
  ? new Map(leaves.map(node => {
1260
1287
  const { name } = node.data;
@@ -1262,10 +1289,21 @@ function stateModelFactory() {
1262
1289
  // to the row name, and measuring '' would size the gutter (and
1263
1290
  // the label's click target) to nothing
1264
1291
  const displayName = treeMetadata[name]?.genome || name;
1265
- return [name, measureTextCanvas(displayName, fontSize)];
1292
+ return [
1293
+ name,
1294
+ measureTextCanvas(displayName, labelReferenceFontSize),
1295
+ ];
1266
1296
  }))
1267
1297
  : new Map();
1268
1298
  },
1299
+ /**
1300
+ * #getter
1301
+ * factor turning a labelWidthMap entry into its width at the current
1302
+ * font size
1303
+ */
1304
+ get labelWidthScale() {
1305
+ return self.fontSize / labelReferenceFontSize;
1306
+ },
1269
1307
  get labelsWidth() {
1270
1308
  // a loop, not Math.max(...widths.values()): spreading a map of every
1271
1309
  // leaf passes one argument per row, and the argument limit is somewhere
@@ -1278,7 +1316,7 @@ function stateModelFactory() {
1278
1316
  max = width;
1279
1317
  }
1280
1318
  }
1281
- return max;
1319
+ return max * this.labelWidthScale;
1282
1320
  },
1283
1321
  /**
1284
1322
  * #getter
@@ -1303,10 +1341,11 @@ function stateModelFactory() {
1303
1341
  .map(t => ({
1304
1342
  model: {
1305
1343
  ...t,
1344
+ kind: 'text',
1306
1345
  data: hideGapsEffective ? skipBlanks(blanks, t.data) : t.data,
1307
1346
  height: rowHeight,
1308
1347
  },
1309
- ReactComponent: TextTrack,
1348
+ ReactComponent: TrackBlocks,
1310
1349
  }));
1311
1350
  },
1312
1351
  /**
@@ -1314,34 +1353,41 @@ function stateModelFactory() {
1314
1353
  */
1315
1354
  get tracks() {
1316
1355
  const conservationTrack = {
1317
- model: {
1318
- id: 'conservation',
1319
- name: 'Conservation',
1320
- height: self.conservationTrackHeight,
1321
- barColor: 'gray',
1322
- },
1323
- ReactComponent: ConservationTrack,
1356
+ id: 'conservation',
1357
+ name: 'Conservation',
1358
+ kind: 'bar',
1359
+ height: self.conservationTrackHeight,
1360
+ barColor: 'gray',
1324
1361
  };
1325
1362
  const propertyConservationTrack = {
1326
- model: {
1327
- id: 'property-conservation',
1328
- name: 'Property conservation',
1329
- height: self.conservationTrackHeight,
1330
- barColor: '#6a51a3',
1331
- },
1332
- ReactComponent: ConservationTrack,
1363
+ id: 'property-conservation',
1364
+ name: 'Property conservation',
1365
+ kind: 'bar',
1366
+ height: self.conservationTrackHeight,
1367
+ barColor: '#6a51a3',
1368
+ };
1369
+ const sequenceLogoTrack = {
1370
+ id: 'sequence-logo',
1371
+ name: 'Sequence logo',
1372
+ kind: 'logo',
1373
+ height: self.sequenceLogoTrackHeight,
1333
1374
  };
1334
1375
  return [
1335
1376
  ...this.adapterTrackModels,
1336
- conservationTrack,
1337
- ...(self.sequenceType === 'amino' ? [propertyConservationTrack] : []),
1377
+ ...[
1378
+ conservationTrack,
1379
+ ...(self.sequenceType === 'amino'
1380
+ ? [propertyConservationTrack]
1381
+ : []),
1382
+ sequenceLogoTrack,
1383
+ ].map(model => ({ model, ReactComponent: TrackBlocks })),
1338
1384
  ];
1339
1385
  },
1340
1386
  /**
1341
1387
  * #getter
1342
1388
  */
1343
1389
  get turnedOnTracks() {
1344
- return this.tracks.filter(f => !self.turnedOffTracks.has(f.model.id));
1390
+ return this.tracks.filter(f => !trackIsOff(self.turnedOffTracks, f.model.id));
1345
1391
  },
1346
1392
  /**
1347
1393
  * #getter
@@ -1575,9 +1621,9 @@ function stateModelFactory() {
1575
1621
  * needs to keep residues readable on top of the boxes.
1576
1622
  */
1577
1623
  get domainBands() {
1624
+ const { blanks } = self;
1578
1625
  const bands = new Map();
1579
1626
  for (const [name, annotations] of Object.entries(self.annotationsByRow)) {
1580
- const { blanks } = self;
1581
1627
  const rowBands = annotations
1582
1628
  .toSorted((a, b) => len(b) - len(a))
1583
1629
  .map(annotation => {
@@ -1687,8 +1733,11 @@ function stateModelFactory() {
1687
1733
  return undefined;
1688
1734
  }
1689
1735
  const { colStats } = self;
1736
+ if (mouseCol >= colStats.numColumns) {
1737
+ return undefined;
1738
+ }
1690
1739
  const total = colStats.total(mouseCol);
1691
- if (mouseCol >= colStats.numColumns || !total) {
1740
+ if (!total) {
1692
1741
  return undefined;
1693
1742
  }
1694
1743
  const gaps = colStats.gapCount(mouseCol);
@@ -1732,6 +1781,12 @@ function stateModelFactory() {
1732
1781
  setConservationTrackHeight(arg) {
1733
1782
  self.conservationTrackHeight = arg;
1734
1783
  },
1784
+ /**
1785
+ * #action
1786
+ */
1787
+ setSequenceLogoTrackHeight(arg) {
1788
+ self.sequenceLogoTrackHeight = arg;
1789
+ },
1735
1790
  /**
1736
1791
  * #action
1737
1792
  */