From 39c30bd27d0662f6806acde8e65f829ff911c897 Mon Sep 17 00:00:00 2001 From: Colin Diesh Date: Fri, 23 Aug 2024 16:03:56 -0400 Subject: [PATCH] Convert to use mobx-state-tree autoruns instead of useEffect, with potential for displaying multiple structures (#13) --- eslint.config.mjs | 8 +- jest.config.js | 6 - package.json | 15 +- .../GenomeMouseoverHighlight.tsx | 21 +- .../ProteinToGenomeClickHighlight.tsx | 24 +- .../ProteinToGenomeHoverHighlight.tsx | 28 +- .../components/AlphaFoldDBSearch.tsx | 21 +- .../components/TranscriptSelector.tsx | 12 +- .../components/UserProvidedStructure.tsx | 7 +- .../components/calculateProteinSequence.ts | 8 +- .../useLocalStructureFileSequence.ts | 37 +- .../useRemoteStructureFileSequence.ts | 31 +- ...ureFromData.ts => addStructureFromData.ts} | 10 +- ...ctureFromURL.ts => addStructureFromURL.ts} | 22 +- .../components/ProteinAlignment.tsx | 87 +- .../components/ProteinAlignmentHelpButton.tsx | 4 +- .../components/ProteinAlignmentHelpDialog.tsx | 19 +- src/ProteinView/components/ProteinView.tsx | 97 +- .../{Header.tsx => ProteinViewHeader.tsx} | 55 +- src/ProteinView/components/SplitString.tsx | 18 +- src/ProteinView/genomeToProtein.ts | 14 +- .../launchRemotePairwiseAlignment.ts | 6 +- src/ProteinView/model.ts | 365 +-- src/ProteinView/proteinToGenomeMapping.ts | 77 +- src/ProteinView/structureModel.ts | 512 ++++ src/ProteinView/useProteinView.ts | 21 +- .../useProteinViewClickBehavior.ts | 50 - .../useProteinViewHoverBehavior.ts | 44 - src/ProteinView/util.ts | 24 +- src/__snapshots__/mappings.test.ts.snap | 448 ++-- src/genomeToTranscriptMapping.ts | 4 +- src/mappings.test.ts | 12 +- src/mappings.ts | 48 +- src/test_data/gene.ts | 6 +- yarn.lock | 2325 +++++------------ 35 files changed, 1781 insertions(+), 2705 deletions(-) delete mode 100644 jest.config.js rename src/ProteinView/{loadStructureFromData.ts => addStructureFromData.ts} (84%) rename src/ProteinView/{loadStructureFromURL.ts => addStructureFromURL.ts} (80%) rename src/ProteinView/components/{Header.tsx => ProteinViewHeader.tsx} (69%) create mode 100644 src/ProteinView/structureModel.ts delete mode 100644 src/ProteinView/useProteinViewClickBehavior.ts delete mode 100644 src/ProteinView/useProteinViewHoverBehavior.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index 8e619d2..7d4702e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -7,12 +7,7 @@ import tseslint from 'typescript-eslint' export default tseslint.config( { - ignores: [ - '**/dist/**/*', - 'jest.config.js', - 'eslint.config.mjs', - 'esbuild.mjs', - ], + ignores: ['**/dist/**/*', 'eslint.config.mjs', 'esbuild.mjs'], }, { languageOptions: { @@ -78,6 +73,7 @@ export default tseslint.config( 'unicorn/no-null': 'off', 'unicorn/prefer-spread': 'off', + 'unicorn/no-nested-ternary': 'off', 'unicorn/no-useless-undefined': 'off', 'unicorn/catch-error-name': 'off', 'unicorn/filename-case': 'off', diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 2e97aa4..0000000 --- a/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testPathIgnorePatterns: ['dist', '/node_modules/'], -} diff --git a/package.json b/package.json index ebfae09..7dfe95f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "src" ], "scripts": { - "test": "jest", + "test": "vitest", "clean": "rimraf dist", "format": "prettier --write .", "prebuild": "npm run clean", @@ -37,8 +37,7 @@ "@mui/material": "^5.12.0", "@mui/system": "^5.12.0", "@mui/x-data-grid": "^7.3.0", - "@types/jest": "^29.5.12", - "@types/node": "^20.11.16", + "@types/node": "^22.5.0", "@types/pako": "^2.0.0", "@types/react": "^18.2.54", "@typescript-eslint/eslint-plugin": "^8.1.0", @@ -48,11 +47,10 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react": "^7.20.3", - "eslint-plugin-react-hooks": "^4.0.8", - "eslint-plugin-react-refresh": "^0.4.3", + "eslint-plugin-react-hooks": "^5.1.0-rc-eb3ad065-20240822", + "eslint-plugin-react-refresh": "^0.4.11", "eslint-plugin-unicorn": "^55.0.0", "fp-ts": "^2.16.9", - "jest": "^29.7.0", "mobx": "^6.0.0", "mobx-react": "^9.0.1", "mobx-state-tree": "5.4.2", @@ -63,11 +61,10 @@ "react-dom": "^18.2.0", "rimraf": "^6.0.1", "rxjs": "^7.0.0", - "ts-jest": "^29.1.2", - "ts-node": "^10.3.0", "tss-react": "^4.9.4", "typescript": "^5.3.3", - "typescript-eslint": "^8.1.0" + "typescript-eslint": "^8.1.0", + "vitest": "^2.0.5" }, "author": "Colin ", "license": "MIT" diff --git a/src/AddHighlightModel/GenomeMouseoverHighlight.tsx b/src/AddHighlightModel/GenomeMouseoverHighlight.tsx index bfed472..1272206 100644 --- a/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +++ b/src/AddHighlightModel/GenomeMouseoverHighlight.tsx @@ -4,30 +4,17 @@ import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' import { getSession } from '@jbrowse/core/util' // locals import Highlight from './Highlight' +import { checkHovered } from '../ProteinView/util' const GenomeMouseoverHighlight = observer(function ({ model, }: { model: LinearGenomeViewModel -}) { - const { hovered } = getSession(model) - return hovered && - typeof hovered === 'object' && - 'hoverPosition' in hovered ? ( - - ) : null -}) - -const HoverHighlight = observer(function ({ - model, -}: { - model: LinearGenomeViewModel }) { const session = getSession(model) - if (session.views.some(s => s.type === 'ProteinView')) { - const { hovered } = session + const { views, hovered } = session + if (checkHovered(hovered) && views.some(s => s.type === 'ProteinView')) { const { assemblyNames } = model - // @ts-expect-error const { coord, refName } = hovered.hoverPosition return ( ) } diff --git a/src/AddHighlightModel/ProteinToGenomeClickHighlight.tsx b/src/AddHighlightModel/ProteinToGenomeClickHighlight.tsx index fd3a3b9..c829061 100644 --- a/src/AddHighlightModel/ProteinToGenomeClickHighlight.tsx +++ b/src/AddHighlightModel/ProteinToGenomeClickHighlight.tsx @@ -16,23 +16,25 @@ const ProteinToGenomeClickHighlight = observer(function ({ }) { const { assemblyManager, views } = getSession(model) const { assemblyNames } = model - const p = views.find(f => f.type === 'ProteinView') as + const proteinView = views.find(f => f.type === 'ProteinView') as | JBrowsePluginProteinViewModel | undefined const assemblyName = assemblyNames[0]! const assembly = assemblyManager.get(assemblyName) return assembly ? ( <> - {p?.clickGenomeHighlights.map((r, idx) => ( - - ))} + {proteinView?.structures.map(structure => + structure.clickGenomeHighlights.map((r, idx) => ( + + )), + )} ) : null }) diff --git a/src/AddHighlightModel/ProteinToGenomeHoverHighlight.tsx b/src/AddHighlightModel/ProteinToGenomeHoverHighlight.tsx index f98b61d..048e454 100644 --- a/src/AddHighlightModel/ProteinToGenomeHoverHighlight.tsx +++ b/src/AddHighlightModel/ProteinToGenomeHoverHighlight.tsx @@ -7,32 +7,32 @@ import { getSession } from '@jbrowse/core/util' import { JBrowsePluginProteinViewModel } from '../ProteinView/model' import Highlight from './Highlight' -type LGV = LinearGenomeViewModel - const ProteinToGenomeHoverHighlight = observer(function ({ model, }: { - model: LGV + model: LinearGenomeViewModel }) { const { assemblyManager, views } = getSession(model) const { assemblyNames } = model - const p = views.find(f => f.type === 'ProteinView') as + const proteinView = views.find(f => f.type === 'ProteinView') as | JBrowsePluginProteinViewModel | undefined const assemblyName = assemblyNames[0]! const assembly = assemblyManager.get(assemblyName) return assembly ? ( <> - {p?.hoverGenomeHighlights.map((r, idx) => ( - - ))} + {proteinView?.structures.map(structure => + structure.hoverGenomeHighlights.map((r, idx) => ( + + )), + )} ) : null }) diff --git a/src/LaunchProteinView/components/AlphaFoldDBSearch.tsx b/src/LaunchProteinView/components/AlphaFoldDBSearch.tsx index a7b95aa..1e3c3d5 100644 --- a/src/LaunchProteinView/components/AlphaFoldDBSearch.tsx +++ b/src/LaunchProteinView/components/AlphaFoldDBSearch.tsx @@ -78,13 +78,14 @@ const AlphaFoldDBSearch = observer(function ({ ? `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-model_v4.cif` : undefined const { - seq: structureSequence, + sequences: structureSequences, isLoading: isRemoteStructureSequenceLoading, error: remoteStructureSequenceError, } = useRemoteStructureFileSequence({ url }) const e = myGeneError || isoformProteinSequencesError || remoteStructureSequenceError + const structureSequence = structureSequences?.[0] useEffect(() => { if (isoformSequences !== undefined) { const ret = @@ -116,7 +117,7 @@ const AlphaFoldDBSearch = observer(function ({ variant="h6" message="Looking up UniProt ID from mygene.info" /> - ) : (uniprotId ? null : ( + ) : uniprotId ? null : (
UniProt ID not found. Search sequence on AlphaFoldDB{' '} - ))} + )} {isIsoformProteinSequencesLoading ? ( { session.addView('ProteinView', { type: 'ProteinView', - url, - seq2: userSelectedProteinSequence?.seq, - feature: selectedTranscript?.toJSON(), - connectedViewId: view.id, + isFloating: true, + structures: [ + { + url, + userProvidedTranscriptSequence: + userSelectedProteinSequence?.seq, + feature: selectedTranscript?.toJSON(), + connectedViewId: view.id, + }, + ], displayName: `Protein view ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`, }) handleClose() diff --git a/src/LaunchProteinView/components/TranscriptSelector.tsx b/src/LaunchProteinView/components/TranscriptSelector.tsx index 5cb27b2..698238a 100644 --- a/src/LaunchProteinView/components/TranscriptSelector.tsx +++ b/src/LaunchProteinView/components/TranscriptSelector.tsx @@ -41,13 +41,13 @@ export default function TranscriptSelector({ .filter(f => !!isoformSequences[f.id()]) .filter( f => - isoformSequences[f.id()].seq.replaceAll('*', '') === + isoformSequences[f.id()]!.seq.replaceAll('*', '') === structureSequence, ) .map(f => ( {getGeneDisplayName(feature)} - {getTranscriptDisplayName(f)} ( - {isoformSequences[f.id()].seq.length}aa) (matches structure + {isoformSequences[f.id()]!.seq.length}aa) (matches structure residues) ))} @@ -55,18 +55,18 @@ export default function TranscriptSelector({ .filter(f => !!isoformSequences[f.id()]) .filter( f => - isoformSequences[f.id()].seq.replaceAll('*', '') !== + isoformSequences[f.id()]!.seq.replaceAll('*', '') !== structureSequence, ) .sort( (a, b) => - isoformSequences[b.id()].seq.length - - isoformSequences[a.id()].seq.length, + isoformSequences[b.id()]!.seq.length - + isoformSequences[a.id()]!.seq.length, ) .map(f => ( {getGeneDisplayName(feature)} - {getTranscriptDisplayName(f)} ( - {isoformSequences[f.id()].seq.length}aa) + {isoformSequences[f.id()]!.seq.length}aa) ))} {isoforms diff --git a/src/LaunchProteinView/components/UserProvidedStructure.tsx b/src/LaunchProteinView/components/UserProvidedStructure.tsx index 05d661f..da7b5f6 100644 --- a/src/LaunchProteinView/components/UserProvidedStructure.tsx +++ b/src/LaunchProteinView/components/UserProvidedStructure.tsx @@ -96,14 +96,15 @@ const UserProvidedStructure = observer(function ({ view, }) const protein = isoformSequences?.[userSelection ?? ''] - const { seq: structureSequence1, error: error3 } = + const { sequences: structureSequences1, error: error3 } = useLocalStructureFileSequence({ file }) - const { seq: structureSequence2, error: error4 } = + const { sequences: structureSequences2, error: error4 } = useRemoteStructureFileSequence({ url: structureURL }) const structureName = file?.name ?? structureURL.slice(structureURL.lastIndexOf('/') + 1) - const structureSequence = structureSequence1 ?? structureSequence2 + const structureSequences = structureSequences1 ?? structureSequences2 + const structureSequence = structureSequences?.[0] useEffect(() => { if (isoformSequences !== undefined) { diff --git a/src/LaunchProteinView/components/calculateProteinSequence.ts b/src/LaunchProteinView/components/calculateProteinSequence.ts index 48791bf..97808aa 100644 --- a/src/LaunchProteinView/components/calculateProteinSequence.ts +++ b/src/LaunchProteinView/components/calculateProteinSequence.ts @@ -53,7 +53,7 @@ function getItemId(feat: Feat) { // filters if successive elements share same start/end export function dedupe(list: Feat[]) { return list.filter( - (item, pos, ary) => !pos || getItemId(item) !== getItemId(ary[pos - 1]), + (item, pos, ary) => !pos || getItemId(item) !== getItemId(ary[pos - 1]!), ) } @@ -102,8 +102,10 @@ export async function fetchProteinSeq({ const refName = feature.get('refName') const session = getSession(view) const { assemblyManager, rpcManager } = session - const [assemblyName] = view?.assemblyNames ?? [] - const assembly = await assemblyManager.waitForAssembly(assemblyName) + const assemblyName = view?.assemblyNames?.[0] + const assembly = assemblyName + ? await assemblyManager.waitForAssembly(assemblyName) + : undefined if (!assembly) { throw new Error('assembly not found') } diff --git a/src/LaunchProteinView/components/useLocalStructureFileSequence.ts b/src/LaunchProteinView/components/useLocalStructureFileSequence.ts index f145a68..597ce2e 100644 --- a/src/LaunchProteinView/components/useLocalStructureFileSequence.ts +++ b/src/LaunchProteinView/components/useLocalStructureFileSequence.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { createPluginUI } from 'molstar/lib/mol-plugin-ui' import { renderReact18 } from 'molstar/lib/mol-plugin-ui/react18' -import { loadStructureFromData } from '../../ProteinView/loadStructureFromData' +import { addStructureFromData } from '../../ProteinView/addStructureFromData' async function structureFileSequenceFetcher( file: File, @@ -12,11 +12,26 @@ async function structureFileSequenceFetcher( target: ret, render: renderReact18, }) - const data = await file.text() - const { seq } = await loadStructureFromData({ data, plugin: p, format }) - p.unmount() - ret.remove() - return seq + + try { + const { model } = await addStructureFromData({ + data: await file.text(), + plugin: p, + format, + }) + return model.obj?.data.sequence.sequences.map(s => { + let seq = '' + const arr = s.sequence.label.toArray() + // eslint-disable-next-line unicorn/no-for-loop,@typescript-eslint/prefer-for-of + for (let i = 0; i < arr.length; i++) { + seq += arr[i]! + } + return seq + }) + } finally { + p.unmount() + ret.remove() + } } export default function useLocalStructureFileSequence({ @@ -26,7 +41,7 @@ export default function useLocalStructureFileSequence({ }) { const [error, setError] = useState() const [isLoading, setLoading] = useState(false) - const [seq, setSeq] = useState() + const [sequences, setSequences] = useState() useEffect(() => { // eslint-disable-next-line @typescript-eslint/no-floating-promises ;(async () => { @@ -39,7 +54,11 @@ export default function useLocalStructureFileSequence({ file, (ext === 'cif' ? 'mmcif' : ext) as 'pdb' | 'mmcif', ) - setSeq(seq) + if (seq) { + setSequences(seq) + } else { + throw new Error('no sequences detected in file') + } } } catch (e) { console.error(e) @@ -49,5 +68,5 @@ export default function useLocalStructureFileSequence({ } })() }, [file]) - return { error, isLoading, seq } + return { error, isLoading, sequences } } diff --git a/src/LaunchProteinView/components/useRemoteStructureFileSequence.ts b/src/LaunchProteinView/components/useRemoteStructureFileSequence.ts index dcb3c9e..d4ae3ae 100644 --- a/src/LaunchProteinView/components/useRemoteStructureFileSequence.ts +++ b/src/LaunchProteinView/components/useRemoteStructureFileSequence.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { createPluginUI } from 'molstar/lib/mol-plugin-ui' import { renderReact18 } from 'molstar/lib/mol-plugin-ui/react18' -import { loadStructureFromURL } from '../../ProteinView/loadStructureFromURL' +import { addStructureFromURL } from '../../ProteinView/addStructureFromURL' async function structureFileSequenceFetcher(url: string) { const ret = document.createElement('div') @@ -9,10 +9,21 @@ async function structureFileSequenceFetcher(url: string) { target: ret, render: renderReact18, }) - const { seq } = await loadStructureFromURL({ url, plugin: p }) - p.unmount() - ret.remove() - return seq + try { + const { model } = await addStructureFromURL({ url, plugin: p }) + return model.obj?.data.sequence.sequences.map(s => { + let seq = '' + const arr = s.sequence.label.toArray() + // eslint-disable-next-line unicorn/no-for-loop,@typescript-eslint/prefer-for-of + for (let i = 0; i < arr.length; i++) { + seq += arr[i]! + } + return seq + }) + } finally { + p.unmount() + ret.remove() + } } export default function useRemoteStructureFileSequence({ @@ -22,7 +33,7 @@ export default function useRemoteStructureFileSequence({ }) { const [error, setError] = useState() const [isLoading, setLoading] = useState(false) - const [seq, setSeq] = useState() + const [sequences, setSequences] = useState() useEffect(() => { // eslint-disable-next-line @typescript-eslint/no-floating-promises ;(async () => { @@ -30,7 +41,11 @@ export default function useRemoteStructureFileSequence({ if (url) { setLoading(true) const seq = await structureFileSequenceFetcher(url) - setSeq(seq) + if (seq) { + setSequences(seq) + } else { + throw new Error('no sequences detected in file') + } } } catch (e) { console.error(e) @@ -40,5 +55,5 @@ export default function useRemoteStructureFileSequence({ } })() }, [url]) - return { error, isLoading, seq } + return { error, isLoading, sequences } } diff --git a/src/ProteinView/loadStructureFromData.ts b/src/ProteinView/addStructureFromData.ts similarity index 84% rename from src/ProteinView/loadStructureFromData.ts rename to src/ProteinView/addStructureFromData.ts index de5e992..d767ae4 100644 --- a/src/ProteinView/loadStructureFromData.ts +++ b/src/ProteinView/addStructureFromData.ts @@ -7,7 +7,7 @@ export interface LoadStructureOptions { } // adapted from https://github.com/molstar/molstar/blob/ab4130d42d0ab2591f62460292ade0203207d4d2/src/apps/viewer/app.ts#L255C1-L259C6 -export async function loadStructureFromData({ +export async function addStructureFromData({ data, format = 'pdb', options, @@ -18,8 +18,6 @@ export async function loadStructureFromData({ options?: LoadStructureOptions & { label?: string; dataLabel?: string } plugin: PluginContext }) { - await plugin.clear() - const _data = await plugin.builders.data.rawData({ data, label: options?.dataLabel, @@ -30,10 +28,6 @@ export async function loadStructureFromData({ format, ) const model = await plugin.builders.structure.createModel(trajectory) - const seq = model.obj?.data.sequence.sequences[0].sequence.label - .toArray() - // @ts-expect-error - .join('') await plugin.builders.structure.hierarchy.applyPreset( trajectory, @@ -44,5 +38,5 @@ export async function loadStructureFromData({ }, ) - return { seq: seq as string } + return { model } } diff --git a/src/ProteinView/loadStructureFromURL.ts b/src/ProteinView/addStructureFromURL.ts similarity index 80% rename from src/ProteinView/loadStructureFromURL.ts rename to src/ProteinView/addStructureFromURL.ts index a57e191..293ca87 100644 --- a/src/ProteinView/loadStructureFromURL.ts +++ b/src/ProteinView/addStructureFromURL.ts @@ -7,7 +7,7 @@ export interface LoadStructureOptions { } // adapted from https://github.com/molstar/molstar/blob/ab4130d42d0ab2591f62460292ade0203207d4d2/src/apps/viewer/app.ts#L230 -export async function loadStructureFromURL({ +export async function addStructureFromURL({ url, format = 'mmcif', isBinary, @@ -20,11 +20,16 @@ export async function loadStructureFromURL({ options?: LoadStructureOptions & { label?: string } plugin: PluginContext }) { - await plugin.clear() - const data = await plugin.builders.data.download( - { url, isBinary }, - { state: { isGhost: true } }, + { + url, + isBinary, + }, + { + state: { + isGhost: true, + }, + }, ) const trajectory = await plugin.builders.structure.parseTrajectory( @@ -32,10 +37,6 @@ export async function loadStructureFromURL({ format, ) const model = await plugin.builders.structure.createModel(trajectory) - const seq = model.obj?.data.sequence.sequences[0].sequence.label - .toArray() - // @ts-expect-error - .join('') await plugin.builders.structure.hierarchy.applyPreset( trajectory, @@ -45,6 +46,5 @@ export async function loadStructureFromURL({ representationPresetParams: options?.representationParams, }, ) - - return { seq: seq as string } + return { model } } diff --git a/src/ProteinView/components/ProteinAlignment.tsx b/src/ProteinView/components/ProteinAlignment.tsx index 2def2fc..2cf6611 100644 --- a/src/ProteinView/components/ProteinAlignment.tsx +++ b/src/ProteinView/components/ProteinAlignment.tsx @@ -1,9 +1,9 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import { observer } from 'mobx-react' import { Tooltip, Typography } from '@mui/material' // locals -import { JBrowsePluginProteinViewModel } from '../model' +import { JBrowsePluginProteinStructureModel } from '../model' import ProteinAlignmentHelpButton from './ProteinAlignmentHelpButton' import { clickProteinToGenome, @@ -14,45 +14,58 @@ import SplitString from './SplitString' const ProteinAlignment = observer(function ({ model, }: { - model: JBrowsePluginProteinViewModel + model: JBrowsePluginProteinStructureModel }) { const { - structureSeqHoverPos, - alignment, + pairwiseAlignment, + pairwiseAlignmentToStructurePosition, structurePositionToAlignmentMap, - alignmentToStructurePosition, + structureSeqHoverPos, showHighlight, } = model - if (!alignment) { - return
No alignment
+ + const [pairwiseAlignmentHoverPos, setPairwiseAlignmentHoverPos] = + useState() + + useEffect(() => { + setPairwiseAlignmentHoverPos( + structureSeqHoverPos === undefined + ? undefined + : structurePositionToAlignmentMap?.[structureSeqHoverPos], + ) + }, [structurePositionToAlignmentMap, structureSeqHoverPos]) + + if (!pairwiseAlignment) { + return
No pairwiseAlignment
} - const a0 = alignment.alns[0]!.seq as string - const a1 = alignment.alns[1]!.seq as string - const con = alignment.consensus - const set = new Set() + const a0 = pairwiseAlignment.alns[0].seq + const a1 = pairwiseAlignment.alns[1].seq + const con = pairwiseAlignment.consensus + const gapSet = new Set() // eslint-disable-next-line unicorn/no-for-loop for (let i = 0; i < con.length; i++) { const letter = con[i] if (letter === '|') { - set.add(i) + gapSet.add(i) } } - const alignmentHoverPos = - structureSeqHoverPos === undefined - ? undefined - : structurePositionToAlignmentMap?.[structureSeqHoverPos] - - function onMouseOver(alignmentPos: number) { - const structureSeqPos = alignmentToStructurePosition[alignmentPos] - model.setHoveredPosition({ structureSeqPos }) - hoverProteinToGenome({ model, structureSeqPos }) + function onMouseOver(p: number) { + setPairwiseAlignmentHoverPos(p) + if (pairwiseAlignmentToStructurePosition) { + const structureSeqPos = pairwiseAlignmentToStructurePosition[p] + model.setHoveredPosition({ structureSeqPos }) + hoverProteinToGenome({ model, structureSeqPos }) + } } - function onClick(alignmentPos: number) { - const structureSeqPos = alignmentToStructurePosition[alignmentPos] - clickProteinToGenome({ model, structureSeqPos }).catch((e: unknown) => { - console.error(e) - }) + function onClick(pairwiseAlignmentPos: number) { + if (pairwiseAlignmentToStructurePosition) { + const structureSeqPos = + pairwiseAlignmentToStructurePosition[pairwiseAlignmentPos]! + clickProteinToGenome({ model, structureSeqPos }).catch((e: unknown) => { + console.error(e) + }) + } } return (
@@ -78,14 +91,14 @@ const ProteinAlignment = observer(function ({ }} >
- - STRUCT  + + GENOME  @@ -95,21 +108,21 @@ const ProteinAlignment = observer(function ({
- - GENOME  + + STRUCT  diff --git a/src/ProteinView/components/ProteinAlignmentHelpButton.tsx b/src/ProteinView/components/ProteinAlignmentHelpButton.tsx index 48aeee0..1f8256e 100644 --- a/src/ProteinView/components/ProteinAlignmentHelpButton.tsx +++ b/src/ProteinView/components/ProteinAlignmentHelpButton.tsx @@ -3,7 +3,7 @@ import { IconButton } from '@mui/material' import { getSession } from '@jbrowse/core/util' // locals -import { JBrowsePluginProteinViewModel } from '../model' +import { JBrowsePluginProteinStructureModel } from '../model' // icons import Help from '@mui/icons-material/Help' @@ -15,7 +15,7 @@ const ProteinAlignmentHelpDialog = lazy( export default function ProteinAlignmentHelpButton({ model, }: { - model: JBrowsePluginProteinViewModel + model: JBrowsePluginProteinStructureModel }) { return ( - This panel shows the computed alignment of the reference genome - sequence to the structure sequence. The structure file (PDB file, - mmCIF file, etc) has a stored representation of the e.g. amino acid - sequence but the sequence in the structure file can differ from the - sequence from the gene on the genome browser + This panel shows the computed pairwise alignment of the reference + genome sequence to the structure sequence. The structure file (PDB + file, mmCIF file, etc) has a stored representation of the e.g. amino + acid sequence but the sequence in the structure file can differ from + the sequence from the gene on the genome browser In order to resolve this, we align the two sequences together (using - EMBOSS needle) to get alignment of the genome's representation of - the protein and the structure file's representation of the - protein. + EMBOSS needle) to get pairwise alignment of the genome's + representation of the protein and the structure file's + representation of the protein. If you need a 100% fidelity protein, you can do a folding with e.g. @@ -48,12 +47,12 @@ export default function ProteinAlignmentHelpDialog({ sequence of the transcript -