react-msaview 4.1.1 → 4.3.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 (91) hide show
  1. package/bundle/index.js +104 -213
  2. package/bundle/index.js.LICENSE.txt +173 -0
  3. package/bundle/index.js.map +1 -0
  4. package/dist/components/TextTrack.d.ts +2 -1
  5. package/dist/components/TextTrack.js.map +1 -1
  6. package/dist/components/Track.js.map +1 -1
  7. package/dist/components/header/Header.js +10 -3
  8. package/dist/components/header/Header.js.map +1 -1
  9. package/dist/components/header/HeaderMenu.js +199 -11
  10. package/dist/components/header/HeaderMenu.js.map +1 -1
  11. package/dist/components/header/MultiAlignmentSelector.js +3 -3
  12. package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
  13. package/dist/components/header/{HeaderMenuExtra.d.ts → SettingsMenu.d.ts} +2 -2
  14. package/dist/components/header/SettingsMenu.js +141 -0
  15. package/dist/components/header/SettingsMenu.js.map +1 -0
  16. package/dist/components/header/ZoomControls.d.ts +1 -1
  17. package/dist/components/header/ZoomControls.js +1 -46
  18. package/dist/components/header/ZoomControls.js.map +1 -1
  19. package/dist/components/header/ZoomMenu.d.ts +6 -0
  20. package/dist/components/header/ZoomMenu.js +33 -0
  21. package/dist/components/header/ZoomMenu.js.map +1 -0
  22. package/dist/components/header/ZoomStar.d.ts +6 -0
  23. package/dist/components/header/ZoomStar.js +40 -0
  24. package/dist/components/header/ZoomStar.js.map +1 -0
  25. package/dist/components/tree/renderTreeCanvas.js +6 -3
  26. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  27. package/dist/flatToTree.d.ts +17 -0
  28. package/dist/flatToTree.js +41 -0
  29. package/dist/flatToTree.js.map +1 -0
  30. package/dist/model.d.ts +21 -28
  31. package/dist/model.js +47 -32
  32. package/dist/model.js.map +1 -1
  33. package/dist/parseAsn1.d.ts +34 -0
  34. package/dist/parseAsn1.js +385 -0
  35. package/dist/parseAsn1.js.map +1 -0
  36. package/dist/parseAsn1.test.d.ts +1 -0
  37. package/dist/parseAsn1.test.js +8 -0
  38. package/dist/parseAsn1.test.js.map +1 -0
  39. package/dist/parsers/ClustalMSA.d.ts +1 -1
  40. package/dist/parsers/ClustalMSA.js.map +1 -1
  41. package/dist/parsers/EmfMSA.d.ts +1 -1
  42. package/dist/parsers/FastaMSA.d.ts +1 -1
  43. package/dist/parsers/StockholmMSA.d.ts +1 -1
  44. package/dist/parsers/StockholmMSA.js.map +1 -1
  45. package/dist/renderToSvg.d.ts +3 -2
  46. package/dist/renderToSvg.js.map +1 -1
  47. package/dist/reparseTree.d.ts +1 -1
  48. package/dist/reparseTree.js +2 -0
  49. package/dist/reparseTree.js.map +1 -1
  50. package/dist/types.d.ts +38 -0
  51. package/dist/types.js +2 -0
  52. package/dist/types.js.map +1 -0
  53. package/dist/util.d.ts +6 -20
  54. package/dist/util.js +19 -0
  55. package/dist/util.js.map +1 -1
  56. package/dist/version.d.ts +1 -1
  57. package/dist/version.js +1 -1
  58. package/dist/webpack.d.ts +5 -0
  59. package/dist/webpack.js +7 -0
  60. package/dist/webpack.js.map +1 -0
  61. package/package.json +4 -3
  62. package/src/__snapshots__/parseAsn1.test.ts.snap +2400 -0
  63. package/src/components/TextTrack.tsx +2 -1
  64. package/src/components/Track.tsx +0 -2
  65. package/src/components/header/Header.tsx +11 -3
  66. package/src/components/header/HeaderMenu.tsx +215 -11
  67. package/src/components/header/MultiAlignmentSelector.tsx +7 -6
  68. package/src/components/header/SettingsMenu.tsx +169 -0
  69. package/src/components/header/ZoomControls.tsx +1 -52
  70. package/src/components/header/ZoomMenu.tsx +42 -0
  71. package/src/components/header/ZoomStar.tsx +74 -0
  72. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +1 -1
  73. package/src/components/msa/renderMSABlock.ts +1 -1
  74. package/src/components/tree/renderTreeCanvas.ts +13 -3
  75. package/src/flatToTree.ts +57 -0
  76. package/src/model.ts +75 -61
  77. package/src/parseAsn1.test.ts +11 -0
  78. package/src/parseAsn1.ts +494 -0
  79. package/src/parsers/ClustalMSA.ts +2 -1
  80. package/src/parsers/EmfMSA.ts +1 -1
  81. package/src/parsers/FastaMSA.ts +1 -1
  82. package/src/parsers/StockholmMSA.ts +4 -1
  83. package/src/renderToSvg.tsx +6 -4
  84. package/src/reparseTree.ts +3 -1
  85. package/src/types.ts +44 -0
  86. package/src/util.ts +26 -22
  87. package/src/version.ts +1 -1
  88. package/src/webpack.ts +9 -0
  89. package/dist/components/header/HeaderMenuExtra.js +0 -122
  90. package/dist/components/header/HeaderMenuExtra.js.map +0 -1
  91. package/src/components/header/HeaderMenuExtra.tsx +0 -135
