react-msaview 3.0.3 → 3.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.
- package/bundle/index.js +45 -31
- package/dist/calculateBlocks.d.ts +12 -0
- package/dist/calculateBlocks.js +23 -0
- package/dist/calculateBlocks.js.map +1 -0
- package/dist/components/ExportSVGDialog.d.ts +6 -0
- package/dist/components/ExportSVGDialog.js +26 -0
- package/dist/components/ExportSVGDialog.js.map +1 -0
- package/dist/components/MSAPanel/MSABlock.js +2 -2
- package/dist/components/MSAPanel/MSABlock.js.map +1 -1
- package/dist/components/MSAPanel/renderMSABlock.d.ts +4 -1
- package/dist/components/MSAPanel/renderMSABlock.js +8 -6
- package/dist/components/MSAPanel/renderMSABlock.js.map +1 -1
- package/dist/components/MSAPanel/renderMSAMouseover.js +0 -1
- package/dist/components/MSAPanel/renderMSAMouseover.js.map +1 -1
- package/dist/components/MinimapSVG.d.ts +6 -0
- package/dist/components/MinimapSVG.js +25 -0
- package/dist/components/MinimapSVG.js.map +1 -0
- package/dist/components/TreePanel/TreeCanvasBlock.js +0 -1
- package/dist/components/TreePanel/TreeCanvasBlock.js.map +1 -1
- package/dist/components/TreePanel/renderTreeCanvas.d.ts +13 -8
- package/dist/components/TreePanel/renderTreeCanvas.js +43 -17
- package/dist/components/TreePanel/renderTreeCanvas.js.map +1 -1
- package/dist/components/ZoomControls.js +15 -14
- package/dist/components/ZoomControls.js.map +1 -1
- package/dist/model.d.ts +72 -66
- package/dist/model.js +81 -70
- package/dist/model.js.map +1 -1
- package/dist/parsers/ClustalMSA.d.ts +18 -5
- package/dist/renderToSvg.d.ts +6 -0
- package/dist/renderToSvg.js +62 -0
- package/dist/renderToSvg.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -1
- package/src/calculateBlocks.ts +58 -0
- package/src/components/ExportSVGDialog.tsx +61 -0
- package/src/components/MSAPanel/MSABlock.tsx +2 -2
- package/src/components/MSAPanel/renderMSABlock.ts +14 -6
- package/src/components/MSAPanel/renderMSAMouseover.ts +0 -1
- package/src/components/MinimapSVG.tsx +57 -0
- package/src/components/TreePanel/TreeCanvasBlock.tsx +0 -1
- package/src/components/TreePanel/renderTreeCanvas.ts +53 -17
- package/src/components/ZoomControls.tsx +21 -13
- package/src/declare.d.ts +1 -0
- package/src/model.ts +86 -88
- package/src/renderToSvg.tsx +102 -0
- package/src/version.ts +1 -1
package/src/model.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Instance, cast, types, addDisposer, SnapshotIn } from 'mobx-state-tree'
|
|
|
4
4
|
import { hierarchy, cluster, HierarchyNode } from 'd3-hierarchy'
|
|
5
5
|
import { ascending } from 'd3-array'
|
|
6
6
|
import Stockholm from 'stockholm-js'
|
|
7
|
+
import { saveAs } from 'file-saver'
|
|
7
8
|
// jbrowse
|
|
8
9
|
import { FileLocation, ElementId } from '@jbrowse/core/util/types/mst'
|
|
9
10
|
import { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
@@ -33,6 +34,9 @@ import colorSchemes from './colorSchemes'
|
|
|
33
34
|
import { UniprotTrack } from './UniprotTrack'
|
|
34
35
|
import { StructureModel } from './StructureModel'
|
|
35
36
|
import { DialogQueueSessionMixin } from './DialogQueue'
|
|
37
|
+
import { renderToSvg } from './renderToSvg'
|
|
38
|
+
import { Theme } from '@mui/material'
|
|
39
|
+
import { blocksX, blocksY } from './calculateBlocks'
|
|
36
40
|
|
|
37
41
|
export interface RowDetails {
|
|
38
42
|
[key: string]: unknown
|
|
@@ -591,85 +595,7 @@ const model = types
|
|
|
591
595
|
},
|
|
592
596
|
}))
|
|
593
597
|
|
|
594
|
-
.views(self => {
|
|
595
|
-
let oldBlocksX: number[] = []
|
|
596
|
-
let oldBlocksY: number[] = []
|
|
597
|
-
let oldValX = 0
|
|
598
|
-
let oldValY = 0
|
|
599
|
-
return {
|
|
600
|
-
/**
|
|
601
|
-
* #getter
|
|
602
|
-
*/
|
|
603
|
-
get initialized() {
|
|
604
|
-
return (
|
|
605
|
-
(self.data.msa ||
|
|
606
|
-
self.data.tree ||
|
|
607
|
-
self.msaFilehandle ||
|
|
608
|
-
self.treeFilehandle) &&
|
|
609
|
-
!self.error
|
|
610
|
-
)
|
|
611
|
-
},
|
|
612
|
-
/**
|
|
613
|
-
* #getter
|
|
614
|
-
*/
|
|
615
|
-
get blocksX() {
|
|
616
|
-
const { scrollX, blockSize: size, colWidth } = self
|
|
617
|
-
const ret = -(size * Math.floor(scrollX / size)) - size
|
|
618
|
-
|
|
619
|
-
const b = []
|
|
620
|
-
for (let i = ret; i < ret + size * 3; i += size) {
|
|
621
|
-
if (i + size > 0) {
|
|
622
|
-
b.push(i)
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
if (
|
|
626
|
-
JSON.stringify(b) !== JSON.stringify(oldBlocksX) ||
|
|
627
|
-
colWidth !== oldValX
|
|
628
|
-
) {
|
|
629
|
-
oldBlocksX = b
|
|
630
|
-
oldValX = colWidth
|
|
631
|
-
}
|
|
632
|
-
return oldBlocksX
|
|
633
|
-
},
|
|
634
|
-
/**
|
|
635
|
-
* #getter
|
|
636
|
-
*/
|
|
637
|
-
get blocksY() {
|
|
638
|
-
const { scrollY, blockSize: size, rowHeight } = self
|
|
639
|
-
const ret = -(size * Math.floor(scrollY / size)) - 2 * size
|
|
640
|
-
|
|
641
|
-
const b = []
|
|
642
|
-
for (let i = ret; i < ret + size * 3; i += size) {
|
|
643
|
-
if (i + size > 0) {
|
|
644
|
-
b.push(i)
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
if (
|
|
648
|
-
JSON.stringify(b) !== JSON.stringify(oldBlocksY) ||
|
|
649
|
-
rowHeight !== oldValY
|
|
650
|
-
) {
|
|
651
|
-
oldBlocksY = b
|
|
652
|
-
oldValY = rowHeight
|
|
653
|
-
}
|
|
654
|
-
return oldBlocksY
|
|
655
|
-
},
|
|
656
|
-
}
|
|
657
|
-
})
|
|
658
598
|
.views(self => ({
|
|
659
|
-
/**
|
|
660
|
-
* #getter
|
|
661
|
-
*/
|
|
662
|
-
get blocks2d() {
|
|
663
|
-
return self.blocksY.flatMap(by => self.blocksX.map(bx => [bx, by]))
|
|
664
|
-
},
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* #getter
|
|
668
|
-
*/
|
|
669
|
-
get done() {
|
|
670
|
-
return self.initialized && (self.data.msa || self.data.tree)
|
|
671
|
-
},
|
|
672
|
-
|
|
673
599
|
/**
|
|
674
600
|
* #getter
|
|
675
601
|
*/
|
|
@@ -930,6 +856,78 @@ const model = types
|
|
|
930
856
|
return this.root.leaves().length * self.rowHeight
|
|
931
857
|
},
|
|
932
858
|
}))
|
|
859
|
+
.views(self => ({
|
|
860
|
+
/**
|
|
861
|
+
* #getter
|
|
862
|
+
*/
|
|
863
|
+
get totalWidth() {
|
|
864
|
+
return self.numColumns * self.colWidth
|
|
865
|
+
},
|
|
866
|
+
}))
|
|
867
|
+
|
|
868
|
+
.views(self => ({
|
|
869
|
+
/**
|
|
870
|
+
* #getter
|
|
871
|
+
*/
|
|
872
|
+
get initialized() {
|
|
873
|
+
return (
|
|
874
|
+
(self.data.msa ||
|
|
875
|
+
self.data.tree ||
|
|
876
|
+
self.msaFilehandle ||
|
|
877
|
+
self.treeFilehandle) &&
|
|
878
|
+
!self.error
|
|
879
|
+
)
|
|
880
|
+
},
|
|
881
|
+
/**
|
|
882
|
+
* #getter
|
|
883
|
+
*/
|
|
884
|
+
get blocksX() {
|
|
885
|
+
return blocksX({
|
|
886
|
+
viewportWidth: self.msaAreaWidth,
|
|
887
|
+
viewportX: -self.scrollX,
|
|
888
|
+
blockSize: self.blockSize,
|
|
889
|
+
mapWidth: self.totalWidth,
|
|
890
|
+
})
|
|
891
|
+
},
|
|
892
|
+
/**
|
|
893
|
+
* #getter
|
|
894
|
+
*/
|
|
895
|
+
get blocksY() {
|
|
896
|
+
return blocksY({
|
|
897
|
+
viewportHeight: self.height,
|
|
898
|
+
viewportY: -self.scrollY,
|
|
899
|
+
blockSize: self.blockSize,
|
|
900
|
+
mapHeight: self.totalHeight,
|
|
901
|
+
})
|
|
902
|
+
},
|
|
903
|
+
}))
|
|
904
|
+
.views(self => ({
|
|
905
|
+
/**
|
|
906
|
+
* #getter
|
|
907
|
+
*/
|
|
908
|
+
get blocks2d() {
|
|
909
|
+
const ret = []
|
|
910
|
+
for (const by of self.blocksY) {
|
|
911
|
+
for (const bx of self.blocksX) {
|
|
912
|
+
ret.push([bx, by])
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return ret
|
|
916
|
+
},
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* #getter
|
|
920
|
+
*/
|
|
921
|
+
get done() {
|
|
922
|
+
return self.initialized && (self.data.msa || self.data.tree)
|
|
923
|
+
},
|
|
924
|
+
/**
|
|
925
|
+
* #getter
|
|
926
|
+
*/
|
|
927
|
+
get maxScrollX() {
|
|
928
|
+
return -self.totalWidth + (self.msaAreaWidth - 100)
|
|
929
|
+
},
|
|
930
|
+
}))
|
|
933
931
|
.actions(self => ({
|
|
934
932
|
/**
|
|
935
933
|
* #action
|
|
@@ -959,22 +957,14 @@ const model = types
|
|
|
959
957
|
* #action
|
|
960
958
|
*/
|
|
961
959
|
doScrollX(deltaX: number) {
|
|
962
|
-
self.scrollX = clamp(
|
|
963
|
-
-(self.numColumns * self.colWidth) + (self.msaAreaWidth - 100),
|
|
964
|
-
self.scrollX + deltaX,
|
|
965
|
-
0,
|
|
966
|
-
)
|
|
960
|
+
self.scrollX = clamp(self.maxScrollX, self.scrollX + deltaX, 0)
|
|
967
961
|
},
|
|
968
962
|
|
|
969
963
|
/**
|
|
970
964
|
* #action
|
|
971
965
|
*/
|
|
972
966
|
setScrollX(n: number) {
|
|
973
|
-
self.scrollX = clamp(
|
|
974
|
-
-(self.numColumns * self.colWidth) + (self.msaAreaWidth - 100),
|
|
975
|
-
n,
|
|
976
|
-
0,
|
|
977
|
-
)
|
|
967
|
+
self.scrollX = clamp(self.maxScrollX, n, 0)
|
|
978
968
|
},
|
|
979
969
|
|
|
980
970
|
/**
|
|
@@ -1227,6 +1217,14 @@ const model = types
|
|
|
1227
1217
|
},
|
|
1228
1218
|
}))
|
|
1229
1219
|
.actions(self => ({
|
|
1220
|
+
/**
|
|
1221
|
+
* #action
|
|
1222
|
+
*/
|
|
1223
|
+
async exportSVG(opts: { theme: Theme; includeMinimap?: boolean }) {
|
|
1224
|
+
const html = await renderToSvg(self as MsaViewModel, opts)
|
|
1225
|
+
const blob = new Blob([html], { type: 'image/svg+xml' })
|
|
1226
|
+
saveAs(blob, 'image.svg')
|
|
1227
|
+
},
|
|
1230
1228
|
/**
|
|
1231
1229
|
* #action
|
|
1232
1230
|
* internal, used for drawing to canvas
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { when } from 'mobx'
|
|
4
|
+
import { renderToStaticMarkup } from '@jbrowse/core/util'
|
|
5
|
+
import { Theme } from '@mui/material'
|
|
6
|
+
|
|
7
|
+
// locals
|
|
8
|
+
import { MsaViewModel } from './model'
|
|
9
|
+
import { renderTreeCanvas } from './components/TreePanel/renderTreeCanvas'
|
|
10
|
+
import { renderMSABlock } from './components/MSAPanel/renderMSABlock'
|
|
11
|
+
import { colorContrast } from './util'
|
|
12
|
+
import MinimapSVG from './components/MinimapSVG'
|
|
13
|
+
|
|
14
|
+
// render LGV to SVG
|
|
15
|
+
export async function renderToSvg(
|
|
16
|
+
model: MsaViewModel,
|
|
17
|
+
opts: { theme: Theme; includeMinimap?: boolean },
|
|
18
|
+
) {
|
|
19
|
+
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
|
|
23
|
+
|
|
24
|
+
const { Context } = await import('svgcanvas')
|
|
25
|
+
const ctx1 = Context(width, height)
|
|
26
|
+
const ctx2 = Context(width, height)
|
|
27
|
+
const contrastScheme = colorContrast(colorScheme, theme)
|
|
28
|
+
renderTreeCanvas({
|
|
29
|
+
model,
|
|
30
|
+
offsetY: scrollY,
|
|
31
|
+
ctx: ctx1,
|
|
32
|
+
theme,
|
|
33
|
+
blockSizeYOverride: height,
|
|
34
|
+
highResScaleFactorOverride: 1,
|
|
35
|
+
})
|
|
36
|
+
renderMSABlock({
|
|
37
|
+
model,
|
|
38
|
+
offsetY: scrollY,
|
|
39
|
+
offsetX: -scrollX,
|
|
40
|
+
contrastScheme,
|
|
41
|
+
ctx: ctx2,
|
|
42
|
+
blockSizeXOverride: width - treeAreaWidth,
|
|
43
|
+
blockSizeYOverride: height,
|
|
44
|
+
highResScaleFactorOverride: 1,
|
|
45
|
+
})
|
|
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,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function MinimapWrapper({
|
|
81
|
+
model,
|
|
82
|
+
children,
|
|
83
|
+
}: {
|
|
84
|
+
model: MsaViewModel
|
|
85
|
+
children: React.ReactNode
|
|
86
|
+
}) {
|
|
87
|
+
const { minimapHeight, treeAreaWidth } = model
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
<g transform={`translate(${treeAreaWidth} 0)`}>
|
|
92
|
+
<MinimapSVG model={model} />
|
|
93
|
+
</g>
|
|
94
|
+
|
|
95
|
+
<g transform={`translate(0 ${minimapHeight})`}>{children}</g>
|
|
96
|
+
</>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function NullWrapper({ children }: { children: React.ReactNode }) {
|
|
101
|
+
return <>{children}</>
|
|
102
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '3.
|
|
1
|
+
export const version = '3.1.1'
|