From c60ec95840f1541eadb6a3bde6a4ed34bbe891e0 Mon Sep 17 00:00:00 2001 From: Shashank Budhanuru Ramaraju Date: Mon, 21 Aug 2023 11:40:22 +0100 Subject: [PATCH] hover highlight exons on multiple rows --- .../glyphs/CanonicalGeneGlyph.ts | 99 +++++++++++++++---- .../src/LinearApolloDisplay/glyphs/Glyph.ts | 3 + .../stateModel/mouseEvents.ts | 62 +++++++++++- 3 files changed, 144 insertions(+), 20 deletions(-) diff --git a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts index 713ea5572..3364c022c 100644 --- a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +++ b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts @@ -302,7 +302,13 @@ export class CanonicalGeneGlyph extends Glyph { } } - drawHover(stateModel: LinearApolloDisplay, ctx: CanvasRenderingContext2D) { + drawHover( + stateModel: LinearApolloDisplay, + ctx: CanvasRenderingContext2D, + rowNum: number, + xOffset: number, + reversed: boolean, + ) { const { apolloHover } = stateModel if (!apolloHover) { return @@ -334,6 +340,9 @@ export class CanonicalGeneGlyph extends Glyph { feature.start, feature.end, feature.length, + rowNum, + xOffset, + reversed, ) } } @@ -344,33 +353,85 @@ export class CanonicalGeneGlyph extends Glyph { start: number, end: number, length: number, + rowNum?: number, + xOffset?: number, + reversed?: boolean, ) { const { apolloRowHeight, lgv, displayedRegions, theme, apolloHover } = stateModel + const { bpPerPx, bpToPx, offsetPx } = lgv + if (!apolloHover) { return } - const { mousePosition } = apolloHover - if (!mousePosition) { + const { feature, topLevelFeature } = apolloHover + + if (!feature || !topLevelFeature) { return } - const { bpPerPx, bpToPx, offsetPx } = lgv - const rowHeight = apolloRowHeight - const { regionNumber, y } = mousePosition - const rowNumber = Math.floor(y / rowHeight) - const displayedRegion = displayedRegions[regionNumber] - const { refName, reversed } = displayedRegion - const startPx = - (bpToPx({ - refName, - coord: reversed ? end : start, - regionNumber, - })?.offsetPx ?? 0) - offsetPx - const top = rowNumber * rowHeight - const widthPx = length / bpPerPx - ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)' - ctx.fillRect(startPx, top, widthPx, rowHeight) + let featureEntry: AnnotationFeatureI | undefined + let childFeature: AnnotationFeatureI | undefined + let featureRow: number | undefined + let i = 0 + topLevelFeature.children?.forEach((f: AnnotationFeatureI) => { + if (f._id === feature?._id) { + featureEntry = f + featureRow = i + } + f.children?.forEach((cf: AnnotationFeatureI) => { + if (cf._id === feature._id) { + childFeature = cf + featureEntry = f + featureRow = i + } + }) + i++ + }) + + let cdsCount = 0 + featureEntry?.children?.forEach((cf: AnnotationFeatureI) => { + if (cf.discontinuousLocations && cf.discontinuousLocations.length > 0) { + cdsCount++ + } + }) + + if (cdsCount > 1 && rowNum && xOffset) { + if (featureEntry === undefined || featureRow === undefined) { + return + } + const widthPx = childFeature + ? childFeature.length / bpPerPx + : featureEntry.length / bpPerPx + const offsetPx = childFeature + ? (childFeature.start - feature.min) / bpPerPx + : (featureEntry.start - feature.min) / bpPerPx + const startPx = reversed ? xOffset - widthPx : xOffset + offsetPx + const top = (rowNum + featureRow) * apolloRowHeight + ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,08)' + ctx.fillRect(startPx, top, widthPx, apolloRowHeight * cdsCount) + } else { + const { mousePosition } = apolloHover + if (!mousePosition) { + return + } + const rowHeight = apolloRowHeight + const { regionNumber, y } = mousePosition + const rowNumber = Math.floor(y / rowHeight) + + const displayedRegion = displayedRegions[regionNumber] + const { refName, reversed } = displayedRegion + const startPx = + (bpToPx({ + refName, + coord: reversed ? end : start, + regionNumber, + })?.offsetPx ?? 0) - offsetPx + const top = rowNumber * rowHeight + const widthPx = length / bpPerPx + ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)' + ctx.fillRect(startPx, top, widthPx, rowHeight) + } } onMouseUp(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) { diff --git a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/Glyph.ts b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/Glyph.ts index 5b961e5c0..7764fffb3 100644 --- a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/Glyph.ts +++ b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/Glyph.ts @@ -35,6 +35,9 @@ export abstract class Glyph { drawHover( _display: LinearApolloDisplayMouseEvents, _overlayCtx: CanvasRenderingContext2D, + _rowNum?: number, + _xOffset?: number, + _reversed?: boolean, ) { return } diff --git a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/stateModel/mouseEvents.ts b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/stateModel/mouseEvents.ts index 655f6e1e4..0e57b892a 100644 --- a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/stateModel/mouseEvents.ts +++ b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/stateModel/mouseEvents.ts @@ -55,6 +55,9 @@ export function mouseEventsModelIntermediateFactory( ) return LinearApolloDisplayRendering.named('LinearApolloDisplayMouseEvents') + .props({ + apolloRowHeight: 20, + }) .volatile(() => ({ apolloDragging: null as { start: { @@ -263,8 +266,65 @@ export function mouseEventsModelFactory( self.featuresHeight, ) + const { apolloHover } = self + if (!apolloHover) { + return + } + const { feature } = apolloHover + if (!feature) { + return + } + let rowNum = 0 + let xOffset = 0 + let reversed = false + self.featureLayouts.forEach((featureLayout, idx) => { + const displayedRegion = self.displayedRegions[idx] + featureLayout.forEach((featureLayoutRow, row) => { + if (rowNum !== 0) { + return + } + featureLayoutRow.forEach(([, f]) => { + f.children?.forEach((cf: AnnotationFeatureI) => { + if (rowNum !== 0) { + return + } + xOffset = + (self.lgv.bpToPx({ + refName: displayedRegion.refName, + coord: feature.min, + regionNumber: idx, + })?.offsetPx ?? 0) - self.lgv.offsetPx + // eslint-disable-next-line prefer-destructuring + reversed = displayedRegion.reversed + + if (cf._id === feature._id) { + rowNum = row + return + } + cf.children?.forEach( + (annotationFeature: AnnotationFeatureI) => { + if (rowNum !== 0) { + return + } + if (annotationFeature._id === feature._id) { + rowNum = row + return + } + }, + ) + }) + }) + }) + }) + // draw mouseover hovers - self.apolloHover?.glyph?.drawHover(self, ctx) + self.apolloHover?.glyph?.drawHover( + self, + ctx, + rowNum, + xOffset, + reversed, + ) // dragging previews if (self.apolloDragging) {