@@ -0,0 +1,74 @@
1
+ import React from 'react'
2
+
3
+ import RestartAlt from '@mui/icons-material/RestartAlt'
4
+ import { IconButton } from '@mui/material'
5
+ import { observer } from 'mobx-react'
6
+ import { makeStyles } from 'tss-react/mui'
7
+
8
+ import type { MsaViewModel } from '../../model'
9
+
10
+ const useStyles = makeStyles()(theme => ({
11
+ dpad: {
12
+ display: 'grid',
13
+ gridTemplateColumns: 'repeat(3, 1fr)',
14
+ },
15
+ icon: {
16
+ padding: theme.spacing(0.5),
17
+ },
18
+ }))
19
+
20
+ const ZoomStar = observer(function ({ model }: { model: MsaViewModel }) {
21
+ const { classes } = useStyles()
22
+ return (
23
+ <div className={classes.dpad}>
24
+ <div />
25
+ <IconButton
26
+ className={classes.icon}
27
+ onClick={() => {
28
+ model.zoomInVertical()
29
+ }}
30
+ >
31
+ Y+
32
+ </IconButton>
33
+ <div />
34
+
35
+ <IconButton
36
+ className={classes.icon}
37
+ onClick={() => {
38
+ model.zoomOutHorizontal()
39
+ }}
40
+ >
41
+ X-
42
+ </IconButton>
43
+ <IconButton
44
+ className={classes.icon}
45
+ onClick={() => {
46
+ model.resetZoom()
47
+ }}
48
+ >
49
+ <RestartAlt />
50
+ </IconButton>
51
+ <IconButton
52
+ className={classes.icon}
53
+ onClick={() => {
54
+ model.zoomInHorizontal()
55
+ }}
56
+ >
57
+ X+
58
+ </IconButton>
59
+
60
+ <div />
61
+ <IconButton
62
+ className={classes.icon}
63
+ onClick={() => {
64
+ model.zoomOutVertical()
65
+ }}
66
+ >
67
+ Y-
68
+ </IconButton>
69
+ <div />
70
+ </div>
71
+ )
72
+ })
73
+
74
+ export default ZoomStar
@@ -1,5 +1,5 @@
1
1
  import type { MsaViewModel } from '../../model'
2
- import type { NodeWithIdsAndLength } from '../../util'
2
+ import type { NodeWithIdsAndLength } from '../../types'
3
3
  import type { HierarchyNode } from 'd3-hierarchy'
4
4
 
