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

Add canvas feature renderer #2671

Draft
wants to merge 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
134112f
Add template for canvasfeaturerenderer
cmdcolin Oct 23, 2021
2d66055
Fix issue with server-side warning in tests
cmdcolin Oct 23, 2021
9064ed3
More canvas
cmdcolin Oct 23, 2021
fe85697
Test
cmdcolin Oct 23, 2021
f136de1
Remove some svg stuff
cmdcolin Oct 23, 2021
b0726cb
Incremental
cmdcolin Oct 23, 2021
1ab3246
Remove padding conditional
cmdcolin Oct 24, 2021
9ce15ad
Misc
cmdcolin Oct 25, 2021
f8f6dfb
Move files around
cmdcolin Oct 25, 2021
a22ad3b
Incremental
cmdcolin Oct 25, 2021
3ee3426
Misc
cmdcolin Oct 25, 2021
bfff951
Merge origin/main
cmdcolin Nov 5, 2021
b57bcc3
Merge origin/main
cmdcolin Jan 24, 2022
9884195
Lint
cmdcolin Jan 24, 2022
ccbc4a5
Merge remote-tracking branch 'origin/main' into add_canvas_feature_re…
cmdcolin Jan 26, 2022
70deeb1
Merge origin/main
cmdcolin Feb 2, 2022
5899a28
Typescripting
cmdcolin Feb 2, 2022
5fe0d5d
Back to main
cmdcolin Feb 3, 2022
dd13abf
More canvas
cmdcolin Feb 3, 2022
abbc0f1
Temp
cmdcolin Feb 3, 2022
e5312f4
Stuff
cmdcolin Feb 5, 2022
8e0fe59
Updates
cmdcolin Feb 5, 2022
dc503ea
Avoid cleanup warnings lgv
cmdcolin Feb 5, 2022
2dd9988
Misc
cmdcolin Feb 6, 2022
6de531b
Merge origin/main
cmdcolin Feb 20, 2022
e548f2f
Merge remote-tracking branch 'origin/main' into add_canvas_feature_re…
cmdcolin Feb 28, 2022
5b87421
Merge origin/main
cmdcolin Oct 28, 2022
427f575
[skip ci] Merge origin/main
cmdcolin Jan 3, 2023
c35a1b5
Strong preference for reading frame based coloring scheme
cmdcolin Oct 30, 2023
5d6e0a5
Misc refactor3
cmdcolin Jun 18, 2024
6adfa38
Merge origin/main
cmdcolin Sep 1, 2024
4d7d672
Merge origin/main
cmdcolin Sep 12, 2024
46b53f0
Merge remote-tracking branch 'origin/main' into add_canvas_feature_re…
cmdcolin Sep 15, 2024
3187fe5
Merge origin/main
cmdcolin Sep 24, 2024
26dfea7
Global feature layout
cmdcolin Sep 25, 2024
7024825
Remove layoutRecords concept
cmdcolin Sep 25, 2024
c04c408
Merge branch 'collapse_introns' into add_canvas_feature_renderer
cmdcolin Sep 25, 2024
e0291c9
Updates
cmdcolin Sep 25, 2024
baf383c
Merge remote-tracking branch 'origin/main' into add_canvas_feature_re…
cmdcolin Sep 27, 2024
5dc12e8
Merge remote-tracking branch 'origin/main' into add_canvas_feature_re…
cmdcolin Oct 22, 2024
dc3f56f
Merge origin/main
cmdcolin Oct 28, 2024
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
28 changes: 12 additions & 16 deletions packages/core/util/Base1DUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,12 @@ export function bpToPx({
}
}
const found = displayedRegions[i]
if (found) {
return {
index: i,
offsetPx: Math.round(bpSoFar / bpPerPx),
}
}

return undefined
return found
? {
index: i,
offsetPx: Math.round(bpSoFar / bpPerPx),
}
: undefined
}

