react-msaview 5.9.0 → 6.0.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.
Files changed (196) hide show
  1. package/bundle/index.js +99 -99
  2. package/bundle/index.js.map +4 -4
  3. package/dist/clustalX.js +22 -22
  4. package/dist/clustalX.js.map +1 -1
  5. package/dist/components/ConservationTrack.js +21 -16
  6. package/dist/components/ConservationTrack.js.map +1 -1
  7. package/dist/components/CopyButton.d.ts +9 -0
  8. package/dist/components/CopyButton.js +23 -0
  9. package/dist/components/CopyButton.js.map +1 -0
  10. package/dist/components/Loading.js +2 -2
  11. package/dist/components/Loading.js.map +1 -1
  12. package/dist/components/MSAView.js +18 -24
  13. package/dist/components/MSAView.js.map +1 -1
  14. package/dist/components/ResizeHandles.js +24 -40
  15. package/dist/components/ResizeHandles.js.map +1 -1
  16. package/dist/components/ScrollbarThumb.d.ts +14 -0
  17. package/dist/components/ScrollbarThumb.js +28 -0
  18. package/dist/components/ScrollbarThumb.js.map +1 -0
  19. package/dist/components/SequenceTextArea.js +3 -10
  20. package/dist/components/SequenceTextArea.js.map +1 -1
  21. package/dist/components/TextTrack.js +26 -21
  22. package/dist/components/TextTrack.js.map +1 -1
  23. package/dist/components/VerticalScrollbar.js +11 -25
  24. package/dist/components/VerticalScrollbar.js.map +1 -1
  25. package/dist/components/dialogs/FeatureDialog.js +3 -3
  26. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  27. package/dist/components/dialogs/{UserProvidedDomainsDialog.d.ts → InterProScanFileDialog.d.ts} +2 -2
  28. package/dist/components/dialogs/{UserProvidedDomainsDialog.js → InterProScanFileDialog.js} +6 -10
  29. package/dist/components/dialogs/InterProScanFileDialog.js.map +1 -0
  30. package/dist/components/dialogs/TrackInfoDialog.d.ts +2 -4
  31. package/dist/components/dialogs/TrackInfoDialog.js +8 -14
  32. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
  33. package/dist/components/header/FileMenu.js +3 -3
  34. package/dist/components/header/FileMenu.js.map +1 -1
  35. package/dist/components/header/GappynessSlider.js +1 -1
  36. package/dist/components/header/GappynessSlider.js.map +1 -1
  37. package/dist/components/header/{getDomainsMenu.d.ts → getAnnotationsMenu.d.ts} +1 -1
  38. package/dist/components/header/{getDomainsMenu.js → getAnnotationsMenu.js} +10 -9
  39. package/dist/components/header/getAnnotationsMenu.js.map +1 -0
  40. package/dist/components/header/settingsMenuItems.js +39 -72
  41. package/dist/components/header/settingsMenuItems.js.map +1 -1
  42. package/dist/components/import/ImportForm.js +1 -1
  43. package/dist/components/import/ImportForm.js.map +1 -1
  44. package/dist/components/import/ImportFormExamples.js +66 -57
  45. package/dist/components/import/ImportFormExamples.js.map +1 -1
  46. package/dist/components/minimap/Minimap.js +11 -22
  47. package/dist/components/minimap/Minimap.js.map +1 -1
  48. package/dist/components/minimap/MinimapSVG.d.ts +2 -1
  49. package/dist/components/minimap/MinimapSVG.js +3 -4
  50. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  51. package/dist/components/minimap/minimapLayout.d.ts +10 -1
  52. package/dist/components/minimap/minimapLayout.js +15 -10
  53. package/dist/components/minimap/minimapLayout.js.map +1 -1
  54. package/dist/components/msa/{DomainLegend.d.ts → AnnotationLegend.d.ts} +2 -2
  55. package/dist/components/msa/{DomainLegend.js → AnnotationLegend.js} +4 -4
  56. package/dist/components/msa/AnnotationLegend.js.map +1 -0
  57. package/dist/components/msa/MSACanvas.js +2 -2
  58. package/dist/components/msa/MSACanvas.js.map +1 -1
  59. package/dist/components/msa/MSACanvasBlock.js +19 -16
  60. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  61. package/dist/components/msa/MSAMouseoverCanvas.js +14 -8
  62. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  63. package/dist/components/msa/MSAPanel.js +2 -2
  64. package/dist/components/msa/MSAPanel.js.map +1 -1
  65. package/dist/components/msa/domainBandCursor.d.ts +17 -0
  66. package/dist/components/msa/domainBandCursor.js +39 -0
  67. package/dist/components/msa/domainBandCursor.js.map +1 -0
  68. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +9 -6
  69. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  70. package/dist/components/msa/renderMSABlock.js +14 -20
  71. package/dist/components/msa/renderMSABlock.js.map +1 -1
  72. package/dist/components/msa/renderMSAMouseover.js +32 -33
  73. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  74. package/dist/components/overlayColors.d.ts +2 -0
  75. package/dist/components/overlayColors.js +5 -0
  76. package/dist/components/overlayColors.js.map +1 -1
  77. package/dist/components/tree/TreeCanvas.js +29 -24
  78. package/dist/components/tree/TreeCanvas.js.map +1 -1
  79. package/dist/components/tree/TreeCanvasBlock.js +19 -12
  80. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  81. package/dist/components/tree/TreeRuler.js +5 -2
  82. package/dist/components/tree/TreeRuler.js.map +1 -1
  83. package/dist/components/tree/renderTreeCanvas.d.ts +0 -38
  84. package/dist/components/tree/renderTreeCanvas.js +17 -17
  85. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  86. package/dist/ggplotPalettes.d.ts +0 -1
  87. package/dist/ggplotPalettes.js +0 -3
  88. package/dist/ggplotPalettes.js.map +1 -1
  89. package/dist/hierarchy.d.ts +2 -6
  90. package/dist/hierarchy.js +4 -6
  91. package/dist/hierarchy.js.map +1 -1
  92. package/dist/index.d.ts +2 -2
  93. package/dist/launchInterProScan.d.ts +0 -23
  94. package/dist/launchInterProScan.js +2 -5
  95. package/dist/launchInterProScan.js.map +1 -1
  96. package/dist/measureTextCanvas.js +21 -6
  97. package/dist/measureTextCanvas.js.map +1 -1
  98. package/dist/model.d.ts +50 -100
  99. package/dist/model.js +209 -219
  100. package/dist/model.js.map +1 -1
  101. package/dist/neighborJoining.js +12 -19
  102. package/dist/neighborJoining.js.map +1 -1
  103. package/dist/parseAsn1.js +32 -43
  104. package/dist/parseAsn1.js.map +1 -1
  105. package/dist/renderToSvg.js +5 -2
  106. package/dist/renderToSvg.js.map +1 -1
  107. package/dist/rowCoordinateCalculations.d.ts +13 -17
  108. package/dist/rowCoordinateCalculations.js +45 -39
  109. package/dist/rowCoordinateCalculations.js.map +1 -1
  110. package/dist/seqPosToGlobalCol.d.ts +6 -19
  111. package/dist/seqPosToGlobalCol.js +6 -33
  112. package/dist/seqPosToGlobalCol.js.map +1 -1
  113. package/dist/types.d.ts +4 -11
  114. package/dist/useCanvasAutorun.d.ts +23 -1
  115. package/dist/useCanvasAutorun.js +20 -8
  116. package/dist/useCanvasAutorun.js.map +1 -1
  117. package/dist/version.d.ts +1 -1
  118. package/dist/version.js +1 -1
  119. package/package.json +5 -5
  120. package/src/clustalX.ts +22 -22
  121. package/src/colSpace.test.ts +27 -0
  122. package/src/components/ConservationTrack.tsx +7 -5
  123. package/src/components/CopyButton.tsx +44 -0
  124. package/src/components/Loading.tsx +10 -1
  125. package/src/components/MSAView.tsx +25 -32
  126. package/src/components/ResizeHandles.tsx +60 -69
  127. package/src/components/ScrollbarThumb.tsx +48 -0
  128. package/src/components/SequenceTextArea.tsx +3 -16
  129. package/src/components/TextTrack.tsx +7 -12
  130. package/src/components/VerticalScrollbar.tsx +19 -33
  131. package/src/components/dialogs/FeatureDialog.tsx +3 -4
  132. package/src/components/dialogs/{UserProvidedDomainsDialog.tsx → InterProScanFileDialog.tsx} +6 -14
  133. package/src/components/dialogs/TrackInfoDialog.tsx +18 -20
  134. package/src/components/header/FileMenu.tsx +3 -3
  135. package/src/components/header/GappynessSlider.tsx +4 -1
  136. package/src/components/header/{getDomainsMenu.ts → getAnnotationsMenu.ts} +10 -9
  137. package/src/components/header/settingsMenuItems.ts +40 -72
  138. package/src/components/import/ImportForm.tsx +1 -1
  139. package/src/components/import/ImportFormExamples.tsx +82 -133
  140. package/src/components/minimap/Minimap.tsx +23 -31
  141. package/src/components/minimap/MinimapSVG.tsx +32 -31
  142. package/src/components/minimap/minimapLayout.test.ts +60 -0
  143. package/src/components/minimap/minimapLayout.ts +15 -10
  144. package/src/components/msa/{DomainLegend.tsx → AnnotationLegend.tsx} +7 -3
  145. package/src/components/msa/MSACanvas.tsx +3 -10
  146. package/src/components/msa/MSACanvasBlock.tsx +16 -21
  147. package/src/components/msa/MSAMouseoverCanvas.tsx +11 -9
  148. package/src/components/msa/MSAPanel.tsx +2 -2
  149. package/src/components/msa/domainBandCursor.ts +41 -0
  150. package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +51 -0
  151. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +13 -8
  152. package/src/components/msa/renderMSABlock.ts +19 -23
  153. package/src/components/msa/renderMSAMouseover.ts +38 -35
  154. package/src/components/overlayColors.ts +6 -0
  155. package/src/components/tree/TreeCanvas.tsx +6 -4
  156. package/src/components/tree/TreeCanvasBlock.tsx +11 -12
  157. package/src/components/tree/TreeRuler.tsx +7 -2
  158. package/src/components/tree/renderTreeCanvas.test.ts +15 -7
  159. package/src/components/tree/renderTreeCanvas.ts +20 -17
  160. package/src/domainBands.test.ts +24 -0
  161. package/src/domainLetterColor.test.ts +80 -0
  162. package/src/ggplotPalettes.ts +0 -4
  163. package/src/hierarchy.test.ts +7 -7
  164. package/src/hierarchy.ts +6 -9
  165. package/src/index.ts +4 -1
  166. package/src/largeTree.test.ts +23 -2
  167. package/src/launchInterProScan.ts +3 -30
  168. package/src/loadErrorDisplay.test.tsx +102 -0
  169. package/src/measureTextCanvas.ts +22 -6
  170. package/src/model.ts +233 -244
  171. package/src/modelFilehandleLoaders.test.ts +137 -0
  172. package/src/neighborJoining.test.ts +12 -0
  173. package/src/neighborJoining.ts +12 -20
  174. package/src/parseAsn1.ts +32 -40
  175. package/src/{renderDomainLegendSvg.test.tsx → renderDomainsSvg.test.tsx} +44 -20
  176. package/src/renderMinimapSvg.test.tsx +109 -0
  177. package/src/renderToSvg.tsx +5 -2
  178. package/src/rowCoordinateCalculations.ts +48 -44
  179. package/src/seqPosToGlobalCol.test.ts +39 -51
  180. package/src/seqPosToGlobalCol.ts +6 -41
  181. package/src/types.ts +10 -15
  182. package/src/useCanvasAutorun.test.tsx +98 -0
  183. package/src/useCanvasAutorun.ts +30 -11
  184. package/src/version.ts +1 -1
  185. package/src/viewChrome.test.tsx +134 -0
  186. package/dist/components/dialogs/DomainDialog.d.ts +0 -6
  187. package/dist/components/dialogs/DomainDialog.js +0 -19
  188. package/dist/components/dialogs/DomainDialog.js.map +0 -1
  189. package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +0 -1
  190. package/dist/components/header/SettingsMenu.d.ts +0 -6
  191. package/dist/components/header/SettingsMenu.js +0 -40
  192. package/dist/components/header/SettingsMenu.js.map +0 -1
  193. package/dist/components/header/getDomainsMenu.js.map +0 -1
  194. package/dist/components/msa/DomainLegend.js.map +0 -1
  195. package/src/components/dialogs/DomainDialog.tsx +0 -45
  196. package/src/components/header/SettingsMenu.tsx +0 -57
