react-msaview 2.0.0 → 2.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 (149) hide show
  1. package/bundle/index.js +35 -33
  2. package/dist/UniprotTrack.js +6 -5
  3. package/dist/UniprotTrack.js.map +1 -1
  4. package/dist/colorSchemes.d.ts +3 -9
  5. package/dist/colorSchemes.js +8 -6
  6. package/dist/colorSchemes.js.map +1 -1
  7. package/dist/components/BoxTrack.d.ts +4 -3
  8. package/dist/components/BoxTrack.js +6 -137
  9. package/dist/components/BoxTrack.js.map +1 -1
  10. package/dist/components/BoxTrackBlock.d.ts +8 -0
  11. package/dist/components/BoxTrackBlock.js +136 -0
  12. package/dist/components/BoxTrackBlock.js.map +1 -0
  13. package/dist/components/Header.d.ts +2 -1
  14. package/dist/components/Header.js +29 -27
  15. package/dist/components/Header.js.map +1 -1
  16. package/dist/components/ImportForm.d.ts +2 -1
  17. package/dist/components/MSABlock.d.ts +8 -0
  18. package/dist/components/MSABlock.js +103 -0
  19. package/dist/components/MSABlock.js.map +1 -0
  20. package/dist/components/MSACanvas.d.ts +2 -1
  21. package/dist/components/MSACanvas.js +3 -101
  22. package/dist/components/MSACanvas.js.map +1 -1
  23. package/dist/components/MSAMouseoverCanvas.d.ts +6 -0
  24. package/dist/components/MSAMouseoverCanvas.js +52 -0
  25. package/dist/components/MSAMouseoverCanvas.js.map +1 -0
  26. package/dist/components/MSAView.d.ts +2 -1
  27. package/dist/components/MSAView.js +10 -36
  28. package/dist/components/MSAView.js.map +1 -1
  29. package/dist/components/MultiAlignmentSelector.d.ts +6 -0
  30. package/dist/components/MultiAlignmentSelector.js +13 -0
  31. package/dist/components/MultiAlignmentSelector.js.map +1 -0
  32. package/dist/components/ResizeHandles.d.ts +3 -2
  33. package/dist/components/Rubberband.d.ts +1 -1
  34. package/dist/components/Rubberband.js +0 -1
  35. package/dist/components/Rubberband.js.map +1 -1
  36. package/dist/components/Ruler.d.ts +2 -1
  37. package/dist/components/Ruler.js +2 -2
  38. package/dist/components/Ruler.js.map +1 -1
  39. package/dist/components/TextTrack.d.ts +2 -1
  40. package/dist/components/Track.d.ts +3 -2
  41. package/dist/components/Track.js +4 -3
  42. package/dist/components/Track.js.map +1 -1
  43. package/dist/components/TreeBranchMenu.d.ts +14 -0
  44. package/dist/components/TreeBranchMenu.js +26 -0
  45. package/dist/components/TreeBranchMenu.js.map +1 -0
  46. package/dist/components/TreeCanvas.d.ts +2 -1
  47. package/dist/components/TreeCanvas.js +2 -320
  48. package/dist/components/TreeCanvas.js.map +1 -1
  49. package/dist/components/TreeCanvasBlock.d.ts +7 -0
  50. package/dist/components/TreeCanvasBlock.js +252 -0
  51. package/dist/components/TreeCanvasBlock.js.map +1 -0
  52. package/dist/components/TreeMenu.d.ts +12 -0
  53. package/dist/components/TreeMenu.js +56 -0
  54. package/dist/components/TreeMenu.js.map +1 -0
  55. package/dist/components/TreeRuler.d.ts +2 -1
  56. package/dist/components/VerticalGuide.d.ts +2 -1
  57. package/dist/components/ZoomControls.d.ts +6 -0
  58. package/dist/components/ZoomControls.js +58 -0
  59. package/dist/components/ZoomControls.js.map +1 -0
  60. package/dist/components/dialogs/AboutDlg.d.ts +4 -0
  61. package/dist/components/{AboutDlg.js → dialogs/AboutDlg.js} +3 -3
  62. package/dist/components/dialogs/AboutDlg.js.map +1 -0
  63. package/dist/components/{AddTrackDlg.d.ts → dialogs/AddTrackDlg.d.ts} +3 -2
  64. package/dist/components/dialogs/AddTrackDlg.js.map +1 -0
  65. package/dist/components/{AnnotationDlg.d.ts → dialogs/AnnotationDlg.d.ts} +3 -2
  66. package/dist/components/dialogs/AnnotationDlg.js.map +1 -0
  67. package/dist/components/dialogs/DetailsDlg.d.ts +7 -0
  68. package/dist/components/{DetailsDlg.js → dialogs/DetailsDlg.js} +3 -2
  69. package/dist/components/dialogs/DetailsDlg.js.map +1 -0
  70. package/dist/components/{MoreInfoDlg.d.ts → dialogs/MoreInfoDlg.d.ts} +2 -1
  71. package/dist/components/dialogs/MoreInfoDlg.js.map +1 -0
  72. package/dist/components/dialogs/SettingsDlg.d.ts +7 -0
  73. package/dist/components/{SettingsDlg.js → dialogs/SettingsDlg.js} +4 -3
  74. package/dist/components/dialogs/SettingsDlg.js.map +1 -0
  75. package/dist/components/{TrackInfoDlg.d.ts → dialogs/TrackInfoDlg.d.ts} +2 -1
  76. package/dist/components/dialogs/TrackInfoDlg.js.map +1 -0
  77. package/dist/components/dialogs/TracklistDlg.d.ts +7 -0
  78. package/dist/components/{TracklistDlg.js → dialogs/TracklistDlg.js} +2 -2
  79. package/dist/components/dialogs/TracklistDlg.js.map +1 -0
  80. package/dist/components/util.js +0 -1
  81. package/dist/components/util.js.map +1 -1
  82. package/dist/model.d.ts +36 -39
  83. package/dist/model.js +25 -22
  84. package/dist/model.js.map +1 -1
  85. package/dist/parseNewick.js +2 -2
  86. package/dist/parseNewick.js.map +1 -1
  87. package/dist/parsers/FastaMSA.d.ts +1 -3
  88. package/dist/parsers/FastaMSA.js +2 -2
  89. package/dist/parsers/FastaMSA.js.map +1 -1
  90. package/dist/parsers/StockholmMSA.d.ts +3 -5
  91. package/dist/parsers/StockholmMSA.js +3 -3
  92. package/dist/parsers/StockholmMSA.js.map +1 -1
  93. package/dist/util.d.ts +5 -7
  94. package/dist/util.js +4 -2
  95. package/dist/util.js.map +1 -1
  96. package/dist/version.d.ts +1 -1
  97. package/dist/version.js +1 -1
  98. package/dist/version.js.map +1 -1
  99. package/package.json +9 -9
  100. package/src/UniprotTrack.ts +6 -7
  101. package/src/colorSchemes.ts +12 -9
  102. package/src/components/BoxTrack.tsx +6 -198
  103. package/src/components/BoxTrackBlock.tsx +198 -0
  104. package/src/components/Header.tsx +49 -60
  105. package/src/components/MSABlock.tsx +164 -0
  106. package/src/components/MSACanvas.tsx +3 -158
  107. package/src/components/MSAMouseoverCanvas.tsx +87 -0
  108. package/src/components/MSAView.tsx +19 -63
  109. package/src/components/MultiAlignmentSelector.tsx +33 -0
  110. package/src/components/Rubberband.tsx +0 -1
  111. package/src/components/Ruler.tsx +2 -1
  112. package/src/components/Track.tsx +9 -6
  113. package/src/components/TreeBranchMenu.tsx +67 -0
  114. package/src/components/TreeCanvas.tsx +2 -507
  115. package/src/components/TreeCanvasBlock.tsx +359 -0
  116. package/src/components/TreeMenu.tsx +105 -0
  117. package/src/components/ZoomControls.tsx +78 -0
  118. package/src/components/{AboutDlg.tsx → dialogs/AboutDlg.tsx} +3 -9
  119. package/src/components/{AddTrackDlg.tsx → dialogs/AddTrackDlg.tsx} +1 -1
  120. package/src/components/{AnnotationDlg.tsx → dialogs/AnnotationDlg.tsx} +2 -2
  121. package/src/components/{DetailsDlg.tsx → dialogs/DetailsDlg.tsx} +5 -5
  122. package/src/components/{SettingsDlg.tsx → dialogs/SettingsDlg.tsx} +6 -6
  123. package/src/components/{TracklistDlg.tsx → dialogs/TracklistDlg.tsx} +2 -4
  124. package/src/components/util.ts +0 -1
  125. package/src/declare.d.ts +0 -1
  126. package/src/model.ts +32 -29
  127. package/src/parseNewick.ts +2 -3
  128. package/src/parsers/FastaMSA.ts +3 -3
  129. package/src/parsers/StockholmMSA.ts +5 -5
  130. package/src/util.ts +8 -5
  131. package/src/version.ts +1 -1
  132. package/dist/components/AboutDlg.d.ts +0 -4
  133. package/dist/components/AboutDlg.js.map +0 -1
  134. package/dist/components/AddTrackDlg.js.map +0 -1
  135. package/dist/components/AnnotationDlg.js.map +0 -1
  136. package/dist/components/DetailsDlg.d.ts +0 -7
  137. package/dist/components/DetailsDlg.js.map +0 -1
  138. package/dist/components/MoreInfoDlg.js.map +0 -1
  139. package/dist/components/SettingsDlg.d.ts +0 -7
  140. package/dist/components/SettingsDlg.js.map +0 -1
  141. package/dist/components/TrackInfoDlg.js.map +0 -1
  142. package/dist/components/TracklistDlg.d.ts +0 -7
  143. package/dist/components/TracklistDlg.js.map +0 -1
  144. /package/dist/components/{AddTrackDlg.js → dialogs/AddTrackDlg.js} +0 -0
  145. /package/dist/components/{AnnotationDlg.js → dialogs/AnnotationDlg.js} +0 -0
  146. /package/dist/components/{MoreInfoDlg.js → dialogs/MoreInfoDlg.js} +0 -0
  147. /package/dist/components/{TrackInfoDlg.js → dialogs/TrackInfoDlg.js} +0 -0
  148. /package/src/components/{MoreInfoDlg.tsx → dialogs/MoreInfoDlg.tsx} +0 -0
  149. /package/src/components/{TrackInfoDlg.tsx → dialogs/TrackInfoDlg.tsx} +0 -0
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "react-msaview",
3
3
  "type": "module",
