@jbrowse/plugin-linear-genome-view 1.5.0 → 1.5.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.
- package/dist/BaseLinearDisplay/components/Block.d.ts +7 -10
- package/dist/LinearGenomeView/components/HelpDialog.d.ts +5 -0
- package/dist/LinearGenomeView/components/LinearGenomeView.d.ts +3 -5
- package/dist/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +4 -0
- package/dist/LinearGenomeView/components/OverviewRubberBand.d.ts +2 -3
- package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +116 -2
- package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +3 -1
- package/dist/LinearGenomeView/components/ScaleBar.d.ts +14 -0
- package/dist/LinearGenomeView/components/util.d.ts +1 -1
- package/dist/LinearGenomeView/index.d.ts +9 -2
- package/dist/plugin-linear-genome-view.cjs.development.js +3163 -2886
- package/dist/plugin-linear-genome-view.cjs.development.js.map +1 -1
- package/dist/plugin-linear-genome-view.cjs.production.min.js +1 -1
- package/dist/plugin-linear-genome-view.cjs.production.min.js.map +1 -1
- package/dist/plugin-linear-genome-view.esm.js +3122 -2845
- package/dist/plugin-linear-genome-view.esm.js.map +1 -1
- package/package.json +4 -4
- package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +0 -1
- package/src/BaseLinearDisplay/components/Block.tsx +20 -33
- package/src/LinearGenomeView/components/Header.tsx +19 -7
- package/src/LinearGenomeView/components/HelpDialog.tsx +81 -0
- package/src/LinearGenomeView/components/ImportForm.tsx +42 -51
- package/src/LinearGenomeView/components/LinearGenomeView.tsx +30 -245
- package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +317 -0
- package/src/LinearGenomeView/components/OverviewRubberBand.tsx +74 -34
- package/src/LinearGenomeView/components/OverviewScaleBar.tsx +326 -177
- package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +145 -145
- package/src/LinearGenomeView/components/SearchResultsDialog.tsx +12 -34
- package/src/LinearGenomeView/components/SequenceDialog.tsx +9 -8
- package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +127 -78
- package/src/LinearGenomeView/components/util.ts +6 -4
- package/src/LinearGenomeView/index.tsx +55 -14
- package/src/declare.d.ts +0 -1
|
@@ -1,39 +1,38 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import
|
|
3
|
-
import { getSession } from '@jbrowse/core/util'
|
|
4
|
-
import { makeStyles, useTheme } from '@material-ui/core/styles'
|
|
5
|
-
import { alpha } from '@material-ui/core/styles'
|
|
6
|
-
import LinearProgress from '@material-ui/core/LinearProgress'
|
|
7
|
-
import { ContentBlock } from '@jbrowse/core/util/blockTypes'
|
|
2
|
+
import { Typography, makeStyles, useTheme, alpha } from '@material-ui/core'
|
|
8
3
|
import { observer } from 'mobx-react'
|
|
9
4
|
import { Instance } from 'mobx-state-tree'
|
|
10
5
|
import clsx from 'clsx'
|
|
11
|
-
|
|
6
|
+
|
|
7
|
+
import Base1DView, { Base1DViewModel } from '@jbrowse/core/util/Base1DViewModel'
|
|
8
|
+
import { getSession } from '@jbrowse/core/util'
|
|
9
|
+
import { ContentBlock } from '@jbrowse/core/util/blockTypes'
|
|
10
|
+
import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
|
|
11
|
+
|
|
12
|
+
// locals
|
|
12
13
|
import {
|
|
13
|
-
|
|
14
|
+
LinearGenomeViewModel,
|
|
14
15
|
HEADER_BAR_HEIGHT,
|
|
15
16
|
HEADER_OVERVIEW_HEIGHT,
|
|
16
17
|
} from '..'
|
|
17
18
|
import { chooseGridPitch } from '../util'
|
|
18
19
|
import OverviewRubberBand from './OverviewRubberBand'
|
|
19
20
|
|
|
21
|
+
const wholeSeqSpacer = 2
|
|
22
|
+
|
|
20
23
|
const useStyles = makeStyles(theme => {
|
|
21
|
-
const scaleBarColor = theme.palette.tertiary
|
|
22
|
-
? theme.palette.tertiary.light
|
|
23
|
-
: theme.palette.primary.light
|
|
24
24
|
return {
|
|
25
25
|
scaleBar: {
|
|
26
|
-
width: '100%',
|
|
27
26
|
height: HEADER_OVERVIEW_HEIGHT,
|
|
28
|
-
|
|
27
|
+
},
|
|
28
|
+
scaleBarBorder: {
|
|
29
|
+
border: '1px solid',
|
|
29
30
|
},
|
|
30
31
|
scaleBarContig: {
|
|
31
32
|
backgroundColor: theme.palette.background.default,
|
|
32
33
|
position: 'absolute',
|
|
33
34
|
top: 0,
|
|
34
35
|
height: HEADER_OVERVIEW_HEIGHT,
|
|
35
|
-
border: '1px solid',
|
|
36
|
-
borderBottomColor: 'black',
|
|
37
36
|
},
|
|
38
37
|
scaleBarContigForward: {
|
|
39
38
|
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 15 9'%3E%3Cpath d='M-.1 0L6 4.5L-.1 9' fill='none' stroke='%23ddd'/%3E%3C/svg%3E")`,
|
|
@@ -43,34 +42,12 @@ const useStyles = makeStyles(theme => {
|
|
|
43
42
|
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 15 9'%3E%3Cpath d='M6 0L0 4.5L6 9' fill='none' stroke='%23ddd'/%3E%3C/svg%3E")`,
|
|
44
43
|
backgroundRepeat: 'repeat',
|
|
45
44
|
},
|
|
46
|
-
|
|
47
|
-
width: 10,
|
|
48
|
-
height: 17.5,
|
|
49
|
-
background: `linear-gradient(-225deg,black 3px, transparent 1px),
|
|
50
|
-
linear-gradient(45deg, black 3px, transparent 1px)`,
|
|
51
|
-
backgroundRepeat: 'repeat-y',
|
|
52
|
-
backgroundSize: '10px 8px',
|
|
53
|
-
borderTopLeftRadius: '2px',
|
|
54
|
-
borderBottomLeftRadius: '2px',
|
|
55
|
-
float: 'left',
|
|
56
|
-
},
|
|
57
|
-
scaleBarRegionIncompleteRight: {
|
|
58
|
-
width: 10,
|
|
59
|
-
height: 17.5,
|
|
60
|
-
background: `linear-gradient(225deg, black 3px, transparent 1px),
|
|
61
|
-
linear-gradient(-45deg, black 3px, transparent 1px)`,
|
|
62
|
-
backgroundRepeat: 'repeat-y',
|
|
63
|
-
backgroundSize: '10px 8px',
|
|
64
|
-
borderTopRightRadius: '2px',
|
|
65
|
-
borderBottomRightRadius: '2px',
|
|
66
|
-
float: 'right',
|
|
67
|
-
},
|
|
45
|
+
|
|
68
46
|
scaleBarRefName: {
|
|
69
47
|
position: 'absolute',
|
|
70
48
|
fontWeight: 'bold',
|
|
71
|
-
lineHeight: 'normal',
|
|
72
49
|
pointerEvents: 'none',
|
|
73
|
-
|
|
50
|
+
zIndex: 100,
|
|
74
51
|
},
|
|
75
52
|
scaleBarLabel: {
|
|
76
53
|
height: HEADER_OVERVIEW_HEIGHT,
|
|
@@ -81,66 +58,64 @@ const useStyles = makeStyles(theme => {
|
|
|
81
58
|
pointerEvents: 'none',
|
|
82
59
|
},
|
|
83
60
|
scaleBarVisibleRegion: {
|
|
84
|
-
background: alpha(scaleBarColor, 0.3),
|
|
85
61
|
position: 'absolute',
|
|
86
62
|
height: HEADER_OVERVIEW_HEIGHT,
|
|
87
63
|
pointerEvents: 'none',
|
|
88
|
-
top: -1,
|
|
89
64
|
zIndex: 100,
|
|
90
|
-
|
|
91
|
-
borderStyle: 'solid',
|
|
92
|
-
borderColor: alpha(scaleBarColor, 0.8),
|
|
93
|
-
boxSizing: 'content-box',
|
|
65
|
+
border: '1px solid',
|
|
94
66
|
},
|
|
95
67
|
overview: {
|
|
96
68
|
height: HEADER_BAR_HEIGHT,
|
|
97
69
|
position: 'relative',
|
|
98
70
|
},
|
|
99
|
-
overviewSvg: {
|
|
71
|
+
overviewSvg: {
|
|
72
|
+
width: '100%',
|
|
73
|
+
position: 'absolute',
|
|
74
|
+
},
|
|
100
75
|
}
|
|
101
76
|
})
|
|
102
77
|
|
|
103
|
-
const wholeSeqSpacer = 2
|
|
104
78
|
const Polygon = observer(
|
|
105
79
|
({
|
|
106
80
|
model,
|
|
107
81
|
overview,
|
|
82
|
+
useOffset = true,
|
|
108
83
|
}: {
|
|
109
84
|
model: LGV
|
|
110
85
|
overview: Instance<Base1DViewModel>
|
|
86
|
+
useOffset?: boolean
|
|
111
87
|
}) => {
|
|
112
88
|
const theme = useTheme()
|
|
113
|
-
const
|
|
114
|
-
const {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} =
|
|
118
|
-
|
|
119
|
-
const polygonColor = theme.palette.tertiary
|
|
120
|
-
? theme.palette.tertiary.light
|
|
121
|
-
: theme.palette.primary.light
|
|
89
|
+
const multiplier = Number(useOffset)
|
|
90
|
+
const { interRegionPaddingWidth, offsetPx, dynamicBlocks, cytobandOffset } =
|
|
91
|
+
model
|
|
92
|
+
const { contentBlocks, totalWidthPxWithoutBorders } = dynamicBlocks
|
|
93
|
+
const { tertiary, primary } = theme.palette
|
|
94
|
+
const polygonColor = tertiary ? tertiary.light : primary.light
|
|
122
95
|
|
|
123
96
|
if (!contentBlocks.length) {
|
|
124
97
|
return null
|
|
125
98
|
}
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const topLeft =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
99
|
+
const first = contentBlocks[0]
|
|
100
|
+
const last = contentBlocks[contentBlocks.length - 1]
|
|
101
|
+
const topLeft =
|
|
102
|
+
(overview.bpToPx({
|
|
103
|
+
...first,
|
|
104
|
+
coord: first.reversed ? first.end : first.start,
|
|
105
|
+
}) || 0) +
|
|
106
|
+
cytobandOffset * multiplier
|
|
107
|
+
const topRight =
|
|
108
|
+
(overview.bpToPx({
|
|
109
|
+
...last,
|
|
110
|
+
coord: last.reversed ? last.start : last.end,
|
|
111
|
+
}) || 0) +
|
|
112
|
+
cytobandOffset * multiplier
|
|
138
113
|
|
|
139
114
|
const startPx = Math.max(0, -offsetPx)
|
|
140
115
|
const endPx =
|
|
141
116
|
startPx +
|
|
142
117
|
totalWidthPxWithoutBorders +
|
|
143
|
-
(contentBlocks.length *
|
|
118
|
+
(contentBlocks.length * interRegionPaddingWidth) / 2
|
|
144
119
|
|
|
145
120
|
const points = [
|
|
146
121
|
[startPx, HEADER_BAR_HEIGHT],
|
|
@@ -150,24 +125,250 @@ const Polygon = observer(
|
|
|
150
125
|
]
|
|
151
126
|
|
|
152
127
|
return (
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{points && (
|
|
159
|
-
<polygon
|
|
160
|
-
points={points.toString()}
|
|
161
|
-
fill={alpha(polygonColor, 0.3)}
|
|
162
|
-
stroke={alpha(polygonColor, 0.8)}
|
|
163
|
-
/>
|
|
164
|
-
)}
|
|
165
|
-
</svg>
|
|
128
|
+
<polygon
|
|
129
|
+
points={points.toString()}
|
|
130
|
+
fill={alpha(polygonColor, 0.3)}
|
|
131
|
+
stroke={alpha(polygonColor, 0.8)}
|
|
132
|
+
/>
|
|
166
133
|
)
|
|
167
134
|
},
|
|
168
135
|
)
|
|
169
136
|
|
|
170
|
-
type LGV =
|
|
137
|
+
type LGV = LinearGenomeViewModel
|
|
138
|
+
|
|
139
|
+
// rounded rect from https://stackoverflow.com/a/45889603/2129219
|
|
140
|
+
// prettier-ignore
|
|
141
|
+
function rightRoundedRect(x:number, y:number, width:number, height:number, radius:number) {
|
|
142
|
+
return "M" + x + "," + y
|
|
143
|
+
+ "h" + (width - radius)
|
|
144
|
+
+ "a" + radius + "," + radius + " 0 0 1 " + radius + "," + radius
|
|
145
|
+
+ "v" + (height - 2 * radius)
|
|
146
|
+
+ "a" + radius + "," + radius + " 0 0 1 " + -radius + "," + radius
|
|
147
|
+
+ "h" + (radius - width)
|
|
148
|
+
+ "z";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// prettier-ignore
|
|
152
|
+
function leftRoundedRect(x:number, y:number, width:number, height:number, radius:number ) {
|
|
153
|
+
return "M" + (x + radius) + "," + y
|
|
154
|
+
+ "h" + (width - radius)
|
|
155
|
+
+ "v" + height
|
|
156
|
+
+ "h" + (radius - width)
|
|
157
|
+
+ "a" + radius + "," + radius + " 0 0 1 " + (-radius) + "," + (-radius)
|
|
158
|
+
+ "v" + (2 * radius - height)
|
|
159
|
+
+ "a" + radius + "," + radius + " 0 0 1 " + radius + "," + (-radius)
|
|
160
|
+
+ "z";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const colorMap: { [key: string]: string | undefined } = {
|
|
164
|
+
gneg: 'rgb(227,227,227)',
|
|
165
|
+
gpos25: 'rgb(142,142,142)',
|
|
166
|
+
gpos50: 'rgb(85,85,85)',
|
|
167
|
+
gpos100: 'rgb(0,0,0)',
|
|
168
|
+
gpos75: 'rgb(57,57,57)',
|
|
169
|
+
gvar: 'rgb(0,0,0)',
|
|
170
|
+
stalk: 'rgb(127,127,127)',
|
|
171
|
+
acen: '#800',
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const Cytobands = observer(
|
|
175
|
+
({
|
|
176
|
+
overview,
|
|
177
|
+
block,
|
|
178
|
+
assembly,
|
|
179
|
+
}: {
|
|
180
|
+
overview: Base1DViewModel
|
|
181
|
+
assembly?: Assembly
|
|
182
|
+
block: ContentBlock
|
|
183
|
+
}) => {
|
|
184
|
+
const { offsetPx } = block
|
|
185
|
+
const cytobands = assembly?.cytobands
|
|
186
|
+
?.map(f => ({
|
|
187
|
+
refName: assembly.getCanonicalRefName(f.get('refName')),
|
|
188
|
+
start: f.get('start'),
|
|
189
|
+
end: f.get('end'),
|
|
190
|
+
type: f.get('type'),
|
|
191
|
+
}))
|
|
192
|
+
.filter(f => f.refName === block.refName)
|
|
193
|
+
.map(f => {
|
|
194
|
+
const { refName, start, end, type } = f
|
|
195
|
+
return [
|
|
196
|
+
overview.bpToPx({
|
|
197
|
+
refName,
|
|
198
|
+
coord: start,
|
|
199
|
+
}),
|
|
200
|
+
overview.bpToPx({
|
|
201
|
+
refName,
|
|
202
|
+
coord: end,
|
|
203
|
+
}),
|
|
204
|
+
type,
|
|
205
|
+
]
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
let firstCent = true
|
|
209
|
+
return cytobands ? (
|
|
210
|
+
<g transform={`translate(-${offsetPx})`}>
|
|
211
|
+
{cytobands.map(([start, end, type], index) => {
|
|
212
|
+
const key = `${start}-${end}-${type}`
|
|
213
|
+
if (type === 'acen' && firstCent) {
|
|
214
|
+
firstCent = false
|
|
215
|
+
return (
|
|
216
|
+
<polygon
|
|
217
|
+
key={key}
|
|
218
|
+
points={[
|
|
219
|
+
[start, 0],
|
|
220
|
+
[end, HEADER_OVERVIEW_HEIGHT / 2],
|
|
221
|
+
[start, HEADER_OVERVIEW_HEIGHT],
|
|
222
|
+
].toString()}
|
|
223
|
+
fill={colorMap[type]}
|
|
224
|
+
/>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
if (type === 'acen' && !firstCent) {
|
|
228
|
+
return (
|
|
229
|
+
<polygon
|
|
230
|
+
key={key}
|
|
231
|
+
points={[
|
|
232
|
+
[start, HEADER_OVERVIEW_HEIGHT / 2],
|
|
233
|
+
[end, 0],
|
|
234
|
+
[end, HEADER_OVERVIEW_HEIGHT],
|
|
235
|
+
].toString()}
|
|
236
|
+
fill={colorMap[type]}
|
|
237
|
+
/>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (index === 0) {
|
|
242
|
+
return (
|
|
243
|
+
<path
|
|
244
|
+
key={key}
|
|
245
|
+
d={leftRoundedRect(
|
|
246
|
+
Math.min(start, end),
|
|
247
|
+
0,
|
|
248
|
+
Math.abs(end - start),
|
|
249
|
+
HEADER_OVERVIEW_HEIGHT,
|
|
250
|
+
8,
|
|
251
|
+
)}
|
|
252
|
+
fill={colorMap[type]}
|
|
253
|
+
/>
|
|
254
|
+
)
|
|
255
|
+
} else if (index === cytobands.length - 1) {
|
|
256
|
+
return (
|
|
257
|
+
<path
|
|
258
|
+
key={key}
|
|
259
|
+
d={rightRoundedRect(
|
|
260
|
+
Math.min(start, end),
|
|
261
|
+
0,
|
|
262
|
+
Math.abs(end - start) - 2,
|
|
263
|
+
HEADER_OVERVIEW_HEIGHT,
|
|
264
|
+
8,
|
|
265
|
+
)}
|
|
266
|
+
fill={colorMap[type]}
|
|
267
|
+
/>
|
|
268
|
+
)
|
|
269
|
+
} else {
|
|
270
|
+
return (
|
|
271
|
+
<rect
|
|
272
|
+
key={key}
|
|
273
|
+
x={Math.min(start, end)}
|
|
274
|
+
y={0}
|
|
275
|
+
width={Math.abs(end - start)}
|
|
276
|
+
height={HEADER_OVERVIEW_HEIGHT}
|
|
277
|
+
fill={colorMap[type]}
|
|
278
|
+
/>
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
})}
|
|
282
|
+
</g>
|
|
283
|
+
) : null
|
|
284
|
+
},
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
const OverviewBox = observer(
|
|
288
|
+
({
|
|
289
|
+
scale,
|
|
290
|
+
model,
|
|
291
|
+
block,
|
|
292
|
+
overview,
|
|
293
|
+
}: {
|
|
294
|
+
scale: number
|
|
295
|
+
model: LGV
|
|
296
|
+
block: ContentBlock
|
|
297
|
+
overview: Base1DViewModel
|
|
298
|
+
}) => {
|
|
299
|
+
const classes = useStyles()
|
|
300
|
+
const { cytobandOffset, showCytobands } = model
|
|
301
|
+
const { start, end, reversed, refName, assemblyName } = block
|
|
302
|
+
const { majorPitch } = chooseGridPitch(scale, 120, 15)
|
|
303
|
+
const { assemblyManager } = getSession(model)
|
|
304
|
+
const assembly = assemblyManager.get(assemblyName)
|
|
305
|
+
const refNameColor = assembly?.getRefNameColor(refName)
|
|
306
|
+
|
|
307
|
+
const tickLabels = []
|
|
308
|
+
for (let i = 0; i < Math.floor((end - start) / majorPitch); i++) {
|
|
309
|
+
const offsetLabel = (i + 1) * majorPitch
|
|
310
|
+
tickLabels.push(reversed ? end - offsetLabel : start + offsetLabel)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div>
|
|
315
|
+
{/* name of sequence */}
|
|
316
|
+
<Typography
|
|
317
|
+
style={{
|
|
318
|
+
left: block.offsetPx + 3,
|
|
319
|
+
color: showCytobands ? 'black' : refNameColor,
|
|
320
|
+
}}
|
|
321
|
+
className={classes.scaleBarRefName}
|
|
322
|
+
>
|
|
323
|
+
{refName}
|
|
324
|
+
</Typography>
|
|
325
|
+
<div
|
|
326
|
+
className={clsx(
|
|
327
|
+
classes.scaleBarContig,
|
|
328
|
+
showCytobands
|
|
329
|
+
? undefined
|
|
330
|
+
: reversed
|
|
331
|
+
? classes.scaleBarContigReverse
|
|
332
|
+
: classes.scaleBarContigForward,
|
|
333
|
+
!showCytobands ? classes.scaleBarBorder : undefined,
|
|
334
|
+
)}
|
|
335
|
+
style={{
|
|
336
|
+
left: block.offsetPx + cytobandOffset,
|
|
337
|
+
width: block.widthPx,
|
|
338
|
+
borderColor: refNameColor,
|
|
339
|
+
}}
|
|
340
|
+
>
|
|
341
|
+
{!showCytobands
|
|
342
|
+
? tickLabels.map((tickLabel, labelIdx) => (
|
|
343
|
+
<Typography
|
|
344
|
+
key={`${JSON.stringify(block)}-${tickLabel}-${labelIdx}`}
|
|
345
|
+
className={classes.scaleBarLabel}
|
|
346
|
+
variant="body2"
|
|
347
|
+
style={{
|
|
348
|
+
left: ((labelIdx + 1) * majorPitch) / scale,
|
|
349
|
+
pointerEvents: 'none',
|
|
350
|
+
color: refNameColor,
|
|
351
|
+
}}
|
|
352
|
+
>
|
|
353
|
+
{tickLabel.toLocaleString('en-US')}
|
|
354
|
+
</Typography>
|
|
355
|
+
))
|
|
356
|
+
: null}
|
|
357
|
+
|
|
358
|
+
{showCytobands ? (
|
|
359
|
+
<svg style={{ width: '100%' }}>
|
|
360
|
+
<Cytobands
|
|
361
|
+
overview={overview}
|
|
362
|
+
assembly={assembly}
|
|
363
|
+
block={block}
|
|
364
|
+
/>
|
|
365
|
+
</svg>
|
|
366
|
+
) : null}
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
)
|
|
370
|
+
},
|
|
371
|
+
)
|
|
171
372
|
|
|
172
373
|
const ScaleBar = observer(
|
|
173
374
|
({
|
|
@@ -180,114 +381,66 @@ const ScaleBar = observer(
|
|
|
180
381
|
scale: number
|
|
181
382
|
}) => {
|
|
182
383
|
const classes = useStyles()
|
|
183
|
-
const
|
|
184
|
-
const {
|
|
185
|
-
const
|
|
186
|
-
const
|
|
384
|
+
const theme = useTheme()
|
|
385
|
+
const { dynamicBlocks, showCytobands, cytobandOffset } = model
|
|
386
|
+
const visibleRegions = dynamicBlocks.contentBlocks
|
|
387
|
+
const overviewVisibleRegions = overview.dynamicBlocks
|
|
388
|
+
const { tertiary, primary } = theme.palette
|
|
389
|
+
const scaleBarColor = tertiary ? tertiary.light : primary.light
|
|
187
390
|
|
|
188
|
-
if (!visibleRegions.
|
|
391
|
+
if (!visibleRegions.length) {
|
|
189
392
|
return null
|
|
190
393
|
}
|
|
191
|
-
const
|
|
394
|
+
const first = visibleRegions[0]
|
|
192
395
|
const firstOverviewPx =
|
|
193
396
|
overview.bpToPx({
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
coord: firstBlock.reversed ? firstBlock.end : firstBlock.start,
|
|
397
|
+
...first,
|
|
398
|
+
coord: first.reversed ? first.end : first.start,
|
|
197
399
|
}) || 0
|
|
198
400
|
|
|
199
|
-
const
|
|
200
|
-
visibleRegions.contentBlocks[visibleRegions.contentBlocks.length - 1]
|
|
401
|
+
const last = visibleRegions[visibleRegions.length - 1]
|
|
201
402
|
const lastOverviewPx =
|
|
202
403
|
overview.bpToPx({
|
|
203
|
-
|
|
204
|
-
coord:
|
|
205
|
-
regionNumber: lastBlock.regionNumber,
|
|
404
|
+
...last,
|
|
405
|
+
coord: last.reversed ? last.start : last.end,
|
|
206
406
|
}) || 0
|
|
207
407
|
|
|
408
|
+
const color = showCytobands ? '#f00' : scaleBarColor
|
|
409
|
+
const transparency = showCytobands ? 0.1 : 0.3
|
|
410
|
+
|
|
208
411
|
return (
|
|
209
412
|
<div className={classes.scaleBar}>
|
|
210
413
|
<div
|
|
211
414
|
className={classes.scaleBarVisibleRegion}
|
|
212
415
|
style={{
|
|
213
416
|
width: lastOverviewPx - firstOverviewPx,
|
|
214
|
-
left: firstOverviewPx,
|
|
417
|
+
left: firstOverviewPx + cytobandOffset,
|
|
418
|
+
background: alpha(color, transparency),
|
|
419
|
+
borderColor: color,
|
|
215
420
|
}}
|
|
216
421
|
/>
|
|
217
422
|
{/* this is the entire scale bar */}
|
|
218
|
-
{overviewVisibleRegions.map((
|
|
219
|
-
|
|
220
|
-
let refNameColor: string | undefined
|
|
221
|
-
if (assembly) {
|
|
222
|
-
refNameColor = assembly.getRefNameColor(seq.refName)
|
|
223
|
-
}
|
|
224
|
-
const regionLength = seq.end - seq.start
|
|
225
|
-
const tickLabels = []
|
|
226
|
-
for (
|
|
227
|
-
let index = 0;
|
|
228
|
-
index < Math.floor(regionLength / gridPitch.majorPitch);
|
|
229
|
-
index++
|
|
230
|
-
) {
|
|
231
|
-
const offsetLabel = (index + 1) * gridPitch.majorPitch
|
|
232
|
-
tickLabels.push(
|
|
233
|
-
seq.reversed ? seq.end - offsetLabel : seq.start + offsetLabel,
|
|
234
|
-
)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return !(seq instanceof ContentBlock) ? (
|
|
423
|
+
{overviewVisibleRegions.map((block, idx) => {
|
|
424
|
+
return !(block instanceof ContentBlock) ? (
|
|
238
425
|
<div
|
|
239
|
-
key={`${JSON.stringify(
|
|
426
|
+
key={`${JSON.stringify(block)}-${idx}`}
|
|
240
427
|
className={classes.scaleBarContig}
|
|
241
428
|
style={{
|
|
242
|
-
width:
|
|
243
|
-
left:
|
|
429
|
+
width: block.widthPx,
|
|
430
|
+
left: block.offsetPx,
|
|
244
431
|
backgroundColor: '#999',
|
|
245
432
|
backgroundImage:
|
|
246
433
|
'repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(255,255,255,.5) 1px, rgba(255,255,255,.5) 3px)',
|
|
247
434
|
}}
|
|
248
435
|
/>
|
|
249
436
|
) : (
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
)}
|
|
258
|
-
style={{
|
|
259
|
-
left: seq.offsetPx,
|
|
260
|
-
width: seq.widthPx,
|
|
261
|
-
borderColor: refNameColor,
|
|
262
|
-
}}
|
|
263
|
-
>
|
|
264
|
-
{/* name of sequence */}
|
|
265
|
-
<Typography
|
|
266
|
-
style={{
|
|
267
|
-
color: refNameColor,
|
|
268
|
-
zIndex: 100,
|
|
269
|
-
}}
|
|
270
|
-
className={classes.scaleBarRefName}
|
|
271
|
-
>
|
|
272
|
-
{seq.refName}
|
|
273
|
-
</Typography>
|
|
274
|
-
|
|
275
|
-
{/* the number labels drawn in overview scale bar*/}
|
|
276
|
-
{tickLabels.map((tickLabel, labelIdx) => (
|
|
277
|
-
<Typography
|
|
278
|
-
key={`${JSON.stringify(seq)}-${tickLabel}-${labelIdx}`}
|
|
279
|
-
className={classes.scaleBarLabel}
|
|
280
|
-
variant="body2"
|
|
281
|
-
style={{
|
|
282
|
-
left: ((labelIdx + 1) * gridPitch.majorPitch) / scale,
|
|
283
|
-
pointerEvents: 'none',
|
|
284
|
-
color: refNameColor,
|
|
285
|
-
}}
|
|
286
|
-
>
|
|
287
|
-
{tickLabel.toLocaleString('en-US')}
|
|
288
|
-
</Typography>
|
|
289
|
-
))}
|
|
290
|
-
</div>
|
|
437
|
+
<OverviewBox
|
|
438
|
+
scale={scale}
|
|
439
|
+
block={block}
|
|
440
|
+
model={model}
|
|
441
|
+
overview={overview}
|
|
442
|
+
key={`${JSON.stringify(block)}-${idx}`}
|
|
443
|
+
/>
|
|
291
444
|
)
|
|
292
445
|
})}
|
|
293
446
|
</div>
|
|
@@ -303,30 +456,22 @@ function OverviewScaleBar({
|
|
|
303
456
|
children: React.ReactNode
|
|
304
457
|
}) {
|
|
305
458
|
const classes = useStyles()
|
|
306
|
-
const { width, displayedRegions } = model
|
|
459
|
+
const { totalBp, width, cytobandOffset, displayedRegions } = model
|
|
307
460
|
|
|
308
461
|
const overview = Base1DView.create({
|
|
309
462
|
displayedRegions: JSON.parse(JSON.stringify(displayedRegions)),
|
|
310
463
|
interRegionPaddingWidth: 0,
|
|
311
464
|
minimumBlockWidth: model.minimumBlockWidth,
|
|
312
465
|
})
|
|
313
|
-
|
|
466
|
+
|
|
467
|
+
const modWidth = width - cytobandOffset
|
|
468
|
+
overview.setVolatileWidth(modWidth)
|
|
314
469
|
overview.showAllRegions()
|
|
315
470
|
|
|
316
471
|
const scale =
|
|
317
|
-
|
|
472
|
+
totalBp / (modWidth - (displayedRegions.length - 1) * wholeSeqSpacer)
|
|
318
473
|
|
|
319
|
-
return
|
|
320
|
-
<>
|
|
321
|
-
<div className={classes.scaleBar}>
|
|
322
|
-
<LinearProgress
|
|
323
|
-
variant="indeterminate"
|
|
324
|
-
style={{ marginTop: 4, width: '100%' }}
|
|
325
|
-
/>
|
|
326
|
-
</div>
|
|
327
|
-
<div>{children}</div>
|
|
328
|
-
</>
|
|
329
|
-
) : (
|
|
474
|
+
return (
|
|
330
475
|
<div>
|
|
331
476
|
<OverviewRubberBand
|
|
332
477
|
model={model}
|
|
@@ -336,7 +481,9 @@ function OverviewScaleBar({
|
|
|
336
481
|
}
|
|
337
482
|
/>
|
|
338
483
|
<div className={classes.overview}>
|
|
339
|
-
<
|
|
484
|
+
<svg height={HEADER_BAR_HEIGHT} className={classes.overviewSvg}>
|
|
485
|
+
<Polygon model={model} overview={overview} />
|
|
486
|
+
</svg>
|
|
340
487
|
{children}
|
|
341
488
|
</div>
|
|
342
489
|
</div>
|
|
@@ -344,3 +491,5 @@ function OverviewScaleBar({
|
|
|
344
491
|
}
|
|
345
492
|
|
|
346
493
|
export default observer(OverviewScaleBar)
|
|
494
|
+
|
|
495
|
+
export { Cytobands, Polygon }
|