@@ -16,6 +16,25 @@ import { isBlank } from './util.js';
16
16
  * This counts only non-gap characters ('-' and '.' are gaps).
17
17
  * Each row can have different seqPos values for the same globalCol due to gaps.
18
18
  */
19
+ /**
20
+ * Index of the first element in `[0, len)` for which `pred` holds, over a range
21
+ * where `pred` is false up to some point and true from there on. Both
22
+ * conversions below are that shape, since `blanks` is sorted ascending.
23
+ */
24
+ function partitionPoint(len, pred) {
25
+ let left = 0;
26
+ let right = len;
27
+ while (left < right) {
28
+ const mid = (left + right) >>> 1;
29
+ if (pred(mid)) {
30
+ right = mid;
31
+ }
32
+ else {
33
+ left = mid + 1;
34
+ }
35
+ }
36
+ return left;
37
+ }
19
38
  /**
20
39
  * Convert a visible column index to a global column index.
21
40
  * This is used when translating mouse/screen coordinates to MSA coordinates.
@@ -25,26 +44,10 @@ import { isBlank } from './util.js';
25
44
  * @returns The corresponding global column index in the full MSA
26
45
  */
27
46
  export function visibleColToGlobalCol(blanks, visibleCol) {
28
- let currentVisibleCol = 0;
29
- let blankArrayIndex = 0;
30
- let globalCol = 0;
31
- const blanksLen = blanks.length;
32
- // Skip any leading blank columns (blanks at the very beginning)
33
- while (blankArrayIndex < blanksLen && blanks[blankArrayIndex] === globalCol) {
34
- blankArrayIndex++;
35
- globalCol++;
36
- }
37
- while (currentVisibleCol < visibleCol) {
38
- currentVisibleCol++;
39
- globalCol++;
40
- // Skip any blank columns after incrementing
41
- while (blankArrayIndex < blanksLen &&
42
- blanks[blankArrayIndex] === globalCol) {
43
- blankArrayIndex++;
44
- globalCol++;
45
- }
46
- }
47
- return globalCol;
47
+ // `blanks[i] - i` counts the visible columns before the i-th blank, so the
48
+ // first blank with more than visibleCol of them in front of it is the first
49
+ // one sitting past this column. Its index is how many blanks shift it right.
50
+ return (visibleCol + partitionPoint(blanks.length, i => blanks[i] - i > visibleCol));
48
51
  }
