Skip to content

Commit

Permalink
Avoid rerendering all the SVG when feature is clicked (#3113)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Jul 26, 2022
1 parent e5b74f5 commit 8d58870
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ function FeatureGlyph(props: {
shouldShowDescription: boolean
fontHeight: number
allowedWidthExpansion: number
exportSVG: unknown
displayModel: DisplayModel
exportSVG?: unknown
displayModel?: DisplayModel
selected?: boolean
reversed?: boolean
topLevel?: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export default observer(
allowedWidthExpansion?: number
feature: Feature
reversed?: boolean
displayModel: DisplayModel
displayModel?: DisplayModel
exportSVG?: unknown
region: Region
exportSVG: unknown
viewParams: {
start: number
end: number
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import GranularRectLayout from '@jbrowse/core/util/layouts/GranularRectLayout'
import PrecomputedLayout from '@jbrowse/core/util/layouts/PrecomputedLayout'
import SimpleFeature from '@jbrowse/core/util/simpleFeature'
import React from 'react'
import { render } from '@testing-library/react'

// locals
import SvgRendererConfigSchema from '../configSchema'
import Rendering from './SvgFeatureRendering'
import SvgOverlay from './SvgOverlay'
Expand All @@ -12,12 +14,21 @@ import '@testing-library/jest-dom/extend-expect'
test('no features', () => {
const { container } = render(
<Rendering
width={500}
height={500}
regions={[{ refName: 'zonk', start: 0, end: 300 }]}
layout={new PrecomputedLayout({ rectangles: {}, totalHeight: 20 })}
viewParams={{ offsetPx: 0, start: 0, end: 50000 }}
config={{}}
blockKey="hello"
features={new Map()}
regions={[
{ refName: 'zonk', start: 0, end: 300, assemblyName: 'volvox' },
]}
layout={
new PrecomputedLayout({
rectangles: {},
totalHeight: 20,
containsNoTransferables: true,
maxHeightReached: false,
})
}
viewParams={{ offsetPx: 0, start: 0, end: 50000, offsetPx1: 5000 }}
config={SvgRendererConfigSchema.create({})}
bpPerPx={3}
/>,
)
Expand All @@ -28,11 +39,12 @@ test('no features', () => {
test('one feature', () => {
const { container } = render(
<Rendering
width={500}
height={500}
regions={[{ refName: 'zonk', start: 0, end: 1000 }]}
blockKey="hello"
regions={[
{ refName: 'zonk', start: 0, end: 1000, assemblyName: 'volvox' },
]}
layout={new GranularRectLayout({ pitchX: 1, pitchY: 1 })}
viewParams={{ offsetPx: 0, start: 0, end: 50000 }}
viewParams={{ offsetPx: 0, start: 0, end: 50000, offsetPx1: 5000 }}
features={
new Map([
['one', new SimpleFeature({ uniqueId: 'one', start: 1, end: 3 })],
Expand All @@ -46,16 +58,45 @@ test('one feature', () => {
expect(container.firstChild).toMatchSnapshot()
})

test('click on one feature, and do not re-render', () => {
let counter = 0
const { container, getByTestId } = render(
<Rendering
blockKey="hello"
regions={[
{ refName: 'zonk', start: 0, end: 1000, assemblyName: 'volvox' },
]}
layout={new GranularRectLayout({ pitchX: 1, pitchY: 1 })}
viewParams={{ offsetPx: 0, start: 0, end: 50000, offsetPx1: 5000 }}
features={
new Map([
['one', new SimpleFeature({ uniqueId: 'one', start: 1, end: 3 })],
['two', new SimpleFeature({ uniqueId: 'two', start: 1, end: 3 })],
['three', new SimpleFeature({ uniqueId: 'three', start: 1, end: 3 })],
])
}
config={SvgRendererConfigSchema.create({})}
bpPerPx={3}
detectRerender={() => counter++}
/>,
)
fireEvent.click(getByTestId('box-one'))
expect(counter).toBe(3)

expect(container.firstChild).toMatchSnapshot()
})

test('one feature (compact mode)', () => {
const config = SvgRendererConfigSchema.create({ displayMode: 'compact' })

const { container } = render(
<Rendering
width={500}
height={500}
regions={[{ refName: 'zonk', start: 0, end: 1000 }]}
blockKey="hello"
regions={[
{ refName: 'zonk', start: 0, end: 1000, assemblyName: 'volvox' },
]}
layout={new GranularRectLayout({ pitchX: 1, pitchY: 1 })}
viewParams={{ offsetPx: 0, start: 0, end: 50000 }}
viewParams={{ offsetPx: 0, start: 0, end: 50000, offsetPx1: 5000 }}
features={
new Map([
[
Expand Down Expand Up @@ -213,11 +254,12 @@ test('processed transcript (reducedRepresentation mode)', () => {
})
const { container } = render(
<Rendering
width={500}
height={500}
regions={[{ refName: 'zonk', start: 0, end: 1000 }]}
blockKey="hello"
regions={[
{ refName: 'zonk', start: 0, end: 1000, assemblyName: 'volvox' },
]}
layout={new GranularRectLayout({ pitchX: 1, pitchY: 1 })}
viewParams={{ offsetPx: 0, start: 0, end: 50000 }}
viewParams={{ offsetPx: 0, start: 0, end: 50000, offsetPx1: 5000 }}
features={
new Map([
['one', new SimpleFeature({ uniqueId: 'one', start: 1, end: 3 })],
Expand All @@ -234,11 +276,12 @@ test('processed transcript (reducedRepresentation mode)', () => {
test('processed transcript', () => {
const { container } = render(
<Rendering
width={500}
height={500}
regions={[{ refName: 'zonk', start: 0, end: 1000 }]}
blockKey="hello"
regions={[
{ refName: 'zonk', start: 0, end: 1000, assemblyName: 'volvox' },
]}
layout={new GranularRectLayout({ pitchX: 1, pitchY: 1 })}
viewParams={{ offsetPx: 0, start: 0, end: 50000 }}
viewParams={{ offsetPx: 0, start: 0, end: 50000, offsetPx1: 5000 }}
features={
new Map([
[
Expand Down Expand Up @@ -392,11 +435,12 @@ test('processed transcript', () => {
test('processed transcript (exons + impliedUTR)', () => {
const { container } = render(
<Rendering
width={500}
height={500}
regions={[{ refName: 'zonk', start: 0, end: 1000 }]}
blockKey="hello"
regions={[
{ refName: 'zonk', start: 0, end: 1000, assemblyName: 'volvox' },
]}
layout={new GranularRectLayout({ pitchX: 1, pitchY: 1 })}
viewParams={{ offsetPx: 0, start: 0, end: 50000 }}
viewParams={{ offsetPx: 0, start: 0, end: 50000, offsetPx1: 5000 }}
features={
new Map([
[
Expand Down Expand Up @@ -1026,18 +1070,20 @@ test('svg selected', () => {
const { container } = render(
<svg>
<SvgOverlay
width={500}
height={500}
blockKey="block1"
region={{ refName: 'zonk', start: 0, end: 1000 }}
region={{
refName: 'zonk',
start: 0,
end: 1000,
assemblyName: 'volvox',
}}
displayModel={{
getFeatureByID: () => {
return [0, 0, 10, 10]
},
featureIdUnderMouse: 'one',
selectedFeatureId: 'one',
}}
config={SvgRendererConfigSchema.create({})}
bpPerPx={3}
/>
</svg>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ function RenderedFeatureGlyph(props: {
region: Region
config: AnyConfigurationModel
layout: BaseLayout<unknown>
extraGlyphs: ExtraGlyphValidator[]
extraGlyphs?: ExtraGlyphValidator[]
displayMode: string
exportSVG: unknown
displayModel: DisplayModel
exportSVG?: unknown
displayModel?: DisplayModel
detectRerender?: () => void
viewParams: {
start: number
end: number
Expand All @@ -42,8 +43,20 @@ function RenderedFeatureGlyph(props: {
}
[key: string]: unknown
}) {
const { feature, bpPerPx, region, config, displayMode, layout, extraGlyphs } =
props
const {
feature,
detectRerender,
bpPerPx,
region,
config,
displayMode,
layout,
extraGlyphs,
} = props

// used for unit testing, difficult to mock out so it is in actual source code
detectRerender?.()

const { reversed } = region
const start = feature.get(reversed ? 'end' : 'start')
const startPx = bpToPx(start, region, bpPerPx)
Expand Down Expand Up @@ -139,15 +152,15 @@ function RenderedFeatureGlyph(props: {

const RenderedFeatures = observer(
(props: {
features: Map<string, Feature>
isFeatureDisplayed: (f: Feature) => boolean
features?: Map<string, Feature>
isFeatureDisplayed?: (f: Feature) => boolean
bpPerPx: number
config: AnyConfigurationModel
displayMode: string
displayModel: DisplayModel
displayModel?: DisplayModel
region: Region
exportSVG: unknown
extraGlyphs: ExtraGlyphValidator[]
exportSVG?: unknown
extraGlyphs?: ExtraGlyphValidator[]
layout: BaseLayout<unknown>
viewParams: {
start: number
Expand All @@ -161,7 +174,9 @@ const RenderedFeatures = observer(
return (
<>
{[...features.values()]
.filter(feature => isFeatureDisplayed(feature))
.filter(feature =>
isFeatureDisplayed ? isFeatureDisplayed(feature) : true,
)
.map(feature => (
<RenderedFeatureGlyph
key={feature.id()}
Expand All @@ -179,18 +194,19 @@ function SvgFeatureRendering(props: {
blockKey: string
regions: Region[]
bpPerPx: number
detectRerender?: () => void
config: AnyConfigurationModel
features: Map<string, Feature>
displayModel: DisplayModel
exportSVG: boolean
displayModel?: DisplayModel
exportSVG?: boolean
viewParams: {
start: number
end: number
offsetPx: number
offsetPx1: number
}
featureDisplayHandler: (f: Feature) => boolean
extraGlyphs: ExtraGlyphValidator[]
featureDisplayHandler?: (f: Feature) => boolean
extraGlyphs?: ExtraGlyphValidator[]
onMouseOut?: React.MouseEventHandler
onMouseDown?: React.MouseEventHandler
onMouseLeave?: React.MouseEventHandler
Expand All @@ -208,7 +224,7 @@ function SvgFeatureRendering(props: {
config,
displayModel = {},
exportSVG,
featureDisplayHandler = () => true,
featureDisplayHandler,
onMouseOut,
onMouseDown,
onMouseLeave,
Expand All @@ -218,6 +234,7 @@ function SvgFeatureRendering(props: {
onMouseUp,
onClick,
} = props

const [region] = regions || []
const width = (region.end - region.start) / bpPerPx
const displayMode = readConfObject(config, 'displayMode') as string
Expand All @@ -227,6 +244,7 @@ function SvgFeatureRendering(props: {
const [height, setHeight] = useState(0)
const [movedDuringLastMouseDown, setMovedDuringLastMouseDown] =
useState(false)

const mouseDown = useCallback(
(event: React.MouseEvent) => {
setMouseIsDown(true)
Expand Down Expand Up @@ -327,6 +345,7 @@ function SvgFeatureRendering(props: {
isFeatureDisplayed={featureDisplayHandler}
{...props}
/>

<SvgOverlay
{...props}
region={region}
Expand Down
5 changes: 3 additions & 2 deletions plugins/svg/src/SvgFeatureRenderer/components/SvgOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ import { Region } from '@jbrowse/core/util/types'
import { observer } from 'mobx-react'

type LayoutRecord = [number, number, number, number]

interface SvgOverlayProps {
region: Region
displayModel: {
displayModel?: {
getFeatureByID?: (arg0: string, arg1: string) => LayoutRecord
selectedFeatureId?: string
featureIdUnderMouse?: string
contextMenuFeature?: Feature
}
bpPerPx: number
blockKey: string
movedDuringLastMouseDown: boolean
movedDuringLastMouseDown?: boolean
onFeatureMouseDown?(
event: React.MouseEvent<SVGRectElement, MouseEvent>,
featureId: string,
Expand Down
Loading

0 comments on commit 8d58870

Please sign in to comment.