diff --git a/packages/core/pluggableElementTypes/renderers/BoxRendererType.ts b/packages/core/pluggableElementTypes/renderers/BoxRendererType.ts index 8e1e2f5aef..99cb3502a8 100644 --- a/packages/core/pluggableElementTypes/renderers/BoxRendererType.ts +++ b/packages/core/pluggableElementTypes/renderers/BoxRendererType.ts @@ -15,7 +15,7 @@ import FeatureRendererType, { ResultsSerialized as FeatureResultsSerialized, ResultsDeserialized as FeatureResultsDeserialized, } from './FeatureRendererType' -import { getLayoutId, Region, Feature } from '../../util' +import { getLayoutId, Region, Feature, max } from '../../util' import { readConfObject, AnyConfigurationModel } from '../../configuration' import SerializableFilterChain from './util/serializableFilterChain' import RpcManager from '../../rpc/RpcManager' @@ -102,6 +102,7 @@ export interface RenderArgsDeserialized extends FeatureRenderArgsDeserialized { export interface RenderResults extends FeatureRenderResults { layout: BaseLayout + blockHeight: number } export interface ResultsSerialized extends FeatureResultsSerialized { @@ -205,6 +206,15 @@ export default class BoxRendererType extends FeatureRendererType { (props.layout as undefined | BaseLayout) || this.createLayoutInWorker(props) const result = await super.render({ ...props, layout }) - return { ...result, layout } + + const rects = layout.getRectangles() + const blockHeight = max( + [...rects] + .filter(([key]) => [...result.features.keys()].includes(key)) + .flatMap(rect => rect[1][3]), // only interested in the 'bottom' value + 0, + ) + + return { ...result, layout, blockHeight } } } diff --git a/plugins/alignments/src/LinearAlignmentsDisplay/models/model.tsx b/plugins/alignments/src/LinearAlignmentsDisplay/models/model.tsx index 60b966ad80..e34fe2ce06 100644 --- a/plugins/alignments/src/LinearAlignmentsDisplay/models/model.tsx +++ b/plugins/alignments/src/LinearAlignmentsDisplay/models/model.tsx @@ -230,6 +230,7 @@ function stateModelFactory( * #action */ resizeHeight(distance: number) { + self.PileupDisplay.resizeHeight(distance) const oldHeight = self.height const newHeight = this.setHeight(self.height + distance) return newHeight - oldHeight @@ -278,15 +279,6 @@ function stateModelFactory( self.setSNPCoverageHeight(self.SNPCoverageDisplay.height) }), ) - - addDisposer( - self, - autorun(() => { - self.PileupDisplay.setHeight( - self.height - self.SNPCoverageDisplay.height, - ) - }), - ) }, /** * #action @@ -359,6 +351,29 @@ function stateModelFactory( const { height, ...rest } = snap return { heightPreConfig: height, ...rest } }) + .actions(self => ({ + afterAttach() { + addDisposer( + self, + autorun(() => { + // Sets the heightPreConfig conditional to whether the user has the layout height setting on (impacts the PileupDisplay) + const { PileupDisplay, SNPCoverageDisplay } = self + + const setting = PileupDisplay.adjustTrackLayoutHeightSetting + + if (setting === 'dynamic' || setting === 'bound') { + self.setHeight(PileupDisplay.height + SNPCoverageDisplay.height) + } + + if (setting === 'static') { + self.PileupDisplay.setHeight( + self.height - SNPCoverageDisplay.height, + ) + } + }), + ) + }, + })) } export default stateModelFactory diff --git a/plugins/alignments/src/LinearPileupDisplay/SharedLinearPileupDisplayMixin.ts b/plugins/alignments/src/LinearPileupDisplay/SharedLinearPileupDisplayMixin.ts index 529261e55c..03066aad4d 100644 --- a/plugins/alignments/src/LinearPileupDisplay/SharedLinearPileupDisplayMixin.ts +++ b/plugins/alignments/src/LinearPileupDisplay/SharedLinearPileupDisplayMixin.ts @@ -530,7 +530,7 @@ export function SharedLinearPileupDisplayMixin( ], }, { - label: 'Set max height...', + label: 'Set max height', onClick: () => { getSession(self).queueDialog(doneCallback => [ SetMaxHeightDialog, @@ -538,6 +538,27 @@ export function SharedLinearPileupDisplayMixin( ]) }, }, + { + label: 'Track height adjustment', + subMenu: [ + { + label: 'Static (resized, or configured height)', + type: 'radio', + checked: self.adjustTrackLayoutHeightSetting === 'static', + onClick: () => + // @ts-expect-error + self.setAdjustTrackLayoutHeightSetting('static'), + }, + { + label: 'Dynamic (auto-adjust to show all features)', + type: 'radio', + checked: self.adjustTrackLayoutHeightSetting === 'dynamic', + onClick: () => + // @ts-expect-error + self.setAdjustTrackLayoutHeightSetting('dynamic'), + }, + ], + }, ] }, } diff --git a/plugins/alignments/src/LinearPileupDisplay/model.ts b/plugins/alignments/src/LinearPileupDisplay/model.ts index d6ba72e9de..196e86e5d9 100644 --- a/plugins/alignments/src/LinearPileupDisplay/model.ts +++ b/plugins/alignments/src/LinearPileupDisplay/model.ts @@ -60,7 +60,11 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) { * #property */ mismatchAlpha: types.maybe(types.boolean), - + /** + * #property + * setting to auto-adjust the layout height of tracks + */ + adjustTrackLayoutHeight: types.optional(types.string, 'static'), /** * #property */ @@ -164,6 +168,12 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) { self.sortReady = false self.featureHeight = n }, + /** + * #action + */ + setAdjustTrackLayoutHeightSetting(setting: 'static' | 'dynamic') { + self.adjustTrackLayoutHeight = setting + }, })) .actions(self => { // resets the sort object and refresh whole display on reload diff --git a/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx b/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx index 717e627452..70df3be576 100644 --- a/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +++ b/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx @@ -101,6 +101,27 @@ function stateModelFactory() { }, })) .views(self => ({ + /** + * #getter + * array of the current block heights rendered + */ + get currentLayoutBlockHeights() { + const view = getContainingView(self) as LinearGenomeViewModel + return view.initialized + ? self.blockDefinitions.contentBlocks.map( + block => self.blockState.get(block.key)?.blockHeight, + ) + : [] + }, + + /** + * #getter + * returns true if all blocks are defined + */ + get allLayoutBlocksRendered() { + return this.currentLayoutBlockHeights.every(b => b !== undefined) + }, + /** * #getter * how many milliseconds to wait for the display to diff --git a/plugins/linear-genome-view/src/BaseLinearDisplay/models/TrackHeightMixin.tsx b/plugins/linear-genome-view/src/BaseLinearDisplay/models/TrackHeightMixin.tsx index d538ba944e..9c7520ee1a 100644 --- a/plugins/linear-genome-view/src/BaseLinearDisplay/models/TrackHeightMixin.tsx +++ b/plugins/linear-genome-view/src/BaseLinearDisplay/models/TrackHeightMixin.tsx @@ -1,5 +1,7 @@ +import { addDisposer, types } from 'mobx-state-tree' import { getConf } from '@jbrowse/core/configuration' -import { types } from 'mobx-state-tree' +import { getSession, max } from '@jbrowse/core/util' +import { autorun } from 'mobx' const minDisplayHeight = 20 @@ -13,27 +15,52 @@ export default function TrackHeightMixin() { /** * #property */ - heightPreConfig: types.maybe( - types.refinement( - 'displayHeight', - types.number, - n => n >= minDisplayHeight, - ), - ), + heightPreConfig: types.number, }) .volatile(() => ({ /** - * #property + * #volatile */ scrollTop: 0, })) .views(self => ({ + /** + * #getter + * returns the value of the track height setting from the view model + */ + get adjustTrackLayoutHeightSetting() { + // @ts-ignore + const { adjustTrackLayoutHeight } = self + + return adjustTrackLayoutHeight + }, + /** + * #getter + * the max height between the rendered blocks and the configured height + */ + get maxLayoutBlockHeight() { + // @ts-expect-error + const confHeight = getConf(self, 'height') as number + // @ts-expect-error + return max(self.currentLayoutBlockHeights, confHeight) + }, + /** + * #getter + * returns the height of the track as a combination of the config and the + * settings + */ get height() { // @ts-expect-error return self.heightPreConfig ?? (getConf(self, 'height') as number) }, })) .actions(self => ({ + notifyStaticSettingChange() { + getSession(self).notify( + 'LGV track height setting has changed to Static to use your manually set height.', + 'info', + ) + }, /** * #action */ @@ -51,9 +78,35 @@ export default function TrackHeightMixin() { * #action */ resizeHeight(distance: number) { + // if the user resizes their height, we want the setting to turn off + // and maintain their set height + if (self.adjustTrackLayoutHeightSetting !== 'static') { + // @ts-ignore + self.setAdjustTrackLayoutHeightSetting('static') + this.notifyStaticSettingChange() + } const oldHeight = self.height const newHeight = this.setHeight(self.height + distance) return newHeight - oldHeight }, })) + .actions(self => ({ + afterAttach() { + addDisposer( + self, + autorun(() => { + // @ts-expect-error + if (self.allLayoutBlocksRendered) { + switch (self.adjustTrackLayoutHeightSetting) { + case 'dynamic': + self.setHeight(self.maxLayoutBlockHeight) + break + default: + break + } + } + }), + ) + }, + })) } diff --git a/plugins/linear-genome-view/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts b/plugins/linear-genome-view/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts index fea99558b5..eff2c4a9be 100644 --- a/plugins/linear-genome-view/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts +++ b/plugins/linear-genome-view/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts @@ -47,6 +47,7 @@ const blockState = types reactElement: undefined as React.ReactElement | undefined, features: undefined as Map | undefined, layout: undefined as any, + blockHeight: undefined as number | undefined, status: '', error: undefined as unknown, message: undefined as string | undefined, @@ -97,6 +98,7 @@ const blockState = types self.reactElement = undefined self.features = undefined self.layout = undefined + self.blockHeight = undefined self.error = undefined self.maxHeightReached = false self.renderProps = undefined @@ -111,6 +113,7 @@ const blockState = types self.reactElement = undefined self.features = undefined self.layout = undefined + self.blockHeight = undefined self.error = undefined self.maxHeightReached = false self.renderProps = undefined @@ -122,6 +125,7 @@ const blockState = types reactElement: React.ReactElement features: Map layout: any + blockHeight: number maxHeightReached: boolean renderProps: any } @@ -134,6 +138,7 @@ const blockState = types reactElement, features, layout, + blockHeight, maxHeightReached, renderProps, } = props @@ -142,6 +147,7 @@ const blockState = types self.reactElement = reactElement self.features = features self.layout = layout + self.blockHeight = blockHeight self.error = undefined self.maxHeightReached = maxHeightReached self.renderProps = renderProps @@ -158,6 +164,7 @@ const blockState = types self.reactElement = undefined self.features = undefined self.layout = undefined + self.blockHeight = undefined self.maxHeightReached = false self.error = error self.renderProps = undefined @@ -172,6 +179,7 @@ const blockState = types self.reactElement = undefined self.features = undefined self.layout = undefined + self.blockHeight = undefined self.error = undefined self.message = undefined self.maxHeightReached = false @@ -302,7 +310,7 @@ async function renderBlockEffect( return undefined } - const { reactElement, features, layout, maxHeightReached } = + const { reactElement, features, layout, blockHeight, maxHeightReached } = await rendererType.renderInClient(rpcManager, { ...renderArgs, ...renderProps, @@ -313,6 +321,7 @@ async function renderBlockEffect( reactElement, features, layout, + blockHeight, maxHeightReached, renderProps, } diff --git a/plugins/linear-genome-view/src/LinearBasicDisplay/model.ts b/plugins/linear-genome-view/src/LinearBasicDisplay/model.ts index ece70116df..9dbb16cad5 100644 --- a/plugins/linear-genome-view/src/LinearBasicDisplay/model.ts +++ b/plugins/linear-genome-view/src/LinearBasicDisplay/model.ts @@ -53,6 +53,11 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) { * #property */ trackMaxHeight: types.maybe(types.number), + /** + * #property + * setting to auto-adjust the layout height of tracks + */ + adjustTrackLayoutHeight: types.optional(types.string, 'static'), /** * #property */ @@ -114,6 +119,15 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) { self.trackDisplayMode ?? getConf(self, ['renderer', 'displayMode']) ) }, + /** + * #getter + */ + get adjustTrackLayoutHeightSetting() { + return ( + self.adjustTrackLayoutHeight || + getConf(self, ['renderer', 'adjustTrackLayoutHeight']) + ) + }, })) .views(self => ({ /** @@ -167,6 +181,12 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) { setMaxHeight(val?: number) { self.trackMaxHeight = val }, + /** + * #action + */ + setAdjustTrackLayoutHeightSetting(setting: 'static' | 'dynamic') { + self.adjustTrackLayoutHeight = setting + }, })) .views(self => { const { @@ -236,6 +256,25 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) { ]) }, }, + { + label: 'Track height adjustment', + subMenu: [ + { + label: 'Static (resized, or configured height)', + type: 'radio', + checked: self.adjustTrackLayoutHeightSetting === 'static', + onClick: () => + { self.setAdjustTrackLayoutHeightSetting('static') }, + }, + { + label: 'Dynamic (auto-adjust to show all features)', + type: 'radio', + checked: self.adjustTrackLayoutHeightSetting === 'dynamic', + onClick: () => + { self.setAdjustTrackLayoutHeightSetting('dynamic') }, + }, + ], + }, { label: 'Edit filters', onClick: () => { diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/TrackRenderingContainer.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/TrackRenderingContainer.tsx index 022deeffeb..c3d3b05dfa 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/components/TrackRenderingContainer.tsx +++ b/plugins/linear-genome-view/src/LinearGenomeView/components/TrackRenderingContainer.tsx @@ -29,6 +29,16 @@ const useStyles = makeStyles()({ background: 'none', zIndex: 2, }, + + dynamicResize: { + overflowY: 'auto', + overflowX: 'hidden', + whiteSpace: 'nowrap', + position: 'relative', + background: 'none', + zIndex: 2, + transition: 'height 0.5s', + }, }) type LGV = LinearGenomeViewModel @@ -49,6 +59,12 @@ const TrackRenderingContainer = observer(function ({ const trackId = getConf(track, 'trackId') const ref = useRef(null) const minimized = track.minimized + const dynamicResize = + display?.adjustTrackLayoutHeightSetting === 'static' + ? false + : display?.PileupDisplay?.adjustTrackLayoutHeightSetting === 'static' + ? false + : true useEffect(() => { if (ref.current) { @@ -61,10 +77,10 @@ const TrackRenderingContainer = observer(function ({ return (
display.setScrollTop(evt.currentTarget.scrollTop)} onDragEnter={onDragEnter} data-testid={`trackRenderingContainer-${id}-${trackId}`} diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap b/plugins/linear-genome-view/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap index b430b68f90..6dc350a51b 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap +++ b/plugins/linear-genome-view/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap @@ -507,7 +507,7 @@ exports[`renders one track, one region 1`] = `
@@ -532,7 +532,7 @@ exports[`renders one track, one region 1`] = ` > @@ -1323,7 +1323,7 @@ exports[`renders two tracks, two regions 1`] = `
@@ -1348,7 +1348,7 @@ exports[`renders two tracks, two regions 1`] = ` > @@ -1363,7 +1363,7 @@ exports[`renders two tracks, two regions 1`] = ` > @@ -1451,7 +1451,7 @@ exports[`renders two tracks, two regions 1`] = `
@@ -1476,7 +1476,7 @@ exports[`renders two tracks, two regions 1`] = ` > @@ -1491,7 +1491,7 @@ exports[`renders two tracks, two regions 1`] = ` > diff --git a/plugins/svg/src/SvgFeatureRenderer/components/SvgFeatureRendering.tsx b/plugins/svg/src/SvgFeatureRenderer/components/SvgFeatureRendering.tsx index 243c95193b..89e7389a2e 100644 --- a/plugins/svg/src/SvgFeatureRenderer/components/SvgFeatureRendering.tsx +++ b/plugins/svg/src/SvgFeatureRenderer/components/SvgFeatureRendering.tsx @@ -21,10 +21,6 @@ import { const xPadding = 3 const yPadding = 5 -// used so that user can click-away-from-feature below the laid out features -// (issue #1248) -const svgHeightPadding = 100 - function RenderedFeatureGlyph(props: { feature: Feature bpPerPx: number @@ -315,8 +311,19 @@ const SvgFeatureRendering = observer(function SvgFeatureRendering(props: { ) useEffect(() => { - setHeight(layout.getTotalHeight()) - }, [layout]) + if ( + // @ts-ignore + displayModel.height && + // @ts-ignore + displayModel.adjustTrackLayoutHeight === 'dynamic' + ) { + // @ts-ignore + setHeight(displayModel.height) + } else { + setHeight(layout.getTotalHeight()) + } + // @ts-ignore + }, [layout, displayModel.height, displayModel.adjustTrackLayoutHeight]) return exportSVG ? ( @@ -49,7 +49,7 @@ exports[`no features 1`] = `
@@ -60,7 +60,7 @@ exports[`one feature (compact mode) 1`] = `
@@ -228,7 +228,7 @@ exports[`one feature 1`] = `
@@ -251,7 +251,7 @@ exports[`processed transcript (exons + impliedUTR) 1`] = `
@@ -445,7 +445,7 @@ exports[`processed transcript (reducedRepresentation mode) 1`] = `
@@ -468,7 +468,7 @@ exports[`processed transcript 1`] = `
diff --git a/plugins/svg/src/SvgFeatureRenderer/configSchema.ts b/plugins/svg/src/SvgFeatureRenderer/configSchema.ts index 5da79e9ae4..39b44c2671 100644 --- a/plugins/svg/src/SvgFeatureRenderer/configSchema.ts +++ b/plugins/svg/src/SvgFeatureRenderer/configSchema.ts @@ -158,6 +158,18 @@ const SvgFeatureRenderer = ConfigurationSchema( defaultValue: 1200, }, + /** + * #slot + */ + adjustTrackLayoutHeight: { + type: 'string', + defaultValue: 'static', + model: types.enumeration('adjustTrackLayoutHeight', [ + 'static', + 'dynamic', + ]), + }, + /** * #slot */ diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap index 3e686743c1..39e9701890 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap @@ -998,7 +998,7 @@ exports[` renders successfully 1`] = `
diff --git a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-arc-display-1-snap.png b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-arc-display-1-snap.png index 1e651f50f3..37a7b57081 100644 Binary files a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-arc-display-1-snap.png and b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-arc-display-1-snap.png differ diff --git a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-arc-display-use-out-of-view-pairing-1-snap.png b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-arc-display-use-out-of-view-pairing-1-snap.png index 07b94b4a3c..04d54409e7 100644 Binary files a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-arc-display-use-out-of-view-pairing-1-snap.png and b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-arc-display-use-out-of-view-pairing-1-snap.png differ diff --git a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-cloud-display-1-snap.png b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-cloud-display-1-snap.png index 41ede63e0f..45ed10265a 100644 Binary files a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-cloud-display-1-snap.png and b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-long-read-cloud-display-1-snap.png differ diff --git a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-arc-display-1-snap.png b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-arc-display-1-snap.png index 52877c531e..66971fa4f0 100644 Binary files a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-arc-display-1-snap.png and b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-arc-display-1-snap.png differ diff --git a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-arc-display-use-out-of-view-pairing-1-snap.png b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-arc-display-use-out-of-view-pairing-1-snap.png index 6f3f10d6ab..40638305f4 100644 Binary files a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-arc-display-use-out-of-view-pairing-1-snap.png and b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-arc-display-use-out-of-view-pairing-1-snap.png differ diff --git a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-cloud-display-1-snap.png b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-cloud-display-1-snap.png index 25d0897742..756525d254 100644 Binary files a/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-cloud-display-1-snap.png and b/products/jbrowse-web/src/tests/__image_snapshots__/alignment-arcs-test-tsx-toggle-short-read-cloud-display-1-snap.png differ