4
4
  "author": "Colin",
5
- "version": "2.0.0",
5
+ "version": "2.1.1",
6
6
  "license": "MIT",
7
7
  "main": "dist/index.js",
8
8
  "files": [
@@ -14,13 +14,13 @@
14
14
  "node": ">=10"
15
15
  },
16
16
  "scripts": {
17
- "lint": "eslint src",
18
17
  "clean": "rimraf dist",
19
18
  "prebuild": "npm run clean",
20
- "prepublishOnly": "node -p \"'export const version = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts",
21
- "build": "tsc && rollup -c",
22
- "preversion": "npm run build",
23
- "postversion": "git push --follow-tags"
19
+ "prepublishOnly": "node output-version.js > src/version.ts && git add -A src && git commit -m '[skip ci] Bump version.ts'",
20
+ "build:esm": "tsc",
21
+ "build:bundle": "rollup -c",
22
+ "build": "npm run build:esm && npm run build:bundle",
23
+ "prepack": "npm run build"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@jbrowse/core": ">=2.0.0",
@@ -33,7 +33,7 @@
33
33
  "react-dom": ">=16.8.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@rollup/plugin-commonjs": "^24.1.0",
36
+ "@rollup/plugin-commonjs": "^25.0.4",
37
37
  "@rollup/plugin-json": "^6.0.0",
38
38
  "@rollup/plugin-node-resolve": "^15.0.2",
39
39
  "@rollup/plugin-replace": "^5.0.2",
@@ -43,14 +43,14 @@
43
43
  "@types/d3": "^7.4.0",
44
44
  "@types/react": "^18.2.0",
45
45
  "@types/react-dom": "^18.2.1",
46
- "rollup": "^3.21.3",
46
+ "rollup": "^3.0.2",
47
47
  "rollup-plugin-polyfill-node": "^0.12.0",
48
48
  "tslib": "^2.1.0",
49
49
  "typescript": "^5.0.4"
50
50
  },