49
52
  /**
50
53
  * Convert a global column index to a visible column index.
@@ -55,25 +58,28 @@ export function visibleColToGlobalCol(blanks, visibleCol) {
55
58
  * @returns The visible column index, or undefined if the column is hidden
56
59
  */
57
60
  export function globalColToVisibleCol(blanks, globalCol) {
58
- // Check if this column is hidden
59
- // Use binary search since blanks is sorted
60
- let left = 0;
61
- let right = blanks.length - 1;
62
- while (left <= right) {
63
- const mid = Math.floor((left + right) / 2);
64
- if (blanks[mid] === globalCol) {
65
- return undefined; // Column is hidden
66
- }
67
- if (blanks[mid] < globalCol) {
68
- left = mid + 1;
69
- }
70
- else {
71
- right = mid - 1;
72
- }
73
- }
74
- // Count blanks before this column (left is now the insertion point)
75
- const blanksBefore = left;
76
- return globalCol - blanksBefore;
61
+ const blanksBefore = countBlanksBefore(blanks, globalCol);
62
+ return blanks[blanksBefore] === globalCol
63
+ ? undefined // Column is hidden
64
+ : globalCol - blanksBefore;
65
+ }
66
+ function countBlanksBefore(blanks, globalCol) {
67
+ return partitionPoint(blanks.length, i => blanks[i] >= globalCol);
68
+ }
69
+ /**
70
+ * How many visible columns come before a global column. For a visible column
71
+ * that is its own visible index; for a hidden one it is the index of the next
72
+ * visible column, i.e. where the hidden column collapses to.
73
+ *
74
+ * That total answer is what spans want: resolving both ends of a span through
75
+ * globalColToVisibleCol instead loses the whole span whenever either end lands
76
+ * in a hidden column.
77
+ *
78
+ * @param blanks - Sorted array of global column indices that are hidden
79
+ * @param globalCol - The global column index in the full MSA
80
+ */
81
+ export function visibleColsBefore(blanks, globalCol) {
82
+ return globalCol - countBlanksBefore(blanks, globalCol);
77
83
  }
