Skip to content

Commit 0bf12a4

Browse files
committed
Working mouseover
1 parent f51d066 commit 0bf12a4

File tree

4 files changed

+160
-15
lines changed

4 files changed

+160
-15
lines changed

plugins/canvas/src/LinearWebGLFeatureDisplay/components/WebGLFeatureComponent.tsx

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -717,8 +717,18 @@ const WebGLFeatureComponent = observer(function WebGLFeatureComponent({
717717
(vr.end - vr.start) / (vr.screenEndPx - vr.screenStartPx)
718718
const leftPx =
719719
vr.screenStartPx + (feature.startBp - vr.start) / blockBpPerPx
720-
const rightPx =
720+
let rightPx =
721721
vr.screenStartPx + (feature.endBp - vr.start) / blockBpPerPx
722+
723+
// Expand to include label area
724+
const labelData = data.floatingLabelsData[featureId]
725+
if (labelData) {
726+
for (const label of labelData.floatingLabels) {
727+
const labelWidth = measureText(label.text, 11)
728+
rightPx = Math.max(rightPx, leftPx + labelWidth)
729+
}
730+
}
731+
722732
const featureWidth = rightPx - leftPx
723733
const topPx = feature.topPx - scrollY
724734
const heightPx = feature.bottomPx - feature.topPx
@@ -787,16 +797,58 @@ const WebGLFeatureComponent = observer(function WebGLFeatureComponent({
787797
)
788798
}
789799

790-
if (
791-
model.selectedFeatureId &&
792-
model.selectedFeatureId !== hoveredFeature?.featureId &&
793-
model.selectedFeatureId !== hoveredSubfeature?.featureId
794-
) {
795-
addFeatureOverlay(
796-
model.selectedFeatureId,
797-
'rgba(0, 100, 255, 0.2)',
798-
'selected',
799-
)
800+
if (model.selectedFeatureId) {
801+
for (const vr of visibleRegions) {
802+
const data = rpcDataMap.get(vr.regionNumber)
803+
if (!data) {
804+
continue
805+
}
806+
const feature = data.flatbushItems.find(
807+
f => f.featureId === model.selectedFeatureId,
808+
)
809+
if (!feature) {
810+
continue
811+
}
812+
if (feature.endBp < vr.start || feature.startBp > vr.end) {
813+
continue
814+
}
815+
816+
const blockBpPerPx =
817+
(vr.end - vr.start) / (vr.screenEndPx - vr.screenStartPx)
818+
const leftPx =
819+
vr.screenStartPx + (feature.startBp - vr.start) / blockBpPerPx
820+
let rightPx =
821+
vr.screenStartPx + (feature.endBp - vr.start) / blockBpPerPx
822+
823+
// Expand to include label area
824+
const selLabelData = data.floatingLabelsData[model.selectedFeatureId]
825+
if (selLabelData) {
826+
for (const label of selLabelData.floatingLabels) {
827+
const labelWidth = measureText(label.text, 11)
828+
rightPx = Math.max(rightPx, leftPx + labelWidth)
829+
}
830+
}
831+
832+
const featureWidth = rightPx - leftPx
833+
const topPx = feature.topPx - scrollY
834+
const heightPx = feature.bottomPx - feature.topPx
835+
836+
overlays.push(
837+
<div
838+
key={`selected-${vr.regionNumber}`}
839+
style={{
840+
position: 'absolute',
841+
left: leftPx - 2,
842+
top: topPx - 2,
843+
width: featureWidth + 4,
844+
height: heightPx + 4,
845+
border: '2px solid rgba(0, 100, 255, 0.8)',
846+
borderRadius: 3,
847+
pointerEvents: 'none',
848+
}}
849+
/>,
850+
)
851+
}
800852
}
801853

802854
return overlays.length > 0 ? overlays : null

plugins/canvas/src/LinearWebGLFeatureDisplay/model.ts

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getContainingTrack,
1212
getContainingView,
1313
getSession,
14+
isFeature,
1415
isSessionModelWithWidgets,
1516
} from '@jbrowse/core/util'
1617
import { addDisposer, flow, isAlive, types } from '@jbrowse/mobx-state-tree'
@@ -73,6 +74,8 @@ export default function stateModelFactory(
7374
configuration: ConfigurationReference(configSchema),
7475
trackShowLabels: types.maybe(types.boolean),
7576
trackShowDescriptions: types.maybe(types.boolean),
77+
trackGeneGlyphMode: types.maybe(types.string),
78+
showOnlyGenes: false,
7679
}),
7780
)
7881
.preProcessSnapshot((snap: any) => {
@@ -126,6 +129,23 @@ export default function stateModelFactory(
126129
)
127130
},
128131

132+
get geneGlyphMode(): string {
133+
return (
134+
self.trackGeneGlyphMode ??
135+
getConf(self, ['renderer', 'geneGlyphMode'])
136+
)
137+
},
138+
139+
get selectedFeatureId() {
140+
if (isAlive(self)) {
141+
const { selection } = getSession(self)
142+
if (isFeature(selection)) {
143+
return selection.id()
144+
}
145+
}
146+
return undefined
147+
},
148+
129149
get colorByCDS() {
130150
const view = getContainingView(self) as LGV
131151
return (view as unknown as { colorByCDS?: boolean }).colorByCDS ?? false
@@ -267,6 +287,14 @@ export default function stateModelFactory(
267287
self.trackShowDescriptions = value
268288
},
269289

290+
setGeneGlyphMode(value: string) {
291+
self.trackGeneGlyphMode = value
292+
},
293+
294+
setShowOnlyGenes(value: boolean) {
295+
self.showOnlyGenes = value
296+
},
297+
270298
showContextMenuForFeature(featureInfo: FlatbushItem) {
271299
const feature = new SimpleFeature({
272300
id: featureInfo.featureId,
@@ -362,6 +390,7 @@ export default function stateModelFactory(
362390
...baseRendererConfig,
363391
showLabels: self.showLabels,
364392
showDescriptions: self.showDescriptions,
393+
geneGlyphMode: self.geneGlyphMode,
365394
}
366395
const result = (await rpcManager.call(
367396
session.id ?? '',
@@ -374,6 +403,7 @@ export default function stateModelFactory(
374403
bpPerPx,
375404
colorByCDS: self.colorByCDS,
376405
sequenceAdapter: self.sequenceAdapter,
406+
showOnlyGenes: self.showOnlyGenes,
377407
},
378408
)) as WebGLFeatureDataResult
379409

@@ -419,6 +449,8 @@ export default function stateModelFactory(
419449
let prevShowLabels: boolean | undefined
420450
let prevShowDescriptions: boolean | undefined
421451
let prevColorByCDS: boolean | undefined
452+
let prevGeneGlyphMode: string | undefined
453+
let prevShowOnlyGenes: boolean | undefined
422454
let prevSettingsInitialized = false
423455

424456
return {
@@ -493,11 +525,15 @@ export default function stateModelFactory(
493525
const showLabels = self.showLabels
494526
const showDescriptions = self.showDescriptions
495527
const colorByCDS = self.colorByCDS
528+
const geneGlyphMode = self.geneGlyphMode
529+
const showOnlyGenes = self.showOnlyGenes
496530
if (
497531
prevSettingsInitialized &&
498532
(showLabels !== prevShowLabels ||
499533
showDescriptions !== prevShowDescriptions ||
500-
colorByCDS !== prevColorByCDS)
534+
colorByCDS !== prevColorByCDS ||
535+
geneGlyphMode !== prevGeneGlyphMode ||
536+
showOnlyGenes !== prevShowOnlyGenes)
501537
) {
502538
if (self.loadedRegions.size > 0) {
503539
refetchForCurrentView()
@@ -507,6 +543,8 @@ export default function stateModelFactory(
507543
prevShowLabels = showLabels
508544
prevShowDescriptions = showDescriptions
509545
prevColorByCDS = colorByCDS
546+
prevGeneGlyphMode = geneGlyphMode
547+
prevShowOnlyGenes = showOnlyGenes
510548
},
511549
{
512550
name: 'SettingsRefetch',
@@ -568,13 +606,38 @@ export default function stateModelFactory(
568606
self.setShowDescriptions(!self.showDescriptions)
569607
},
570608
},
609+
{
610+
label: 'Show only genes',
611+
type: 'checkbox',
612+
checked: self.showOnlyGenes,
613+
onClick: () => {
614+
self.setShowOnlyGenes(!self.showOnlyGenes)
615+
},
616+
},
571617
],
572618
},
619+
{
620+
label: 'Gene glyph',
621+
subMenu: (
622+
[
623+
{ value: 'all', label: 'All transcripts' },
624+
{ value: 'longest', label: 'Longest transcript' },
625+
{ value: 'longestCoding', label: 'Longest coding transcript' },
626+
] as const
627+
).map(({ value, label }) => ({
628+
label,
629+
type: 'radio' as const,
630+
checked: self.geneGlyphMode === value,
631+
onClick: () => {
632+
self.setGeneGlyphMode(value)
633+
},
634+
})),
635+
},
573636
]
574637
},
575638
}))
576639
.postProcessSnapshot(snap => {
577-
const { trackShowLabels, trackShowDescriptions, ...rest } = snap as Omit<
640+
const { trackShowLabels, trackShowDescriptions, trackGeneGlyphMode, showOnlyGenes, ...rest } = snap as Omit<
578641
typeof snap,
579642
symbol
580643
>
@@ -583,6 +646,8 @@ export default function stateModelFactory(
583646
// Only persist if explicitly set (not undefined)
584647
...(trackShowLabels !== undefined && { trackShowLabels }),
585648
...(trackShowDescriptions !== undefined && { trackShowDescriptions }),
649+
...(trackGeneGlyphMode !== undefined && { trackGeneGlyphMode }),
650+
...(showOnlyGenes && { showOnlyGenes }),
586651
} as typeof snap
587652
})
588653
}

plugins/canvas/src/RenderWebGLFeatureDataRPC/executeRenderWebGLFeatureData.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ export async function executeRenderWebGLFeatureData({
547547
bpPerPx: requestedBpPerPx,
548548
colorByCDS,
549549
sequenceAdapter,
550+
showOnlyGenes,
550551
statusCallback = () => {},
551552
stopToken,
552553
} = args as RenderWebGLFeatureDataArgs & {
@@ -565,7 +566,7 @@ export async function executeRenderWebGLFeatureData({
565566
assemblyName: region.assemblyName ?? '',
566567
}
567568

568-
const featuresArray = await updateStatus(
569+
let featuresArray = await updateStatus(
569570
'Fetching features',
570571
statusCallback,
571572
() =>
@@ -576,6 +577,10 @@ export async function executeRenderWebGLFeatureData({
576577

577578
checkStopToken2(stopTokenCheck)
578579

580+
if (showOnlyGenes) {
581+
featuresArray = featuresArray.filter(f => f.get('type') === 'gene')
582+
}
583+
579584
// Genomic positions are integers, but region bounds from the view can be fractional.
580585
// Use floor to get integer reference point for storing position offsets.
581586
const regionStart = Math.floor(region.start)
@@ -817,9 +822,31 @@ export async function executeRenderWebGLFeatureData({
817822
let subfeatureFlatbushData = new ArrayBuffer(0)
818823

819824
if (flatbushItems.length > 0) {
825+
const reversed = region.reversed ?? false
820826
const featureIndex = new Flatbush(flatbushItems.length)
821827
for (const item of flatbushItems) {
822-
featureIndex.add(item.startBp, item.topPx, item.endBp, item.bottomPx)
828+
let hitStartBp = item.startBp
829+
let hitEndBp = item.endBp
830+
const labelData = floatingLabelsData[item.featureId]
831+
if (labelData) {
832+
let maxLabelWidthPx = 0
833+
for (const label of labelData.floatingLabels) {
834+
const w = measureText(label.text, 11)
835+
if (w > maxLabelWidthPx) {
836+
maxLabelWidthPx = w
837+
}
838+
}
839+
const featureWidthPx = (item.endBp - item.startBp) / bpPerPx
840+
if (maxLabelWidthPx > featureWidthPx) {
841+
const extraBp = (maxLabelWidthPx - featureWidthPx) * bpPerPx
842+
if (reversed) {
843+
hitStartBp -= extraBp
844+
} else {
845+
hitEndBp += extraBp
846+
}
847+
}
848+
}
849+
featureIndex.add(hitStartBp, item.topPx, hitEndBp, item.bottomPx)
823850
}
824851
featureIndex.finish()
825852
flatbushData = featureIndex.data

plugins/canvas/src/RenderWebGLFeatureDataRPC/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface RenderWebGLFeatureDataArgs {
2323
bpPerPx: number
2424
colorByCDS?: boolean
2525
sequenceAdapter?: Record<string, unknown>
26+
showOnlyGenes?: boolean
2627
}
2728

2829
export interface WebGLFeatureDataResult {

0 commit comments

Comments
 (0)