react-msaview 3.1.2 → 3.1.4

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 (77) hide show
  1. package/bundle/index.js +31 -31
  2. package/dist/DialogQueue.js +2 -4
  3. package/dist/DialogQueue.js.map +1 -1
  4. package/dist/UniprotTrack.js +1 -1
  5. package/dist/UniprotTrack.js.map +1 -1
  6. package/dist/components/BoxTrackBlock.js +3 -4
  7. package/dist/components/BoxTrackBlock.js.map +1 -1
  8. package/dist/components/ExportSVGDialog.js +17 -4
  9. package/dist/components/ExportSVGDialog.js.map +1 -1
  10. package/dist/components/ImportForm/util.js +1 -0
  11. package/dist/components/ImportForm/util.js.map +1 -1
  12. package/dist/components/Loading.d.ts +6 -0
  13. package/dist/components/Loading.js +12 -0
  14. package/dist/components/Loading.js.map +1 -0
  15. package/dist/components/MSAPanel/MSABlock.js +3 -3
  16. package/dist/components/MSAPanel/MSABlock.js.map +1 -1
  17. package/dist/components/MSAPanel/MSAMouseoverCanvas.js +7 -9
  18. package/dist/components/MSAPanel/MSAMouseoverCanvas.js.map +1 -1
  19. package/dist/components/MSAPanel/index.js +1 -1
  20. package/dist/components/MSAPanel/index.js.map +1 -1
  21. package/dist/components/MSAPanel/renderMSABlock.d.ts +3 -1
  22. package/dist/components/MSAPanel/renderMSABlock.js +10 -10
  23. package/dist/components/MSAPanel/renderMSABlock.js.map +1 -1
  24. package/dist/components/MSAPanel/renderMSAMouseover.js +5 -8
  25. package/dist/components/MSAPanel/renderMSAMouseover.js.map +1 -1
  26. package/dist/components/MSAView.js +20 -16
  27. package/dist/components/MSAView.js.map +1 -1
  28. package/dist/components/TextTrack.js +1 -1
  29. package/dist/components/TextTrack.js.map +1 -1
  30. package/dist/components/TreePanel/TreeCanvasBlock.js +11 -13
  31. package/dist/components/TreePanel/TreeCanvasBlock.js.map +1 -1
  32. package/dist/components/TreePanel/TreeNodeMenu.js +4 -6
  33. package/dist/components/TreePanel/TreeNodeMenu.js.map +1 -1
  34. package/dist/components/TreePanel/renderTreeCanvas.js +12 -13
  35. package/dist/components/TreePanel/renderTreeCanvas.js.map +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/layout.js +5 -0
  39. package/dist/layout.js.map +1 -1
  40. package/dist/measureTextCanvas.d.ts +1 -0
  41. package/dist/measureTextCanvas.js +13 -0
  42. package/dist/measureTextCanvas.js.map +1 -0
  43. package/dist/model.d.ts +53 -47
  44. package/dist/model.js +20 -40
  45. package/dist/model.js.map +1 -1
  46. package/dist/parseNewick.js +1 -2
  47. package/dist/parseNewick.js.map +1 -1
  48. package/dist/parsers/ClustalMSA.js +3 -4
  49. package/dist/parsers/ClustalMSA.js.map +1 -1
  50. package/dist/parsers/FastaMSA.js +3 -4
  51. package/dist/parsers/FastaMSA.js.map +1 -1
  52. package/dist/parsers/StockholmMSA.js +13 -19
  53. package/dist/parsers/StockholmMSA.js.map +1 -1
  54. package/dist/renderToSvg.d.ts +1 -0
  55. package/dist/renderToSvg.js +78 -22
  56. package/dist/renderToSvg.js.map +1 -1
  57. package/dist/util.js +7 -4
  58. package/dist/util.js.map +1 -1
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +1 -1
  61. package/package.json +1 -23
  62. package/src/components/ExportSVGDialog.tsx +48 -13
  63. package/src/components/ImportForm/util.ts +1 -0
  64. package/src/components/Loading.tsx +27 -0
  65. package/src/components/MSAPanel/MSABlock.tsx +2 -1
  66. package/src/components/MSAPanel/MSAMouseoverCanvas.tsx +6 -6
  67. package/src/components/MSAPanel/index.tsx +2 -2
  68. package/src/components/MSAPanel/renderMSABlock.ts +10 -1
  69. package/src/components/MSAPanel/renderMSAMouseover.ts +3 -17
  70. package/src/components/MSAView.tsx +28 -30
  71. package/src/components/TreePanel/TreeCanvasBlock.tsx +5 -6
  72. package/src/components/TreePanel/renderTreeCanvas.ts +4 -6
  73. package/src/index.ts +1 -1
  74. package/src/measureTextCanvas.ts +14 -0
  75. package/src/model.ts +14 -16
  76. package/src/renderToSvg.tsx +156 -40
  77. package/src/version.ts +1 -1