78
84
  /**
79
85
  * Convert a visible column to a row-specific sequence position.
@@ -1 +1 @@
1
- {"version":3,"file":"rowCoordinateCalculations.js","sourceRoot":"","sources":["../src/rowCoordinateCalculations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rowCoordinateCalculations.js","sourceRoot":"","sources":["../src/rowCoordinateCalculations.ts"],"names":[],"mappings":"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"}
@@ -1,26 +1,13 @@
1
1
  /**
2
- * Convert a sequence position (ungapped, 0-based) to a global column index.
3
- * This finds the global column that contains the Nth non-gap character.
2
+ * The global column of every ungapped sequence position in a row, i.e.
3
+ * index[seqPos] is the column holding the seqPos-th non-gap character.
4
4
  *
5
- * @param row - The row's sequence string (including gaps)
6
- * @param seqPos - The sequence position (0-based count of non-gap characters)
7
- * @returns The global column index containing the seqPos-th non-gap character
5
+ * A row is indexed once and then answers seqPos -> column by lookup, which is
6
+ * what makes the domain overlay affordable: it resolves one position per feature
7
+ * per row on every redraw, and scanning the row for each would be quadratic.
8
8
  *
9
9
  * @example
10
10
  * // Row: "A-TG-C" (A at 0, T at 2, G at 3, C at 5)
11
- * seqPosToGlobalCol({ row: "A-TG-C", seqPos: 0 }) // → 0 (A)
12
- * seqPosToGlobalCol({ row: "A-TG-C", seqPos: 1 }) // → 2 (T)
13
- * seqPosToGlobalCol({ row: "A-TG-C", seqPos: 2 }) // → 3 (G)
14
- * seqPosToGlobalCol({ row: "A-TG-C", seqPos: 3 }) // → 5 (C)
15
- */
16
- /**
17
- * The global column of every ungapped sequence position in a row, i.e.
18
- * index[seqPos] is the column holding the seqPos-th non-gap character. Amortizes
19
- * repeated seqPosToGlobalCol lookups (the domain overlay does one per feature
20
- * per row) into a single pass per row.
11
+ * buildSeqPosIndex('A-TG-C') // → [0, 2, 3, 5]
21
12
  */
22
13
  export declare function buildSeqPosIndex(row: string): Int32Array<ArrayBuffer>;
23
- export declare function seqPosToGlobalCol({ row, seqPos, }: {
24
- row: string;
25
- seqPos: number;
26
- }): number;
@@ -1,24 +1,14 @@
1
- import { isBlank } from './util.js';
2
1
  /**
3
- * Convert a sequence position (ungapped, 0-based) to a global column index.
4
- * This finds the global column that contains the Nth non-gap character.
2
+ * The global column of every ungapped sequence position in a row, i.e.
3
+ * index[seqPos] is the column holding the seqPos-th non-gap character.
5
4
  *
6
- * @param row - The row's sequence string (including gaps)
7
- * @param seqPos - The sequence position (0-based count of non-gap characters)
8
- * @returns The global column index containing the seqPos-th non-gap character
5
+ * A row is indexed once and then answers seqPos -> column by lookup, which is
6
+ * what makes the domain overlay affordable: it resolves one position per feature
7
+ * per row on every redraw, and scanning the row for each would be quadratic.
9
8
  *
10
9
  * @example
11
10
  * // Row: "A-TG-C" (A at 0, T at 2, G at 3, C at 5)
12
- * seqPosToGlobalCol({ row: "A-TG-C", seqPos: 0 }) // → 0 (A)
13
- * seqPosToGlobalCol({ row: "A-TG-C", seqPos: 1 }) // → 2 (T)
14
- * seqPosToGlobalCol({ row: "A-TG-C", seqPos: 2 }) // → 3 (G)
15
- * seqPosToGlobalCol({ row: "A-TG-C", seqPos: 3 }) // → 5 (C)
16
- */
17
- /**
18
- * The global column of every ungapped sequence position in a row, i.e.
19
- * index[seqPos] is the column holding the seqPos-th non-gap character. Amortizes
20
- * repeated seqPosToGlobalCol lookups (the domain overlay does one per feature
21
- * per row) into a single pass per row.
11
+ * buildSeqPosIndex('A-TG-C') // → [0, 2, 3, 5]
22
12
  */
