jbrowse-components
jbrowse-components copied to clipboard
Add new view type multilevel linear view
Resolves #70
Adds Multilevel Linear View (MLLV), a view type that allows users to have 2 or more linear genome views displayed and linked through a single view.
MLLV acts/reacts based on the first view in the display, the 'anchor', and projects a red highlight of the area being observed in the anchor to subsequent views.
Subsequent views can be zoomed in and out from the anchor individually, can show regions independent of the anchor, etc.
Subsequent views have unique headers to the anchor, where the anchor displays a typical cytoband/region to help contextualize its location.
Todo:
- minimize individual views
- change link such that when linked actions on the sub views do not change the anchor view
- change square/align such that subsequent views always align to the anchor view's current location (preserving their zoom level)
Codecov Report
:exclamation: No coverage uploaded for pull request base (
main@ebd548c
). Click here to learn what that means. The diff coverage is53.28%
.
:exclamation: Current head 4db289a differs from pull request most recent head 46aa987. Consider uploading reports for the commit 46aa987 to get more accurate results
@@ Coverage Diff @@
## main #2929 +/- ##
=======================================
Coverage ? 59.36%
=======================================
Files ? 680
Lines ? 29257
Branches ? 7114
=======================================
Hits ? 17368
Misses ? 11565
Partials ? 324
Impacted Files | Coverage Δ | |
---|---|---|
packages/core/ui/ReturnToImportFormDialog.tsx | 8.33% <0.00%> (ø) |
|
...iew/src/LinearGenomeView/components/ImportForm.tsx | 77.33% <ø> (ø) |
|
products/jbrowse-desktop/src/corePlugins.ts | 100.00% <ø> (ø) |
|
products/jbrowse-web/src/corePlugins.ts | 100.00% <ø> (ø) |
|
...evel-linear-view/src/MultilevelLinearView/model.ts | 26.92% <26.92%> (ø) |
|
.../src/MultilevelLinearView/components/Subheader.tsx | 33.33% <33.33%> (ø) |
|
...src/MultilevelLinearView/components/ImportForm.tsx | 40.87% <40.87%> (ø) |
|
...w/src/LinearGenomeView/components/MiniControls.tsx | 56.25% <46.15%> (ø) |
|
...iew/src/MultilevelLinearView/components/Header.tsx | 64.70% <64.70%> (ø) |
|
...inear-view/src/LinearGenomeMultilevelView/model.ts | 66.92% <66.92%> (ø) |
|
... and 11 more |
:mega: We’re building smart automated test selection to slash your CI/CD build times. Learn more
- There seems to be something happening where if you open a track, the highlighted area for that view starts to extend into the header of the view below it (see second view below)
I wasn't able to replicate this, can I get more specific steps? Added everything else 😄 thanks!
There seems to be something happening where if you open a track, the highlighted area for that view starts to extend into the header of the view below it (see second view below)
I wasn't able to replicate this, can I get more specific steps? Added everything else 😀️ thanks!
Sure thing. Here's what I did:
- Use Volvox config
- Open MLLV with three views
- In the middle view, open the "GFF3Tabix genes" track
Here's a link to a session that shows this:
https://s3.amazonaws.com/jbrowse.org/code/jb2/multilevel-linear-view/index.html?config=test_data%2Fvolvox%2Fconfig.json&session=share-O7AF87-05b&password=9hRyH
I tried this on a couple different browsers, so I don't think it's a browser-specific problem.
random note: was just testing out and found using the search box in the import form on this branch in https://jbrowse.org/code/jb2/multilevel-linear-view/ produces error
TypeError: Cannot read properties of undefined (reading 'searchScope')
also just for recap we decided on wednesday to do a UI review google meet, if people are available can come to meeting today
document on UI review https://docs.google.com/document/d/1oDwszZZLteq5EVLQoUDExQlPb_6KiCoy-rICeEl4v5Y/edit?usp=sharing
I added some notes with comparison to gbrowse 2 there
share link to a demo: http://localhost:3000/?config=http%3A%2F%2Flocalhost%3A3000%2Ftest_data%2Fconfig_demo.json&session=share-kptPlXMYi6&password=Hvg9X
screenshot:
try zooming to region, like this
when your views are linked and let me know how that feels
think you could make a share link from the CI deployment of this branch? it's at https://jbrowse.org/code/jb2/multilevel-linear-view/?config=test_data%2Fvolvox%2Fconfig.json
think you could make a share link from the CI deployment of this branch? it's at https://jbrowse.org/code/jb2/multilevel-linear-view/?config=test_data%2Fvolvox%2Fconfig.json
https://jbrowse.org/code/jb2/multilevel-linear-view/?config=test_data%2Fvolvox%2Fconfig.json&session=share-rUfuitiT5W&password=ULQL2
let me know if that's sufficient
my initial impression from visiting a share link is that this feels pretty intuitive, which is great
I can try to review in more detail but I think that the look and feel is quite good!
I would make marker showing the region being zoomed in on visible even if it is small e.g. force it to be Math.max(3px, actual px value of the region)
random aside: i think this was a discussion at meeting but I would not add tests just to bump up coverage numbers, just try to see which things feel like they would be "worth" covering. To me, the tests that give me the most confidence oftentimes is that the system is not completely broken, which for me means integration tests like those in products/jbrowse-web/src/tests/
it is up to you how to test the system but that is just a perspective from me :)
some more random things
-
if I try to zoom in on a region using the upper panel (via a click and drag rubberband), then it actually performs that action on the lower panel. is the upper level locked and you can't zoom in on it? is there a way to unlock it?
-
given that there are multiple regions displayed, is our "refnameautocomplete" accurate in trying to display a single region? maybe we should not display a refname autocomplete at all. searching for a gene name for example is a little weird in this view because the zoom level does not zoom in on the gene I select (e.g. search for BRCA1, click a gene, but the result is a zoomed out region)
- I am occasionally getting "dead state tree node passed to RPC call" testing on production instance https://jbrowse.org/code/jb2/multilevel-linear-view/?config=test_data%2Fconfig_demo.json have you seen this? it's a bit hard to reproduce but might be something to look at.
screenshot with dead state tree node error. I refreshed page and it worked but this happened imemdiately after turning a track on
@cmdcolin
-
Re: coverage ;; originally the tests I had covered about as much as the "linear-comparative-view", but since there's a lot of extra little components to this it tanked the codecov and rob requested I bump it up to more normal levels. I tried to get as much integration testing as possible in there, but files like the
model.ts
files were still at ~20% coverage without the unit tests...I agree there might be excess of unit tests, but at the same time I have satisfactory integration tests, so I think I'll leave it as long as the quantity of them isn't a problem. - re: zoom in on region ;; this is working as intended, because of the stipulation that each view maintain its proportional relation with the "details" view. That being said, the Overview will always be at that zoom level and as such when "unlinked" will do nothing when you try to zoom in. I'm not married to this and can change it so when "unlinked" you can do whatever you want to the other views, just let me know with certainty you think it would be an improvement for the user to be able to do this.
- re: refnameautocomplete ;; it's searching on the details view, so the expected behaviour is it would nav the details view to that location (and if linked, the other views as well). It seems it isn't zooming into the feature like the linear genome view does (it maintains the zoom level when you nav), I can fix that to behave more like the LGV.
- re: deadstate ;; I've never seen this, but I will dedicate some time into replicating / fixing
currently viewMenu->Return to import form does not do anything (error in js console: model.clearView is not a function)