5
5
  export function renderBoxFeatureCanvasBlock({
@@ -1,7 +1,7 @@
1
1
  import { getClustalXColor, getPercentIdentityColor } from '../../colorSchemes'
2
2
 
3
3
  import type { MsaViewModel } from '../../model'
4
- import type { NodeWithIdsAndLength } from '../../util'
4
+ import type { NodeWithIdsAndLength } from '../../types'
5
5
  import type { Theme } from '@mui/material'
6
6
  import type { HierarchyNode } from 'd3-hierarchy'
7
7
 
@@ -31,9 +31,15 @@ export function renderTree({
31
31
  theme: Theme
32
32
  blockSizeYOverride?: number
33
33
  }) {
34
- const { hierarchy, showBranchLen, blockSize } = model
34
+ const {
35
+ hierarchy,
36
+ allBranchesLength0,
37
+ showBranchLen: showBranchLenPre,
38
+ blockSize,
39
+ } = model
35
40
  const by = blockSizeYOverride || blockSize
36
41
  ctx.strokeStyle = theme.palette.text.primary
42
+ const showBranchLen = allBranchesLength0 ? false : showBranchLenPre
37
43
  for (const link of hierarchy.links()) {
38
44
  const { source, target } = link
39
45
  if (target.height === 0 && !showBranchLen) {
@@ -77,12 +83,14 @@ export function renderNodeBubbles({
77
83
  }) {
78
84
  const {
79
85
  hierarchy,
80
- showBranchLen,
86
+ showBranchLen: showBranchLenPre,
87
+ allBranchesLength0,
81
88
  collapsed,
82
89
  blockSize,
83
90
  marginLeft: ml,
84
91
  } = model
85
92
  const by = blockSizeYOverride || blockSize
93
+ const showBranchLen = allBranchesLength0 ? false : showBranchLenPre
86
94
  for (const node of hierarchy.descendants()) {
87
95
  const val = showBranchLen ? 'len' : 'y'
88
96
  // @ts-expect-error
@@ -131,7 +139,8 @@ export function renderTreeLabels({
131
139
  }) {
132
140
  const {
133
141
  fontSize,
134
- showBranchLen,
142
+ showBranchLen: showBranchLenPre,
143
+ allBranchesLength0,
135
144
  treeMetadata,
136
145
  hierarchy,
137
146
  collapsed,
@@ -153,6 +162,7 @@ export function renderTreeLabels({
153
162
  } else {
154
163
  ctx.textAlign = 'start'
155
164
  }
165
+ const showBranchLen = allBranchesLength0 ? false : showBranchLenPre
156
166
  for (const node of leaves) {
157
167
  const {
158
168
  data: { name, id },
@@ -0,0 +1,57 @@
1
+ // Define the input item interface
2
+ interface FlatItem {
3
+ id: number
4
+ parent?: number
5
+ }
6
+
7
+ // Define the tree node interface
8
+ interface TreeNode {
9
+ id: string
10
+ name: string
11
+ parent?: string
12
+ children: TreeNode[]
13
+ }
14
+
15
+ /**
16
+ * Parses a flat list of items into a tree structure
17
+ * @param items - Array of flat items with id and optional parent
18
+ * @returns Array of root tree nodes
19
+ */
20
+ export function flatToTree(items: FlatItem[]): TreeNode {
21
+ // Create a map to store all nodes by their id for quick lookup
22
+ const nodeMap = new Map<number, TreeNode>()
23
+
24
+ // First pass: Create all tree nodes
25
+ items.forEach(item => {
26
+ nodeMap.set(item.id, {
27
+ ...item,
28
+ id: `${item.id}`,
29
+ name: `${item.id}`,
30
+ parent: item.parent !== undefined ? `${item.parent}` : undefined,
31
+ children: [],
32
+ })
33
+ })
34
+
35
+ // Second pass: Build parent-child relationships
36
+ const roots: TreeNode[] = []
37
+
38
+ items.forEach(item => {
39
+ const node = nodeMap.get(item.id)!
40
+
41
+ if (item.parent !== undefined) {
42
+ // This item has a parent, add it to parent's children
43
+ const parentNode = nodeMap.get(item.parent)
44
+ if (parentNode) {
45
+ parentNode.children.push(node)
46
+ } else {
47
+ // Parent doesn't exist, treat as root
48
+ roots.push(node)
49
+ }
50
+ } else {
51
+ // This item has no parent, it's a root node
52
+ roots.push(node)
53
+ }
54
+ })
55
+
56
+ return roots[0]!
57
+ }
package/src/model.ts CHANGED
@@ -1,11 +1,10 @@
1
- import React from 'react'
2
-
3
1
  import { groupBy, notEmpty, sum } from '@jbrowse/core/util'
4
2
  import { openLocation } from '@jbrowse/core/util/io'
5
3
  import { ElementId, FileLocation } from '@jbrowse/core/util/types/mst'
6
4
  import { colord } from 'colord'
7
5
  import { ascending } from 'd3-array'
8
6
  import { cluster, hierarchy } from 'd3-hierarchy'
7
+ import { parseEmfTree } from 'emf-js'
9
8
  import { saveAs } from 'file-saver'
10
9
  import { autorun, transaction } from 'mobx'
11
10
  import { addDisposer, cast, types } from 'mobx-state-tree'
@@ -15,16 +14,17 @@ import Stockholm from 'stockholm-js'
15
14
  import { blocksX, blocksY } from './calculateBlocks'
16
15
  import colorSchemes from './colorSchemes'
17
16
  import TextTrack from './components/TextTrack'
17
+ import { flatToTree } from './flatToTree'
18
18
  import palettes from './ggplotPalettes'
19
19
  import { measureTextCanvas } from './measureTextCanvas'
20
20
  import { DataModelF } from './model/DataModel'
21
21
  import { DialogQueueSessionMixin } from './model/DialogQueue'
22
22
  import { MSAModelF } from './model/msaModel'
23
23
  import { TreeModelF } from './model/treeModel'
24
+ import { parseAsn1 } from './parseAsn1'
24
25
  import parseNewick from './parseNewick'
25
26
  import ClustalMSA from './parsers/ClustalMSA'
26
27
  import EmfMSA from './parsers/EmfMSA'
27
- import EmfTree from './parsers/EmfTree'
28
28
  import FastaMSA from './parsers/FastaMSA'
29
29
  import StockholmMSA from './parsers/StockholmMSA'
30
30
  import { reparseTree } from './reparseTree'
@@ -36,46 +36,31 @@ import {
36
36
  clamp,
37
37
  collapse,
38
38
  generateNodeIds,
39
+ isGzip,
39
40
  len,
41
+ localStorageGetBoolean,
42
+ localStorageSetBoolean,
40
43
  maxLength,
41
44
  setBrLength,
42
45
  skipBlanks,
43
46
  } from './util'
44
47
 
45
48
  import type { InterProScanResults } from './launchInterProScan'
46
- import type { NodeWithIds, NodeWithIdsAndLength } from './util'
49
+ import type {
50
+ Accession,
51
+ BasicTrack,
52
+ NodeWithIds,
53
+ NodeWithIdsAndLength,
54
+ TextTrackModel,
55
+ } from './types'
47
56
  import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
48
57
  import type { Theme } from '@mui/material'
49
58
  import type { HierarchyNode } from 'd3-hierarchy'
50
59
  import type { Instance } from 'mobx-state-tree'
51
60
 
52
- export interface Accession {
53
- accession: string
54
- name: string
55
- description: string
56
- }
57
- export interface BasicTrackModel {
58
- id: string
59
- name: string
60
- associatedRowName?: string
61
- height: number
62
- }
63
-
64
- export interface TextTrackModel extends BasicTrackModel {
65
- customColorScheme?: Record<string, string>
66
- data: string
67
- }
68
-
69
- export interface ITextTrack {
70
- ReactComponent: React.FC<any>
71
- model: TextTrackModel
72
- }
73
-
74
- export type BasicTrack = ITextTrack
75
-
76
- export function isGzip(buf: Uint8Array) {
77
- return buf[0] === 31 && buf[1] === 139 && buf[2] === 8
78
- }
61
+ const defaultRowHeight = 16
62
+ const defaultColWidth = 12
63
+ const showZoomStarKey = 'msa-showZoomStar'
79
64
 
80
65
  /**
81
66
  * #stateModel MsaView
@@ -101,6 +86,10 @@ function stateModelFactory() {
101
86
  * #property
102
87
  */
103
88
  showDomains: false,
89
+ /**
90
+ * #property
91
+ */
92
+ hideGaps: true,
104
93
  /**
105
94
  * #property
106
95
  */
@@ -125,15 +114,6 @@ function stateModelFactory() {
125
114
  * #property
126
115
  */
127
116
  drawMsaLetters: true,
128
- /**
129
- * #property
130
- */
131
- hideGaps: false,
132
-
133
- /**
134
- * #property
135
- */
136
- drawTreeText: true,
137
117
 
138
118
  /**
139
119
  * #property
@@ -145,7 +125,7 @@ function stateModelFactory() {
145
125
  * #property
146
126
  * height of each row, px
147
127
  */
148
- rowHeight: 18,
128
+ rowHeight: defaultRowHeight,
149
129
 
150
130
  /**
151
131
  * #property
@@ -163,7 +143,7 @@ function stateModelFactory() {
163
143
  * #property
164
144
  * width of columns, px
165
145
  */
166
- colWidth: 14,
146
+ colWidth: defaultColWidth,
167
147
 
168
148
  /**
169
149
  * #property
@@ -250,6 +230,12 @@ function stateModelFactory() {
250
230
  * screens
251
231
  */
252
232
  highResScaleFactor: 2,
233
+
234
+ /**
235
+ * #volatile
236
+ * obtained from localStorage
237
+ */
238
+ showZoomStar: localStorageGetBoolean(showZoomStarKey, false),
253
239
  /**
254
240
  * #volatile
255
241
  */
@@ -363,6 +349,12 @@ function stateModelFactory() {
363
349
  setLoadingMSA(arg: boolean) {
364
350
  self.loadingMSA = arg
365
351
  },
352
+ /**
353
+ * #volatile
354
+ */
355
+ setShowZoomStar(arg: boolean) {
356
+ self.showZoomStar = arg
357
+ },
366
358
  /**
367
359
  * #action
368
360
  */
@@ -635,26 +627,26 @@ function stateModelFactory() {
635
627
  */
636
628
  get tree(): NodeWithIds {
637
629
  const text = self.data.tree
638
- let ret: NodeWithIds
639
- if (text) {
640
- let t: string
641
- if (text.startsWith('SEQ')) {
642
- const r = new EmfTree(text)
643
- t = r.data.tree
644
- } else {
645
- t = text
646
- }
647
- ret = generateNodeIds(parseNewick(t))
648
- } else {
649
- ret = this.MSA?.getTree() || {
650
- noTree: true,
651
- children: [],
652
- id: 'empty',
653
- name: 'empty',
654
- }
655
- }
656
- return reparseTree(ret)
630
+ return text
631
+ ? reparseTree(
632
+ generateNodeIds(
633
+ text.startsWith('BioTreeContainer')
634
+ ? flatToTree(parseAsn1(text))
635
+ : parseNewick(
636
+ text.startsWith('SEQ') ? parseEmfTree(text).tree : text,
637
+ ),
638
+ ),
639
+ )
640
+ : reparseTree(
641
+ this.MSA?.getTree() || {
642
+ noTree: true,
643
+ children: [],
644
+ id: 'empty',
645
+ name: 'empty',
646
+ },
647
+ )
657
648
  },
649
+
658
650
  /**
659
651
  * #getter
660
652
  */
@@ -846,6 +838,13 @@ function stateModelFactory() {
846
838
  get leaves() {
847
839
  return this.hierarchy.leaves()
848
840
  },
841
+
842
+ /**
843
+ * #getter
844
+ */
845
+ get allBranchesLength0() {
846
+ return this.hierarchy.links().every(s => !s.source.data.length)
847
+ },
849
848
  }))
850
849
  .views(self => ({
851
850
  /**
@@ -937,6 +936,13 @@ function stateModelFactory() {
937
936
  self.drawMsaLetters = arg
938
937
  },
939
938
 
939
+ /**
940
+ * #action
941
+ */
942
+ resetZoom() {
943
+ self.setColWidth(defaultColWidth)
944
+ self.setRowHeight(defaultRowHeight)
945
+ },
940
946
  /**
941
947
  * #action
942
948
  */
@@ -1379,6 +1385,14 @@ function stateModelFactory() {
1379
1385
  }),
1380
1386
  )
1381
1387
 
1388
+ // autorun saves local settings
1389
+ addDisposer(
1390
+ self,
1391
+ autorun(() => {
1392
+ localStorageSetBoolean(showZoomStarKey, self.showZoomStar)
1393
+ }),
1394
+ )
1395
+
1382
1396
  // autorun opens treeFilehandle
1383
1397
  addDisposer(
1384
1398
  self,
@@ -0,0 +1,11 @@
1
+ import fs from 'fs'
2
+
3
+ import { expect, test } from 'vitest'
4
+
5
+ import { parseAsn1 } from './parseAsn1'
6
+
7
+ const r = fs.readFileSync(require.resolve('../test/data/tree.asn'), 'utf8')
8
+
9
+ test('real data file', () => {
10
+ expect(parseAsn1(r)).toMatchSnapshot()
11
+ })