@@ -42,8 +42,7 @@ const TreeCanvasBlock = observer(function ({
42
42
  const [toggleNodeMenu, setToggleNodeMenu] = useState<TooltipData>()
43
43
  const [hoverElt, setHoverElt] = useState<ClickEntry>()
44
44
 
45
- const { scrollY, treeAreaWidth, margin, blockSize, highResScaleFactor } =
46
- model
45
+ const { scrollY, treeAreaWidth, blockSize, highResScaleFactor } = model
47
46
 
48
47
  const width = treeAreaWidth + padding
49
48
  const height = blockSize
@@ -83,7 +82,7 @@ const TreeCanvasBlock = observer(function ({
83
82
 
84
83
  ctx.resetTransform()
85
84
  ctx.clearRect(0, 0, treeAreaWidth + padding, blockSize)
86
- ctx.translate(margin.left, -offsetY)
85
+ ctx.translate(0, -offsetY)
87
86
 
88
87
  if (hoverElt) {
89
88
  const { minX, maxX, minY, maxY } = hoverElt
@@ -91,10 +90,10 @@ const TreeCanvasBlock = observer(function ({
91
90
  ctx.fillStyle = 'rgba(0,0,0,0.1)'
92
91
  ctx.fillRect(minX, minY, maxX - minX, maxY - minY)
93
92
  }
94
- }, [hoverElt, margin.left, offsetY, blockSize, treeAreaWidth])
93
+ }, [hoverElt, offsetY, blockSize, treeAreaWidth])
95
94
 
96
95
  function hoverBranchClickMap(event: React.MouseEvent) {
97
- const x = event.nativeEvent.offsetX - margin.left
96
+ const x = event.nativeEvent.offsetX
98
97
  const y = event.nativeEvent.offsetY
99
98
 
100
99
  const [entry] = clickMap.current.search({
@@ -110,7 +109,7 @@ const TreeCanvasBlock = observer(function ({
110
109
  }
111
110
 
112
111
  function hoverNameClickMap(event: React.MouseEvent) {
113
- const x = event.nativeEvent.offsetX - margin.left
112
+ const x = event.nativeEvent.offsetX
114
113
  const y = event.nativeEvent.offsetY
115
114
  const [entry] = clickMap.current.search({
116
115
  minX: x,
@@ -133,7 +133,6 @@ export function renderTreeLabels({
133
133
  drawTree,
134
134
  structures,
135
135
  treeAreaWidth,
136
- margin,
137
136
  noTree,
138
137
  } = model
139
138
  const by = blockSizeYOverride || blockSize
@@ -179,7 +178,7 @@ export function renderTreeLabels({
179
178
  })
180
179
  } else if (labelsAlignRight) {
181
180
  const smallPadding = 2
182
- const offset = treeAreaWidth - smallPadding - margin.left
181
+ const offset = treeAreaWidth - smallPadding
183
182
  if (drawTree && !noTree) {
184
183
  const { width } = ctx.measureText(displayName)
185
184
  ctx.moveTo(xp + radius + 2, y)
@@ -188,8 +187,8 @@ export function renderTreeLabels({
188
187
  }
189
188
  ctx.fillText(displayName, offset, yp)
190
189
  clickMap?.insert({
191
- minX: treeAreaWidth - margin.left - width,
192
- maxX: treeAreaWidth - margin.left,
190
+ minX: treeAreaWidth - width,
191
+ maxX: treeAreaWidth,
193
192
  minY: yp - height,
194
193
  maxY: yp,
195
194
  name,
@@ -235,7 +234,6 @@ export function renderTreeCanvas({
235
234
  drawNodeBubbles,
236
235
  treeWidth,
237
236
  highResScaleFactor,
238
- margin,
239
237
  blockSize,
240
238
  fontSize,
241
239
  rowHeight,
@@ -254,7 +252,7 @@ export function renderTreeCanvas({
254
252
  nref < 0 ? -Infinity : highResScaleFactorOverride || highResScaleFactor
255
253
  ctx.scale(k, k)
256
254
  ctx.clearRect(0, 0, treeWidth + padding, by)
257
- ctx.translate(margin.left, -offsetY)
255
+ ctx.translate(0, -offsetY)
258
256
 
259
257
  const font = ctx.font
260
258
  ctx.font = font.replace(/\d+px/, `${fontSize}px`)
package/src/index.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { default as MSAView } from './components/MSAView'
1
+ export { default as MSAView } from './components/Loading'
2
2
  export { default as MSAModel, type MsaViewModel } from './model'
@@ -0,0 +1,14 @@
1
+ let canvasHandle: HTMLCanvasElement | undefined
2
+
3
+ export default function measureTextCanvas(text: string, fontSize: number) {
4
+ if (!canvasHandle) {
5
+ canvasHandle = document.createElement('canvas')
6
+ }
7
+
8
+ const ctx = canvasHandle.getContext('2d')
9
+ if (!ctx) {
10
+ throw new Error('no canvas context')
11
+ }
12
+ ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
13
+ return ctx.measureText(text).width
14
+ }
package/src/model.ts CHANGED
@@ -9,7 +9,7 @@ import { saveAs } from 'file-saver'
9
9
  import { FileLocation, ElementId } from '@jbrowse/core/util/types/mst'
10
10
  import { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
11
11
  import { openLocation } from '@jbrowse/core/util/io'
12
- import { measureText, notEmpty, sum } from '@jbrowse/core/util'
12
+ import { notEmpty, sum } from '@jbrowse/core/util'
13
13
  import BaseViewModel from '@jbrowse/core/pluggableElementTypes/models/BaseViewModel'
14
14
 
15
15
  // locals
@@ -37,6 +37,7 @@ import { DialogQueueSessionMixin } from './DialogQueue'
37
37
  import { renderToSvg } from './renderToSvg'
38
38
  import { Theme } from '@mui/material'
39
39
  import { blocksX, blocksY } from './calculateBlocks'
40
+ import measureTextCanvas from './measureTextCanvas'
40
41
 
41
42
  export interface RowDetails {
42
43
  [key: string]: unknown
@@ -44,13 +45,13 @@ export interface RowDetails {
44
45
  range?: { start: number; end: number }
45
46
  }
46
47
 
47
- interface BasicTrackModel {
48
+ export interface BasicTrackModel {
48
49
  id: string
49
50
  name: string
50
51
  associatedRowName?: string
51
52
  height: number
52
53
  }
53
- interface Structure {
54
+ export interface Structure {
54
55
  pdb: string
55
56
  startPos: number
56
57
  endPos: number
@@ -79,8 +80,9 @@ export interface IBoxTrack {
79
80
  model: BoxTrackModel
80
81
  }
81
82
 
82
- type BasicTrack = IBoxTrack | ITextTrack
83
- type StructureSnap = SnapshotIn<typeof StructureModel>
83
+ export type BasicTrack = IBoxTrack | ITextTrack
84
+
85
+ export type StructureSnap = SnapshotIn<typeof StructureModel>
84
86
 
85
87
  /**
86
88
  * #stateModel MsaView
@@ -336,14 +338,6 @@ const model = types
336
338
  */
337
339
  error: undefined as unknown,
338
340
 
339
- /**
340
- * #volatile
341
- */
342
- margin: {
343
- left: 20,
344
- top: 20,
345
- },
346
-
347
341
  /**
348
342
  * #volatile
349
343
  */
@@ -1008,7 +1002,7 @@ const model = types
1008
1002
  if (rowHeight > 5) {
1009
1003
  for (const node of hierarchy.leaves()) {
1010
1004
  x = Math.max(
1011
- measureText(
1005
+ measureTextCanvas(
1012
1006
  treeMetadata[node.data.name]?.genome || node.data.name,
1013
1007
  fontSize,
1014
1008
  ),
@@ -1220,7 +1214,11 @@ const model = types
1220
1214
  /**
1221
1215
  * #action
1222
1216
  */
1223
- async exportSVG(opts: { theme: Theme; includeMinimap?: boolean }) {
1217
+ async exportSVG(opts: {
1218
+ theme: Theme
1219
+ includeMinimap?: boolean
1220
+ exportType: string
1221
+ }) {
1224
1222
  const html = await renderToSvg(self as MsaViewModel, opts)
1225
1223
  const blob = new Blob([html], { type: 'image/svg+xml' })
1226
1224
  saveAs(blob, 'image.svg')
@@ -1288,7 +1286,7 @@ const model = types
1288
1286
  autorun(async () => {
1289
1287
  if (self.treeWidthMatchesArea) {
1290
1288
  self.setTreeWidth(
1291
- Math.max(50, self.treeAreaWidth - self.labelsWidth - 20),
1289
+ Math.max(50, self.treeAreaWidth - self.labelsWidth - 10),
1292
1290
  )
1293
1291
  }
1294
1292
  }),
@@ -11,23 +11,129 @@ import { renderMSABlock } from './components/MSAPanel/renderMSABlock'
11
11
  import { colorContrast } from './util'
12
12
  import MinimapSVG from './components/MinimapSVG'
13
13
 
14
- // render LGV to SVG
15
14
  export async function renderToSvg(
16
15
  model: MsaViewModel,
17
- opts: { theme: Theme; includeMinimap?: boolean },
16
+ opts: { theme: Theme; includeMinimap?: boolean; exportType: string },
18
17
  ) {
19
18
  await when(() => !!model.initialized)
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- const { width, height, scrollX, scrollY, colorScheme, treeAreaWidth } = model
22
- const { theme, includeMinimap } = opts
19
+ const { width, height, scrollX, scrollY } = model
20
+ const { exportType, theme, includeMinimap } = opts
23
21
 
22
+ if (exportType === 'entire') {
23
+ return render({
24
+ width: model.totalWidth + model.treeAreaWidth,
25
+ height: model.totalHeight,
26
+ theme,
27
+ model,
28
+ offsetY: 0,
29
+ offsetX: 0,
30
+ includeMinimap,
31
+ })
32
+ } else if (exportType === 'viewport') {
33
+ return render({
34
+ width,
35
+ height,
36
+ theme,
37
+ model,
38
+ offsetY: scrollY,
39
+ offsetX: -scrollX,
40
+ includeMinimap,
41
+ })
42
+ } else {
43
+ throw new Error('unknown export type')
44
+ }
45
+ }
46
+
47
+ async function render({
48
+ width,
49
+ height,
50
+ offsetX,
51
+ offsetY,
52
+ theme,
53
+ model,
54
+ includeMinimap,
55
+ }: {
56
+ width: number
57
+ height: number
58
+ offsetX: number
59
+ offsetY: number
60
+ theme: Theme
61
+ model: MsaViewModel
62
+ includeMinimap?: boolean
63
+ }) {
24
64
  const { Context } = await import('svgcanvas')
65
+ const Wrapper = includeMinimap ? MinimapWrapper : NullWrapper
66
+
67
+ return renderToStaticMarkup(
68
+ <SvgWrapper width={width} height={height}>
69
+ <Wrapper model={model}>
70
+ <CoreRendering
71
+ Context={Context}
72
+ model={model}
73
+ theme={theme}
74
+ offsetX={offsetX}
75
+ offsetY={offsetY}
76
+ width={width}
77
+ height={height}
78
+ />
79
+ </Wrapper>
80
+ </SvgWrapper>,
81
+ createRoot,
82
+ )
83
+ }
84
+
85
+ // function renderMultiline({ model }: { model: MsaViewModel }) {
86
+ // const { treeAreaWidth, height } = model
87
+ // const clipId = 'tree'
88
+ // return (
89
+ // <Wrapper model={model}>
90
+ // <defs>
91
+ // <clipPath id={clipId}>
92
+ // <rect x={0} y={0} width={treeAreaWidth} height={height} />
93
+ // </clipPath>
94
+ // </defs>
95
+ // <g
96
+ // clipPath={`url(#${clipId})`}
97
+ // /* eslint-disable-next-line react/no-danger */
98
+ // dangerouslySetInnerHTML={{ __html: ctx1.getSvg().innerHTML }}
99
+ // />
100
+ // <g
101
+ // transform={`translate(${treeAreaWidth} 0)`}
102
+ // /* eslint-disable-next-line react/no-danger */
103
+ // dangerouslySetInnerHTML={{ __html: ctx2.getSvg().innerHTML }}
104
+ // />
105
+ // </Wrapper>
106
+ // )
107
+ // }
108
+
109
+ function CoreRendering({
110
+ model,
111
+ theme,
112
+ width,
113
+ height,
114
+ offsetX,
115
+ offsetY,
116
+ Context,
117
+ }: {
118
+ model: MsaViewModel
119
+ theme: Theme
120
+ width: number
121
+ height: number
122
+ offsetX: number
123
+ offsetY: number
124
+ Context: (
125
+ width: number,
126
+ height: number,
127
+ ) => CanvasRenderingContext2D & { getSvg: () => { innerHTML: string } }
128
+ }) {
129
+ const clipId = 'tree'
130
+ const { treeAreaWidth, colorScheme } = model
131
+ const contrastScheme = colorContrast(colorScheme, theme)
25
132
  const ctx1 = Context(width, height)
26
133
  const ctx2 = Context(width, height)
27
- const contrastScheme = colorContrast(colorScheme, theme)
28
134
  renderTreeCanvas({
29
135
  model,
30
- offsetY: scrollY,
136
+ offsetY,
31
137
  ctx: ctx1,
32
138
  theme,
33
139
  blockSizeYOverride: height,
@@ -35,45 +141,33 @@ export async function renderToSvg(
35
141
  })
36
142
  renderMSABlock({
37
143
  model,
38
- offsetY: scrollY,
39
- offsetX: -scrollX,
144
+ theme,
145
+ offsetY,
146
+ offsetX,
40
147
  contrastScheme,
41
148
  ctx: ctx2,
42
149
  blockSizeXOverride: width - treeAreaWidth,
43
150
  blockSizeYOverride: height,
44
151
  highResScaleFactorOverride: 1,
45
152
  })
46
-
47
- const Wrapper = includeMinimap ? MinimapWrapper : NullWrapper
48
- const clipId = 'tree'
49
- // the xlink namespace is used for rendering <image> tag
50
- return renderToStaticMarkup(
51
- <svg
52
- width={width}
53
- height={height}
54
- xmlns="http://www.w3.org/2000/svg"
55
- xmlnsXlink="http://www.w3.org/1999/xlink"
56
- viewBox={[0, 0, width, height].toString()}
57
- >
58
- <Wrapper model={model}>
59
- <defs>
60
- <clipPath id={clipId}>
61
- <rect x={0} y={0} width={treeAreaWidth} height={height} />
62
- </clipPath>
63
- </defs>
64
- <g
65
- clipPath={`url(#${clipId})`}
66
- /* eslint-disable-next-line react/no-danger */
67
- dangerouslySetInnerHTML={{ __html: ctx1.getSvg().innerHTML }}
68
- />
69
- <g
70
- transform={`translate(${treeAreaWidth} 0)`}
71
- /* eslint-disable-next-line react/no-danger */
72
- dangerouslySetInnerHTML={{ __html: ctx2.getSvg().innerHTML }}
73
- />
74
- </Wrapper>
75
- </svg>,
76
- createRoot,
153
+ return (
154
+ <>
155
+ <defs>
156
+ <clipPath id={clipId}>
157
+ <rect x={0} y={0} width={treeAreaWidth} height={height} />
158
+ </clipPath>
159
+ </defs>
160
+ <g
161
+ clipPath={`url(#${clipId})`}
162
+ /* eslint-disable-next-line react/no-danger */
163
+ dangerouslySetInnerHTML={{ __html: ctx1.getSvg().innerHTML }}
164
+ />
165
+ <g
166
+ transform={`translate(${treeAreaWidth} 0)`}
167
+ /* eslint-disable-next-line react/no-danger */
168
+ dangerouslySetInnerHTML={{ __html: ctx2.getSvg().innerHTML }}
169
+ />
170
+ </>
77
171
  )
78
172
  }
79
173
 
@@ -97,6 +191,28 @@ function MinimapWrapper({
97
191
  )
98
192
  }
99
193
 
194
+ function SvgWrapper({
195
+ width,
196
+ height,
197
+ children,
198
+ }: {
199
+ width: number
200
+ height: number
201
+ children: React.ReactNode
202
+ }) {
203
+ return (
204
+ <svg
205
+ width={width}
206
+ height={height}
207
+ xmlns="http://www.w3.org/2000/svg"
208
+ xmlnsXlink="http://www.w3.org/1999/xlink"
209
+ viewBox={[0, 0, width, height].toString()}
210
+ >
211
+ {children}
212
+ </svg>
213
+ )
214
+ }
215
+
100
216
  function NullWrapper({ children }: { children: React.ReactNode }) {
101
217
  return <>{children}</>
102
218
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '3.1.2'
1
+ export const version = '3.1.4'