export function bpToPxMap({
Expand Down Expand Up @@ -277,12 +275,10 @@ export function bpToPxMap({
}
}
const found = displayedRegions[i]
if (found) {
return {
index: i,
offsetPx: Math.round(bpSoFar / bpPerPx),
}
}

return map
return found
? {
index: i,
offsetPx: Math.round(bpSoFar / bpPerPx),
}
: map
}
4 changes: 2 additions & 2 deletions packages/core/util/compositeMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ export default class CompositeMap<T, U> {

*[Symbol.iterator]() {
for (const key of this.keys()) {
yield [key, this.get(key)]
yield [key, this.get(key)] as const
}
}

*entries() {
for (const k of this.keys()) {
yield [k, this.get(k)]
yield [k, this.get(k)] as const
}
}
}
8 changes: 5 additions & 3 deletions packages/core/util/layouts/GranularRectLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,12 +505,13 @@ export default class GranularRectLayout<T> implements BaseLayout<T> {
}

getRectangles(): Map<string, RectTuple> {
// @ts-expect-error
return new Map(
[...this.rectangles.entries()].map(([id, rect]) => {
const { l, r, originalHeight, top } = rect
const { l, r, originalHeight, top, data } = rect
const t = (top || 0) * this.pitchY
const b = t + originalHeight
return [id, [l * this.pitchX, t, r * this.pitchX, b]] // left, top, right, bottom
return [id, [l * this.pitchX, t, r * this.pitchX, b, data]] // left, top, right, bottom
}),
)
}
Expand All @@ -531,7 +532,8 @@ export default class GranularRectLayout<T> implements BaseLayout<T> {
const x2 = region.end
// add +/- pitchX to avoid resolution causing errors
if (segmentsIntersect(x1, x2, y1 - this.pitchX, y2 + this.pitchX)) {
regionRectangles[id] = [y1, t, y2, b]
// @ts-expect-error
regionRectangles[id] = [y1, t, y2, b, rect.data]
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions plugins/canvas/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"presets": [
// need this to be able to use spread operator on Set and Map
// see https://github.com/formium/tsdx/issues/376#issuecomment-566750042
["@babel/preset-env", { "loose": false }],
// can remove this if all .js files are converted to .ts
"@babel/preset-react"
]
}
49 changes: 49 additions & 0 deletions plugins/canvas/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@jbrowse/plugin-canvas",
"version": "2.16.0",
"description": "JBrowse 2 plugin for canvas features",
"keywords": [
"jbrowse",
"jbrowse2"
],
"license": "Apache-2.0",
"homepage": "https://jbrowse.org",
"bugs": "https://github.com/GMOD/jbrowse-components/issues",
"repository": {
"type": "git",
"url": "https://github.com/GMOD/jbrowse-components.git",
"directory": "plugins/canvas"
},
"author": "JBrowse Team",
"distMain": "dist/index.js",
"distModule": "esm/index.js",
"srcMain": "src/index.ts",
"srcModule": "src/index.ts",
"main": "src/index.ts",
"files": [
"dist",
"esm"
],
"scripts": {
"prebuild": "npm run clean",
"build": "npm-run-all build:*",
"build:esm": "tsc --build tsconfig.build.esm.json",
"build:commonjs": "tsc --build tsconfig.build.commonjs.json",
"clean": "rimraf dist esm *.tsbuildinfo",
"test": "cd ../..; jest --passWithNoTests plugins/linear-genome-view",
"prepublishOnly": "yarn test",
"prepack": "yarn build && yarn useDist",
"postpack": "yarn useSrc",
"useDist": "node ../../scripts/useDist.js",
"useSrc": "node ../../scripts/useSrc.js"
},
"peerDependencies": {
"@jbrowse/core": "^2.0.0",
"mobx-react": "^9.0.0",
"mobx-state-tree": "^5.0.0",
"react": ">=16.8.0"
},
"publishConfig": {
"access": "public"
}
}
137 changes: 137 additions & 0 deletions plugins/canvas/src/CanvasFeatureRenderer/CanvasFeatureRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import BoxRendererType, {
RenderArgsDeserialized as BoxRenderArgsDeserialized,
} from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType'
import { BaseLayout } from '@jbrowse/core/util/layouts/BaseLayout'
import { iterMap, Feature, notEmpty } from '@jbrowse/core/util'
import { renderToAbstractCanvas } from '@jbrowse/core/util/offscreenCanvasUtils'

// locals
import BoxGlyph from './FeatureGlyphs/Box'
import GeneGlyph from './FeatureGlyphs/Gene'
import { LaidOutFeatureRect } from './FeatureGlyph'

export interface PostDrawFeatureRectWithGlyph extends LaidOutFeatureRect {
glyph: BoxGlyph
}

export interface LaidOutFeatureRectWithGlyph extends LaidOutFeatureRect {
glyph: BoxGlyph
}

export interface RenderArgsDeserialized extends BoxRenderArgsDeserialized {
highResolutionScaling: number
}

export interface RenderArgsDeserializedWithFeaturesAndLayout
extends RenderArgsDeserialized {
features: Map<string, Feature>
layout: BaseLayout<Feature>
regionSequence?: string
width: number
height: number
}

export default class CanvasRenderer extends BoxRendererType {
supportsSVG = true

layoutFeature(
feature: Feature,
layout: BaseLayout<Feature>,
props: RenderArgsDeserialized,
): LaidOutFeatureRectWithGlyph | null {
const region = props.regions[0]!
const glyph =
feature.get('type') === 'gene' ? new GeneGlyph() : new BoxGlyph()
const fRect = glyph.layoutFeature(
{
region,
...props,
},
layout,
feature,
)
return fRect
? {
...fRect,
glyph,
}
: null
}

drawRect(
ctx: CanvasRenderingContext2D,
fRect: LaidOutFeatureRectWithGlyph,
props: RenderArgsDeserialized,
) {
const { regions } = props
const region = regions[0]!
fRect.glyph.renderFeature(ctx, { ...props, region }, fRect)
}

async makeImageData(
ctx: CanvasRenderingContext2D,
layoutRecords: LaidOutFeatureRectWithGlyph[],
props: RenderArgsDeserializedWithFeaturesAndLayout,
) {
for (const fRect of layoutRecords) {
this.drawRect(ctx, fRect, props)
}

return undefined
}

async render(renderProps: RenderArgsDeserialized) {
const { bpPerPx, regions } = renderProps
const features = await this.getFeatures(renderProps)
const layout = this.createLayoutInWorker(renderProps)
const region = regions[0]!
const featureMap = features

const layoutRecords = iterMap(
featureMap.values(),
feature => this.layoutFeature(feature, layout, renderProps),
featureMap.size,
).filter(notEmpty)

const width = (region.end - region.start) / bpPerPx
const height = Math.max(layout.getTotalHeight(), 1)

const res = await renderToAbstractCanvas(width, height, renderProps, ctx =>
this.makeImageData(ctx, layoutRecords, {
...renderProps,
layout,
features,
width,
height,
}),
)

const results = await super.render({
...renderProps,
...res,
layoutRecords,
features,
layout,
height,
width,
})

return {
...results,
...res,
features,
layout,
height,
width,
maxHeightReached: layout.maxHeightReached,
}
}
}

export {
type RenderArgs,
type RenderArgsSerialized,
type RenderResults,
type ResultsSerialized,
type ResultsDeserialized,
} from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType'
101 changes: 101 additions & 0 deletions plugins/canvas/src/CanvasFeatureRenderer/FeatureGlyph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
readConfObject,
AnyConfigurationModel,
} from '@jbrowse/core/configuration'
import { bpSpanPx, Feature } from '@jbrowse/core/util'
import { Region } from '@jbrowse/core/util/types'
import { BaseLayout } from '@jbrowse/core/util/layouts/BaseLayout'