23
13
  export function buildSeqPosIndex(row) {
24
14
  const index = new Int32Array(row.length);
@@ -31,21 +21,4 @@ export function buildSeqPosIndex(row) {
31
21
  }
32
22
  return index.subarray(0, n);
33
23
  }
34
- export function seqPosToGlobalCol({ row, seqPos, }) {
35
- let nonGapCount = 0;
36
- let globalCol = 0;
37
- // Find the seqPos-th non-gap character
38
- while (globalCol < row.length) {
39
- if (!isBlank(row[globalCol])) {
40
- if (nonGapCount === seqPos) {
41
- return globalCol;
42
- }
43
- nonGapCount++;
44
- }
45
- globalCol++;
46
- }
47
- // If seqPos is 0 and we didn't find any non-gap character, return 0
48
- // Otherwise return globalCol (which is row.length at this point)
49
- return seqPos === 0 ? 0 : globalCol;
50
- }
51
24
  //# sourceMappingURL=seqPosToGlobalCol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"seqPosToGlobalCol.js","sourceRoot":"","sources":["../src/seqPosToGlobalCol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC;;;;;;;;;;;;;;GAcG;AACH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACxC,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC1C,oEAAoE;QACpE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC7C,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAA;QAClB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAChC,GAAG,EACH,MAAM,GAIP;IACC,IAAI,WAAW,GAAG,CAAC,CAAA;IACnB,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,uCAAuC;IACvC,OAAO,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YAC7B,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;gBAC3B,OAAO,SAAS,CAAA;YAClB,CAAC;YACD,WAAW,EAAE,CAAA;QACf,CAAC;QACD,SAAS,EAAE,CAAA;IACb,CAAC;IACD,oEAAoE;IACpE,iEAAiE;IACjE,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACrC,CAAC"}
1
+ {"version":3,"file":"seqPosToGlobalCol.js","sourceRoot":"","sources":["../src/seqPosToGlobalCol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACxC,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC1C,oEAAoE;QACpE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC7C,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAA;QAClB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAC7B,CAAC"}
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Annotation } from 'msa-parsers';
1
2
  export interface Accession {
2
3
  accession: string;
3
4
  name: string;
@@ -20,18 +21,10 @@ export interface BasicTrack {
20
21
  ReactComponent: React.FC<any>;
21
22
  model: TextTrackModel & BarTrackModel;
22
23
  }
23
- export interface TidyDomainAnnotation {
24
- id: string;
25
- name: string;
26
- accession: string;
27
- description: string;
28
- featureType?: string;
29
- start: number;
30
- end: number;
31
- strand?: number;
32
- }
24
+ export type { Annotation };
25
+ export type TidyDomainAnnotation = Annotation;
33
26
  export interface DomainBand {
34
- annotation: TidyDomainAnnotation;
27
+ annotation: Annotation;
35
28
  startCol: number;
36
29
  endCol: number;
37
30
  stackIndex: number;
@@ -1,2 +1,24 @@
1
1
  import type React from 'react';
2
- export declare function useCanvasAutorun(draw: (ctx: CanvasRenderingContext2D) => void, deps: React.DependencyList): React.RefObject<HTMLCanvasElement | null>;
2
+ /**
3
+ * Wires a canvas ref to a mobx autorun: grabs the 2d context once the canvas
4
+ * mounts and re-runs `draw` whenever any observable it reads changes. Collapses
5
+ * the getContext/null-guard/autorun-dispose boilerplate repeated by every canvas
6
+ * block host.
7
+ *
8
+ * `width`/`height` are the canvas *backing store* size the caller renders onto
9
+ * the element (logical size times the high-res scale factor), and they are load
10
+ * bearing rather than informational. Assigning either resets the canvas bitmap,
11
+ * and React assigns them on every commit where they changed -- which lands
12
+ * *after* the mobx reaction that already redrew at the old size. Without a redraw
13
+ * on the far side of that commit the canvas is left blank: it is what blanked the
14
+ * tree on a tree-area resize and the consensus tracks on a vertical zoom.
15
+ *
16
+ * `deps` covers everything else the effect should re-subscribe on, matching a
17
+ * normal useEffect dep array.
18
+ */
19
+ export declare function useCanvasAutorun({ draw, width, height, deps, }: {
20
+ draw: (ctx: CanvasRenderingContext2D) => void;
21
+ width: number;
22
+ height: number;
23
+ deps: React.DependencyList;
24
+ }): React.RefObject<HTMLCanvasElement | null>;
@@ -1,14 +1,26 @@
1
1
  import { useEffect, useRef } from 'react';
2
2
  import { autorun } from 'mobx';
3
- // Wires a canvas ref to a mobx autorun: grabs the 2d context once the canvas
4
- // mounts and re-runs `draw` whenever any observable it reads changes. Collapses
5
- // the getContext/null-guard/autorun-dispose boilerplate repeated by every
6
- // canvas block host. `deps` controls when the effect (and thus the context
7
- // lookup) is torn down and re-created, matching a normal useEffect dep array.
8
- export function useCanvasAutorun(draw, deps) {
3
+ /**
4
+ * Wires a canvas ref to a mobx autorun: grabs the 2d context once the canvas
5
+ * mounts and re-runs `draw` whenever any observable it reads changes. Collapses
6
+ * the getContext/null-guard/autorun-dispose boilerplate repeated by every canvas
7
+ * block host.
8
+ *
9
+ * `width`/`height` are the canvas *backing store* size the caller renders onto
10
+ * the element (logical size times the high-res scale factor), and they are load
11
+ * bearing rather than informational. Assigning either resets the canvas bitmap,
12
+ * and React assigns them on every commit where they changed -- which lands
13
+ * *after* the mobx reaction that already redrew at the old size. Without a redraw
14
+ * on the far side of that commit the canvas is left blank: it is what blanked the
15
+ * tree on a tree-area resize and the consensus tracks on a vertical zoom.
16
+ *
17
+ * `deps` covers everything else the effect should re-subscribe on, matching a
18
+ * normal useEffect dep array.
19
+ */
20
+ export function useCanvasAutorun({ draw, width, height, deps, }) {
9
21
  const ref = useRef(null);
10
22
  // the autorun tracks its own observable reads, so `draw` is intentionally not
11
- // a dependency; `deps` alone controls when the effect re-subscribes
23
+ // a dependency; `deps` plus the canvas size control when it re-subscribes
12
24
  useEffect(() => {
13
25
  const ctx = ref.current?.getContext('2d');
14
26
  return ctx
@@ -17,7 +29,7 @@ export function useCanvasAutorun(draw, deps) {
17
29
  })
18
30
  : undefined;
19
31
  // eslint-disable-next-line react-hooks/exhaustive-deps
20
- }, deps);
32
+ }, [...deps, width, height]);
21
33
  return ref;
22
34
  }
