Skip to content

Commit 2ec00d0

Browse files
committed
Add highlight overlay
1 parent a8ded7e commit 2ec00d0

17 files changed

Lines changed: 59609 additions & 42811 deletions

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default tseslint.config(
99
'webpack.config.js',
1010
'dist/*',
1111
'esm/*',
12+
'test/*.min.js',
1213
'example/*',
1314
'eslint.config.mjs',
1415
'esbuild.mjs',

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
"@babel/preset-react": "^7.10.4",
3030
"@emotion/react": "^11.10.4",
3131
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
32-
"@jbrowse/core": "^3.0.1",
33-
"@jbrowse/plugin-data-management": "^3.0.1",
34-
"@jbrowse/plugin-linear-genome-view": "^3.0.1",
32+
"@jbrowse/core": "^4.1.1",
33+
"@jbrowse/mobx-state-tree": "^5.4.1",
34+
"@jbrowse/plugin-data-management": "^4.1.1",
35+
"@jbrowse/plugin-linear-genome-view": "^4.1.1",
3536
"@mui/material": "^7.0.1",
3637
"@mui/system": "^7.0.1",
3738
"@mui/x-data-grid": "^8.2.0",
@@ -48,7 +49,6 @@
4849
"eslint-plugin-unicorn": "^62.0.0",
4950
"mobx": "^6.0.0",
5051
"mobx-react": "^9.0.1",
51-
"mobx-state-tree": "^5.4.1",
5252
"prettier": "^3.4.2",
5353
"pretty-bytes": "^7.0.0",
5454
"react": "^19.0.0",

src/BigMafAdapter/BigMafAdapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
22
import { SimpleFeature, updateStatus } from '@jbrowse/core/util'
33
import { openLocation } from '@jbrowse/core/util/io'
44
import { ObservableCreate } from '@jbrowse/core/util/rxjs'
5-
import { getSnapshot } from 'mobx-state-tree'
5+
import { getSnapshot } from '@jbrowse/mobx-state-tree'
66
import { firstValueFrom, toArray } from 'rxjs'
77

88
import parseNewick from '../parseNewick'

src/LinearMafDisplay/components/LinearMafDisplayComponent.tsx

Lines changed: 25 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,24 @@
11
import React, { useRef } from 'react'
22

33
import { Menu } from '@jbrowse/core/ui'
4-
import { getContainingView, getEnv, getSession } from '@jbrowse/core/util'
4+
import {
5+
getContainingView,
6+
getEnv,
7+
getSession,
8+
isSessionModelWithWidgets,
9+
} from '@jbrowse/core/util'
510
import { useTheme } from '@mui/material'
611
import { observer } from 'mobx-react'
712

813
import Crosshairs from './Crosshairs'
914
import MAFTooltip from './MAFTooltip'
15+
import MsaHighlightOverlay from './MsaHighlightOverlay'
1016
import YScaleBars from './Sidebar/YScaleBars'
1117
import { useDragSelection } from './useDragSelection'
1218

1319
import type { LinearMafDisplayModel } from '../stateModel'
1420
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
1521

16-
const MsaHighlightOverlay = observer(function MsaHighlightOverlay({
17-
model,
18-
view,
19-
height,
20-
}: {
21-
model: LinearMafDisplayModel
22-
view: LinearGenomeViewModel
23-
height: number
24-
}) {
25-
const { msaHighlights } = model
26-
if (msaHighlights.length === 0) {
27-
return null
28-
}
29-
30-
const { offsetPx } = view
31-
const displayedRegion = view.displayedRegions[0]
32-
if (!displayedRegion) {
33-
return null
34-
}
35-
36-
return (
37-
<>
38-
{msaHighlights.map((highlight, idx) => {
39-
// Check if highlight is on the displayed refName
40-
if (highlight.refName !== displayedRegion.refName) {
41-
return null
42-
}
43-
44-
const startPx = (highlight.start - displayedRegion.start) / view.bpPerPx - offsetPx
45-
const endPx = (highlight.end - displayedRegion.start) / view.bpPerPx - offsetPx
46-
const widthPx = Math.max(endPx - startPx, 2)
47-
48-
return (
49-
<div
50-
key={idx}
51-
style={{
52-
position: 'absolute',
53-
left: startPx,
54-
top: 0,
55-
width: widthPx,
56-
height,
57-
backgroundColor: 'rgba(255, 165, 0, 0.4)',
58-
border: '1px solid rgba(255, 165, 0, 0.8)',
59-
pointerEvents: 'none',
60-
}}
61-
/>
62-
)
63-
})}
64-
</>
65-
)
66-
})
67-
6822
const LinearMafDisplay = observer(function (props: {
6923
model: LinearMafDisplayModel
7024
}) {
@@ -189,21 +143,26 @@ const LinearMafDisplay = observer(function (props: {
189143
Math.max(contextCoord.dragStartX, contextCoord.dragEndX),
190144
]
191145

192-
const widget = session.addWidget('MafSequenceWidget', 'mafSequence')
193-
widget.setData({
194-
adapterConfig: model.adapterConfig,
195-
samples: model.samples,
196-
regions: [
146+
if (isSessionModelWithWidgets(session)) {
147+
const widget = session.addWidget(
148+
'MafSequenceWidget',
149+
'mafSequence',
197150
{
198-
refName,
199-
start: view.pxToBp(s).coord - 1,
200-
end: view.pxToBp(e).coord,
201-
assemblyName,
151+
adapterConfig: model.adapterConfig,
152+
samples: model.samples,
153+
regions: [
154+
{
155+
refName,
156+
start: view.pxToBp(s).coord - 1,
157+
end: view.pxToBp(e).coord,
158+
assemblyName,
159+
},
160+
],
161+
connectedViewId: view.id,
202162
},
203-
],
204-
connectedViewId: view.id,
205-
})
206-
session.showWidget(widget)
163+
)
164+
session.showWidget(widget)
165+
}
207166
setContextCoord(undefined)
208167
},
209168
},
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from 'react'
2+
3+
import { observer } from 'mobx-react'
4+
5+
import type { LinearMafDisplayModel } from '../stateModel'
6+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
7+
8+
const MsaHighlightOverlay = observer(function MsaHighlightOverlay({
9+
model,
10+
view,
11+
height,
12+
}: {
13+
model: LinearMafDisplayModel
14+
view: LinearGenomeViewModel
15+
height: number
16+
}) {
17+
const { msaHighlights } = model
18+
if (msaHighlights.length === 0) {
19+
return null
20+
}
21+
22+
const { offsetPx } = view
23+
const displayedRegion = view.displayedRegions[0]
24+
if (!displayedRegion) {
25+
return null
26+
}
27+
28+
return (
29+
<>
30+
{msaHighlights.map((highlight, idx) => {
31+
// Check if highlight is on the displayed refName
32+
if (highlight.refName !== displayedRegion.refName) {
33+
return null
34+
}
35+
36+
const startPx =
37+
(highlight.start - displayedRegion.start) / view.bpPerPx - offsetPx
38+
const endPx =
39+
(highlight.end - displayedRegion.start) / view.bpPerPx - offsetPx
40+
const widthPx = Math.max(endPx - startPx, 2)
41+
42+
return (
43+
<div
44+
key={idx}
45+
style={{
46+
position: 'absolute',
47+
left: startPx,
48+
top: 0,
49+
width: widthPx,
50+
height,
51+
backgroundColor: 'rgba(255, 165, 0, 0.4)',
52+
border: '1px solid rgba(255, 165, 0, 0.8)',
53+
pointerEvents: 'none',
54+
}}
55+
/>
56+
)
57+
})}
58+
</>
59+
)
60+
})
61+
62+
export default MsaHighlightOverlay

src/LinearMafDisplay/components/Sidebar/SvgWrapper.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import React, { useEffect, useRef } from 'react'
22

33
import { ResizeHandle } from '@jbrowse/core/ui'
44
import { getContainingView } from '@jbrowse/core/util'
5+
import { isAlive } from '@jbrowse/mobx-state-tree'
56
import { autorun } from 'mobx'
67
import { observer } from 'mobx-react'
7-
import { isAlive } from 'mobx-state-tree'
88
import { makeStyles } from 'tss-react/mui'
99

1010
import type { LinearMafDisplayModel } from '../../stateModel'

src/LinearMafDisplay/stateModel.ts

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ import { lazy } from 'react'
22

33
import { ConfigurationReference, getConf } from '@jbrowse/core/configuration'
44
import {
5+
SessionWithWidgets,
56
getContainingTrack,
67
getContainingView,
78
getEnv,
89
getSession,
910
max,
1011
measureText,
11-
SessionWithWidgets,
1212
} from '@jbrowse/core/util'
1313
import { getRpcSessionId } from '@jbrowse/core/util/tracks'
14+
import { addDisposer, isAlive, types } from '@jbrowse/mobx-state-tree'
1415
import { ascending } from 'd3-array'
1516
import { cluster, hierarchy } from 'd3-hierarchy'
1617
import deepEqual from 'fast-deep-equal'
1718
import { autorun } from 'mobx'
18-
import { addDisposer, isAlive, types } from 'mobx-state-tree'
1919

2020
import { computeNodeDescendantNames, maxLength, setBrLength } from './util'
2121
import { normalize } from '../util'
@@ -26,9 +26,18 @@ import type {
2626
AnyConfigurationModel,
2727
AnyConfigurationSchemaType,
2828
} from '@jbrowse/core/configuration'
29+
import type { Instance } from '@jbrowse/mobx-state-tree'
2930
import type { ExportSvgDisplayOptions } from '@jbrowse/plugin-linear-genome-view'
3031
import type { HierarchyNode } from 'd3-hierarchy'
31-
import type { Instance } from 'mobx-state-tree'
32+
33+
const defaultRowHeight = 15
34+
const defaultRowProportion = 0.8
35+
const defaultShowAllLetters = false
36+
const defaultMismatchRendering = true
37+
const defaultShowBranchLen = false
38+
const defaultTreeAreaWidth = 80
39+
const defaultShowAsUpperCase = true
40+
const defaultShowSidebar = true
3241

3342
const SetRowHeightDialog = lazy(
3443
() => import('./components/SetRowHeightDialog/SetRowHeightDialog'),
@@ -63,37 +72,37 @@ export default function stateModelFactory(
6372
/**
6473
* #property
6574
*/
66-
rowHeight: 15,
75+
rowHeight: defaultRowHeight,
6776
/**
6877
* #property
6978
*/
70-
rowProportion: 0.8,
79+
rowProportion: defaultRowProportion,
7180
/**
7281
* #property
7382
*/
74-
showAllLetters: false,
83+
showAllLetters: defaultShowAllLetters,
7584
/**
7685
* #property
7786
*/
78-
mismatchRendering: true,
87+
mismatchRendering: defaultMismatchRendering,
7988

8089
/**
8190
* #property
8291
*/
83-
showBranchLen: false,
92+
showBranchLen: defaultShowBranchLen,
8493

8594
/**
8695
* #property
8796
*/
88-
treeAreaWidth: 80,
97+
treeAreaWidth: defaultTreeAreaWidth,
8998
/**
9099
* #property
91100
*/
92-
showAsUpperCase: true,
101+
showAsUpperCase: defaultShowAsUpperCase,
93102
/**
94103
* #property
95104
*/
96-
showSidebar: true,
105+
showSidebar: defaultShowSidebar,
97106
}),
98107
)
99108
.volatile(() => ({
@@ -481,7 +490,11 @@ export default function stateModelFactory(
481490
(v as { connectedViewId?: string }).connectedViewId === view.id
482491
) {
483492
const msaView = v as {
484-
connectedHighlights?: { refName: string; start: number; end: number }[]
493+
connectedHighlights?: {
494+
refName: string
495+
start: number
496+
end: number
497+
}[]
485498
}
486499
if (msaView.connectedHighlights) {
487500
for (const h of msaView.connectedHighlights) {
@@ -537,7 +550,7 @@ export default function stateModelFactory(
537550
adapterConfig: self.adapterConfig,
538551
statusCallback: (message: string) => {
539552
if (isAlive(self)) {
540-
self.setMessage(message)
553+
self.setStatusMessage(message)
541554
}
542555
},
543556
})) as { samples: Sample[]; tree: NodeWithIds | undefined },
@@ -563,6 +576,43 @@ export default function stateModelFactory(
563576
},
564577
}
565578
})
579+
.postProcessSnapshot(snap => {
580+
const {
581+
rowHeight,
582+
rowProportion,
583+
showAllLetters,
584+
mismatchRendering,
585+
showBranchLen,
586+
treeAreaWidth,
587+
showAsUpperCase,
588+
showSidebar,
589+
...rest
590+
} = snap as typeof snap & {
591+
rowHeight?: number
592+
rowProportion?: number
593+
showAllLetters?: boolean
594+
mismatchRendering?: boolean
595+
showBranchLen?: boolean
596+
treeAreaWidth?: number
597+
showAsUpperCase?: boolean
598+
showSidebar?: boolean
599+
}
600+
return {
601+
...(rest as Omit<typeof rest, symbol>),
602+
...(rowHeight !== defaultRowHeight ? { rowHeight } : {}),
603+
...(rowProportion !== defaultRowProportion ? { rowProportion } : {}),
604+
...(showAllLetters !== defaultShowAllLetters ? { showAllLetters } : {}),
605+
...(mismatchRendering !== defaultMismatchRendering
606+
? { mismatchRendering }
607+
: {}),
608+
...(showBranchLen !== defaultShowBranchLen ? { showBranchLen } : {}),
609+
...(treeAreaWidth !== defaultTreeAreaWidth ? { treeAreaWidth } : {}),
610+
...(showAsUpperCase !== defaultShowAsUpperCase
611+
? { showAsUpperCase }
612+
: {}),
613+
...(showSidebar !== defaultShowSidebar ? { showSidebar } : {}),
614+
}
615+
})
566616
}
567617

568618
export type LinearMafDisplayStateModel = ReturnType<typeof stateModelFactory>

src/LinearMafRenderer/LinearMafRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default class LinearMafRenderer extends FeatureRendererType {
2626
const bpExpansion = 1
2727

2828
return {
29-
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
29+
// xref https://github.com/mobxjs/@jbrowse/mobx-state-tree/issues/1524 for Omit
3030
...(region as Omit<typeof region, symbol>),
3131
start: Math.floor(Math.max(start - bpExpansion, 0)),
3232
end: Math.ceil(end + bpExpansion),

0 commit comments

Comments
 (0)