export interface ViewInfo {
bpPerPx: number
region: Region
config: AnyConfigurationModel
}

export interface FeatureRect {
l: number
w: number
f: Feature
}

export interface PreLaidOutFeatureRect {
l: number
w: number
f: Feature
h: number
t: number
}

export interface LaidOutFeatureRect extends FeatureRect {
label?: {
text: string
offsetY: number
}
description?: {
text: string
offsetY: number
}
t: number
h: number
rect: any
}

export interface Rect {
l: number
t: number
w: number
}

export default abstract class FeatureGlyph {
layoutFeature(
viewArgs: ViewInfo,
layout: BaseLayout<Feature>,
feature: Feature,
) {
const fRect = this.getFeatureRectangle(viewArgs, feature)

const { region, bpPerPx } = viewArgs
const scale = 1 / bpPerPx
const leftBase = region.start
const startbp = fRect.l / scale + leftBase
const endbp = (fRect.l + fRect.w) / scale + leftBase
const top = layout.addRect(feature.id(), startbp, endbp, fRect.h, {
label: feature.get('name') || feature.get('id'),
refName: feature.get('refName'),
})

return top === null
? null
: {
...fRect,
f: feature,
t: top,
}
}

abstract renderFeature(
context: CanvasRenderingContext2D,
viewInfo: ViewInfo,
fRect: LaidOutFeatureRect,
): void

getFeatureRectangle(viewInfo: ViewInfo, feature: Feature) {
const { region, bpPerPx } = viewInfo
const s = feature.get('start')
const e = feature.get('end')
const [leftPx, rightPx] = bpSpanPx(s, e, region, bpPerPx)

return {
l: leftPx,
w: rightPx - leftPx,
h: this.getFeatureHeight(viewInfo, feature),
f: feature,
t: 0,
rect: undefined as Rect | undefined,
}
}

getFeatureHeight(viewInfo: ViewInfo, feature: Feature) {
return readConfObject(viewInfo.config, 'height', { feature }) as number
}
}
Loading
Loading