Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve SVG performance by avoiding re-render when feature is clicked #3113

Merged
merged 1 commit into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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