Skip to content

Commit

Permalink
Add optimizations to the canvas rendering (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin authored Aug 5, 2024
1 parent c40da75 commit c688964
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 167 deletions.
4 changes: 2 additions & 2 deletions lib/src/colorSchemes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,11 +352,11 @@ export default transform(colorSchemes, ([key, val]) => [
// should be WLVIMAFCHPY, colprot.xml says e.g. %#ACFHILMVWYPp" which has Y
export function getClustalXColor(
stats: Record<string, number>,
total: number,
model: { columns: Record<string, string> },
row: string,
col: number,
) {
const total = Object.values(stats).reduce((a, b) => a + b, 0)
const l = model.columns[row][col]
const {
W = 0,
Expand Down Expand Up @@ -499,11 +499,11 @@ export function getClustalXColor(
// should be WLVIMAFCHPY, colprot.xml says e.g. %#ACFHILMVWYPp" which has Y
export function getPercentIdentityColor(
stats: Record<string, number>,
total: number,
model: { columns: Record<string, string> },
row: string,
col: number,
) {
const total = Object.values(stats).reduce((a, b) => a + b, 0)
const l = model.columns[row][col]
const entries = Object.entries(stats)
let ent = 0
Expand Down
2 changes: 1 addition & 1 deletion lib/src/components/VerticalScrollbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const VerticalScrollbar = observer(({ model }: { model: MsaViewModel }) => {
cursor: 'pointer',
boxSizing: 'border-box',
width: 20,
height: b - t,
height: Math.max(b - t, 20),
zIndex: 100,
}}
onMouseOver={() => setHovered(true)}
Expand Down
88 changes: 48 additions & 40 deletions lib/src/components/dialogs/UserProvidedDomainsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import {
Typography,
} from '@mui/material'
import { getSession } from '@jbrowse/core/util'
import { Dialog } from '@jbrowse/core/ui'

// locals
import type { MsaViewModel } from '../../model'
import { jsonfetch } from '../../fetchUtils'
import type { InterProScanResponse } from '../../launchInterProScan'
import { Dialog } from '@jbrowse/core/ui'

const UserProvidedDomainsDialog = observer(function ({
handleClose,
Expand All @@ -38,51 +38,59 @@ const UserProvidedDomainsDialog = observer(function ({
open
>
<DialogContent>
<div style={{ display: 'flex', margin: 30 }}>
<div>
<Typography>
Open a JSON file of InterProScan results that you run remotely on
EBI servers or locally
</Typography>

<FormControl component="fieldset">
<RadioGroup
value={choice}
onChange={event => setChoice(event.target.value)}
>
<FormControlLabel value="url" control={<Radio />} label="URL" />
<FormControlLabel value="file" control={<Radio />} label="File" />
</RadioGroup>
</FormControl>
{choice === 'url' ? (
<div>
<Typography>Open a InterProScan JSON file remote URL</Typography>
<TextField
label="URL"
value={interProURL}
onChange={event => setInterProURL(event.target.value)}
/>
</div>
) : null}
{choice === 'file' ? (
<div style={{ paddingTop: 20 }}>
<Typography>
Open a InterProScan JSON file file from your local drive
</Typography>
<Button variant="outlined" component="label">
Choose File
<input
type="file"
hidden
onChange={({ target }) => {
const file = target?.files?.[0]
if (file) {
setFile(file)
}
}}
<div style={{ display: 'flex', margin: 30 }}>
<FormControl component="fieldset">
<RadioGroup
value={choice}
onChange={event => setChoice(event.target.value)}
>
<FormControlLabel value="url" control={<Radio />} label="URL" />
<FormControlLabel
value="file"
control={<Radio />}
label="File"
/>
</RadioGroup>
</FormControl>
{choice === 'url' ? (
<div>
<Typography>
Open a InterProScan JSON file remote URL
</Typography>
<TextField
label="URL"
value={interProURL}
onChange={event => setInterProURL(event.target.value)}
/>
</Button>
</div>
) : null}
</div>
) : null}
{choice === 'file' ? (
<div style={{ paddingTop: 20 }}>
<Typography>
Open a InterProScan JSON file file from your local drive
</Typography>
<Button variant="outlined" component="label">
Choose File
<input
type="file"
hidden
onChange={({ target }) => {
const file = target?.files?.[0]
if (file) {
setFile(file)
}
}}
/>
</Button>
</div>
) : null}
</div>
</div>
</DialogContent>
<DialogActions>
Expand Down
2 changes: 0 additions & 2 deletions lib/src/components/header/HeaderMenuExtra.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import Visibility from '@mui/icons-material/Visibility'
import FilterAlt from '@mui/icons-material/FilterAlt'
import Search from '@mui/icons-material/Search'
import PhotoCamera from '@mui/icons-material/PhotoCamera'
import RestartAlt from '@mui/icons-material/RestartAlt'
import FolderOpen from '@mui/icons-material/FolderOpen'
import Settings from '@mui/icons-material/Settings'
import Assignment from '@mui/icons-material/Assignment'
import List from '@mui/icons-material/List'
Expand Down
5 changes: 3 additions & 2 deletions lib/src/components/minimap/Minimap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
const s = left * unit
const e = right * unit
const fill = 'rgba(66, 119, 127, 0.3)'
const w = Math.max(e - s, 20)

useEffect(() => {
function fn(event: MouseEvent) {
Expand Down Expand Up @@ -63,7 +64,7 @@ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
background: hovered ? 'rgba(66,119,127,0.6)' : fill,
cursor: 'pointer',
height: barHeight,
width: e - s,
width: w,
zIndex: 100,
}}
onMouseOver={() => setHovered(true)}
Expand All @@ -80,7 +81,7 @@ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
<polygon
fill={fill}
points={[
[e, 0],
[s + w, 0],
[s, 0],
[0, polygonHeight],
[msaAreaWidth, polygonHeight],
Expand Down
3 changes: 1 addition & 2 deletions lib/src/components/msa/renderBoxFeatureCanvasBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function renderBoxFeatureCanvasBlock({
highResScaleFactorOverride?: number
blockSizeYOverride?: number
}) {
const { hierarchy, blockSize, rowHeight, highResScaleFactor, showDomains } =
const { leaves, blockSize, rowHeight, highResScaleFactor, showDomains } =
model
if (showDomains) {
const k = highResScaleFactorOverride || highResScaleFactor
Expand All @@ -28,7 +28,6 @@ export function renderBoxFeatureCanvasBlock({
ctx.scale(k, k)
ctx.translate(-offsetX, rowHeight / 2 - offsetY)

const leaves = hierarchy.leaves()
const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
const yEnd = Math.max(0, Math.ceil((offsetY + by + rowHeight) / rowHeight))
const visibleLeaves = leaves.slice(yStart, yEnd)
Expand Down
14 changes: 10 additions & 4 deletions lib/src/components/msa/renderMSABlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export function renderMSABlock({
blockSizeYOverride?: number
}) {
const {
hierarchy,
colWidth,
blockSize,
rowHeight,
fontSize,
highResScaleFactor,
actuallyShowDomains,
leaves,
} = model
const k = highResScaleFactorOverride || highResScaleFactor
const bx = blockSizeXOverride || blockSize
Expand All @@ -45,8 +45,6 @@ export function renderMSABlock({
ctx.textAlign = 'center'
ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)

const leaves = hierarchy.leaves()

const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
const yEnd = Math.max(0, Math.ceil((offsetY + by + rowHeight) / rowHeight))
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
Expand Down Expand Up @@ -101,6 +99,7 @@ function drawTiles({
colorSchemeName,
colorScheme,
colStats,
colStatsSums,
columns,
colWidth,
rowHeight,
Expand All @@ -116,10 +115,17 @@ function drawTiles({
const letter = str[i]
const color =
colorSchemeName === 'clustalx_protein_dynamic'
? getClustalXColor(colStats[xStart + i], model, name, xStart + i)
? getClustalXColor(
colStats[xStart + i],
colStatsSums[xStart + i],
model,
name,
xStart + i,
)
: colorSchemeName === 'percent_identity_dynamic'
? getPercentIdentityColor(
colStats[xStart + i],
colStatsSums[xStart + i],
model,
name,
xStart + i,
Expand Down
3 changes: 2 additions & 1 deletion lib/src/components/tree/renderTreeCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export function renderTreeLabels({
treeWidth,
treeAreaWidthMinusMargin,
marginLeft,
leaves,
noTree,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
rowHeight: _rowHeight, // this is needed for redrawing after zoom change
Expand All @@ -156,7 +157,7 @@ export function renderTreeLabels({
} else {
ctx.textAlign = 'start'
}
for (const node of hierarchy.leaves()) {
for (const node of leaves) {
const {
data: { name, id },
// @ts-expect-error
Expand Down
42 changes: 37 additions & 5 deletions lib/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ function stateModelFactory() {
* #volatile
* size of blocks of content to be drawn, px
*/
blockSize: 1000,
blockSize: 500,

/**
* #volatile
Expand Down Expand Up @@ -618,7 +618,7 @@ function stateModelFactory() {
* #getter
*/
get rowNames(): string[] {
return this.hierarchy.leaves().map(n => n.data.name)
return this.leaves.map(n => n.data.name)
},
/**
* #getter
Expand Down Expand Up @@ -738,6 +738,17 @@ function stateModelFactory() {
}
return r
},

/**
* #getter
*/
get colStatsSums() {
return Object.fromEntries(
Object.entries(this.colStats).map(([key, val]) => {
return [key, sum(Object.values(val))]
}),
)
},
/**
* #getter
* generates a new tree that is clustered with x,y positions
Expand All @@ -756,7 +767,14 @@ function stateModelFactory() {
* #getter
*/
get totalHeight() {
return this.root.leaves().length * self.rowHeight
return this.leaves.length * self.rowHeight
},

/**
* #getter
*/
get leaves() {
return this.hierarchy.leaves()
},
}))
.views(self => ({
Expand Down Expand Up @@ -942,9 +960,9 @@ function stateModelFactory() {
*/
get labelsWidth() {
let x = 0
const { rowHeight, hierarchy, treeMetadata, fontSize } = self
const { rowHeight, leaves, treeMetadata, fontSize } = self
if (rowHeight > 5) {
for (const node of hierarchy.leaves()) {
for (const node of leaves) {
x = Math.max(
measureTextCanvas(
treeMetadata[node.data.name]?.genome || node.data.name,
Expand Down Expand Up @@ -1104,6 +1122,9 @@ function stateModelFactory() {
}
return types
},
/**
* #getter
*/
get tidyAnnotations() {
const ret = []
const { interProAnnotations } = self
Expand Down Expand Up @@ -1321,6 +1342,17 @@ function stateModelFactory() {
}),
)

addDisposer(
self,
autorun(() => {
// force colStats not to go stale,
// xref solution https://github.com/mobxjs/mobx/issues/266#issuecomment-222007278
// xref problem https://github.com/GMOD/react-msaview/issues/75
self.colStats
self.colStatsSums
self.columns
}),
)
// autorun synchronizes treeWidth with treeAreaWidth
addDisposer(
self,
Expand Down
10 changes: 10 additions & 0 deletions lib/src/model/DataModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,14 @@ export function DataModelF() {
self.treeMetadata = treeMetadata
},
}))
.postProcessSnapshot(snap => {
const { tree, msa, treeMetadata } = snap
const max = 50_000
return {
tree: tree && tree.length > max ? undefined : tree,
msa: msa && msa.length > max ? undefined : msa,
treeMetadata:
treeMetadata && treeMetadata.length > max ? undefined : treeMetadata,
}
})
}
2 changes: 0 additions & 2 deletions lib/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,6 @@ export function collapse(d: HierarchyNode<NodeWithIds>) {
// @ts-expect-error
d._children = d.children
// @ts-expect-error
// d._children.forEach(collapse)
// @ts-expect-error
d.children = null
}
}
Expand Down
Loading

0 comments on commit c688964

Please sign in to comment.