diff --git a/packages/core/ui/BaseTooltip.tsx b/packages/core/ui/BaseTooltip.tsx index 3921ea289b..0465770659 100644 --- a/packages/core/ui/BaseTooltip.tsx +++ b/packages/core/ui/BaseTooltip.tsx @@ -1,4 +1,5 @@ import { + offset, useClientPoint, useFloating, useInteractions, @@ -43,6 +44,7 @@ export default function BaseTooltip({ const { refs, floatingStyles, context } = useFloating({ placement, strategy: 'fixed', + middleware: [offset(5)], }) const clientPoint = useClientPoint(context, clientPointCoords) diff --git a/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRenderer.ts b/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRenderer.ts index cf56e37cd7..7cec141abb 100644 --- a/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRenderer.ts +++ b/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRenderer.ts @@ -15,17 +15,11 @@ export default class MultiVariantBaseRenderer extends FeatureRendererType { const { makeImageData } = await import('./makeImageData') - const rest = await renderToAbstractCanvas( - width, - height, - renderProps, - async ctx => { - await makeImageData(ctx, { - ...renderProps, - features, - }) - return undefined - }, + const rest = await renderToAbstractCanvas(width, height, renderProps, ctx => + makeImageData(ctx, { + ...renderProps, + features, + }), ) const results = await super.render({ diff --git a/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRendering.tsx b/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRendering.tsx index 8f96022b16..477061310b 100644 --- a/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRendering.tsx +++ b/plugins/variants/src/MultiLinearVariantRenderer/MultiVariantRendering.tsx @@ -1,14 +1,13 @@ -import { useRef } from 'react' +import { useMemo, useRef } from 'react' import { PrerenderedCanvas } from '@jbrowse/core/ui' import { observer } from 'mobx-react' +import RBush from 'rbush' import type { Source } from '../types' import type { Feature } from '@jbrowse/core/util' import type { Region } from '@jbrowse/core/util/types' -// locals - const MultiVariantRendering = observer(function (props: { regions: Region[] features: Map @@ -18,16 +17,51 @@ const MultiVariantRendering = observer(function (props: { sources: Source[] scrollTop: number totalHeight: number + rbush: RBush<{ genotype: string }> + displayModel: any onMouseLeave?: (event: React.MouseEvent) => void onMouseMove?: (event: React.MouseEvent, arg?: Feature) => void onFeatureClick?: (event: React.MouseEvent, arg?: Feature) => void }) { const { totalHeight, scrollTop } = props + const { rbush, displayModel } = props const ref = useRef(null) + const rbush2 = useMemo( + () => new RBush<{ genotype: string }>().fromJSON(rbush), + [rbush], + ) + + function getFeatureUnderMouse(eventClientX: number, eventClientY: number) { + let offsetX = 0 + let offsetY = 0 + if (ref.current) { + const r = ref.current.getBoundingClientRect() + offsetX = eventClientX - r.left + offsetY = eventClientY - r.top + } + const ret = rbush2.search({ + minX: offsetX, + maxX: offsetX + 3, + minY: offsetY, + maxY: offsetY + 3, + }) + return ret[0]?.genotype + } return (
+ displayModel.setHoveredGenotype?.( + getFeatureUnderMouse(e.clientX, e.clientY), + ) + } + onMouseLeave={() => { + displayModel.setHoveredGenotype?.(undefined) + }} + onMouseOut={() => { + displayModel.setHoveredGenotype?.(undefined) + }} style={{ overflow: 'visible', position: 'relative', diff --git a/plugins/variants/src/MultiLinearVariantRenderer/makeImageData.ts b/plugins/variants/src/MultiLinearVariantRenderer/makeImageData.ts index c5ba3508ce..fdc439a9f2 100644 --- a/plugins/variants/src/MultiLinearVariantRenderer/makeImageData.ts +++ b/plugins/variants/src/MultiLinearVariantRenderer/makeImageData.ts @@ -7,6 +7,7 @@ import { import { getFeaturesThatPassMinorAlleleFrequencyFilter } from '../util' import type { MultiRenderArgsDeserialized } from './types' +import RBush from 'rbush' const fudgeFactor = 0.6 const f2 = fudgeFactor / 2 @@ -55,20 +56,27 @@ export async function makeImageData( features.values(), minorAlleleFrequencyFilter, ) + const rbush = new RBush() for (const feature of mafs) { const [leftPx, rightPx] = featureSpanPx(feature, region, bpPerPx) const w = Math.max(Math.round(rightPx - leftPx), 2) const samp = feature.get('genotypes') as Record - let t = -scrollTop + let y = -scrollTop const s = sources.length for (let j = 0; j < s; j++) { const { name, HP } = sources[j]! const genotype = samp[name] const x = Math.floor(leftPx) - const y = t const h = Math.max(rowHeight, 1) if (genotype) { + rbush.insert({ + minX: x - f2, + maxX: x + w + f2, + minY: y - f2, + maxY: y + h + f2, + genotype, + }) const isPhased = genotype.includes('|') if (renderingMode === 'phased') { if (isPhased) { @@ -83,7 +91,10 @@ export async function makeImageData( drawColorAlleleCount(alleles, ctx, x, y, w, h) } } - t += rowHeight + y += rowHeight } } + return { + rbush: rbush.toJSON(), + } }