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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderToSvg.js","sourceRoot":"","sources":["../src/renderToSvg.tsx"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "3.
|
|
1
|
+
export declare const version = "3.1.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '3.
|
|
1
|
+
export const version = '3.1.1';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-msaview",
|
|
3
3
|
"author": "Colin",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.1.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"files": [
|
|
@@ -57,14 +57,17 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
59
59
|
"@typescript-eslint/parser": "^6.21.0",
|
|
60
|
+
"canvas2svg": "^1.0.16",
|
|
60
61
|
"clustal-js": "^2.0.0",
|
|
61
62
|
"colord": "^2.9.3",
|
|
62
63
|
"copy-to-clipboard": "^3.3.1",
|
|
63
64
|
"d3-array": "^3.2.3",
|
|
64
65
|
"d3-hierarchy": "^3.1.2",
|
|
66
|
+
"file-saver": "^2.0.5",
|
|
65
67
|
"normalize-wheel": "^1.0.1",
|
|
66
68
|
"rbush": "^3.0.1",
|
|
67
69
|
"stockholm-js": "^1.0.10",
|
|
70
|
+
"svgcanvas": "^2.5.0",
|
|
68
71
|
"tss-react": "^4.8.3"
|
|
69
72
|
}
|
|
70
73
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function blocksY({
|
|
2
|
+
mapHeight,
|
|
3
|
+
blockSize,
|
|
4
|
+
viewportY,
|
|
5
|
+
viewportHeight,
|
|
6
|
+
}: {
|
|
7
|
+
mapHeight: number
|
|
8
|
+
blockSize: number
|
|
9
|
+
viewportY: number
|
|
10
|
+
viewportHeight: number
|
|
11
|
+
}) {
|
|
12
|
+
const clampedViewportY = Math.max(
|
|
13
|
+
0,
|
|
14
|
+
Math.min(viewportY, mapHeight * blockSize - viewportHeight),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// Calculate visible tile ranges
|
|
18
|
+
const minTileY = Math.floor(clampedViewportY / blockSize)
|
|
19
|
+
const maxTileY = Math.floor(
|
|
20
|
+
(clampedViewportY + viewportHeight - 1) / blockSize,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// Generate list of visible tiles
|
|
24
|
+
const blocksY = []
|
|
25
|
+
for (let tileY = minTileY; tileY <= maxTileY; tileY++) {
|
|
26
|
+
blocksY.push(tileY * blockSize)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return blocksY
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function blocksX({
|
|
33
|
+
mapWidth,
|
|
34
|
+
blockSize,
|
|
35
|
+
viewportX,
|
|
36
|
+
viewportWidth,
|
|
37
|
+
}: {
|
|
38
|
+
mapWidth: number
|
|
39
|
+
blockSize: number
|
|
40
|
+
viewportX: number
|
|
41
|
+
viewportWidth: number
|
|
42
|
+
}) {
|
|
43
|
+
const clampedViewportX = Math.max(
|
|
44
|
+
0,
|
|
45
|
+
Math.min(viewportX, mapWidth - viewportWidth),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const minTileX = Math.floor(clampedViewportX / blockSize)
|
|
49
|
+
const maxTileX = Math.floor(
|
|
50
|
+
(clampedViewportX + viewportWidth - 1) / blockSize,
|
|
51
|
+
)
|
|
52
|
+
const blocksX = []
|
|
53
|
+
for (let tileX = minTileX; tileX <= maxTileX; tileX++) {
|
|
54
|
+
blocksX.push(tileX * blockSize)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return blocksX
|
|
58
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Dialog, ErrorMessage } from '@jbrowse/core/ui'
|
|
3
|
+
import {
|
|
4
|
+
DialogContent,
|
|
5
|
+
DialogActions,
|
|
6
|
+
Button,
|
|
7
|
+
Checkbox,
|
|
8
|
+
FormControlLabel,
|
|
9
|
+
useTheme,
|
|
10
|
+
Typography,
|
|
11
|
+
} from '@mui/material'
|
|
12
|
+
import { MsaViewModel } from '../model'
|
|
13
|
+
|
|
14
|
+
export default function ExportSVGDialog({
|
|
15
|
+
model,
|
|
16
|
+
onClose,
|
|
17
|
+
}: {
|
|
18
|
+
model: MsaViewModel
|
|
19
|
+
onClose: () => void
|
|
20
|
+
}) {
|
|
21
|
+
const [includeMinimap, setIncludeMinimap] = useState(true)
|
|
22
|
+
const [error, setError] = useState<unknown>()
|
|
23
|
+
const theme = useTheme()
|
|
24
|
+
return (
|
|
25
|
+
<Dialog onClose={() => onClose()} open title="Export SVG">
|
|
26
|
+
<DialogContent>
|
|
27
|
+
<Typography>Export SVG of current view</Typography>
|
|
28
|
+
{error ? <ErrorMessage error={error} /> : null}
|
|
29
|
+
<FormControlLabel
|
|
30
|
+
control={
|
|
31
|
+
<Checkbox
|
|
32
|
+
checked={includeMinimap}
|
|
33
|
+
onChange={event => setIncludeMinimap(event.target.checked)}
|
|
34
|
+
/>
|
|
35
|
+
}
|
|
36
|
+
label="Include minimap?"
|
|
37
|
+
/>
|
|
38
|
+
</DialogContent>
|
|
39
|
+
<DialogActions>
|
|
40
|
+
<Button
|
|
41
|
+
variant="contained"
|
|
42
|
+
color="primary"
|
|
43
|
+
onClick={async () => {
|
|
44
|
+
try {
|
|
45
|
+
await model.exportSVG({ theme, includeMinimap })
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error(e)
|
|
48
|
+
setError(e)
|
|
49
|
+
}
|
|
50
|
+
onClose()
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
Submit
|
|
54
|
+
</Button>
|
|
55
|
+
<Button variant="contained" color="secondary" onClick={() => onClose()}>
|
|
56
|
+
Cancel
|
|
57
|
+
</Button>
|
|
58
|
+
</DialogActions>
|
|
59
|
+
</Dialog>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -4,7 +4,7 @@ import { useTheme } from '@mui/material'
|
|
|
4
4
|
import { observer } from 'mobx-react'
|
|
5
5
|
|
|
6
6
|
// locals
|
|
7
|
-
import {
|
|
7
|
+
import { renderMSABlock } from './renderMSABlock'
|
|
8
8
|
import { MsaViewModel } from '../../model'
|
|
9
9
|
import { colorContrast } from '../../util'
|
|
10
10
|
|
|
@@ -40,7 +40,7 @@ const MSABlock = observer(function ({
|
|
|
40
40
|
return
|
|
41
41
|
}
|
|
42
42
|
return autorun(() => {
|
|
43
|
-
|
|
43
|
+
renderMSABlock({
|
|
44
44
|
ctx,
|
|
45
45
|
offsetX,
|
|
46
46
|
offsetY,
|
|
@@ -4,18 +4,24 @@ import { getClustalXColor, getPercentIdentityColor } from '../../colorSchemes'
|
|
|
4
4
|
import { NodeWithIdsAndLength } from '../../util'
|
|
5
5
|
import { HierarchyNode } from 'd3-hierarchy'
|
|
6
6
|
|
|
7
|
-
export function
|
|
7
|
+
export function renderMSABlock({
|
|
8
8
|
model,
|
|
9
9
|
offsetX,
|
|
10
10
|
offsetY,
|
|
11
11
|
contrastScheme,
|
|
12
12
|
ctx,
|
|
13
|
+
highResScaleFactorOverride,
|
|
14
|
+
blockSizeXOverride,
|
|
15
|
+
blockSizeYOverride,
|
|
13
16
|
}: {
|
|
14
17
|
offsetX: number
|
|
15
18
|
offsetY: number
|
|
16
19
|
model: MsaViewModel
|
|
17
20
|
contrastScheme: Record<string, string>
|
|
18
21
|
ctx: CanvasRenderingContext2D
|
|
22
|
+
highResScaleFactorOverride?: number
|
|
23
|
+
blockSizeXOverride?: number
|
|
24
|
+
blockSizeYOverride?: number
|
|
19
25
|
}) {
|
|
20
26
|
const {
|
|
21
27
|
hierarchy,
|
|
@@ -25,20 +31,22 @@ export function renderBlock({
|
|
|
25
31
|
fontSize,
|
|
26
32
|
highResScaleFactor,
|
|
27
33
|
} = model
|
|
34
|
+
const k = highResScaleFactorOverride || highResScaleFactor
|
|
35
|
+
const bx = blockSizeXOverride || blockSize
|
|
36
|
+
const by = blockSizeYOverride || blockSize
|
|
28
37
|
ctx.resetTransform()
|
|
29
|
-
ctx.scale(
|
|
30
|
-
ctx.clearRect(0, 0,
|
|
38
|
+
ctx.scale(k, k)
|
|
39
|
+
ctx.clearRect(0, 0, bx, by)
|
|
31
40
|
ctx.translate(-offsetX, rowHeight / 2 - offsetY)
|
|
32
41
|
ctx.textAlign = 'center'
|
|
33
42
|
ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
|
|
34
43
|
|
|
35
44
|
const leaves = hierarchy.leaves()
|
|
36
|
-
const b = blockSize
|
|
37
45
|
|
|
38
46
|
const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
|
|
39
|
-
const yEnd = Math.max(0, Math.ceil((offsetY +
|
|
47
|
+
const yEnd = Math.max(0, Math.ceil((offsetY + by + rowHeight) / rowHeight))
|
|
40
48
|
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
|
|
41
|
-
const xEnd = Math.max(0, Math.ceil((offsetX +
|
|
49
|
+
const xEnd = Math.max(0, Math.ceil((offsetX + bx) / colWidth))
|
|
42
50
|
const visibleLeaves = leaves.slice(yStart, yEnd)
|
|
43
51
|
|
|
44
52
|
drawTiles({
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { observer } from 'mobx-react'
|
|
3
|
+
import { MsaViewModel } from '../model'
|
|
4
|
+
|
|
5
|
+
const MinimapSVG = observer(function ({ model }: { model: MsaViewModel }) {
|
|
6
|
+
const {
|
|
7
|
+
scrollX,
|
|
8
|
+
msaAreaWidth: W,
|
|
9
|
+
minimapHeight: H,
|
|
10
|
+
colWidth,
|
|
11
|
+
numColumns,
|
|
12
|
+
} = model
|
|
13
|
+
|
|
14
|
+
const BAR_HEIGHT = 12
|
|
15
|
+
const H2 = H - 12
|
|
16
|
+
|
|
17
|
+
const unit = W / numColumns / colWidth
|
|
18
|
+
const left = -scrollX
|
|
19
|
+
const right = left + W
|
|
20
|
+
const s = left * unit
|
|
21
|
+
const e = right * unit
|
|
22
|
+
const fill = 'rgba(66, 119, 127, 0.3)'
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<rect
|
|
27
|
+
x={0}
|
|
28
|
+
y={0}
|
|
29
|
+
width={W}
|
|
30
|
+
height={BAR_HEIGHT}
|
|
31
|
+
stroke="#555"
|
|
32
|
+
fill="none"
|
|
33
|
+
/>
|
|
34
|
+
<rect
|
|
35
|
+
x={Math.max(0, s)}
|
|
36
|
+
y={0}
|
|
37
|
+
width={e - s}
|
|
38
|
+
height={BAR_HEIGHT}
|
|
39
|
+
fill={fill}
|
|
40
|
+
stroke="#555"
|
|
41
|
+
/>
|
|
42
|
+
<g transform={`translate(0 ${BAR_HEIGHT})`}>
|
|
43
|
+
<polygon
|
|
44
|
+
fill={fill}
|
|
45
|
+
points={[
|
|
46
|
+
[e, 0],
|
|
47
|
+
[s, 0],
|
|
48
|
+
[0, H2],
|
|
49
|
+
[W, H2],
|
|
50
|
+
].toString()}
|
|
51
|
+
/>
|
|
52
|
+
</g>
|
|
53
|
+
</>
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export default MinimapSVG
|
|
@@ -158,7 +158,6 @@ const TreeCanvasBlock = observer(function ({
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
const ret = hoverNameClickMap(event) || hoverBranchClickMap(event)
|
|
161
|
-
console.log({ ret })
|
|
162
161
|
ref.current.style.cursor = ret ? 'pointer' : 'default'
|
|
163
162
|
setHoverElt(hoverNameClickMap(event))
|
|
164
163
|
}}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import RBush from 'rbush'
|
|
2
|
+
import { Theme } from '@mui/material'
|
|
2
3
|
|
|
3
4
|
// locals
|
|
4
5
|
import { MsaViewModel } from '../../model'
|
|
5
|
-
import { Theme } from '@mui/material'
|
|
6
6
|
|
|
7
7
|
export const padding = 600
|
|
8
8
|
const extendBounds = 5
|
|
@@ -24,13 +24,16 @@ export function renderTree({
|
|
|
24
24
|
ctx,
|
|
25
25
|
model,
|
|
26
26
|
theme,
|
|
27
|
+
blockSizeYOverride,
|
|
27
28
|
}: {
|
|
28
29
|
offsetY: number
|
|
29
30
|
ctx: CanvasRenderingContext2D
|
|
30
31
|
model: MsaViewModel
|
|
31
32
|
theme: Theme
|
|
33
|
+
blockSizeYOverride?: number
|
|
32
34
|
}) {
|
|
33
35
|
const { hierarchy, showBranchLen, blockSize } = model
|
|
36
|
+
const by = blockSizeYOverride || blockSize
|
|
34
37
|
ctx.strokeStyle = theme.palette.text.primary
|
|
35
38
|
for (const { source, target } of hierarchy.links()) {
|
|
36
39
|
const y = showBranchLen ? 'len' : 'y'
|
|
@@ -44,7 +47,7 @@ export function renderTree({
|
|
|
44
47
|
// 1d line intersection to check if line crosses block at all, this is
|
|
45
48
|
// an optimization that allows us to skip drawing most tree links
|
|
46
49
|
// outside the block
|
|
47
|
-
if (offsetY +
|
|
50
|
+
if (offsetY + by >= y1 && y2 >= offsetY) {
|
|
48
51
|
ctx.beginPath()
|
|
49
52
|
ctx.moveTo(sx, sy)
|
|
50
53
|
ctx.lineTo(sx, ty)
|
|
@@ -59,14 +62,17 @@ export function renderNodeBubbles({
|
|
|
59
62
|
clickMap,
|
|
60
63
|
offsetY,
|
|
61
64
|
model,
|
|
65
|
+
blockSizeYOverride,
|
|
62
66
|
}: {
|
|
63
67
|
ctx: CanvasRenderingContext2D
|
|
64
|
-
clickMap
|
|
68
|
+
clickMap?: RBush<ClickEntry>
|
|
65
69
|
offsetY: number
|
|
66
70
|
model: MsaViewModel
|
|
67
71
|
theme: Theme
|
|
72
|
+
blockSizeYOverride?: number
|
|
68
73
|
}) {
|
|
69
74
|
const { hierarchy, showBranchLen, collapsed, blockSize } = model
|
|
75
|
+
const by = blockSizeYOverride || blockSize
|
|
70
76
|
for (const node of hierarchy.descendants()) {
|
|
71
77
|
const val = showBranchLen ? 'len' : 'y'
|
|
72
78
|
const {
|
|
@@ -80,7 +86,7 @@ export function renderNodeBubbles({
|
|
|
80
86
|
if (
|
|
81
87
|
branchset.length &&
|
|
82
88
|
y > offsetY - extendBounds &&
|
|
83
|
-
y < offsetY +
|
|
89
|
+
y < offsetY + by + extendBounds
|
|
84
90
|
) {
|
|
85
91
|
ctx.strokeStyle = 'black'
|
|
86
92
|
ctx.fillStyle = collapsed.includes(id) ? 'black' : 'white'
|
|
@@ -89,7 +95,7 @@ export function renderNodeBubbles({
|
|
|
89
95
|
ctx.fill()
|
|
90
96
|
ctx.stroke()
|
|
91
97
|
|
|
92
|
-
clickMap
|
|
98
|
+
clickMap?.insert({
|
|
93
99
|
minX: x - radius,
|
|
94
100
|
maxX: x - radius + d,
|
|
95
101
|
minY: y - radius,
|
|
@@ -108,12 +114,14 @@ export function renderTreeLabels({
|
|
|
108
114
|
offsetY,
|
|
109
115
|
ctx,
|
|
110
116
|
clickMap,
|
|
117
|
+
blockSizeYOverride,
|
|
111
118
|
}: {
|
|
112
119
|
model: MsaViewModel
|
|
113
120
|
offsetY: number
|
|
114
121
|
ctx: CanvasRenderingContext2D
|
|
115
|
-
clickMap
|
|
122
|
+
clickMap?: RBush<ClickEntry>
|
|
116
123
|
theme: Theme
|
|
124
|
+
blockSizeYOverride?: number
|
|
117
125
|
}) {
|
|
118
126
|
const {
|
|
119
127
|
rowHeight,
|
|
@@ -128,6 +136,7 @@ export function renderTreeLabels({
|
|
|
128
136
|
margin,
|
|
129
137
|
noTree,
|
|
130
138
|
} = model
|
|
139
|
+
const by = blockSizeYOverride || blockSize
|
|
131
140
|
if (labelsAlignRight) {
|
|
132
141
|
ctx.textAlign = 'right'
|
|
133
142
|
ctx.setLineDash([1, 3])
|
|
@@ -147,7 +156,7 @@ export function renderTreeLabels({
|
|
|
147
156
|
|
|
148
157
|
const displayName = treeMetadata[name]?.genome || name
|
|
149
158
|
|
|
150
|
-
if (y > offsetY - extendBounds && y < offsetY +
|
|
159
|
+
if (y > offsetY - extendBounds && y < offsetY + by + extendBounds) {
|
|
151
160
|
// note: +rowHeight/4 matches with -rowHeight/4 in msa
|
|
152
161
|
const yp = y + rowHeight / 4
|
|
153
162
|
const xp = showBranchLen ? len : x
|
|
@@ -160,7 +169,7 @@ export function renderTreeLabels({
|
|
|
160
169
|
|
|
161
170
|
if (!drawTree && !labelsAlignRight) {
|
|
162
171
|
ctx.fillText(displayName, 0, yp)
|
|
163
|
-
clickMap
|
|
172
|
+
clickMap?.insert({
|
|
164
173
|
minX: 0,
|
|
165
174
|
maxX: width,
|
|
166
175
|
minY: yp - height,
|
|
@@ -178,7 +187,7 @@ export function renderTreeLabels({
|
|
|
178
187
|
ctx.stroke()
|
|
179
188
|
}
|
|
180
189
|
ctx.fillText(displayName, offset, yp)
|
|
181
|
-
clickMap
|
|
190
|
+
clickMap?.insert({
|
|
182
191
|
minX: treeAreaWidth - margin.left - width,
|
|
183
192
|
maxX: treeAreaWidth - margin.left,
|
|
184
193
|
minY: yp - height,
|
|
@@ -188,7 +197,7 @@ export function renderTreeLabels({
|
|
|
188
197
|
})
|
|
189
198
|
} else {
|
|
190
199
|
ctx.fillText(displayName, xp + d, yp)
|
|
191
|
-
clickMap
|
|
200
|
+
clickMap?.insert({
|
|
192
201
|
minX: xp + d,
|
|
193
202
|
maxX: xp + d + width,
|
|
194
203
|
minY: yp - height,
|
|
@@ -208,14 +217,18 @@ export function renderTreeCanvas({
|
|
|
208
217
|
ctx,
|
|
209
218
|
offsetY,
|
|
210
219
|
theme,
|
|
220
|
+
highResScaleFactorOverride,
|
|
221
|
+
blockSizeYOverride,
|
|
211
222
|
}: {
|
|
212
223
|
model: MsaViewModel
|
|
213
224
|
offsetY: number
|
|
214
225
|
ctx: CanvasRenderingContext2D
|
|
215
|
-
clickMap
|
|
226
|
+
clickMap?: RBush<ClickEntry>
|
|
216
227
|
theme: Theme
|
|
228
|
+
highResScaleFactorOverride?: number
|
|
229
|
+
blockSizeYOverride?: number
|
|
217
230
|
}) {
|
|
218
|
-
clickMap
|
|
231
|
+
clickMap?.clear()
|
|
219
232
|
const {
|
|
220
233
|
noTree,
|
|
221
234
|
drawTree,
|
|
@@ -231,24 +244,47 @@ export function renderTreeCanvas({
|
|
|
231
244
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
232
245
|
nref,
|
|
233
246
|
} = model
|
|
247
|
+
const by = blockSizeYOverride || blockSize
|
|
234
248
|
|
|
235
249
|
ctx.resetTransform()
|
|
236
|
-
|
|
237
|
-
ctx.
|
|
250
|
+
const k = highResScaleFactorOverride || highResScaleFactor
|
|
251
|
+
ctx.scale(k, k)
|
|
252
|
+
ctx.clearRect(0, 0, treeWidth + padding, by)
|
|
238
253
|
ctx.translate(margin.left, -offsetY)
|
|
239
254
|
|
|
255
|
+
// console.log(ctx.font)
|
|
240
256
|
const font = ctx.font
|
|
241
257
|
ctx.font = font.replace(/\d+px/, `${fontSize}px`)
|
|
242
258
|
|
|
243
259
|
if (!noTree && drawTree) {
|
|
244
|
-
renderTree({
|
|
260
|
+
renderTree({
|
|
261
|
+
ctx,
|
|
262
|
+
offsetY,
|
|
263
|
+
model,
|
|
264
|
+
theme,
|
|
265
|
+
blockSizeYOverride,
|
|
266
|
+
})
|
|
245
267
|
|
|
246
268
|
if (drawNodeBubbles) {
|
|
247
|
-
renderNodeBubbles({
|
|
269
|
+
renderNodeBubbles({
|
|
270
|
+
ctx,
|
|
271
|
+
offsetY,
|
|
272
|
+
clickMap,
|
|
273
|
+
model,
|
|
274
|
+
theme,
|
|
275
|
+
blockSizeYOverride,
|
|
276
|
+
})
|
|
248
277
|
}
|
|
249
278
|
}
|
|
250
279
|
|
|
251
280
|
if (rowHeight >= 5) {
|
|
252
|
-
renderTreeLabels({
|
|
281
|
+
renderTreeLabels({
|
|
282
|
+
ctx,
|
|
283
|
+
offsetY,
|
|
284
|
+
model,
|
|
285
|
+
clickMap,
|
|
286
|
+
theme,
|
|
287
|
+
blockSizeYOverride,
|
|
288
|
+
})
|
|
253
289
|
}
|
|
254
290
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { Suspense, lazy, useState } from 'react'
|
|
2
2
|
import { IconButton } from '@mui/material'
|
|
3
3
|
import { observer } from 'mobx-react'
|
|
4
4
|
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
|
|
@@ -11,12 +11,16 @@ import MoreVert from '@mui/icons-material/MoreVert'
|
|
|
11
11
|
import ZoomIn from '@mui/icons-material/ZoomIn'
|
|
12
12
|
import ZoomOut from '@mui/icons-material/ZoomOut'
|
|
13
13
|
|
|
14
|
+
// lazies
|
|
15
|
+
const ExportSVGDialog = lazy(() => import('./ExportSVGDialog'))
|
|
16
|
+
|
|
14
17
|
const ZoomControls = observer(function ZoomControls({
|
|
15
18
|
model,
|
|
16
19
|
}: {
|
|
17
20
|
model: MsaViewModel
|
|
18
21
|
}) {
|
|
19
22
|
const { colWidth, rowHeight } = model
|
|
23
|
+
const [exportSvgDialogOpen, setExportSvgDialogOpen] = useState(false)
|
|
20
24
|
return (
|
|
21
25
|
<>
|
|
22
26
|
<IconButton
|
|
@@ -39,27 +43,19 @@ const ZoomControls = observer(function ZoomControls({
|
|
|
39
43
|
menuItems={[
|
|
40
44
|
{
|
|
41
45
|
label: 'Decrease row height',
|
|
42
|
-
onClick: () =>
|
|
43
|
-
model.setRowHeight(Math.max(1.5, rowHeight * 0.75))
|
|
44
|
-
},
|
|
46
|
+
onClick: () => model.setRowHeight(Math.max(1.5, rowHeight * 0.75)),
|
|
45
47
|
},
|
|
46
48
|
{
|
|
47
49
|
label: 'Increase row height',
|
|
48
|
-
onClick: () =>
|
|
49
|
-
model.setRowHeight(rowHeight * 1.5)
|
|
50
|
-
},
|
|
50
|
+
onClick: () => model.setRowHeight(rowHeight * 1.5),
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
label: 'Decrease col width',
|
|
54
|
-
onClick: () =>
|
|
55
|
-
model.setColWidth(Math.max(1, colWidth * 0.75))
|
|
56
|
-
},
|
|
54
|
+
onClick: () => model.setColWidth(Math.max(1, colWidth * 0.75)),
|
|
57
55
|
},
|
|
58
56
|
{
|
|
59
57
|
label: 'Increase col width',
|
|
60
|
-
onClick: () =>
|
|
61
|
-
model.setColWidth(colWidth * 1.5)
|
|
62
|
-
},
|
|
58
|
+
onClick: () => model.setColWidth(colWidth * 1.5),
|
|
63
59
|
},
|
|
64
60
|
{
|
|
65
61
|
label: 'Reset zoom to default',
|
|
@@ -68,10 +64,22 @@ const ZoomControls = observer(function ZoomControls({
|
|
|
68
64
|
model.setRowHeight(20)
|
|
69
65
|
},
|
|
70
66
|
},
|
|
67
|
+
{
|
|
68
|
+
label: 'Export SVG',
|
|
69
|
+
onClick: () => setExportSvgDialogOpen(true),
|
|
70
|
+
},
|
|
71
71
|
]}
|
|
72
72
|
>
|
|
73
73
|
<MoreVert />
|
|
74
74
|
</CascadingMenuButton>
|
|
75
|
+
{exportSvgDialogOpen ? (
|
|
76
|
+
<Suspense fallback={null}>
|
|
77
|
+
<ExportSVGDialog
|
|
78
|
+
model={model}
|
|
79
|
+
onClose={() => setExportSvgDialogOpen(false)}
|
|
80
|
+
/>
|
|
81
|
+
</Suspense>
|
|
82
|
+
) : null}
|
|
75
83
|
</>
|
|
76
84
|
)
|
|
77
85
|
})
|
package/src/declare.d.ts
CHANGED