@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.
Files changed (33) hide show
  1. package/dist/BaseLinearDisplay/components/Block.d.ts +7 -10
  2. package/dist/LinearGenomeView/components/HelpDialog.d.ts +5 -0
  3. package/dist/LinearGenomeView/components/LinearGenomeView.d.ts +3 -5
  4. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +4 -0
  5. package/dist/LinearGenomeView/components/OverviewRubberBand.d.ts +2 -3
  6. package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +116 -2
  7. package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +3 -1
  8. package/dist/LinearGenomeView/components/ScaleBar.d.ts +14 -0
  9. package/dist/LinearGenomeView/components/util.d.ts +1 -1
  10. package/dist/LinearGenomeView/index.d.ts +9 -2
  11. package/dist/plugin-linear-genome-view.cjs.development.js +3163 -2886
  12. package/dist/plugin-linear-genome-view.cjs.development.js.map +1 -1
  13. package/dist/plugin-linear-genome-view.cjs.production.min.js +1 -1
  14. package/dist/plugin-linear-genome-view.cjs.production.min.js.map +1 -1
  15. package/dist/plugin-linear-genome-view.esm.js +3122 -2845
  16. package/dist/plugin-linear-genome-view.esm.js.map +1 -1
  17. package/package.json +4 -4
  18. package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +0 -1
  19. package/src/BaseLinearDisplay/components/Block.tsx +20 -33
  20. package/src/LinearGenomeView/components/Header.tsx +19 -7
  21. package/src/LinearGenomeView/components/HelpDialog.tsx +81 -0
  22. package/src/LinearGenomeView/components/ImportForm.tsx +42 -51
  23. package/src/LinearGenomeView/components/LinearGenomeView.tsx +30 -245
  24. package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +317 -0
  25. package/src/LinearGenomeView/components/OverviewRubberBand.tsx +74 -34
  26. package/src/LinearGenomeView/components/OverviewScaleBar.tsx +326 -177
  27. package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +145 -145
  28. package/src/LinearGenomeView/components/SearchResultsDialog.tsx +12 -34
  29. package/src/LinearGenomeView/components/SequenceDialog.tsx +9 -8
  30. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +127 -78
  31. package/src/LinearGenomeView/components/util.ts +6 -4
  32. package/src/LinearGenomeView/index.tsx +55 -14
  33. package/src/declare.d.ts +0 -1
@@ -1,39 +1,38 @@
1
1
  import React from 'react'
2
- import Base1DView, { Base1DViewModel } from '@jbrowse/core/util/Base1DViewModel'
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
- import { Typography } from '@material-ui/core'
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
- LinearGenomeViewStateModel,
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
- overflow: 'hidden',
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
- scaleBarRegionIncompleteLeft: {
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
- left: 5,
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
- borderWidth: 1,
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: { position: 'absolute' },
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 classes = useStyles()
114
- const {
115
- offsetPx,
116
- dynamicBlocks: { contentBlocks, totalWidthPxWithoutBorders },
117
- } = model
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 firstBlock = contentBlocks[0]
127
- const lastBlock = contentBlocks[contentBlocks.length - 1]
128
- const topLeft = overview.bpToPx({
129
- refName: firstBlock.refName,
130
- coord: firstBlock.reversed ? firstBlock.end : firstBlock.start,
131
- regionNumber: firstBlock.regionNumber,
132
- })
133
- const topRight = overview.bpToPx({
134
- refName: lastBlock.refName,
135
- coord: lastBlock.reversed ? lastBlock.start : lastBlock.end,
136
- regionNumber: lastBlock.regionNumber,
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 * model.interRegionPaddingWidth) / 2
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
- <svg
154
- height={HEADER_BAR_HEIGHT}
155
- width="100%"
156
- className={classes.overviewSvg}
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 = Instance<LinearGenomeViewStateModel>
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 { dynamicBlocks: visibleRegions } = model
184
- const { assemblyManager } = getSession(model)
185
- const gridPitch = chooseGridPitch(scale, 120, 15)
186
- const { dynamicBlocks: overviewVisibleRegions } = overview
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.contentBlocks.length) {
391
+ if (!visibleRegions.length) {
189
392
  return null
190
393
  }
191
- const firstBlock = visibleRegions.contentBlocks[0]
394
+ const first = visibleRegions[0]
192
395
  const firstOverviewPx =
193
396
  overview.bpToPx({
194
- refName: firstBlock.refName,
195
- regionNumber: firstBlock.regionNumber,
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 lastBlock =
200
- visibleRegions.contentBlocks[visibleRegions.contentBlocks.length - 1]
401
+ const last = visibleRegions[visibleRegions.length - 1]
201
402
  const lastOverviewPx =
202
403
  overview.bpToPx({
203
- refName: lastBlock.refName,
204
- coord: lastBlock.reversed ? lastBlock.start : lastBlock.end,
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((seq, idx) => {
219
- const assembly = assemblyManager.get(seq.assemblyName)
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(seq)}-${idx}`}
426
+ key={`${JSON.stringify(block)}-${idx}`}
240
427
  className={classes.scaleBarContig}
241
428
  style={{
242
- width: seq.widthPx,
243
- left: seq.offsetPx,
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
- <div
251
- key={`${JSON.stringify(seq)}-${idx}`}
252
- className={clsx(
253
- classes.scaleBarContig,
254
- seq.reversed
255
- ? classes.scaleBarContigReverse
256
- : classes.scaleBarContigForward,
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
- overview.setVolatileWidth(width)
466
+
467
+ const modWidth = width - cytobandOffset
468
+ overview.setVolatileWidth(modWidth)
314
469
  overview.showAllRegions()
315
470
 
316
471
  const scale =
317
- model.totalBp / (width - (displayedRegions.length - 1) * wholeSeqSpacer)
472
+ totalBp / (modWidth - (displayedRegions.length - 1) * wholeSeqSpacer)
318
473
 
319
- return !displayedRegions.length ? (
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
- <Polygon model={model} overview={overview} />
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 }