new dialog, current commit applies it to all views, open to re-word/work if needed
in a recent demo we noticed closing a view is frighteningly easy
Why don’t we make closing it reversible? Could add recentlyClosedViews as a volatile on the session and use one of the timed snack bar notifications with an undo like google does with gmail
On Fri, Jul 22, 2022 at 7:37 AM Caroline Bridge @.***> wrote:
[image: image] https://user-images.githubusercontent.com/83305007/180462297-740b7104-c792-4d57-8a43-d683967ea104.png
new dialog, current commit applies it to all views, open to re-word/work if needed
in a recent demo we noticed closing a view is frighteningly easy
— Reply to this email directly, view it on GitHub https://github.com/GMOD/jbrowse-components/pull/2929#issuecomment-1192640622, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAASAFICPDD4RUGZI7BTWF3VVKWYTANCNFSM5T4RANMA . You are receiving this because you commented.Message ID: @.***>
Would it be possible to move the view-closing into its own PR so it can move through the workflow independently of the MLLV?
any thoughts on making this an external plugin?
limiting the files changed to non-plugins/multilevel-linear-view the result is a pretty minimal diff which could be merged, then the multilevel plugin itself could be spun into an external plugin? i know this would challenge/change the testing that the mllv does, but i think this could work well as an external plugin
core of this PR's diff after possibly externalized stuff is removed
diff --git a/packages/core/ui/ReturnToImportFormDialog.tsx b/packages/core/ui/ReturnToImportFormDialog.tsx
index a54fcee3f..b22ed6887 100644
--- a/packages/core/ui/ReturnToImportFormDialog.tsx
+++ b/packages/core/ui/ReturnToImportFormDialog.tsx
@@ -33,7 +33,7 @@ function ReturnToImportFormDialog({
return (
<Dialog maxWidth="xl" open onClose={handleClose}>
<DialogTitle>
- Reference sequence
+ Are you sure you want to return to the import form?
{handleClose ? (
<IconButton
className={classes.closeButton}
@@ -48,11 +48,18 @@ function ReturnToImportFormDialog({
<DialogContent>
<Typography>
- Are you sure you want to return to the import form? This will lose
- your current view
+ Upon returning to the import form you will lose your work on your
+ current view.
</Typography>
</DialogContent>
<DialogActions>
+ <Button
+ onClick={() => handleClose()}
+ color="secondary"
+ variant="contained"
+ >
+ Cancel
+ </Button>
<Button
onClick={() => {
model.clearView()
@@ -62,14 +69,7 @@ function ReturnToImportFormDialog({
color="primary"
autoFocus
>
- OK
- </Button>
- <Button
- onClick={() => handleClose()}
- color="secondary"
- variant="contained"
- >
- Cancel
+ Confirm
</Button>
</DialogActions>
</Dialog>
diff --git a/plugins/data-management/src/PluginStoreWidget/components/__snapshots__/PluginStoreWidget.test.js.snap b/plugins/data-management/src/PluginStoreWidget/components/__snapshots__/PluginStoreWidget.test.js.snap
index 1ebc6dd92..17409031a 100644
--- a/plugins/data-management/src/PluginStoreWidget/components/__snapshots__/PluginStoreWidget.test.js.snap
+++ b/plugins/data-management/src/PluginStoreWidget/components/__snapshots__/PluginStoreWidget.test.js.snap
@@ -457,6 +457,28 @@ exports[`<PluginStoreWidget /> renders with the available plugins 1`] = `
MenusPlugin
</p>
</li>
+ <li
+ class="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding css-ypie1g-MuiListItem-root"
+ >
+ <svg
+ aria-hidden="true"
+ aria-label="This plugin was installed by an administrator, you cannot remove it."
+ class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium tss-1qzgyfr-lockedPluginTooltip css-havevq-MuiSvgIcon-root"
+ data-mui-internal-clone-element="true"
+ data-testid="LockIcon"
+ focusable="false"
+ viewBox="0 0 24 24"
+ >
+ <path
+ d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"
+ />
+ </svg>
+ <p
+ class="MuiTypography-root MuiTypography-body1 css-k0xfey-MuiTypography-root"
+ >
+ MultilevelLinearViewPlugin
+ </p>
+ </li>
<li
class="MuiListItem-root MuiListItem-dense MuiListItem-gutters MuiListItem-padding css-ypie1g-MuiListItem-root"
>
diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/Header.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/Header.tsx
index 08028be9e..353360d7a 100644
--- a/plugins/linear-genome-view/src/LinearGenomeView/components/Header.tsx
+++ b/plugins/linear-genome-view/src/LinearGenomeView/components/Header.tsx
@@ -119,11 +119,14 @@ const Controls = ({ model }: { model: LGV }) => {
}
const LinearGenomeViewHeader = observer(({ model }: { model: LGV }) => {
- return model.hideHeaderOverview ? (
- <Controls model={model} />
+ return model.hideHeaderOverview || model.hideHeader ? (
+ <Controls model={model} data-testid="controls_lgv" />
) : (
<OverviewScaleBar model={model}>
- <Controls model={model} />
+ {
+ // @ts-ignore
+ !model.hideControls ? <Controls model={model} /> : null
+ }
</OverviewScaleBar>
)
})
diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/ImportForm.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/ImportForm.tsx
index 39855bc28..e7072d3b9 100644
--- a/plugins/linear-genome-view/src/LinearGenomeView/components/ImportForm.tsx
+++ b/plugins/linear-genome-view/src/LinearGenomeView/components/ImportForm.tsx
@@ -92,7 +92,6 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
location = results[0].getLocation()
trackId = results[0].getTrackId()
}
-
model.navToLocString(location, selectedAsm)
if (trackId) {
model.showTrack(trackId)
diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/LinearGenomeView.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/LinearGenomeView.tsx
index 44d697dda..c358cf165 100644
--- a/plugins/linear-genome-view/src/LinearGenomeView/components/LinearGenomeView.tsx
+++ b/plugins/linear-genome-view/src/LinearGenomeView/components/LinearGenomeView.tsx
@@ -6,11 +6,9 @@ import { observer } from 'mobx-react'
// locals
import { LinearGenomeViewModel } from '..'
-import Header from './Header'
import TrackContainer from './TrackContainer'
import TracksContainer from './TracksContainer'
import ImportForm from './ImportForm'
-import MiniControls from './MiniControls'
import GetSequenceDialog from './GetSequenceDialog'
import SearchResultsDialog from './SearchResultsDialog'
@@ -45,7 +43,7 @@ const useStyles = makeStyles()(theme => ({
}))
const LinearGenomeView = observer(({ model }: { model: LGV }) => {
- const { tracks, error, hideHeader, initialized, hasDisplayedRegions } = model
+ const { tracks, error, initialized, hasDisplayedRegions } = model
const { classes } = useStyles()
if (!initialized && !error) {
@@ -59,6 +57,9 @@ const LinearGenomeView = observer(({ model }: { model: LGV }) => {
return <ImportForm model={model} />
}
+ const MiniControlsComponent = model.MiniControlsComponent()
+ const HeaderComponent = model.HeaderComponent()
+
return (
<div style={{ position: 'relative' }}>
{model.seqDialogDisplayed ? (
@@ -73,19 +74,9 @@ const LinearGenomeView = observer(({ model }: { model: LGV }) => {
handleClose={() => model.setSearchResults(undefined, undefined)}
/>
) : null}
- {!hideHeader ? (
- <Header model={model} />
- ) : (
- <div
- style={{
- position: 'absolute',
- right: 0,
- zIndex: 1001,
- }}
- >
- <MiniControls model={model} />
- </div>
- )}
+
+ <HeaderComponent model={model} />
+ <MiniControlsComponent model={model} />
<TracksContainer model={model}>
{!tracks.length ? (
<Paper variant="outlined" className={classes.note}>
@@ -107,7 +98,8 @@ const LinearGenomeView = observer(({ model }: { model: LGV }) => {
)}
</Paper>
) : (
- tracks.map(track => (
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ tracks.map((track: any) => (
<TrackContainer key={track.id} model={model} track={track} />
))
)}
diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/MiniControls.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/MiniControls.tsx
index 4484a8d13..72c111313 100644
--- a/plugins/linear-genome-view/src/LinearGenomeView/components/MiniControls.tsx
+++ b/plugins/linear-genome-view/src/LinearGenomeView/components/MiniControls.tsx
@@ -9,46 +9,48 @@ import { LinearGenomeViewModel } from '..'
const MiniControls = observer((props: { model: LinearGenomeViewModel }) => {
const { model } = props
- const { bpPerPx, maxBpPerPx, minBpPerPx, scaleFactor } = model
+ const { bpPerPx, maxBpPerPx, minBpPerPx, scaleFactor, hideHeader } = model
const [anchorEl, setAnchorEl] = useState<HTMLElement>()
- return (
- <Paper style={{ background: '#aaa7' }}>
- <IconButton
- color="secondary"
- onClick={event => setAnchorEl(event.currentTarget)}
- >
- <ArrowDown fontSize="small" />
- </IconButton>
+ return hideHeader ? (
+ <div style={{ position: 'absolute', right: '0px', zIndex: '1001' }}>
+ <Paper style={{ background: '#aaa7' }}>
+ <IconButton
+ color="secondary"
+ onClick={event => setAnchorEl(event.currentTarget)}
+ >
+ <ArrowDown fontSize="small" />
+ </IconButton>
- <IconButton
- data-testid="zoom_out"
- onClick={() => model.zoom(bpPerPx * 2)}
- disabled={bpPerPx >= maxBpPerPx - 0.0001 || scaleFactor !== 1}
- color="secondary"
- >
- <ZoomOut fontSize="small" />
- </IconButton>
- <IconButton
- data-testid="zoom_in"
- onClick={() => model.zoom(model.bpPerPx / 2)}
- disabled={bpPerPx <= minBpPerPx + 0.0001 || scaleFactor !== 1}
- color="secondary"
- >
- <ZoomIn fontSize="small" />
- </IconButton>
- <Menu
- anchorEl={anchorEl}
- open={Boolean(anchorEl)}
- onMenuItemClick={(_, callback) => {
- callback()
- setAnchorEl(undefined)
- }}
- onClose={() => setAnchorEl(undefined)}
- menuItems={model.menuItems()}
- />
- </Paper>
- )
+ <IconButton
+ data-testid="zoom_out"
+ onClick={() => model.zoom(bpPerPx * 2)}
+ disabled={bpPerPx >= maxBpPerPx - 0.0001 || scaleFactor !== 1}
+ color="secondary"
+ >
+ <ZoomOut fontSize="small" />
+ </IconButton>
+ <IconButton
+ data-testid="zoom_in"
+ onClick={() => model.zoom(model.bpPerPx / 2)}
+ disabled={bpPerPx <= minBpPerPx + 0.0001 || scaleFactor !== 1}
+ color="secondary"
+ >
+ <ZoomIn fontSize="small" />
+ </IconButton>
+ <Menu
+ anchorEl={anchorEl}
+ open={Boolean(anchorEl)}
+ onMenuItemClick={(_, callback) => {
+ callback()
+ setAnchorEl(undefined)
+ }}
+ onClose={() => setAnchorEl(undefined)}
+ menuItems={model.menuItems()}
+ />
+ </Paper>
+ </div>
+ ) : null
})
export default MiniControls
diff --git a/plugins/linear-genome-view/src/LinearGenomeView/index.tsx b/plugins/linear-genome-view/src/LinearGenomeView/index.tsx
index a99767853..156d39e99 100644
--- a/plugins/linear-genome-view/src/LinearGenomeView/index.tsx
+++ b/plugins/linear-genome-view/src/LinearGenomeView/index.tsx
@@ -1,4 +1,4 @@
-import { lazy } from 'react'
+import React, { lazy } from 'react'
import { getConf, AnyConfigurationModel } from '@jbrowse/core/configuration'
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
import { Region } from '@jbrowse/core/util/types'
@@ -52,7 +52,11 @@ import MenuOpenIcon from '@mui/icons-material/MenuOpen'
import { renderToSvg } from './components/LinearGenomeViewSvg'
import RefNameAutocomplete from './components/RefNameAutocomplete'
import SearchBox from './components/SearchBox'
+import MiniControls from './components/MiniControls'
+import Header from './components/Header'
import ExportSvgDlg from './components/ExportSvgDialog'
+import ZoomControls from './components/ZoomControls'
+import LinearGenomeView from './components/LinearGenomeView'
const SequenceSearchDialog = lazy(
() => import('./components/SequenceSearchDialog'),
@@ -192,6 +196,15 @@ export function stateModelFactory(pluginManager: PluginManager) {
},
}))
.views(self => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ MiniControlsComponent(): React.FC<any> {
+ return MiniControls
+ },
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ HeaderComponent(): React.FC<any> {
+ return Header
+ },
get assemblyErrors() {
const { assemblyManager } = getSession(self)
const { assemblyNames } = self
@@ -356,11 +369,9 @@ export function stateModelFactory(pluginManager: PluginManager) {
setError(error: Error | undefined) {
self.volatileError = error
},
-
toggleHeader() {
self.hideHeader = !self.hideHeader
},
-
toggleHeaderOverview() {
self.hideHeaderOverview = !self.hideHeaderOverview
},
@@ -1220,7 +1231,14 @@ export function stateModelFactory(pluginManager: PluginManager) {
}))
}
-export { renderToSvg, RefNameAutocomplete, SearchBox }
+export {
+ renderToSvg,
+ RefNameAutocomplete,
+ SearchBox,
+ ExportSvgDlg,
+ ZoomControls,
+ LinearGenomeView,
+}
export type LinearGenomeViewStateModel = ReturnType<typeof stateModelFactory>
export type LinearGenomeViewModel = Instance<LinearGenomeViewStateModel>
export { default as ReactComponent } from './components/LinearGenomeView'
I don't have strong feelings on it being an external plugin or part of core, but I would ask:
- might there be any future considerations for wanting to extend MLLV for other view types
- might there be any concerns for visibility considering this was a core concept in GBrowse
- what specifically makes it more optional/plugin-like rather than feature/core-like ;; this might be a useful definition to establish for future new view types as well
might there be any future considerations for wanting to extend MLLV for other view types
i don't see a particularly pressing concern currently
might there be any concerns for visibility considering this was a core concept in GBrowse
there will be less visibility as a plugin, at least for now, if it was made external, but I think that it won't cause concern or anxiety
what specifically makes it more optional/plugin-like rather than feature/core-like ;; this might be a useful definition to establish for future new view types as well
this is likely hard to define. that said, I think plugins are a great system to innovate in. vscode, neovim, etc. have dedicated plugin communities that reinvent the state of the art constantly. jb2 plugin ecosystem hasn't taken off yet but, it could:)
things in core become very permanent (especially with jbrowse because removing things causes configs to fail to load), so core stuff has to be pretty certain
https://github.com/GMOD/jbrowse-components/pull/3227 is adding some infrastructure for this and https://github.com/GMOD/jbrowse-plugin-multilevel-linear-view is taking over for now as the mllv provider ... may come back to this as a core plugin re: internal meeting discussion today, but we'll revisit if there is a use case for this feature in core