diff --git a/packages/core/util/layouts/GranularRectLayout.ts b/packages/core/util/layouts/GranularRectLayout.ts index f59b5a299e..f52dabe816 100644 --- a/packages/core/util/layouts/GranularRectLayout.ts +++ b/packages/core/util/layouts/GranularRectLayout.ts @@ -1,4 +1,3 @@ -import { ObservableMap } from 'mobx' import { objectFromEntries } from '../index' import { RectTuple, @@ -319,7 +318,7 @@ export default class GranularRectLayout implements BaseLayout { private bitmap: LayoutRow[] - private rectangles: ObservableMap> + private rectangles: Map> public maxHeightReached: boolean @@ -329,6 +328,12 @@ export default class GranularRectLayout implements BaseLayout { private pTotalHeight: number + /* + * + * pitchX - layout grid pitch in the X direction + * pitchY - layout grid pitch in the Y direction + * maxHeight - maximum layout height, default Infinity (no max) + */ constructor({ pitchX = 10, pitchY = 10, @@ -336,11 +341,8 @@ export default class GranularRectLayout implements BaseLayout { hardRowLimit = 3000, displayMode = 'normal', }: { - /** layout grid pitch in the X direction */ pitchX?: number - /** layout grid pitch in the Y direction */ pitchY?: number - /** maximum layout height, default Infinity (no max) */ maxHeight?: number displayMode?: string hardRowLimit?: number @@ -358,7 +360,7 @@ export default class GranularRectLayout implements BaseLayout { } this.bitmap = [] - this.rectangles = new ObservableMap() + this.rectangles = new Map() this.maxHeight = Math.ceil(maxHeight / this.pitchY) this.pTotalHeight = 0 // total height, in units of bitmap squares (px/pitchY) } @@ -375,14 +377,14 @@ export default class GranularRectLayout implements BaseLayout { data?: Record, ): number | null { // if we have already laid it out, return its layout - // console.log(`${this.id} add ${id}`) const storedRec = this.rectangles.get(id) if (storedRec) { if (storedRec.top === null) { return null } - // add it to the bitmap again, since that bitmap range may have been discarded + // add it to the bitmap again, since that bitmap range may have been + // discarded this.addRectToBitmap(storedRec) return storedRec.top * this.pitchY } @@ -391,13 +393,11 @@ export default class GranularRectLayout implements BaseLayout { const pRight = Math.floor(right / this.pitchX) const pHeight = Math.ceil(height / this.pitchY) - // const midX = Math.floor((pLeft + pRight) / 2) const rectangle: Rectangle = { id, l: pLeft, r: pRight, top: null, - // mX: midX, h: pHeight, originalHeight: height, data, @@ -423,7 +423,6 @@ export default class GranularRectLayout implements BaseLayout { this.addRectToBitmap(rectangle) this.rectangles.set(id, rectangle) this.pTotalHeight = Math.max(this.pTotalHeight || 0, top + pHeight) - // console.log(`G2 ${data.get('name')} ${top}`) return top * this.pitchY } @@ -467,7 +466,7 @@ export default class GranularRectLayout implements BaseLayout { return } - const data = rect.data || true + const data = rect.data || rect.id || true const { bitmap } = this const yEnd = rect.top + rect.h if (rect.r - rect.l > maxFeaturePitchWidth) { diff --git a/packages/core/util/layouts/PrecomputedLayout.ts b/packages/core/util/layouts/PrecomputedLayout.ts index e9c0a0161f..390412c0d4 100644 --- a/packages/core/util/layouts/PrecomputedLayout.ts +++ b/packages/core/util/layouts/PrecomputedLayout.ts @@ -5,6 +5,15 @@ import { BaseLayout, Rectangle, } from './BaseLayout' +import RBush from 'rbush' + +export interface Layout { + minX: number + minY: number + maxX: number + maxY: number + name: string +} export default class PrecomputedLayout implements BaseLayout { private rectangles: Map @@ -13,11 +22,23 @@ export default class PrecomputedLayout implements BaseLayout { public maxHeightReached: boolean + private rbush: RBush + constructor({ rectangles, totalHeight, maxHeightReached }: SerializedLayout) { this.rectangles = new Map(Object.entries(rectangles)) // rectangles is of the form "featureId": [leftPx, topPx, rightPx, bottomPx] this.totalHeight = totalHeight this.maxHeightReached = maxHeightReached + this.rbush = new RBush() + for (const [key, layout] of Object.entries(rectangles)) { + this.rbush.insert({ + minX: layout[0], + minY: layout[1], + maxX: layout[2], + maxY: layout[3], + name: key, + }) + } } addRect(id: string) { @@ -44,6 +65,11 @@ export default class PrecomputedLayout implements BaseLayout { throw new Error('Method not implemented.') } + getByCoord(x: number, y: number) { + const rect = { minX: x, minY: y, maxX: x + 1, maxY: y + 1 } + return this.rbush.collides(rect) ? this.rbush.search(rect)[0].name : [] + } + addRectToBitmap(_rect: Rectangle, _data: Record): void { throw new Error('Method not implemented.') } diff --git a/plugins/alignments/src/PileupRenderer/components/PileupRendering.tsx b/plugins/alignments/src/PileupRenderer/components/PileupRendering.tsx index 2b4c38deef..69058d7fb7 100644 --- a/plugins/alignments/src/PileupRenderer/components/PileupRendering.tsx +++ b/plugins/alignments/src/PileupRenderer/components/PileupRendering.tsx @@ -151,12 +151,11 @@ function PileupRendering(props: { const px = region.reversed ? width - offsetX : offsetX const clientBp = region.start + bpPerPx * px - const feats = displayModel.getFeatureOverlapping( + const featIdUnderMouse = displayModel.getFeatureOverlapping( blockKey, clientBp, offsetY, ) - const featIdUnderMouse = feats.length ? feats[0].name : undefined if (onMouseMove) { onMouseMove(event, featIdUnderMouse) diff --git a/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx b/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx index 62511e25e9..98af7c69a6 100644 --- a/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +++ b/plugins/linear-genome-view/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx @@ -117,8 +117,6 @@ export const BaseLinearDisplay = types }, })) .views(self => { - let stale = false // used to make rtree refresh, the mobx reactivity fails for some reason - let rbush: Record> = {} return { /** * a CompositeMap of `featureId -> feature obj` that @@ -131,7 +129,6 @@ export const BaseLinearDisplay = types featureMaps.push(block.features) } } - stale = true return new CompositeMap(featureMaps) }, @@ -155,53 +152,11 @@ export const BaseLinearDisplay = types layoutMaps.set(block.key, block.layout.getRectangles()) } } - stale = true return layoutMaps }, - /** - * a CompositeMap of `featureId -> feature obj` that - * just looks in all the block data for that feature - * - * when you are not using the rtree you can use this - * method because it still provides a stable reference - * of a featureId to a layout record (when using the - * rtree, you cross contaminate the coordinates) - */ - get layoutFeatures() { - const layoutMaps = [] - for (const block of self.blockState.values()) { - if (block && block.layout && block.layout.rectangles) { - layoutMaps.push(block.layout.getRectangles()) - } - } - stale = true // make rtree refresh - return new CompositeMap(layoutMaps) - }, - get rtree() { - if (stale) { - rbush = {} as Record> - for (const [blockKey, layoutFeatures] of this.blockLayoutFeatures) { - rbush[blockKey] = new RBush() - const r = rbush[blockKey] - for (const [key, layout] of layoutFeatures) { - r.insert({ - minX: layout[0], - minY: layout[1], - maxX: layout[2], - maxY: layout[3], - name: key, - }) - } - } - stale = false - } - return rbush - }, getFeatureOverlapping(blockKey: string, x: number, y: number) { - const rect = { minX: x, minY: y, maxX: x + 1, maxY: y + 1 } - const rtree = this.rtree[blockKey] - return rtree && rtree.collides(rect) ? rtree.search(rect) : [] + return self.blockState.get(blockKey)?.layout?.getByCoord(x, y) }, } }) diff --git a/plugins/svg/src/SvgFeatureRenderer/components/SvgFeatureRendering.js b/plugins/svg/src/SvgFeatureRenderer/components/SvgFeatureRendering.js index 2ce313be06..4822f03a5a 100644 --- a/plugins/svg/src/SvgFeatureRenderer/components/SvgFeatureRendering.js +++ b/plugins/svg/src/SvgFeatureRenderer/components/SvgFeatureRendering.js @@ -273,14 +273,11 @@ function SvgFeatureRendering(props) { const px = region.reversed ? width - offsetX : offsetX const clientBp = region.start + bpPerPx * px - const feats = displayModel.getFeatureOverlapping( + const featureIdCurrentlyUnderMouse = displayModel.getFeatureOverlapping( blockKey, clientBp, offsetY, ) - const featureIdCurrentlyUnderMouse = feats.length - ? feats[0].name - : undefined if (onMouseMove) { onMouseMove(event, featureIdCurrentlyUnderMouse) diff --git a/test_data/volvox/config_main_thread.json b/test_data/volvox/config_main_thread.json index 0c9723c742..7c111e3c94 100644 --- a/test_data/volvox/config_main_thread.json +++ b/test_data/volvox/config_main_thread.json @@ -36,5 +36,60 @@ } } } + ], + "tracks": [ + { + "type": "AlignmentsTrack", + "trackId": "volvox_bam_pileup", + "name": "volvox-sorted.bam (contigA LinearPileupDisplay)", + "category": ["Integration test"], + "assemblyNames": ["volvox"], + "adapter": { + "type": "BamAdapter", + "bamLocation": { + "uri": "volvox-sorted-altname.bam" + }, + "index": { + "location": { + "uri": "volvox-sorted-altname.bam.bai" + } + } + }, + "displays": [ + { + "type": "LinearPileupDisplay", + "displayId": "volvox_bam_pileup_pileup" + } + ] + }, + { + "type": "AlignmentsTrack", + "trackId": "volvox_alignments", + "name": "volvox-sorted.bam (ctgA, svg)", + "category": ["Integration test"], + "assemblyNames": ["volvox", "volvox2"], + "adapter": { + "type": "BamAdapter", + "bamLocation": { + "uri": "volvox-sorted.bam" + }, + "index": { + "location": { + "uri": "volvox-sorted.bam.bai" + } + } + }, + "displays": [ + { + "type": "LinearAlignmentsDisplay", + "displayId": "volvox_alignments_alignments", + "pileupDisplay": { + "type": "LinearPileupDisplay", + "displayId": "volvox_bam_altname_alignments_pileup", + "defaultRendering": "svg" + } + } + ] + } ] }