23
35
  //# sourceMappingURL=useCanvasAutorun.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCanvasAutorun.js","sourceRoot":"","sources":["../src/useCanvasAutorun.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEzC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAI9B,6EAA6E;AAC7E,gFAAgF;AAChF,0EAA0E;AAC1E,2EAA2E;AAC3E,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAC9B,IAA6C,EAC7C,IAA0B;IAE1B,MAAM,GAAG,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IAC3C,8EAA8E;IAC9E,oEAAoE;IACpE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,CAAA;QACzC,OAAO,GAAG;YACR,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;gBACX,IAAI,CAAC,GAAG,CAAC,CAAA;YACX,CAAC,CAAC;YACJ,CAAC,CAAC,SAAS,CAAA;QACb,uDAAuD;IACzD,CAAC,EAAE,IAAI,CAAC,CAAA;IACR,OAAO,GAAG,CAAA;AACZ,CAAC"}
1
+ {"version":3,"file":"useCanvasAutorun.js","sourceRoot":"","sources":["../src/useCanvasAutorun.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEzC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAI9B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,IAAI,EACJ,KAAK,EACL,MAAM,EACN,IAAI,GAML;IACC,MAAM,GAAG,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IAC3C,8EAA8E;IAC9E,0EAA0E;IAC1E,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,CAAA;QACzC,OAAO,GAAG;YACR,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;gBACX,IAAI,CAAC,GAAG,CAAC,CAAA;YACX,CAAC,CAAC;YACJ,CAAC,CAAC,SAAS,CAAA;QACb,uDAAuD;IACzD,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IAC5B,OAAO,GAAG,CAAA;AACZ,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "5.9.0";
1
+ export declare const version = "6.0.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '5.9.0';
1
+ export const version = '6.0.0';
2
2
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-msaview",
3
3
  "author": "Colin",
4
- "version": "5.9.0",
4
+ "version": "6.0.0",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -43,8 +43,8 @@
43
43
  "@gmod/newick": "^1.0.0",
44
44
  "colord": "^2.9.3",
45
45
  "flatbush": "^4.6.2",
46
- "@jbrowse/svgcanvas": "5.9.0",
47
- "msa-parsers": "5.9.0"
46
+ "@jbrowse/svgcanvas": "6.0.0",
47
+ "msa-parsers": "6.0.0"
48
48
  },
49
49
  "scripts": {
50
50
  "clean": "rimraf dist bundle",
@@ -53,8 +53,8 @@
53
53
  "build:esm": "tsc --project tsconfig.build.json",
54
54
  "build:umd": "node esbuild.mjs",
55
55
  "build": "pnpm clean && pnpm build:esm && pnpm build:umd",
56
- "statedocs": "rm -rf apidocs && mkdir apidocs && node --experimental-strip-types docgen/generateStateModelDocs.ts",
57
- "poststatedocs": "pnpm format",
56
+ "statedocs": "node --experimental-strip-types docgen/generateStateModelDocs.ts",
57
+ "poststatedocs": "oxfmt apidocs",
58
58
  "test": "vitest"
59
59
  },
60
60
  "main": "dist/index.js"
package/src/clustalX.ts CHANGED
@@ -20,34 +20,34 @@ const CYAN = 'rgb(26, 179, 179)'
20
20
  */
21
21
  export function clustalXColumnColors(counts: ColumnCounts, col: number) {
22
22
  const total = counts.total(col)
23
- const n = (letter: string) => counts.count(col, letter)
24
- const W = n('W')
25
- const L = n('L')
26
- const V = n('V')
27
- const I = n('I')
28
- const M = n('M')
29
- const A = n('A')
30
- const F = n('F')
31
- const C = n('C')
32
- const H = n('H')
33
- const P = n('P')
34
- const R = n('R')
35
- const K = n('K')
36
- const Q = n('Q')
37
- const E = n('E')
38
- const D = n('D')
39
- const T = n('T')
40
- const S = n('S')
41
- const G = n('G')
42
- const Y = n('Y')
43
- const N = n('N')
23
+ const count = (letter: string) => counts.count(col, letter)
24
+ const W = count('W')
25
+ const L = count('L')
26
+ const V = count('V')
27
+ const I = count('I')
28
+ const M = count('M')
29
+ const A = count('A')
30
+ const F = count('F')
31
+ const C = count('C')
32
+ const H = count('H')
33
+ const P = count('P')
34
+ const R = count('R')
35
+ const K = count('K')
36
+ const Q = count('Q')
37
+ const E = count('E')
38
+ const D = count('D')
39
+ const T = count('T')
40
+ const S = count('S')
41
+ const G = count('G')
42
+ const Y = count('Y')
43
+ const N = count('N')
44
44
 
45
45
  const hydrophobic = W + L + V + I + M + A + F + C + H + P + Y
46
46
  const KR = K + R
47
47
  const QE = Q + E
48
48
  const ED = E + D
49
49
  const TS = T + S
50
- const p = (n: number) => n / total
50
+ const p = (subtotal: number) => subtotal / total
51
51
 
52
52
  const colors: Record<string, string> = {}
53
53
 
@@ -38,3 +38,30 @@ test('the last column agrees with the column statistics', () => {
38
38
  expect(model.mouseOverColumnStats?.col).toBe(9)
39
39
  expect(model.visibleColToRowLetter('b', 9)).toBe('C')
40
40
  })
41
+
42
+ // enough rows to force the vertical scrollbar, which eats into the width the
43
+ // alignment canvas actually gets
44
+ function makeTallModel() {
45
+ const names = Array.from({ length: 60 }, (_, i) => `seq${i}`)
46
+ const model = MSAModelF().create({
47
+ id: 'col-fit-test',
48
+ type: 'MsaView',
49
+ msaFormat: 'fasta',
50
+ height: 300,
51
+ data: { msa: names.map(n => `>${n}\n${'ACGT'.repeat(50)}`).join('\n') },
52
+ })
53
+ model.setWidth(800)
54
+ return model
55
+ }
56
+
57
+ test('fit horizontally leaves every column on screen', () => {
58
+ const model = makeTallModel()
59
+ expect(model.showVerticalScrollbar).toBe(true)
60
+
61
+ model.fitHorizontally()
62
+ // the canvas is the msa area minus the scrollbar; fitting to the msa area
63
+ // alone pushed the last columns past the right edge, with no minimap to
64
+ // show that anything was cut off
65
+ expect(model.totalWidth).toBeLessThanOrEqual(model.msaCanvasWidth)
66
+ expect(model.showHorizontalScrollbar).toBe(false)
67
+ })
@@ -25,9 +25,9 @@ const ConservationBlock = observer(function ({
25
25
  }) {
26
26
  const { blockSize, scrollX, highResScaleFactor } = model
27
27
 
28
- const ref = useCanvasAutorun(
29
- ctx => {
30
- const { blockSize, colWidth, highResScaleFactor } = model
28
+ const ref = useCanvasAutorun({
29
+ draw: ctx => {
30
+ const { colWidth } = model
31
31
  ctx.resetTransform()
32
32
  ctx.scale(highResScaleFactor, highResScaleFactor)
33
33
  ctx.clearRect(0, 0, blockSize, trackHeight)
@@ -43,8 +43,10 @@ const ConservationBlock = observer(function ({
43
43
  blockSize,
44
44
  })
45
45
  },
46
- [model, track, offsetX, trackHeight],
47
- )
46
+ width: blockSize * highResScaleFactor,
47
+ height: trackHeight * highResScaleFactor,
48
+ deps: [model, track, offsetX, trackHeight],
49
+ })
48
50
 
49
51
  return (
50
52
  <canvas
@@ -0,0 +1,44 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+
3
+ import { Button } from '@mui/material'
4
+
5
+ import copy from '../vendor/copyToClipboard.ts'
6
+
7
+ /**
8
+ * Copy-to-clipboard button that confirms on the label for a moment. The timer is
9
+ * cleared on unmount, since these live in dialogs the copy itself often closes.
10
+ */
11
+ export default function CopyButton({
12
+ text,
13
+ label = 'Copy to clipboard',
14
+ }: {
15
+ text: string
16
+ label?: string
17
+ }) {
18
+ const [copied, setCopied] = useState(false)
19
+ const timer = useRef<ReturnType<typeof setTimeout>>(undefined)
20
+
21
+ useEffect(
22
+ () => () => {
23
+ clearTimeout(timer.current)
24
+ },
25
+ [],
26
+ )
27
+
28
+ return (
29
+ <Button
30
+ variant="contained"
31
+ color="primary"
32
+ onClick={() => {
33
+ copy(text)
34
+ setCopied(true)
35
+ clearTimeout(timer.current)
36
+ timer.current = setTimeout(() => {
37
+ setCopied(false)
38
+ }, 500)
39
+ }}
40
+ >
41
+ {copied ? 'Copied!' : label}
42
+ </Button>
43
+ )
44
+ }
@@ -83,7 +83,8 @@ const Reset = observer(function ({
83
83
  })
84
84
 
85
85
  const Loading = observer(function ({ model }: { model: MsaViewModel }) {
86
- const { isLoading, dataInitialized, msaFilehandle, treeFilehandle } = model
86
+ const { isLoading, dataInitialized, error, msaFilehandle, treeFilehandle } =
87
+ model
87
88
  const hasPendingFilehandle = !!(msaFilehandle || treeFilehandle)
88
89
 
89
90
  return (
@@ -97,6 +98,14 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
97
98
  ) : (
98
99
  <MSAView model={model} />
99
100
  )
101
+ ) : error ? (
102
+ // Ahead of the spinner, because a failed load KEEPS its filehandle --
103
+ // only an abort clears it. So hasPendingFilehandle stays true, this
104
+ // fell to the spinner below, and the ImportForm that renders
105
+ // `model.error` was never reached: the reader got "Downloading file"
106
+ // and a Cancel button for as long as they were willing to watch it.
107
+ // Found on AlphaFold's files/msa/ prefix answering 403 to every key.
108
+ <Reset model={model} error={error} />
100
109
  ) : hasPendingFilehandle || isLoading ? (
101
110
  <LoadingSpinner model={model} />
102
111
  ) : (
@@ -11,6 +11,7 @@ import VerticalScrollbar from './VerticalScrollbar.tsx'
11
11
  import Header from './header/Header.tsx'
12
12
  import Minimap from './minimap/Minimap.tsx'
13
13
  import MSAPanel from './msa/MSAPanel.tsx'
14
+ import { clickColor, hoverColor } from './overlayColors.ts'
14
15
  import TreePanel from './tree/TreePanel.tsx'
15
16
  import TreeRuler from './tree/TreeRuler.tsx'
16
17
 
@@ -39,51 +40,43 @@ const TrackColumnIndicator = observer(function ({
39
40
  treeAreaWidth,
40
41
  resizeHandleWidth,
41
42
  totalTrackAreaHeight,
42
- msaAreaWidth,
43
- verticalScrollbarWidth,
43
+ msaCanvasWidth,
44
44
  } = model
45
45
 
46
- const left = treeAreaWidth + resizeHandleWidth
47
- const clipWidth = msaAreaWidth - verticalScrollbarWidth
46
+ // hovered column, then the pinned one, matching the alignment's own overlay
47
+ const bands = [
48
+ { col: mouseCol, color: hoverColor },
49
+ { col: mouseClickCol, color: clickColor },
50
+ ]
48
51
 
49
52
  return (
50
53
  <div
51
54
  style={{
52
55
  position: 'absolute',
53
- left,
56
+ left: treeAreaWidth + resizeHandleWidth,
54
57
  top: 0,
55
- width: clipWidth,
58
+ width: msaCanvasWidth,
56
59
  height: totalTrackAreaHeight,
57
60
  overflow: 'hidden',
58
61
  pointerEvents: 'none',
59
62
  }}
60
63
  >
61
- {mouseCol !== undefined ? (
62
- <div
63
- style={{
64
- position: 'absolute',
65
- left: mouseCol * colWidth + scrollX,
66
- top: 0,
67
- width: colWidth,
68
- height: totalTrackAreaHeight,
69
- backgroundColor: 'rgba(0,0,0,0.15)',
70
- zIndex: 100,
71
- }}
72
- />
73
- ) : null}
74
- {mouseClickCol !== undefined ? (
75
- <div
76
- style={{
77
- position: 'absolute',
78
- left: mouseClickCol * colWidth + scrollX,
79
- top: 0,
80
- width: colWidth,
81
- height: totalTrackAreaHeight,
82
- backgroundColor: 'rgba(128,128,0,0.2)',
83
- zIndex: 100,
84
- }}
85
- />
86
- ) : null}
64
+ {bands.map(({ col, color }) =>
65
+ col === undefined ? null : (
66
+ <div
67
+ key={color}
68
+ style={{
69
+ position: 'absolute',
70
+ left: col * colWidth + scrollX,
71
+ top: 0,
72
+ width: colWidth,
73
+ height: totalTrackAreaHeight,
74
+ backgroundColor: color,
75
+ zIndex: 100,
76
+ }}
77
+ />
78
+ ),
79
+ )}
87
80
  </div>
88
81
  )
89
82
  })