diff --git a/src/ProteinView/loadStructureFromData.ts b/src/ProteinView/addStructureFromData.ts similarity index 94% rename from src/ProteinView/loadStructureFromData.ts rename to src/ProteinView/addStructureFromData.ts index 0c469e1..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, diff --git a/src/ProteinView/loadStructureFromURL.ts b/src/ProteinView/addStructureFromURL.ts similarity index 89% rename from src/ProteinView/loadStructureFromURL.ts rename to src/ProteinView/addStructureFromURL.ts index 70743e8..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( diff --git a/src/ProteinView/components/ProteinAlignment.tsx b/src/ProteinView/components/ProteinAlignment.tsx index 2def2fc..5ae5ade 100644 --- a/src/ProteinView/components/ProteinAlignment.tsx +++ b/src/ProteinView/components/ProteinAlignment.tsx @@ -3,7 +3,7 @@ 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,7 +14,7 @@ import SplitString from './SplitString' const ProteinAlignment = observer(function ({ model, }: { - model: JBrowsePluginProteinViewModel + model: JBrowsePluginProteinStructureModel }) { const { structureSeqHoverPos, @@ -26,8 +26,8 @@ const ProteinAlignment = observer(function ({ if (!alignment) { return
No alignment
} - const a0 = alignment.alns[0]!.seq as string - const a1 = alignment.alns[1]!.seq as string + const a0 = alignment.alns[0].seq + const a1 = alignment.alns[1].seq const con = alignment.consensus const set = new Set() // eslint-disable-next-line unicorn/no-for-loop diff --git a/src/ProteinView/components/ProteinView.tsx b/src/ProteinView/components/ProteinView.tsx index e297e76..c5a1ee4 100644 --- a/src/ProteinView/components/ProteinView.tsx +++ b/src/ProteinView/components/ProteinView.tsx @@ -29,88 +29,75 @@ const ProteinView = observer(function ({ }: { model: JBrowsePluginProteinViewModel }) { - const { url, data, showControls } = model - const { plugin, seq, parentRef, error } = useProteinView({ - url, - data, + const { showControls } = model + const { plugin, parentRef, error } = useProteinView({ showControls, }) return error ? ( ) : ( - + ) }) const ProteinViewContainer = observer(function ({ model, plugin, - seq, parentRef, }: { model: JBrowsePluginProteinViewModel plugin?: PluginContext - seq?: string parentRef?: React.RefObject }) { const { width, height, - structureSeqToTranscriptSeqPosition, - seq2, - structureSeqHoverPos, showHighlight, - alignment, + // structureSeqToTranscriptSeqPosition, + // structureSeqHoverPos, + // alignment, } = model - const { error } = useProteinViewClickBehavior({ plugin, model }) - useProteinViewHoverBehavior({ plugin, model }) + // const { error } = useProteinViewClickBehavior({ plugin, model }) + // useProteinViewHoverBehavior({ plugin, model }) - const structure = - plugin?.managers.structure.hierarchy.current.structures[0]?.cell.obj?.data - - useEffect(() => { - model.setSeqs(seq, seq2) - }, [seq, model, seq2]) - - useEffect(() => { - if (!plugin || !structureSeqToTranscriptSeqPosition || !structure) { - return - } - if (showHighlight) { - for (const coord of Object.keys(structureSeqToTranscriptSeqPosition)) { - selectResidue({ - structure, - plugin, - selectedResidue: +coord + 1, - }) - } - } else { - clearSelection({ plugin }) - } - }, [plugin, structure, showHighlight, structureSeqToTranscriptSeqPosition]) - - useEffect(() => { - if (!plugin || !structure) { - return - } - - if (structureSeqHoverPos === undefined) { - console.warn('not found') - } else { - highlightResidue({ - structure, - plugin, - selectedResidue: structureSeqHoverPos, - }) - } - }, [plugin, structure, structureSeqHoverPos]) + // const structure = + // plugin?.managers.structure.hierarchy.current.structures[0]?.cell.obj?.data + // useEffect(() => { + // if (!plugin || !structureSeqToTranscriptSeqPosition || !structure) { + // return + // } + // if (showHighlight) { + // for (const coord of Object.keys(structureSeqToTranscriptSeqPosition)) { + // selectResidue({ + // structure, + // plugin, + // selectedResidue: +coord + 1, + // }) + // } + // } else { + // clearSelection({ plugin }) + // } + // }, [plugin, structure, showHighlight, structureSeqToTranscriptSeqPosition]) + // + // useEffect(() => { + // if (!plugin || !structure) { + // return + // } + // + // if (structureSeqHoverPos === undefined) { + // console.warn('not found') + // } else { + // highlightResidue({ + // structure, + // plugin, + // selectedResidue: structureSeqHoverPos, + // }) + // } + // }, [plugin, structure, structureSeqHoverPos]) + const error = undefined + const alignment = undefined return (
{error ? : null} diff --git a/src/ProteinView/genomeToProtein.ts b/src/ProteinView/genomeToProtein.ts index 6bfb396..c6eb05f 100644 --- a/src/ProteinView/genomeToProtein.ts +++ b/src/ProteinView/genomeToProtein.ts @@ -1,21 +1,17 @@ import { getSession } from '@jbrowse/core/util' import { checkHovered } from './util' -import { JBrowsePluginProteinViewModel } from './model' +import { JBrowsePluginProteinStructureModel } from './model' export function genomeToProtein({ model, }: { - model: JBrowsePluginProteinViewModel + model: JBrowsePluginProteinStructureModel }): number | undefined { const { hovered } = getSession(model) const { genomeToTranscriptSeqMapping, connectedView } = model - if ( - !connectedView?.initialized || + return !connectedView?.initialized || !genomeToTranscriptSeqMapping || !checkHovered(hovered) - ) { - return undefined - } - - return genomeToTranscriptSeqMapping.g2p[hovered.hoverPosition.coord] + ? undefined + : genomeToTranscriptSeqMapping.g2p[hovered.hoverPosition.coord] } diff --git a/src/ProteinView/model.ts b/src/ProteinView/model.ts index fba3a03..1dac78a 100644 --- a/src/ProteinView/model.ts +++ b/src/ProteinView/model.ts @@ -2,13 +2,12 @@ import { autorun } from 'mobx' import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes' import { ElementId } from '@jbrowse/core/util/types/mst' import { Region as IRegion } from '@jbrowse/core/util/types' -import { Instance, addDisposer, types } from 'mobx-state-tree' +import { Instance, addDisposer, getParent, types } from 'mobx-state-tree' import { SimpleFeature, SimpleFeatureSerialized, getSession, } from '@jbrowse/core/util' -import { parsePairwise } from 'clustal-js' import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' // locals @@ -19,12 +18,12 @@ import { genomeToTranscriptSeqMapping, structurePositionToAlignmentMap, transcriptPositionToAlignmentMap, + PairwiseAlignment, } from '../mappings' import { PluginContext } from 'molstar/lib/mol-plugin/context' type LGV = LinearGenomeViewModel type MaybeLGV = LGV | undefined -type PairwiseAlignment = ReturnType type MaybePairwiseAlignment = PairwiseAlignment | undefined type StructureModel = Awaited< ReturnType @@ -40,9 +39,61 @@ const Structure = types * #property */ data: types.maybe(types.string), + /** + * #property + */ + connectedViewId: types.maybe(types.string), + /** + * #property + */ + alignment: types.frozen(), + /** + * #property + */ + feature: types.frozen(), + /** + * #property + */ + userProvidedTranscriptSequence: types.string, }) .volatile(() => ({ + /** + * #volatile + */ model: undefined as StructureModel | undefined, + /** + * #volatile + */ + clickGenomeHighlights: [] as IRegion[], + /** + * #volatile + */ + hoverGenomeHighlights: [] as IRegion[], + + /** + * #volatile + */ + clickPosition: undefined as + | { + structureSeqPos: number + code: string + chain: string + } + | undefined, + /** + * #volatile + */ + hoverPosition: undefined as + | { + structureSeqPos: number + code?: string + chain?: string + } + | undefined, + /** + * #volatile + */ + alignmentStatus: '', })) .actions(self => ({ /** @@ -53,6 +104,9 @@ const Structure = types }, })) .views(self => ({ + /** + * #getter + */ get structureSequences() { return self.model?.obj?.data.sequence.sequences.map(s => { let seq = '' @@ -64,7 +118,256 @@ const Structure = types return seq }) }, + /** + * #getter + */ + get connectedView() { + const { views } = getSession(self) + return views.find(f => f.id === self.connectedViewId) as MaybeLGV + }, + })) + .actions(self => ({ + /** + * #action + */ + setClickedPosition(arg?: { + structureSeqPos: number + code: string + chain: string + }) { + self.clickPosition = arg + }, + /** + * #action + */ + setClickGenomeHighlights(r: IRegion[]) { + self.clickGenomeHighlights = r + }, + /** + * #action + */ + clearClickGenomeHighlights() { + self.clickGenomeHighlights = [] + }, + /** + * #action + */ + setHoverGenomeHighlights(r: IRegion[]) { + self.hoverGenomeHighlights = r + }, + /** + * #action + */ + clearHoverGenomeHighlights() { + self.hoverGenomeHighlights = [] + }, + /** + * #action + */ + setHoveredPosition(arg?: { + structureSeqPos: number + chain?: string + code?: string + }) { + self.hoverPosition = arg + }, + /** + * #action + */ + setAlignment(r?: PairwiseAlignment) { + self.alignment = r + }, + /** + * #action + */ + setAlignmentStatus(str: string) { + self.alignmentStatus = str + }, + })) + .views(self => ({ + /** + * #getter + */ + get structureSeqToTranscriptSeqPosition() { + return self.alignment + ? structureSeqVsTranscriptSeqMap(self.alignment) + .structureSeqToTranscriptSeqPosition + : undefined + }, + /** + * #getter + */ + get transcriptSeqToStructureSeqPosition() { + return self.alignment + ? structureSeqVsTranscriptSeqMap(self.alignment) + .transcriptSeqToStructureSeqPosition + : undefined + }, + /** + * #getter + */ + get structurePositionToAlignmentMap() { + return self.alignment + ? structurePositionToAlignmentMap(self.alignment) + : undefined + }, + /** + * #getter + */ + get transcriptPositionToAlignmentMap() { + return self.alignment + ? transcriptPositionToAlignmentMap(self.alignment) + : undefined + }, + /** + * #getter + */ + get alignmentToTranscriptPosition() { + return this.transcriptPositionToAlignmentMap + ? invertMap(this.transcriptPositionToAlignmentMap) + : undefined + }, + /** + * #getter + */ + get alignmentToStructurePosition() { + return this.structurePositionToAlignmentMap + ? invertMap(this.structurePositionToAlignmentMap) + : undefined + }, + /** + * #getter + */ + get clickString() { + const r = self.clickPosition + return r ? toStr(r) : '' + }, + /** + * #getter + */ + get hoverString() { + const r = self.hoverPosition + return r ? toStr(r) : '' + }, + /** + * #getter + */ + get genomeToTranscriptSeqMapping() { + return self.feature + ? genomeToTranscriptSeqMapping(new SimpleFeature(self.feature)) + : undefined + }, + /** + * #getter + */ + get structureSeqHoverPos(): number | undefined { + return self.hoverPosition?.structureSeqPos + }, + + /** + * #getter + */ + get exactMatch() { + const r1 = self.userProvidedTranscriptSequence.replaceAll('*', '') + const r2 = self.structureSequences?.[0]?.replaceAll('*', '') + return r1 === r2 + }, + get zoomToBaseLevel(): boolean { + // @ts-expect-error + return getParent(self, 2).zoomToBaseLevel + }, + get showHighlight(): boolean { + // @ts-expect-error + return getParent(self, 2).showHighlight + }, })) + .actions(self => ({ + afterAttach() { + // pairwise align transcript sequence to structure residues + addDisposer( + self, + autorun(async () => { + try { + const { + userProvidedTranscriptSequence, + structureSequences, + exactMatch, + } = self + const seq1 = userProvidedTranscriptSequence + const seq2 = structureSequences?.[0] + if (!!self.alignment || !seq1 || !seq2) { + return + } + const r1 = seq1.replaceAll('*', '') + const r2 = seq2.replaceAll('*', '') + if (exactMatch) { + let consensus = '' + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < r1.length; i++) { + consensus += '|' + } + self.setAlignment({ + consensus, + alns: [ + { id: 'seq1', seq: r1 }, + { id: 'seq2', seq: r2 }, + ], + }) + } else { + const alignment = await launchPairwiseAlignment({ + seq1: r1, + seq2: r2, + algorithm: 'emboss_needle', + onProgress: arg => { + self.setAlignmentStatus(arg) + }, + }) + self.setAlignment(alignment.alignment) + + // showHighlight when we are + getParent(self, 2).setShowHighlight(true) + getParent(self, 2).setShowAlignment(true) + } + } catch (e) { + console.error(e) + getParent(self, 2).setError(e) + } + }), + ) + + // convert hover over the genome to structure position + addDisposer( + self, + autorun(() => { + const { hovered } = getSession(self) + const { + transcriptSeqToStructureSeqPosition, + genomeToTranscriptSeqMapping, + connectedView, + } = self + if ( + !connectedView?.initialized || + !genomeToTranscriptSeqMapping || + !checkHovered(hovered) + ) { + return undefined + } + + const pos = + genomeToTranscriptSeqMapping.g2p[hovered.hoverPosition.coord] + const c0 = pos + ? transcriptSeqToStructureSeqPosition?.[pos] + : undefined + if (c0 !== undefined) { + self.setHoveredPosition({ + structureSeqPos: c0, + }) + } + }), + ) + }, + })) + /** * #stateModel Protein3dViewPlugin * extends @@ -97,27 +400,7 @@ function stateModelFactory() { * #property */ height: types.optional(types.number, 650), - /** - * #property - */ - feature: types.frozen(), - /** - * #property - */ - seq1: types.maybe(types.string), - /** - * #property - */ - seq2: types.maybe(types.string), - /** - * #property - */ - alignment: types.frozen(), - /** - * #property - */ - connectedViewId: types.maybe(types.string), /** * #property */ @@ -133,52 +416,13 @@ function stateModelFactory() { }), ) .volatile(() => ({ - /** - * #volatile - */ - clickGenomeHighlights: [] as IRegion[], - /** - * #volatile - */ - hoverGenomeHighlights: [] as IRegion[], - /** - * #volatile - */ - error: undefined as unknown, - /** - * #volatile - */ - clickPosition: undefined as - | { - structureSeqPos: number - code: string - chain: string - } - | undefined, - /** - * #volatile - */ - hoverPosition: undefined as - | { - structureSeqPos: number - code?: string - chain?: string - } - | undefined, /** * #volatile */ progress: '', + error: undefined as unknown, })) - .views(self => ({ - /** - * #getter - */ - get connectedView() { - const { views } = getSession(self) - return views.find(f => f.id === self.connectedViewId) as MaybeLGV - }, - })) + .actions(self => ({ /** * #action @@ -186,81 +430,21 @@ function stateModelFactory() { setShowAlignment(f: boolean) { self.showAlignment = f }, - /** - * #action - */ - setHoveredPosition(arg?: { - structureSeqPos: number - chain?: string - code?: string - }) { - self.hoverPosition = arg - }, - /** - * #action - */ - setSeqs(str1?: string, str2?: string) { - self.seq1 = str1 - self.seq2 = str2 - }, + /** * #action */ setShowControls(arg: boolean) { self.showControls = arg }, - /** - * #action - */ - setProgress(str: string) { - self.progress = str - }, - /** - * #action - */ - setClickedPosition(arg?: { - structureSeqPos: number - code: string - chain: string - }) { - self.clickPosition = arg - }, - /** - * #action - */ - setClickGenomeHighlights(r: IRegion[]) { - self.clickGenomeHighlights = r - }, - /** - * #action - */ - clearClickGenomeHighlights() { - self.clickGenomeHighlights = [] - }, - /** - * #action - */ - setHoverGenomeHighlights(r: IRegion[]) { - self.hoverGenomeHighlights = r - }, - /** - * #action - */ - clearHoverGenomeHighlights() { - self.hoverGenomeHighlights = [] - }, + /** * #action */ setError(e: unknown) { self.error = e }, - /** - * #action - */ - setAlignment(r?: PairwiseAlignment) { - self.alignment = r - }, + /** * #action */ @@ -274,172 +458,6 @@ function stateModelFactory() { self.zoomToBaseLevel = arg }, })) - .views(self => ({ - /** - * #getter - */ - get structureSeqToTranscriptSeqPosition() { - return self.alignment - ? structureSeqVsTranscriptSeqMap(self.alignment) - .structureSeqToTranscriptSeqPosition - : undefined - }, - /** - * #getter - */ - get transcriptSeqToStructureSeqPosition() { - return self.alignment - ? structureSeqVsTranscriptSeqMap(self.alignment) - .transcriptSeqToStructureSeqPosition - : undefined - }, - /** - * #getter - */ - get structurePositionToAlignmentMap() { - return self.alignment - ? structurePositionToAlignmentMap(self.alignment) - : undefined - }, - /** - * #getter - */ - get transcriptPositionToAlignmentMap() { - return self.alignment - ? transcriptPositionToAlignmentMap(self.alignment) - : undefined - }, - /** - * #getter - */ - get alignmentToTranscriptPosition() { - return this.transcriptPositionToAlignmentMap - ? invertMap(this.transcriptPositionToAlignmentMap) - : undefined - }, - /** - * #getter - */ - get alignmentToStructurePosition() { - return this.structurePositionToAlignmentMap - ? invertMap(this.structurePositionToAlignmentMap) - : undefined - }, - /** - * #getter - */ - get clickString() { - const r = self.clickPosition - return r ? toStr(r) : '' - }, - /** - * #getter - */ - get hoverString() { - const r = self.hoverPosition - return r ? toStr(r) : '' - }, - /** - * #getter - */ - get genomeToTranscriptSeqMapping() { - return self.feature - ? genomeToTranscriptSeqMapping(new SimpleFeature(self.feature)) - : undefined - }, - /** - * #getter - */ - get structureSeqHoverPos(): number | undefined { - return self.hoverPosition?.structureSeqPos - }, - - get exactMatch() { - const r1 = self.seq1?.replaceAll('*', '') - const r2 = self.seq2?.replaceAll('*', '') - return r1 === r2 - }, - })) - .actions(self => ({ - afterAttach() { - // pairwise align transcript sequence to structure residues - addDisposer( - self, - autorun(async () => { - try { - const { seq1, seq2, exactMatch } = self - if (!!self.alignment || !seq1 || !seq2) { - return - } - const r1 = seq1.replaceAll('*', '') - const r2 = seq2.replaceAll('*', '') - if (exactMatch) { - let consensus = '' - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < r1.length; i++) { - consensus += '|' - } - self.setAlignment({ - consensus, - alns: [ - { id: 'seq1', seq: r1 }, - { id: 'seq2', seq: r2 }, - ], - }) - } else { - const alignment = await launchPairwiseAlignment({ - seq1: r1, - seq2: r2, - algorithm: 'emboss_needle', - onProgress: arg => { - self.setProgress(arg) - }, - }) - self.setAlignment(alignment.alignment) - - // showHighlight when we are - self.setShowHighlight(true) - self.setShowAlignment(true) - } - } catch (e) { - console.error(e) - self.setError(e) - } - }), - ) - - // convert hover over the genome to structure position - addDisposer( - self, - autorun(() => { - const { hovered } = getSession(self) - const { - transcriptSeqToStructureSeqPosition, - genomeToTranscriptSeqMapping, - connectedView, - } = self - if ( - !connectedView?.initialized || - !genomeToTranscriptSeqMapping || - !checkHovered(hovered) - ) { - return undefined - } - - const pos = - genomeToTranscriptSeqMapping.g2p[hovered.hoverPosition.coord] - const c0 = pos - ? transcriptSeqToStructureSeqPosition?.[pos] - : undefined - if (c0 !== undefined) { - self.setHoveredPosition({ - structureSeqPos: c0, - }) - } - }), - ) - }, - })) } export default stateModelFactory @@ -449,3 +467,7 @@ export type JBrowsePluginProteinViewStateModel = ReturnType< > export type JBrowsePluginProteinViewModel = Instance + +export type JBrowsePluginProteinStructureStateModel = typeof Structure +export type JBrowsePluginProteinStructureModel = + Instance diff --git a/src/ProteinView/proteinToGenomeMapping.ts b/src/ProteinView/proteinToGenomeMapping.ts index 6339fe6..76034fd 100644 --- a/src/ProteinView/proteinToGenomeMapping.ts +++ b/src/ProteinView/proteinToGenomeMapping.ts @@ -2,14 +2,17 @@ import { getSession } from '@jbrowse/core/util' import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' // locals -import { JBrowsePluginProteinViewModel } from './model' +import { + JBrowsePluginProteinStructureModel, + JBrowsePluginProteinViewModel, +} from './model' export function proteinToGenomeMapping({ model, structureSeqPos, }: { structureSeqPos: number - model: JBrowsePluginProteinViewModel + model: JBrowsePluginProteinStructureModel }) { const { genomeToTranscriptSeqMapping, @@ -38,7 +41,7 @@ export async function clickProteinToGenome({ structureSeqPos, }: { structureSeqPos: number - model: JBrowsePluginProteinViewModel + model: JBrowsePluginProteinStructureModel }) { const session = getSession(model) const result = proteinToGenomeMapping({ structureSeqPos, model }) @@ -75,7 +78,7 @@ export function hoverProteinToGenome({ structureSeqPos, }: { structureSeqPos: number - model: JBrowsePluginProteinViewModel + model: JBrowsePluginProteinStructureModel }) { const session = getSession(model) const result = proteinToGenomeMapping({ structureSeqPos, model }) diff --git a/src/ProteinView/useProteinView.ts b/src/ProteinView/useProteinView.ts index a134c9e..b3989e8 100644 --- a/src/ProteinView/useProteinView.ts +++ b/src/ProteinView/useProteinView.ts @@ -5,23 +5,14 @@ import { createPluginUI } from 'molstar/lib/mol-plugin-ui' import { renderReact18 } from 'molstar/lib/mol-plugin-ui/react18' import { DefaultPluginUISpec } from 'molstar/lib/mol-plugin-ui/spec' -// locals -import { loadStructureFromURL } from './loadStructureFromURL' -import { loadStructureFromData } from './loadStructureFromData' - export default function useProteinView({ - url, - data, showControls, }: { - url?: string - data?: string showControls: boolean }) { const parentRef = useRef(null) const [plugin, setPlugin] = useState() const [error, setError] = useState() - const [seq, setSeq] = useState('') useEffect(() => { let p: PluginContext | undefined @@ -48,14 +39,6 @@ export default function useProteinView({ }) await p.initialized setPlugin(p) - - if (url) { - const { seq } = await loadStructureFromURL({ url, plugin: p }) - setSeq(seq) - } else if (data) { - const { seq } = await loadStructureFromData({ data, plugin: p }) - setSeq(seq) - } } catch (e) { console.error(e) setError(e) @@ -64,7 +47,16 @@ export default function useProteinView({ return () => { p?.unmount() } - }, [url, data, showControls]) + }, [showControls]) - return { parentRef, error, plugin, seq } + return { parentRef, error, plugin } } + +// +// if (url) { +// const { model } = await addStructureFromURL({ url, plugin: p }) +// setModel(model) +// } else if (data) { +// const { model } = await addStructureFromData({ data, plugin: p }) +// setModel(model) +// } diff --git a/src/ProteinView/useProteinViewClickBehavior.ts b/src/ProteinView/useProteinViewClickBehavior.ts index 48d6b86..9c53cae 100644 --- a/src/ProteinView/useProteinViewClickBehavior.ts +++ b/src/ProteinView/useProteinViewClickBehavior.ts @@ -3,7 +3,7 @@ import { getSession } from '@jbrowse/core/util' import { PluginContext } from 'molstar/lib/mol-plugin/context' // local -import { JBrowsePluginProteinViewModel } from './model' +import { JBrowsePluginProteinStructureModel } from './model' import { clickProteinToGenome } from './proteinToGenomeMapping' import { StructureElement, @@ -15,7 +15,7 @@ export default function useProteinViewClickActionBehavior({ model, }: { plugin?: PluginContext - model: JBrowsePluginProteinViewModel + model: JBrowsePluginProteinStructureModel }) { const [error, setError] = useState() const session = getSession(model) @@ -23,7 +23,7 @@ export default function useProteinViewClickActionBehavior({ if (!plugin) { return } - plugin.behaviors.interaction.click.subscribe(event => { + const ret = plugin.behaviors.interaction.click.subscribe(event => { if (StructureElement.Loci.is(event.current.loci)) { const loc = StructureElement.Loci.getFirstLocation(event.current.loci) if (loc) { @@ -45,6 +45,10 @@ export default function useProteinViewClickActionBehavior({ } } }) + return () => { + ret.unsubscribe() + } }, [plugin, session, model]) + return { error } } diff --git a/src/ProteinView/useProteinViewHoverBehavior.ts b/src/ProteinView/useProteinViewHoverBehavior.ts index 43dc132..8304c8d 100644 --- a/src/ProteinView/useProteinViewHoverBehavior.ts +++ b/src/ProteinView/useProteinViewHoverBehavior.ts @@ -3,7 +3,7 @@ import { getSession } from '@jbrowse/core/util' import { PluginContext } from 'molstar/lib/mol-plugin/context' // local -import { JBrowsePluginProteinViewModel } from './model' +import { JBrowsePluginProteinStructureModel } from './model' import { StructureElement, StructureProperties as Props, @@ -15,7 +15,7 @@ export default function useProteinViewClickActionBehavior({ model, }: { plugin?: PluginContext - model: JBrowsePluginProteinViewModel + model: JBrowsePluginProteinStructureModel }) { const session = getSession(model) useEffect(() => { diff --git a/src/mappings.test.ts b/src/mappings.test.ts index 698f37b..c89ac07 100644 --- a/src/mappings.test.ts +++ b/src/mappings.test.ts @@ -11,6 +11,7 @@ test('test', () => { }) test('mapping', () => { + // @ts-expect-error const res = genomeToTranscriptSeqMapping(new SimpleFeature(feature)) const { p2g } = res const aln = structureSeqVsTranscriptSeqMap(alignment) diff --git a/src/mappings.ts b/src/mappings.ts index 24ac624..2905034 100644 --- a/src/mappings.ts +++ b/src/mappings.ts @@ -1,14 +1,15 @@ import { Feature } from '@jbrowse/core/util' import { genomeToTranscriptSeqMapping as g2p } from 'g2p_mapper' - -export interface Alignment { - alns: { - id: string - seq: string - }[] +export interface AlignmentRow { + id: string + seq: string +} +export interface PairwiseAlignment { + consensus: string + alns: readonly [AlignmentRow, AlignmentRow] } -export function structureSeqVsTranscriptSeqMap(alignment: Alignment) { +export function structureSeqVsTranscriptSeqMap(alignment: PairwiseAlignment) { const structureSeq = alignment.alns[0].seq const transcriptSeq = alignment.alns[1].seq if (structureSeq.length !== transcriptSeq.length) { @@ -54,9 +55,9 @@ export function structureSeqVsTranscriptSeqMap(alignment: Alignment) { } } -export function structurePositionToAlignmentMap(alignment: Alignment) { +export function structurePositionToAlignmentMap(alignment: PairwiseAlignment) { const structureSeq = alignment.alns[0].seq - const structurePositionToAlignment = {} as Record + const structurePositionToAlignment = {} as Record for (let i = 0, j = 0; i < structureSeq.length; i++) { if (structureSeq[i] !== '-') { @@ -68,9 +69,9 @@ export function structurePositionToAlignmentMap(alignment: Alignment) { return structurePositionToAlignment } -export function transcriptPositionToAlignmentMap(alignment: Alignment) { +export function transcriptPositionToAlignmentMap(alignment: PairwiseAlignment) { const transcriptSeq = alignment.alns[1].seq - const transcriptPositionToAlignment = {} as Record + const transcriptPositionToAlignment = {} as Record for (let i = 0, j = 0; i < transcriptSeq.length; i++) { if (transcriptSeq[i] !== '-') { diff --git a/src/test_data/gene.ts b/src/test_data/gene.ts index a3e3c8f..4d76fe6 100644 --- a/src/test_data/gene.ts +++ b/src/test_data/gene.ts @@ -586,7 +586,7 @@ export const feature = { uniqueId: '590611027-offset-601794870-0', parentId: '590611027-offset-601794870', __jbrowsefmt: {}, -} +} as const export const alignment = { consensus: @@ -601,4 +601,4 @@ export const alignment = { seq: '--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------MKAAYLSMFGKEDHKPFGDDEVELFRAVPGLKLKIAGKSLPTEKFAIRKSRRYFSSNPISLPVPALEMMYIWNGYAVIGKQPKLTDGILEIITKAEEMLEKGPENEYSVDDECLVKLLKGLCLKYLGRVQEAEENFRSISANEKKIKYDHYLIPNALLELALLLMEQDRNEEAIKLLESAKQNYKNYSMESRTHFRIQAATLQAKSSLENSSRSMVSSVSL*', }, ], -} +} as const diff --git a/yarn.lock b/yarn.lock index 84c1428..95078cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,9 +19,9 @@ picocolors "^1.0.0" "@babel/compat-data@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5" - integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ== + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" + integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": version "7.25.2" @@ -44,12 +44,12 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.25.0", "@babel/generator@^7.7.2": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" - integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== +"@babel/generator@^7.25.0", "@babel/generator@^7.25.4", "@babel/generator@^7.7.2": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.4.tgz#1dc63c1c9caae9e6dc24e264eac254eb25005669" + integrity sha512-NFtZmZsyzDPJnk9Zg3BbTfKKc9UlHYzD0E//p2Z3B9nCwwtJW9T0gVbCz8+fBngnn4zf1Dr3IK8PHQQHq0lDQw== dependencies: - "@babel/types" "^7.25.0" + "@babel/types" "^7.25.4" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" @@ -83,7 +83,7 @@ "@babel/helper-validator-identifier" "^7.24.7" "@babel/traverse" "^7.25.2" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.8.0": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== @@ -129,12 +129,12 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3": - version "7.25.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.3.tgz#91fb126768d944966263f0657ab222a642b82065" - integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.4.tgz#af4f2df7d02440286b7de57b1c21acfb2a6f257a" + integrity sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA== dependencies: - "@babel/types" "^7.25.2" + "@babel/types" "^7.25.4" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -249,16 +249,16 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" - integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz#04db9ce5a9043d9c635e75ae7969a2cd50ca97ff" + integrity sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.9", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.25.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" - integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.4.tgz#6ef37d678428306e7d75f054d5b1bdb8cf8aa8ee" + integrity sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w== dependencies: regenerator-runtime "^0.14.0" @@ -272,22 +272,22 @@ "@babel/types" "^7.25.0" "@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2": - version "7.25.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.3.tgz#f1b901951c83eda2f3e29450ce92743783373490" - integrity sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ== + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.4.tgz#648678046990f2957407e3086e97044f13c3e18e" + integrity sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.0" - "@babel/parser" "^7.25.3" + "@babel/generator" "^7.25.4" + "@babel/parser" "^7.25.4" "@babel/template" "^7.25.0" - "@babel/types" "^7.25.2" + "@babel/types" "^7.25.4" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.3.3": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125" - integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4", "@babel/types@^7.3.3": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.4.tgz#6bcb46c72fdf1012a209d016c07f769e10adcb5f" + integrity sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ== dependencies: "@babel/helper-string-parser" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" @@ -2019,9 +2019,9 @@ clsx@^2.1.0, clsx@^2.1.1: integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== clustal-js@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/clustal-js/-/clustal-js-2.0.1.tgz#485b23334950fc2040067b799dda432b0a6a7273" - integrity sha512-rdntqVPd4tromWENkOz1P/XACIFLaHPhQ1hwBmEbLFVBn5s6oRLMEvgMf63k0y2gk81ti2vecWwFOlhGLOuFfw== + version "2.0.2" + resolved "https://registry.yarnpkg.com/clustal-js/-/clustal-js-2.0.2.tgz#db9b517c5232a3828d6d943e07f38955dc55c31e" + integrity sha512-50GM4CUEkvbW1ILF30TgDgaLuim0dg8S3MQfCeVk1VCJLx33IKuQHVrT7CBmhotF0T4cFbS/3LCPnCY0UcOwrg== co@^4.6.0: version "4.6.0"