51
51
  "dependencies": {
52
52
  "clustal-js": "^1.0.3",
53
- "color": "^3.1.3",
53
+ "colord": "^2.9.3",
54
54
  "copy-to-clipboard": "^3.3.1",
55
55
  "d3-array": "^3.2.3",
56
56
  "d3-hierarchy": "^3.1.2",
@@ -31,16 +31,15 @@ export const UniprotTrack = types
31
31
  autorun(async () => {
32
32
  try {
33
33
  const { accession } = self
34
- const url = `https://rest.uniprot.org/uniprotkb/${accession}.gff`
35
- const response = await fetch(url)
36
- if (!response.ok) {
34
+ const accessionSansVersion = accession?.replace(/\.[^/.]+$/, '')
35
+ const url = `https://rest.uniprot.org/uniprotkb/${accessionSansVersion}.gff`
36
+ const res = await fetch(url)
37
+ if (!res.ok) {
37
38
  throw new Error(
38
- `HTTP ${
39
- response.status
40
- } fetching ${url}: ${await response.text()}`,
39
+ `HTTP ${res.status} fetching ${url}: ${await res.text()}`,
41
40
  )
42
41
  }
43
- self.setData(await response.text())
42
+ self.setData(await res.text())
44
43
  } catch (e) {
45
44
  console.error(e)
46
45
  self.setError(e)
@@ -1,6 +1,9 @@
1
- import Color from 'color'
1
+ import { colord, extend } from 'colord'
2
+ import namesPlugin from 'colord/plugins/names'
2
3
  import { transform } from './util'
3
4
 
5
+ extend([namesPlugin])
6
+
4
7
  const colorSchemes = {
5
8
  clustal: {
6
9
  G: 'orange',
@@ -332,13 +335,13 @@ const colorSchemes = {
332
335
  '-': 'gray',
333
336
  '.': 'gray',
334
337
  },
335
- } as { [key: string]: { [key: string]: string } }
338
+ } as Record<string, Record<string, string>>
336
339
 
337
340
  // turn all supplied colors to hex colors which getContrastText from mui
338
341
  // requires
339
342
  export default transform(colorSchemes, ([key, val]) => [
340
343
  key,
341
- transform(val, ([letter, color]) => [letter, Color(color).hex()]),
344
+ transform(val, ([letter, color]) => [letter, colord(color).toHex()]),
342
345
  ])
343
346
 
344
347
  // info http://www.jalview.org/help/html/colourSchemes/clustal.html
@@ -347,7 +350,7 @@ export default transform(colorSchemes, ([key, val]) => [
347
350
  // scheme says there the jalview.org colorscheme says WLVIMAFCHP but it
348
351
  // should be WLVIMAFCHPY, colprot.xml says e.g. %#ACFHILMVWYPp" which has Y
349
352
  export function getClustalXColor(
350
- stats: { [key: string]: number },
353
+ stats: Record<string, number>,
351
354
  model: { columns: Record<string, string> },
352
355
  row: string,
353
356
  col: number,
@@ -494,7 +497,7 @@ export function getClustalXColor(
494
497
  // scheme says there the jalview.org colorscheme says WLVIMAFCHP but it
495
498
  // should be WLVIMAFCHPY, colprot.xml says e.g. %#ACFHILMVWYPp" which has Y
496
499
  export function getPercentIdentityColor(
497
- stats: { [key: string]: number },
500
+ stats: Record<string, number>,
498
501
  model: { columns: Record<string, string> },
499
502
  row: string,
500
503
  col: number,
@@ -504,10 +507,10 @@ export function getPercentIdentityColor(
504
507
  const entries = Object.entries(stats)
505
508
  let ent = 0
506
509
  let letter = ''
507
- for (let i = 0; i < entries.length; i++) {
508
- if (entries[i][1] > ent && entries[i][0] !== '-') {
509
- letter = entries[i][0]
510
- ent = entries[i][1]
510
+ for (const entry of entries) {
511
+ if (entry[1] > ent && entry[0] !== '-') {
512
+ letter = entry[0]
513
+ ent = entry[1]
511
514
  }
512
515
  }
513
516
  const proportion = ent / total
@@ -1,201 +1,11 @@
1
- import React, { useRef, useMemo, useEffect } from 'react'
1
+ import React from 'react'
2
2
  import { observer } from 'mobx-react'
3
- import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
4
3
 
5
4
  // locals
6
5
  import { IBoxTrack, MsaViewModel } from '../model'
7
- import Layout from '../layout'
6
+ import BoxTrackBlock from './BoxTrackBlock'
8
7
 
9
- interface Feat {
10
- start: number
11
- end: number
12
- }
13
- const AnnotationBlock = observer(function ({
14
- track,
15
- model,
16
- offsetX,
17
- }: {
18
- track: IBoxTrack
19
- model: MsaViewModel
20
- offsetX: number
21
- }) {
22
- const {
23
- blockSize,
24
- colWidth,
25
- blanks,
26
- rowHeight,
27
- highResScaleFactor,
28
- scrollX,
29
- } = model
30
- const {
31
- model: { height, features, associatedRowName },
32
- } = track
33
-
34
- const feats: Feat[] = isStateTreeNode(features)
35
- ? // @ts-expect-error
36
- getSnapshot(features)
37
- : features
38
-
39
- const layout = useMemo(() => {
40
- const temp = new Layout()
41
-
42
- feats?.forEach((feature, index) => {
43
- const { start, end } = feature
44
- if (associatedRowName) {
45
- const s = model.rowSpecificBpToPx(associatedRowName, start - 1)
46
- const e = model.rowSpecificBpToPx(associatedRowName, end)
47
- temp.addRect(`${index}`, s, e, rowHeight, feature)
48
- } else {
49
- const s = model.globalBpToPx(start - 1)
50
- const e = model.globalBpToPx(end)
51
- temp.addRect(`${index}`, s, e, rowHeight, feature)
52
- }
53
- })
54
- return temp
55
-
56
- // might convert to autorun based drawing
57
- // eslint-disable-next-line react-hooks/exhaustive-deps
58
- }, [rowHeight, feats, associatedRowName, model, blanks])
59
-
60
- const ref = useRef<HTMLCanvasElement>(null)
61
- const labelRef = useRef<HTMLCanvasElement>(null)
62
- const mouseoverRef = useRef<HTMLCanvasElement>(null)
63
-
64
- useEffect(() => {
65
- if (!ref.current) {
66
- return
67
- }
68
-
69
- const ctx = ref.current.getContext('2d')
70
- if (!ctx) {
71
- return
72
- }
73
-
74
- ctx.resetTransform()
75
- ctx.scale(highResScaleFactor, highResScaleFactor)
76
- ctx.clearRect(0, 0, blockSize, height)
77
- ctx.translate(-offsetX, 0)
78
- ctx.textAlign = 'center'
79
- ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
80
-
81
- const xStart = Math.max(0, Math.floor(offsetX / colWidth))
82
- ctx.fillStyle = 'goldenrod'
83
- layout.rectangles.forEach(value => {
84
- const { minX, maxX, minY, maxY } = value
85
-
86
- const x1 = (minX - xStart) * colWidth + offsetX - (offsetX % colWidth)
87
- const x2 = (maxX - xStart) * colWidth + offsetX - (offsetX % colWidth)
88
-
89
- if (x2 - x1 > 0) {
90
- ctx.fillRect(x1, minY, x2 - x1, (maxY - minY) / 2)
91
- }
92
- })
93
- }, [
94
- associatedRowName,
95
- blockSize,
96
- colWidth,
97
- layout.rectangles,
98
- model,
99
- rowHeight,
100
- height,
101
- offsetX,
102
- highResScaleFactor,
103
- features,
104
- blanks,
105
- ])
106
-
107
- useEffect(() => {
108
- if (!labelRef.current) {
109
- return
110
- }
111
-
112
- const ctx = labelRef.current.getContext('2d')
113
- if (!ctx) {
114
- return
115
- }
116
-
117
- // this logic is very similar to MSACanvas
118
- ctx.resetTransform()
119
- ctx.scale(highResScaleFactor, highResScaleFactor)
120
- ctx.clearRect(0, 0, blockSize, height)
121
- ctx.translate(-offsetX, 0)
122
- ctx.textAlign = 'center'
123
- ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
124
-
125
- ctx.fillStyle = 'black'
126
- ctx.textAlign = 'left'
127
- layout.rectangles.forEach(value => {
128
- const { minX, maxX, maxY, minY } = value
129
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
- const feature = value.data as any
131
-
132
- const x1 = minX * colWidth
133
- const x2 = maxX * colWidth
134
-
135
- if (x2 - x1 > 0) {
136
- const note = feature.attributes?.Note?.[0]
137
- const name = feature.attributes?.Name?.[0]
138
- const type = feature.type
139
- ctx.fillText(
140
- [type, name, note].filter(f => !!f).join(' - '),
141
- Math.max(Math.min(-scrollX, x2), x1),
142
- minY + (maxY - minY),
143
- )
144
- }
145
- })
146
- }, [
147
- blockSize,
148
- colWidth,
149
- scrollX,
150
- highResScaleFactor,
151
- height,
152
- layout.rectangles,
153
- offsetX,
154
- features,
155
- model,
156
- rowHeight,
157
- blanks,
158
- ])
159
-
160
- return !features ? null : (
161
- <>
162
- <canvas
163
- ref={ref}
164
- height={height * highResScaleFactor}
165
- width={blockSize * highResScaleFactor}
166
- style={{
167
- position: 'absolute',
168
- left: scrollX + offsetX,
169
- width: blockSize,
170
- height,
171
- }}
172
- />
173
- <canvas
174
- ref={labelRef}
175
- height={height * highResScaleFactor}
176
- width={blockSize * highResScaleFactor}
177
- style={{
178
- position: 'absolute',
179
- left: scrollX + offsetX,
180
- width: blockSize,
181
- height,
182
- }}
183
- />
184
- <canvas
185
- ref={mouseoverRef}
186
- height={height * highResScaleFactor}
187
- width={blockSize * highResScaleFactor}
188
- style={{
189
- position: 'absolute',
190
- left: scrollX + offsetX,
191
- width: blockSize,
192
- height,
193
- }}
194
- />
195
- </>
196
- )
197
- })
198
- const AnnotationTrack = observer(function ({
8
+ const BoxTrack = observer(function ({
199
9
  model,
200
10
  track,
201
11
  }: {
@@ -203,9 +13,7 @@ const AnnotationTrack = observer(function ({
203
13
  track: IBoxTrack
204
14
  }) {
205
15
  const { blocksX, msaAreaWidth } = model
206
- const {
207
- model: { height },
208
- } = track
16
+ const { height } = track.model
209
17
  return (
210
18
  <div
211
19
  style={{
@@ -216,10 +24,10 @@ const AnnotationTrack = observer(function ({
216
24
  }}
217
25
  >
218
26
  {blocksX.map(bx => (
219
- <AnnotationBlock track={track} key={bx} model={model} offsetX={bx} />
27
+ <BoxTrackBlock track={track} key={bx} model={model} offsetX={bx} />
220
28
  ))}
221
29
  </div>
222
30
  )
223
31
  })
224
32
 
225
- export default AnnotationTrack
33
+ export default BoxTrack
@@ -0,0 +1,198 @@
1
+ import React, { useRef, useMemo, useEffect } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
4
+
5
+ // locals
6
+ import { IBoxTrack, MsaViewModel } from '../model'
7
+ import Layout from '../layout'
8
+
9
+ interface Feat {
10
+ start: number
11
+ end: number
12
+ }
13
+
14
+ const BoxTrackBlock = observer(function ({
15
+ track,
16
+ model,
17
+ offsetX,
18
+ }: {
19
+ track: IBoxTrack
20
+ model: MsaViewModel
21
+ offsetX: number
22
+ }) {
23
+ const {
24
+ blockSize,
25
+ colWidth,
26
+ blanks,
27
+ rowHeight,
28
+ highResScaleFactor,
29
+ scrollX,
30
+ } = model
31
+ const { height, features, associatedRowName } = track.model
32
+
33
+ const feats: Feat[] = isStateTreeNode(features)
34
+ ? // @ts-expect-error
35
+ getSnapshot(features)
36
+ : features
37
+
38
+ const layout = useMemo(() => {
39
+ const temp = new Layout()
40
+
41
+ feats?.forEach((feature, index) => {
42
+ const { start, end } = feature
43
+ if (associatedRowName) {
44
+ const s = model.rowSpecificBpToPx(associatedRowName, start - 1)
45
+ const e = model.rowSpecificBpToPx(associatedRowName, end)
46
+ temp.addRect(`${index}`, s, e, rowHeight, feature)
47
+ } else {
48
+ const s = model.globalBpToPx(start - 1)
49
+ const e = model.globalBpToPx(end)
50
+ temp.addRect(`${index}`, s, e, rowHeight, feature)
51
+ }
52
+ })
53
+ return temp
54
+
55
+ // might convert to autorun based drawing
56
+ // eslint-disable-next-line react-hooks/exhaustive-deps
57
+ }, [rowHeight, feats, associatedRowName, model, blanks])
58
+
59
+ const ref = useRef<HTMLCanvasElement>(null)
60
+ const labelRef = useRef<HTMLCanvasElement>(null)
61
+ const mouseoverRef = useRef<HTMLCanvasElement>(null)
62
+
63
+ useEffect(() => {
64
+ if (!ref.current) {
65
+ return
66
+ }
67
+
68
+ const ctx = ref.current.getContext('2d')
69
+ if (!ctx) {
70
+ return
71
+ }
72
+
73
+ ctx.resetTransform()
74
+ ctx.scale(highResScaleFactor, highResScaleFactor)
75
+ ctx.clearRect(0, 0, blockSize, height)
76
+ ctx.translate(-offsetX, 0)
77
+ ctx.textAlign = 'center'
78
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
79
+
80
+ const xStart = Math.max(0, Math.floor(offsetX / colWidth))
81
+ ctx.fillStyle = 'goldenrod'
82
+ layout.rectangles.forEach(value => {
83
+ const { minX, maxX, minY, maxY } = value
84
+
85
+ const x1 = (minX - xStart) * colWidth + offsetX - (offsetX % colWidth)
86
+ const x2 = (maxX - xStart) * colWidth + offsetX - (offsetX % colWidth)
87
+
88
+ if (x2 - x1 > 0) {
89
+ ctx.fillRect(x1, minY, x2 - x1, (maxY - minY) / 2)
90
+ }
91
+ })
92
+ }, [
93
+ associatedRowName,
94
+ blockSize,
95
+ colWidth,
96
+ layout.rectangles,
97
+ model,
98
+ rowHeight,
99
+ height,
100
+ offsetX,
101
+ highResScaleFactor,
102
+ features,
103
+ blanks,
104
+ ])
105
+
106
+ useEffect(() => {
107
+ if (!labelRef.current) {
108
+ return
109
+ }
110
+
111
+ const ctx = labelRef.current.getContext('2d')
112
+ if (!ctx) {
113
+ return
114
+ }
115
+
116
+ // this logic is very similar to MSACanvas
117
+ ctx.resetTransform()
118
+ ctx.scale(highResScaleFactor, highResScaleFactor)
119
+ ctx.clearRect(0, 0, blockSize, height)
120
+ ctx.translate(-offsetX, 0)
121
+ ctx.textAlign = 'center'
122
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
123
+
124
+ ctx.fillStyle = 'black'
125
+ ctx.textAlign = 'left'
126
+ for (const value of layout.rectangles.values()) {
127
+ const { minX, maxX, maxY, minY, data } = value
128
+
129
+ const x1 = minX * colWidth
130
+ const x2 = maxX * colWidth
131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
+ const feature = data as any
133
+
134
+ if (x2 - x1 > 0) {
135
+ const note = feature.attributes?.Note?.[0]
136
+ const name = feature.attributes?.Name?.[0]
137
+ const type = feature.type
138
+ ctx.fillText(
139
+ [type, name, note].filter(f => !!f).join(' - '),
140
+ Math.max(Math.min(-scrollX, x2), x1),
141
+ minY + (maxY - minY),
142
+ )
143
+ }
144
+ }
145
+ }, [
146
+ blockSize,
147
+ colWidth,
148
+ scrollX,
149
+ highResScaleFactor,
150
+ height,
151
+ layout.rectangles,
152
+ offsetX,
153
+ features,
154
+ model,
155
+ rowHeight,
156
+ blanks,
157
+ ])
158
+
159
+ return !features ? null : (
160
+ <>
161
+ <canvas
162
+ ref={ref}
163
+ height={height * highResScaleFactor}
164
+ width={blockSize * highResScaleFactor}
165
+ style={{
166
+ position: 'absolute',
167
+ left: scrollX + offsetX,
168
+ width: blockSize,
169
+ height,
170
+ }}
171
+ />
172
+ <canvas
173
+ ref={labelRef}
174
+ height={height * highResScaleFactor}
175
+ width={blockSize * highResScaleFactor}
176
+ style={{
177
+ position: 'absolute',
178
+ left: scrollX + offsetX,
179
+ width: blockSize,
180
+ height,
181
+ }}
182
+ />
183
+ <canvas
184
+ ref={mouseoverRef}
185
+ height={height * highResScaleFactor}
186
+ width={blockSize * highResScaleFactor}
187
+ style={{
188
+ position: 'absolute',
189
+ left: scrollX + offsetX,
190
+ width: blockSize,
191
+ height,
192
+ }}
193
+ />
194
+ </>
195
+ )
196
+ })
197
+
198
+ export default BoxTrackBlock