diff --git a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/GeneGlyph.ts b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/GeneGlyph.ts index 32ed3777..e2210c26 100644 --- a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +++ b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/GeneGlyph.ts @@ -5,6 +5,7 @@ import { alpha } from '@mui/material' import { LinearApolloDisplay } from '../stateModel' import { isMousePositionWithFeatureAndGlyph, + LinearApolloDisplayMouseEvents, MousePosition, MousePositionWithFeatureAndGlyph, } from '../stateModel/mouseEvents' @@ -291,9 +292,41 @@ function getFeatureFromLayout( ): AnnotationFeature | undefined { const featureInThisRow: AnnotationFeature[] = featuresForRow(feature)[row] for (const f of featureInThisRow) { + let featureObj if (bp >= f.min && bp <= f.max && f.parent) { - return f + featureObj = f } + if (!featureObj) { + continue + } + if ( + featureObj.type === 'CDS' && + featureObj.parent && + featureObj.parent.type === 'mRNA' + ) { + const { max, min } = featureObj + const { cdsLocations } = featureObj.parent + for (const cdsLoc of cdsLocations) { + const firstLoc = cdsLoc.at(0) + const lastLoc = cdsLoc.at(-1) + + if ( + firstLoc && + firstLoc.min === min && + lastLoc && + lastLoc.max === max + ) { + for (const loc of cdsLoc) { + if (bp >= loc.min && bp <= loc.max) { + return featureObj + } + } + break + } + } + return featureObj.parent + } + return featureObj } return feature } @@ -499,9 +532,73 @@ function getDraggableFeatureInfo( return } +function drawTooltip( + display: LinearApolloDisplayMouseEvents, + context: CanvasRenderingContext2D, +): void { + const { apolloHover, apolloRowHeight, lgv, theme } = display + if (!apolloHover) { + return + } + const { feature, topLevelFeature } = apolloHover + const position = display.getFeatureLayoutPosition(feature) + if (!position) { + return + } + const { layoutIndex, layoutRow } = position + const rowIdx = getRowForFeature(topLevelFeature, feature) + const { bpPerPx, displayedRegions, offsetPx } = lgv + const displayedRegion = displayedRegions[layoutIndex] + const { refName, reversed } = displayedRegion + + let location = 'Loc: ' + + const { length, max, min } = feature + location += `${min + 1}–${max}` + + let startPx = + (lgv.bpToPx({ + refName, + coord: reversed ? max : min, + regionNumber: layoutIndex, + })?.offsetPx ?? 0) - offsetPx + const top = (layoutRow + (rowIdx ?? 0)) * apolloRowHeight + const widthPx = length / bpPerPx + + const featureType = `Type: ${feature.type}` + const { attributes } = feature + const featureName = attributes.get('gff_name')?.find((name) => name !== '') + const textWidth = [ + context.measureText(featureType).width, + context.measureText(location).width, + ] + if (featureName) { + textWidth.push(context.measureText(`Name: ${featureName}`).width) + } + const maxWidth = Math.max(...textWidth) + + startPx = startPx + widthPx + 5 + context.fillStyle = alpha(theme?.palette.text.primary ?? 'rgb(1, 1, 1)', 0.7) + context.fillRect(startPx, top, maxWidth + 4, textWidth.length === 3 ? 45 : 35) + context.beginPath() + context.moveTo(startPx, top) + context.lineTo(startPx - 5, top + 5) + context.lineTo(startPx, top + 10) + context.fill() + context.fillStyle = theme?.palette.background.default ?? 'rgba(255, 255, 255)' + let textTop = top + 12 + context.fillText(featureType, startPx + 2, textTop) + if (featureName) { + textTop = textTop + 12 + context.fillText(`Name: ${featureName}`, startPx + 2, textTop) + } + textTop = textTop + 12 + context.fillText(location, startPx + 2, textTop) +} + // False positive here, none of these functions use "this" /* eslint-disable @typescript-eslint/unbound-method */ -const { drawTooltip, getContextMenuItems, onMouseLeave } = boxGlyph +const { getContextMenuItems, onMouseLeave } = boxGlyph /* eslint-enable @typescript-eslint/unbound-method */ export const geneGlyph: Glyph = {