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.
Files changed (47) hide show
  1. package/bundle/index.js +45 -31
  2. package/dist/calculateBlocks.d.ts +12 -0
  3. package/dist/calculateBlocks.js +23 -0
  4. package/dist/calculateBlocks.js.map +1 -0
  5. package/dist/components/ExportSVGDialog.d.ts +6 -0
  6. package/dist/components/ExportSVGDialog.js +26 -0
  7. package/dist/components/ExportSVGDialog.js.map +1 -0
  8. package/dist/components/MSAPanel/MSABlock.js +2 -2
  9. package/dist/components/MSAPanel/MSABlock.js.map +1 -1
  10. package/dist/components/MSAPanel/renderMSABlock.d.ts +4 -1
  11. package/dist/components/MSAPanel/renderMSABlock.js +8 -6
  12. package/dist/components/MSAPanel/renderMSABlock.js.map +1 -1
  13. package/dist/components/MSAPanel/renderMSAMouseover.js +0 -1
  14. package/dist/components/MSAPanel/renderMSAMouseover.js.map +1 -1
  15. package/dist/components/MinimapSVG.d.ts +6 -0
  16. package/dist/components/MinimapSVG.js +25 -0
  17. package/dist/components/MinimapSVG.js.map +1 -0
  18. package/dist/components/TreePanel/TreeCanvasBlock.js +0 -1
  19. package/dist/components/TreePanel/TreeCanvasBlock.js.map +1 -1
  20. package/dist/components/TreePanel/renderTreeCanvas.d.ts +13 -8
  21. package/dist/components/TreePanel/renderTreeCanvas.js +43 -17
  22. package/dist/components/TreePanel/renderTreeCanvas.js.map +1 -1
  23. package/dist/components/ZoomControls.js +15 -14
  24. package/dist/components/ZoomControls.js.map +1 -1
  25. package/dist/model.d.ts +72 -66
  26. package/dist/model.js +81 -70
  27. package/dist/model.js.map +1 -1
  28. package/dist/parsers/ClustalMSA.d.ts +18 -5
  29. package/dist/renderToSvg.d.ts +6 -0
  30. package/dist/renderToSvg.js +62 -0
  31. package/dist/renderToSvg.js.map +1 -0
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/package.json +4 -1
  35. package/src/calculateBlocks.ts +58 -0
  36. package/src/components/ExportSVGDialog.tsx +61 -0
  37. package/src/components/MSAPanel/MSABlock.tsx +2 -2
  38. package/src/components/MSAPanel/renderMSABlock.ts +14 -6
  39. package/src/components/MSAPanel/renderMSAMouseover.ts +0 -1
  40. package/src/components/MinimapSVG.tsx +57 -0
  41. package/src/components/TreePanel/TreeCanvasBlock.tsx +0 -1
  42. package/src/components/TreePanel/renderTreeCanvas.ts +53 -17
  43. package/src/components/ZoomControls.tsx +21 -13
  44. package/src/declare.d.ts +1 -0
  45. package/src/model.ts +86 -88
  46. package/src/renderToSvg.tsx +102 -0
  47. 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.0.3'
1
+ export const version = '3.1.1'