diff --git a/packages/core/BaseFeatureWidget/BaseFeatureDetail/index.tsx b/packages/core/BaseFeatureWidget/BaseFeatureDetail/index.tsx index a4f971a383..753f380d3c 100644 --- a/packages/core/BaseFeatureWidget/BaseFeatureDetail/index.tsx +++ b/packages/core/BaseFeatureWidget/BaseFeatureDetail/index.tsx @@ -20,15 +20,16 @@ import { getEnv, getSession, assembleLocString, + toLocale, ParsedLocString, + SimpleFeatureSerialized, } from '../../util' import { ErrorMessage } from '../../ui' import SequenceFeatureDetails from '../SequenceFeatureDetails' import { BaseCardProps, BaseProps } from '../types' -import { SimpleFeatureSerialized } from '../../util' import SimpleField from './SimpleField' import Attributes from './Attributes' -import { generateTitle, isEmpty, toLocale } from './util' +import { generateTitle, isEmpty } from './util' // coreDetails are omitted in some circumstances const coreDetails = [ diff --git a/packages/core/BaseFeatureWidget/BaseFeatureDetail/util.ts b/packages/core/BaseFeatureWidget/BaseFeatureDetail/util.ts index 843b215679..055cbf5ed4 100644 --- a/packages/core/BaseFeatureWidget/BaseFeatureDetail/util.ts +++ b/packages/core/BaseFeatureWidget/BaseFeatureDetail/util.ts @@ -20,10 +20,6 @@ export function generateMaxWidth(array: unknown[][], prefix: string[]) { ) } -export function toLocale(n: number) { - return n.toLocaleString('en-US') -} - // pick using a path from an object, similar to _.get from lodash with special // logic for Descriptions from e.g. VCF headers // diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx index e974df17e8..65f86ee5c6 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx +++ b/plugins/alignments/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx @@ -1,131 +1,24 @@ -import React, { useState } from 'react' -import { Link, Paper } from '@mui/material' +import React from 'react' +import { Paper } from '@mui/material' import { observer } from 'mobx-react' -import copy from 'copy-to-clipboard' import clone from 'clone' import { FeatureDetails } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail' -import { IAnyStateTreeNode } from 'mobx-state-tree' // locals -import { getTag, navToLoc } from './util' -import SupplementaryAlignments from './AlignmentsFeatureSuppAligns' -import AlignmentFlags from './AlignmentsFeatureFlags' +import { getTag } from './util' +import { tags } from './tagInfo' +import { AlignmentFeatureWidgetModel } from './stateModelFactory' -const omit = ['clipPos', 'flags'] - -const tags = { - AM: 'The smallest template-independent mapping quality in the template', - AS: 'Alignment score generated by aligner', - BC: 'Barcode sequence identifying the sample', - BQ: 'Offset to base alignment quality (BAQ)', - BZ: 'Phred quality of the unique molecular barcode bases in the {OX} tag', - CB: 'Cell identifier', - CC: 'Reference name of the next hit', - CM: 'Edit distance between the color sequence and the color reference (see also {NM})', - CO: 'Free-text comments', - CP: 'Leftmost coordinate of the next hit', - CQ: 'Color read base qualities', - CR: 'Cellular barcode sequence bases (uncorrected)', - CS: 'Color read sequence', - CT: 'Complete read annotation tag, used for consensus annotation dummy features', - CY: 'Phred quality of the cellular barcode sequence in the {CR} tag', - E2: 'The 2nd most likely base calls', - FI: 'The index of segment in the template', - FS: 'Segment suffix', - FZ: 'Flow signal intensities', - GC: 'Reserved for backwards compatibility reasons', - GQ: 'Reserved for backwards compatibility reasons', - GS: 'Reserved for backwards compatibility reasons', - H0: 'Number of perfect hits', - H1: 'Number of 1-difference hits (see also {NM})', - H2: 'Number of 2-difference hits', - HI: 'Query hit index', - IH: 'Query hit total count', - LB: 'Library', - MC: 'CIGAR string for mate/next segment', - MD: 'String encoding mismatched and deleted reference bases', - MF: 'Reserved for backwards compatibility reasons', - MI: 'Molecular identifier; a string that uniquely identifies the molecule from which the record was derived', - ML: 'Base modification probabilities', - MM: 'Base modifications / methylation ', - MQ: 'Mapping quality of the mate/next segment', - NH: 'Number of reported alignments that contain the query in the current record', - NM: 'Edit distance to the reference', - OA: 'Original alignment', - OC: 'Original CIGAR (deprecated; use {OA} instead)', - OP: 'Original mapping position (deprecated; use {OA} instead)', - OQ: 'Original base quality', - OX: 'Original unique molecular barcode bases', - PG: 'Program', - PQ: 'Phred likelihood of the template', - PT: 'Read annotations for parts of the padded read sequence', - PU: 'Platform unit', - Q2: 'Phred quality of the mate/next segment sequence in the {R2} tag', - QT: 'Phred quality of the sample barcode sequence in the {BC} tag', - QX: 'Quality score of the unique molecular identifier in the {RX} tag', - R2: 'Sequence of the mate/next segment in the template', - RG: 'Read group', - RT: 'Reserved for backwards compatibility reasons', - RX: 'Sequence bases of the (possibly corrected) unique molecular identifier', - S2: 'Reserved for backwards compatibility reasons', - SA: 'Other canonical alignments in a chimeric alignment', - SM: 'Template-independent mapping quality', - SQ: 'Reserved for backwards compatibility reasons', - TC: 'The number of segments in the template', - TS: 'Transcript strand', - U2: 'Phred probability of the 2nd call being wrong conditional on the best being wrong', - UQ: 'Phred likelihood of the segment, conditional on the mapping being correct', -} - -function Formatter({ value }: { value: unknown }) { - const [show, setShow] = useState(false) - const [copied, setCopied] = useState(false) - const display = String(value) - return display.length > 100 ? ( - <> - - -
{show ? display : `${display.slice(0, 100)}...`}
- - ) : ( -
{display}
- ) -} +// local components +import SuppAlignments from './SuppAlignments' +import Flags from './Flags' +import PairLink from './PairLink' +import Formatter from './Formatter' -function PairLink({ - locString, - model, -}: { - locString: string - model: IAnyStateTreeNode -}) { - return ( - { - event.preventDefault() - // eslint-disable-next-line @typescript-eslint/no-floating-promises - navToLoc(locString, model) - }} - href="#" - > - {locString} - - ) -} +const omit = ['clipPos', 'flags'] const AlignmentsFeatureDetails = observer(function (props: { - model: IAnyStateTreeNode + model: AlignmentFeatureWidgetModel }) { const { model } = props const feat = clone(model.featureData) @@ -136,7 +29,7 @@ const AlignmentsFeatureDetails = observer(function (props: { {...props} omit={omit} // @ts-expect-error - descriptions={{ ...tags, tags }} + descriptions={{ ...tags, tags: tags }} feature={feat} formatter={(value, key) => key === 'next_segment_position' ? ( @@ -146,10 +39,8 @@ const AlignmentsFeatureDetails = observer(function (props: { ) } /> - {SA ? : null} - {feat.flags !== undefined ? ( - - ) : null} + {SA ? : null} + {feat.flags !== undefined ? : null} ) }) diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/BreakendOptionDialog.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/BreakendOptionDialog.tsx new file mode 100644 index 0000000000..e4eb8dd3bf --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/BreakendOptionDialog.tsx @@ -0,0 +1,140 @@ +import React, { useState } from 'react' +import { observer } from 'mobx-react' +import { + Button, + Checkbox, + DialogActions, + DialogContent, + FormControlLabel, +} from '@mui/material' +import { makeStyles } from 'tss-react/mui' +import { getSession } from '@jbrowse/core/util' +import { Dialog } from '@jbrowse/core/ui' +import { ViewType } from '@jbrowse/core/pluggableElementTypes' + +// locals +import { AlignmentFeatureWidgetModel } from './stateModelFactory' +import { getBreakpointSplitView } from './launchBreakpointSplitView' +import { getSnapshot } from 'mobx-state-tree' +import { ReducedFeature } from './getSAFeatures' + +const useStyles = makeStyles()({ + block: { + display: 'block', + }, +}) + +interface Track { + id: string + displays: { id: string; [key: string]: unknown }[] + [key: string]: unknown +} + +function stripIds(arr: Track[]) { + return arr.map(({ id, displays, ...rest }) => ({ + ...rest, + displays: displays.map(({ id, ...rest }) => rest), + })) +} + +function Checkbox2({ + checked, + label, + onChange, +}: { + checked: boolean + label: string + onChange: (event: React.ChangeEvent) => void +}) { + const { classes } = useStyles() + return ( + } + label={label} + /> + ) +} + +const BreakendOptionDialog = observer(function ({ + model, + handleClose, + f1, + f2, +}: { + model: AlignmentFeatureWidgetModel + handleClose: () => void + f1: ReducedFeature + f2: ReducedFeature + viewType: ViewType +}) { + const [copyTracks, setCopyTracks] = useState(true) + const [mirror, setMirror] = useState(true) + + return ( + + + setCopyTracks(event.target.checked)} + label="Copy tracks into the new view" + /> + setMirror(event.target.checked)} + label="Mirror tracks vertically in vertically stacked view" + /> + + + + + + + ) +}) + +export default BreakendOptionDialog diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/AlignmentsFeatureFlags.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/Flags.tsx similarity index 100% rename from plugins/alignments/src/AlignmentsFeatureDetail/AlignmentsFeatureFlags.tsx rename to plugins/alignments/src/AlignmentsFeatureDetail/Flags.tsx diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/Formatter.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/Formatter.tsx new file mode 100644 index 0000000000..9b79ea580f --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/Formatter.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react' +import copy from 'copy-to-clipboard' + +// this 'show more...' used specifically as a formatter on alignments feature +// details because long SEQ or CRAM files, even a single div full of a ton of +// data from a long read, can slow down the rest of the app +export default function Formatter({ value }: { value: unknown }) { + const [show, setShow] = useState(false) + const [copied, setCopied] = useState(false) + const display = String(value) + return display.length > 100 ? ( + <> + + +
{show ? display : `${display.slice(0, 100)}...`}
+ + ) : ( +
{display}
+ ) +} diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/LaunchBreakpointSplitViewPanel.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/LaunchBreakpointSplitViewPanel.tsx new file mode 100644 index 0000000000..22116d634a --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/LaunchBreakpointSplitViewPanel.tsx @@ -0,0 +1,100 @@ +import React, { lazy, useEffect, useState } from 'react' +import { Typography, Link, Tooltip } from '@mui/material' +import { + SimpleFeature, + SimpleFeatureSerialized, + getSession, + toLocale, +} from '@jbrowse/core/util' +import { makeStyles } from 'tss-react/mui' +import { ErrorMessage } from '@jbrowse/core/ui' +import { ViewType } from '@jbrowse/core/pluggableElementTypes' + +// locals +import { AlignmentFeatureWidgetModel } from './stateModelFactory' +import { ReducedFeature, getSAFeatures } from './getSAFeatures' + +// lazies +const BreakendOptionDialog = lazy(() => import('./BreakendOptionDialog')) + +const useStyles = makeStyles()({ + cursor: { + cursor: 'pointer', + }, +}) +export default function LaunchBreakpointSplitViewPanel({ + model, + feature, + viewType, +}: { + model: AlignmentFeatureWidgetModel + feature: SimpleFeatureSerialized + viewType: ViewType +}) { + const { classes } = useStyles() + const session = getSession(model) + const { view } = model + const [res, setRes] = useState() + const [error, setError] = useState() + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ;(async () => { + try { + const feats = await getSAFeatures({ + view, + feature: new SimpleFeature(feature), + }) + setRes(feats) + } catch (e) { + setError(e) + console.error(e) + } + })() + }, [feature, view]) + + const ret = [] as [ReducedFeature, ReducedFeature][] + if (res) { + for (let i = 0; i < res.length - 1; i++) { + ret.push([res[i], res[i + 1]] as const) + } + } + return ( + <> + {ret.length ? ( +
+ + Launch split views with breakend source and target + + {error ? : null} +
    + {ret.map((arg, index) => { + const [f1, f2] = arg + return ( +
  • + + { + event.preventDefault() + session.queueDialog(handleClose => [ + BreakendOptionDialog, + { handleClose, f1, f2, model, viewType }, + ]) + }} + > + {f1.refName}: + {toLocale(f1.strand === 1 ? f1.end : f1.start)} ->{' '} + {f2.refName}: + {toLocale(f2.strand === 1 ? f2.start : f2.end)} + + +
  • + ) + })} +
+
+ ) : null} + + ) +} diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/PairLink.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/PairLink.tsx new file mode 100644 index 0000000000..4bea71a1d4 --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/PairLink.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Link } from '@mui/material' + +// locals +import { AlignmentFeatureWidgetModel } from './stateModelFactory' +import { navToLoc } from './util' + +export default function PairLink({ + locString, + model, +}: { + locString: string + model: AlignmentFeatureWidgetModel +}) { + return ( + { + event.preventDefault() + // eslint-disable-next-line @typescript-eslint/no-floating-promises + navToLoc(locString, model) + }} + href="#" + > + {locString} + + ) +} diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/SuppAlignments.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/SuppAlignments.tsx new file mode 100644 index 0000000000..d8b1f128c0 --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/SuppAlignments.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { BaseCard } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail' +import { SimpleFeatureSerialized, getEnv, getSession } from '@jbrowse/core/util' +import { ViewType } from '@jbrowse/core/pluggableElementTypes' + +// locals +import { AlignmentFeatureWidgetModel } from './stateModelFactory' +import SuppAlignmentsLocStrings from './SuppAlignmentsLocStrings' +import LaunchBreakpointSplitViewPanel from './LaunchBreakpointSplitViewPanel' + +export default function SuppAlignments(props: { + tag: string + model: AlignmentFeatureWidgetModel + feature: SimpleFeatureSerialized +}) { + const { model, tag, feature } = props + const session = getSession(model) + const { pluginManager } = getEnv(session) + let viewType: ViewType | undefined + + try { + viewType = pluginManager.getViewType('BreakpointSplitView') + } catch (e) { + // ignore + } + + return ( + + + {viewType ? ( + + ) : null} + + ) +} diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/AlignmentsFeatureSuppAligns.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/SuppAlignmentsLocStrings.tsx similarity index 74% rename from plugins/alignments/src/AlignmentsFeatureDetail/AlignmentsFeatureSuppAligns.tsx rename to plugins/alignments/src/AlignmentsFeatureDetail/SuppAlignmentsLocStrings.tsx index 4c3d11c98a..006ea61ede 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/AlignmentsFeatureSuppAligns.tsx +++ b/plugins/alignments/src/AlignmentsFeatureDetail/SuppAlignmentsLocStrings.tsx @@ -1,17 +1,19 @@ import React from 'react' import { Typography, Link } from '@mui/material' -import { BaseCard } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail' +// locals import { getLengthOnRef } from '../MismatchParser' -import { IAnyStateTreeNode } from 'mobx-state-tree' import { navToLoc } from './util' +import { AlignmentFeatureWidgetModel } from './stateModelFactory' -export default function SupplementaryAlignments(props: { +export default function SuppAlignmentsLocStrings({ + tag, + model, +}: { tag: string - model: IAnyStateTreeNode + model: AlignmentFeatureWidgetModel }) { - const { tag, model } = props return ( - +
List of supplementary alignment locations
    {tag @@ -23,9 +25,9 @@ export default function SupplementaryAlignments(props: { const extra = Math.floor(saLength / 5) const start = +saStart const end = +saStart + saLength - const locString = `${saRef}:${Math.max(1, start - extra)}-${ - end + extra - }` + const sp = start - extra + const ep = end + extra + const locString = `${saRef}:${Math.max(1, sp)}-${ep}` const displayStart = start.toLocaleString('en-US') const displayEnd = end.toLocaleString('en-US') const displayString = `${saRef}:${displayStart}-${displayEnd} (${saStrand}) [${saLength}bp]` @@ -38,7 +40,6 @@ export default function SupplementaryAlignments(props: { // eslint-disable-next-line @typescript-eslint/no-floating-promises navToLoc(locString, model) }} - href="#" > {displayString} @@ -46,6 +47,6 @@ export default function SupplementaryAlignments(props: { ) })}
- +
) } diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/__snapshots__/index.test.tsx.snap b/plugins/alignments/src/AlignmentsFeatureDetail/__snapshots__/index.test.tsx.snap index d34f8d8c6f..57cf48360c 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/__snapshots__/index.test.tsx.snap +++ b/plugins/alignments/src/AlignmentsFeatureDetail/__snapshots__/index.test.tsx.snap @@ -1,320 +1,322 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`open up a widget 1`] = ` -
+
- - - ctgA_3_555_0:0:0_2:0... - match - -
-
-
+
- - + +
-
-
-

- Core details -

-
- Position -
+ Core details +

- ctgA:3..102 (+) -
-
-
-
- Name -
-
- - ctgA_3_555_0:0:0_2:0:0_102d - -
-
-
-
- Length -
-
- - 100 - -
-
-
-
- Type -
-
- - match - -
-
-
-

- Attributes -

-
-
- seq -
-
-
- TTGTTGCGGAGTTGAACAACGGCATTAGGAACACTTCCGTCTCTCACTTTTATACGATTATGATTGGTTCTTTAGCCTTGGTTTAGATTGGTAGTAGTAG +
+ Position +
+
+ ctgA:3..102 (+)
-
-
-
-
- score
-
- 37 +
+ Name +
+
+ + ctgA_3_555_0:0:0_2:0:0_102d +
-
-
- qual +
+ Length +
+
+ + 100 + +
- - -
- 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 1... + + match +
-
-
-
+

- MQ -

+ Attributes +

-
- 37 +
+ seq +
+
+
+ TTGTTGCGGAGTTGAACAACGGCATTAGGAACACTTCCGTCTCTCACTTTTATACGATTATGATTGGTTCTTTAGCCTTGGTTTAGATTGGTAGTAGTAG +
-
-
-
-
- CIGAR
-
- 100M +
+ score +
+
+
+ 37 +
-
-
- length_on_ref +
+ qual +
+
+ + +
+ 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 1... +
+
-
- 100 +
+ MQ +
+
+
+ 37 +
-
-
- template_length +
+ CIGAR +
+
+
+ 100M +
+
-
- 0 +
+ length_on_ref +
+
+
+ 100 +
-
-
- seq_length +
+ template_length +
+
+
+ 0 +
+
-
- 100 +
+ seq_length +
+
+
+ 100 +
-
-
-
+
+ +
diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/configSchema.ts b/plugins/alignments/src/AlignmentsFeatureDetail/configSchema.ts new file mode 100644 index 0000000000..d0cd96fcf7 --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/configSchema.ts @@ -0,0 +1,3 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' + +export const configSchema = ConfigurationSchema('AlignmentsFeatureWidget', {}) diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/getSAFeatures.ts b/plugins/alignments/src/AlignmentsFeatureDetail/getSAFeatures.ts new file mode 100644 index 0000000000..89180a8de7 --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/getSAFeatures.ts @@ -0,0 +1,69 @@ +import { Feature, getSession } from '@jbrowse/core/util' + +// locals +import { featurizeSA, getClip, getLengthSansClipping } from '../MismatchParser' +import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' +import { getTag } from '../util' + +export interface ReducedFeature { + refName: string + start: number + clipPos: number + end: number + strand: number + seqLength: number + syntenyId?: number + uniqueId: string + mate: { + refName: string + start: number + end: number + syntenyId?: number + uniqueId?: string + } +} + +export async function getSAFeatures({ + view, + feature, +}: { + view: LinearGenomeViewModel + feature: Feature +}) { + const { assemblyManager } = getSession(view) + const cigar = feature.get('CIGAR') as string + const origStrand = feature.get('strand') as number + const SA = (getTag(feature, 'SA') as string) || '' + const readName = feature.get('name') as string + const clipPos = getClip(cigar, 1) + + // get the canonical refname for the read because if the + // read.get('refName') is chr1 and the actual fasta refName is 1 then no + // tracks can be opened on the top panel of the linear read vs ref + const assembly = await assemblyManager.waitForAssembly(view.assemblyNames[0]) + if (!assembly) { + throw new Error('assembly not found') + } + + const suppAlns = featurizeSA(SA, feature.id(), origStrand, readName, true) + + const feat = feature.toJSON() + feat.clipPos = clipPos + feat.strand = 1 + + feat.mate = { + refName: readName, + start: clipPos, + end: clipPos + getLengthSansClipping(cigar), + } + const features = [feat, ...suppAlns] as ReducedFeature[] + + features.forEach((f, idx) => { + f.refName = assembly?.getCanonicalRefName(f.refName) || f.refName + f.syntenyId = idx + f.mate.syntenyId = idx + f.mate.uniqueId = `${f.uniqueId}_mate` + }) + features.sort((a, b) => a.clipPos - b.clipPos) + return features +} diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/index.test.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/index.test.tsx index 0bc088efde..fafbf1ddb8 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/index.test.tsx +++ b/plugins/alignments/src/AlignmentsFeatureDetail/index.test.tsx @@ -5,7 +5,7 @@ import PluginManager from '@jbrowse/core/PluginManager' import { ConfigurationSchema } from '@jbrowse/core/configuration' // locals -import { stateModelFactory } from '.' +import { stateModelFactory } from './stateModelFactory' import ReactComponent from './AlignmentsFeatureDetail' test('open up a widget', () => { @@ -42,6 +42,6 @@ test('open up a widget', () => { const { container, getByText } = render( , ) - expect(container.firstChild).toMatchSnapshot() + expect(container).toMatchSnapshot() expect(getByText('ctgA:3..102 (+)')).toBeTruthy() }) diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/index.ts b/plugins/alignments/src/AlignmentsFeatureDetail/index.ts index fffaf31b1b..11863eb336 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/index.ts +++ b/plugins/alignments/src/AlignmentsFeatureDetail/index.ts @@ -1,33 +1,18 @@ import { lazy } from 'react' import PluginManager from '@jbrowse/core/PluginManager' -import { ConfigurationSchema } from '@jbrowse/core/configuration' -import { types } from 'mobx-state-tree' import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType' -import { stateModelFactory as baseModelFactory } from '@jbrowse/core/BaseFeatureWidget' +import { configSchema } from './configSchema' +import { stateModelFactory } from './stateModelFactory' -const configSchema = ConfigurationSchema('AlignmentsFeatureWidget', {}) - -export function stateModelFactory(pluginManager: PluginManager) { - const baseModel = baseModelFactory(pluginManager) - return types.compose( - baseModel, - types.model('AlignmentsFeatureWidget', { - type: types.literal('AlignmentsFeatureWidget'), - }), - ) -} - -export default function register(pluginManager: PluginManager) { +export default function AlignmentFeatureDetailsF(pluginManager: PluginManager) { pluginManager.addWidgetType( () => new WidgetType({ name: 'AlignmentsFeatureWidget', heading: 'Feature details', - configSchema, + configSchema: configSchema, stateModel: stateModelFactory(pluginManager), ReactComponent: lazy(() => import('./AlignmentsFeatureDetail')), }), ) } - -export { configSchema } diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/launchBreakpointSplitView.tsx b/plugins/alignments/src/AlignmentsFeatureDetail/launchBreakpointSplitView.tsx new file mode 100644 index 0000000000..9802681227 --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/launchBreakpointSplitView.tsx @@ -0,0 +1,64 @@ +import { getSession } from '@jbrowse/core/util' +import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' + +// locals +import { ReducedFeature } from './getSAFeatures' + +export function getBreakpointSplitView({ + f1, + f2, + view, +}: { + f1: ReducedFeature + f2: ReducedFeature + view: LinearGenomeViewModel +}) { + const { assemblyName } = view.displayedRegions[0] + const { assemblyManager } = getSession(view) + const assembly = assemblyManager.get(assemblyName) + if (!assembly) { + throw new Error(`assembly ${assemblyName} not found`) + } + if (!assembly.regions) { + throw new Error(`assembly ${assemblyName} regions not loaded`) + } + const topRegion = assembly.regions.find(f => f.refName === f1.refName) + const bottomRegion = assembly.regions.find(f => f.refName === f2.refName) + + if (!topRegion || !bottomRegion) { + throw new Error( + `unable to find the refName for the top or bottom of the breakpoint view`, + ) + } + const topMarkedRegion = [{ ...topRegion }, { ...topRegion }] + const bottomMarkedRegion = [{ ...bottomRegion }, { ...bottomRegion }] + + const s = f1.strand === 1 ? f1.end : f1.start + const e = f2.strand === 1 ? f2.start : f2.end + + topMarkedRegion[0].end = s + topMarkedRegion[1].start = s + 1 + bottomMarkedRegion[0].end = e + bottomMarkedRegion[1].start = e + 1 + const bpPerPx = 10 + return { + type: 'BreakpointSplitView', + views: [ + { + type: 'LinearGenomeView', + displayedRegions: topMarkedRegion, + hideHeader: true, + bpPerPx, + offsetPx: (topRegion.start + s) / bpPerPx, + }, + { + type: 'LinearGenomeView', + displayedRegions: bottomMarkedRegion, + hideHeader: true, + bpPerPx, + offsetPx: (bottomRegion.start + e) / bpPerPx, + }, + ], + displayName: `breakend split detail`, + } +} diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/stateModelFactory.ts b/plugins/alignments/src/AlignmentsFeatureDetail/stateModelFactory.ts new file mode 100644 index 0000000000..ab41015898 --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/stateModelFactory.ts @@ -0,0 +1,19 @@ +import PluginManager from '@jbrowse/core/PluginManager' +import { Instance, types } from 'mobx-state-tree' +import { stateModelFactory as baseModelFactory } from '@jbrowse/core/BaseFeatureWidget' + +export function stateModelFactory(pluginManager: PluginManager) { + const baseModel = baseModelFactory(pluginManager) + return types.compose( + baseModel, + types.model('AlignmentsFeatureWidget', { + type: types.literal('AlignmentsFeatureWidget'), + }), + ) +} + +export type AlignmentFeatureWidgetStateModel = ReturnType< + typeof stateModelFactory +> +export type AlignmentFeatureWidgetModel = + Instance diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/tagInfo.ts b/plugins/alignments/src/AlignmentsFeatureDetail/tagInfo.ts new file mode 100644 index 0000000000..e668521114 --- /dev/null +++ b/plugins/alignments/src/AlignmentsFeatureDetail/tagInfo.ts @@ -0,0 +1,63 @@ +export const tags = { + AM: 'The smallest template-independent mapping quality in the template', + AS: 'Alignment score generated by aligner', + BC: 'Barcode sequence identifying the sample', + BQ: 'Offset to base alignment quality (BAQ)', + BZ: 'Phred quality of the unique molecular barcode bases in the {OX} tag', + CB: 'Cell identifier', + CC: 'Reference name of the next hit', + CM: 'Edit distance between the color sequence and the color reference (see also {NM})', + CO: 'Free-text comments', + CP: 'Leftmost coordinate of the next hit', + CQ: 'Color read base qualities', + CR: 'Cellular barcode sequence bases (uncorrected)', + CS: 'Color read sequence', + CT: 'Complete read annotation tag, used for consensus annotation dummy features', + CY: 'Phred quality of the cellular barcode sequence in the {CR} tag', + E2: 'The 2nd most likely base calls', + FI: 'The index of segment in the template', + FS: 'Segment suffix', + FZ: 'Flow signal intensities', + GC: 'Reserved for backwards compatibility reasons', + GQ: 'Reserved for backwards compatibility reasons', + GS: 'Reserved for backwards compatibility reasons', + H0: 'Number of perfect hits', + H1: 'Number of 1-difference hits (see also {NM})', + H2: 'Number of 2-difference hits', + HI: 'Query hit index', + IH: 'Query hit total count', + LB: 'Library', + MC: 'CIGAR string for mate/next segment', + MD: 'String encoding mismatched and deleted reference bases', + MF: 'Reserved for backwards compatibility reasons', + MI: 'Molecular identifier; a string that uniquely identifies the molecule from which the record was derived', + ML: 'Base modification probabilities', + MM: 'Base modifications / methylation ', + MQ: 'Mapping quality of the mate/next segment', + NH: 'Number of reported alignments that contain the query in the current record', + NM: 'Edit distance to the reference', + OA: 'Original alignment', + OC: 'Original CIGAR (deprecated; use {OA} instead)', + OP: 'Original mapping position (deprecated; use {OA} instead)', + OQ: 'Original base quality', + OX: 'Original unique molecular barcode bases', + PG: 'Program', + PQ: 'Phred likelihood of the template', + PT: 'Read annotations for parts of the padded read sequence', + PU: 'Platform unit', + Q2: 'Phred quality of the mate/next segment sequence in the {R2} tag', + QT: 'Phred quality of the sample barcode sequence in the {BC} tag', + QX: 'Quality score of the unique molecular identifier in the {RX} tag', + R2: 'Sequence of the mate/next segment in the template', + RG: 'Read group', + RT: 'Reserved for backwards compatibility reasons', + RX: 'Sequence bases of the (possibly corrected) unique molecular identifier', + S2: 'Reserved for backwards compatibility reasons', + SA: 'Other canonical alignments in a chimeric alignment', + SM: 'Template-independent mapping quality', + SQ: 'Reserved for backwards compatibility reasons', + TC: 'The number of segments in the template', + TS: 'Transcript strand', + U2: 'Phred probability of the 2nd call being wrong conditional on the best being wrong', + UQ: 'Phred likelihood of the segment, conditional on the mapping being correct', +} diff --git a/plugins/alignments/src/LinearSNPCoverageDisplay/components/Tooltip.tsx b/plugins/alignments/src/LinearSNPCoverageDisplay/components/Tooltip.tsx index 16e582ce24..627a078256 100644 --- a/plugins/alignments/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +++ b/plugins/alignments/src/LinearSNPCoverageDisplay/components/Tooltip.tsx @@ -1,6 +1,6 @@ import React from 'react' import { observer } from 'mobx-react' -import { Feature } from '@jbrowse/core/util' +import { Feature, toLocale } from '@jbrowse/core/util' import { Tooltip } from '@jbrowse/plugin-wiggle' type Count = Record< @@ -27,12 +27,14 @@ interface SNPInfo { '1': number } -const en = (n: number) => n.toLocaleString('en-US') const toP = (s = 0) => +(+s).toFixed(1) + const pct = (n: number, total: number) => `${toP((n / (total || 1)) * 100)}%` + interface Props { feature: Feature } + const TooltipContents = React.forwardRef( function TooltipContents2({ feature }, reactRef) { const start = feature.get('start') @@ -48,7 +50,10 @@ const TooltipContents = React.forwardRef( '0': r0, ...info } = feature.get('snpinfo') as SNPInfo - const loc = [name, start === end ? en(start) : `${en(start)}..${en(end)}`] + const loc = [ + name, + start === end ? toLocale(start) : `${toLocale(start)}..${toLocale(end)}`, + ] .filter(f => !!f) .join(':') diff --git a/plugins/variants/src/VariantFeatureWidget/BreakendOptionDialog.tsx b/plugins/variants/src/VariantFeatureWidget/BreakendOptionDialog.tsx index a5021cc363..685e8a9282 100644 --- a/plugins/variants/src/VariantFeatureWidget/BreakendOptionDialog.tsx +++ b/plugins/variants/src/VariantFeatureWidget/BreakendOptionDialog.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import React, { useState } from 'react' import { observer } from 'mobx-react' import { @@ -10,9 +9,11 @@ import { } from '@mui/material' import { makeStyles } from 'tss-react/mui' import { getSnapshot } from 'mobx-state-tree' -// jbrowse import { Dialog } from '@jbrowse/core/ui' import { getSession, Feature } from '@jbrowse/core/util' +import { ViewType } from '@jbrowse/core/pluggableElementTypes' +// locals +import { VariantFeatureWidgetModel } from './stateModelFactory' const useStyles = makeStyles()({ block: { @@ -20,43 +21,63 @@ const useStyles = makeStyles()({ }, }) +interface Track { + id: string + displays: { id: string; [key: string]: unknown }[] + [key: string]: unknown +} + +function stripIds(arr: Track[]) { + return arr.map(({ id, displays, ...rest }) => ({ + ...rest, + displays: displays.map(({ id, ...rest }) => rest), + })) +} + +function Checkbox2({ + checked, + label, + onChange, +}: { + checked: boolean + label: string + onChange: (event: React.ChangeEvent) => void +}) { + const { classes } = useStyles() + return ( + } + label={label} + /> + ) +} + const BreakendOptionDialog = observer(function ({ model, handleClose, feature, viewType, }: { - model: any + model: VariantFeatureWidgetModel handleClose: () => void feature: Feature - viewType: any + viewType: ViewType }) { - const { classes } = useStyles() const [copyTracks, setCopyTracks] = useState(true) - const [mirrorTracks, setMirrorTracks] = useState(true) + const [mirror, setMirror] = useState(true) return ( - setCopyTracks(event.target.checked)} - /> - } + setCopyTracks(event.target.checked)} label="Copy tracks into the new view" /> - - setMirrorTracks(event.target.checked)} - /> - } + setMirror(event.target.checked)} label="Mirror tracks vertically in vertically stacked view" /> @@ -66,32 +87,32 @@ const BreakendOptionDialog = observer(function ({ const { view } = model const session = getSession(model) try { + // @ts-expect-error const viewSnapshot = viewType.snapshotFromBreakendFeature( feature, view, ) - - interface Track { - trackId: string - [key: string]: unknown - } - function remapIds(arr: Track[]) { - return arr.map(v => ({ - ...v, - id: `${v.trackId}-${Math.random()}`, - })) - } - viewSnapshot.views[0].offsetPx -= view.width / 2 + 100 - viewSnapshot.views[1].offsetPx -= view.width / 2 + 100 - viewSnapshot.featureData = feature + const [view1, view2] = viewSnapshot.views // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const viewTracks = getSnapshot(view.tracks) as Track[] - viewSnapshot.views[0].tracks = remapIds(viewTracks) - viewSnapshot.views[1].tracks = remapIds( - mirrorTracks ? [...viewTracks].reverse() : viewTracks, - ) - session.addView('BreakpointSplitView', viewSnapshot) + session.addView('BreakpointSplitView', { + ...viewSnapshot, + views: [ + { + ...view1, + tracks: stripIds(viewTracks), + offsetPx: view1.offsetPx - view.width / 2 + 100, + }, + { + ...view2, + tracks: stripIds( + mirror ? [...viewTracks].reverse() : viewTracks, + ), + offsetPx: view2.offsetPx - view.width / 2 + 100, + }, + ], + }) } catch (e) { console.error(e) session.notify(`${e}`) diff --git a/plugins/variants/src/VariantFeatureWidget/BreakendPanel.tsx b/plugins/variants/src/VariantFeatureWidget/BreakendPanel.tsx index 1cfd2fa353..ac4efce128 100644 --- a/plugins/variants/src/VariantFeatureWidget/BreakendPanel.tsx +++ b/plugins/variants/src/VariantFeatureWidget/BreakendPanel.tsx @@ -1,37 +1,33 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import React, { useState } from 'react' +import React, { lazy } from 'react' import { Link, Typography } from '@mui/material' -import SimpleFeature, { +import { + getEnv, + getSession, + SimpleFeature, SimpleFeatureSerialized, -} from '@jbrowse/core/util/simpleFeature' -import { getEnv, getSession } from '@jbrowse/core/util' +} from '@jbrowse/core/util' import { BaseCard } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail' -import BreakendOptionDialog from './BreakendOptionDialog' +import { ViewType } from '@jbrowse/core/pluggableElementTypes' -export default function BreakendPanel(props: { +import { VariantFeatureWidgetModel } from './stateModelFactory' + +// lazies +const BreakendOptionDialog = lazy(() => import('./BreakendOptionDialog')) + +function LocStringList({ + locStrings, + model, +}: { locStrings: string[] - model: any - feature: SimpleFeatureSerialized + model: VariantFeatureWidgetModel }) { - const { model, locStrings, feature } = props const session = getSession(model) - const { pluginManager } = getEnv(session) - const [breakpointDialog, setBreakpointDialog] = useState(false) - let viewType - - try { - viewType = pluginManager.getViewType('BreakpointSplitView') - } catch (e) { - // ignore - } - - const simpleFeature = new SimpleFeature(feature) return ( - +
Link to linear view of breakend endpoints
    - {locStrings.map(locString => ( -
  • + {locStrings.map((locString, index) => ( +
  • { @@ -56,37 +52,76 @@ export default function BreakendPanel(props: {
  • ))}
- {viewType ? ( -
- - Launch split views with breakend source and target - -
    - {locStrings.map(locString => ( -
  • - { - event.preventDefault() - setBreakpointDialog(true) - }} - > - {`${feature.refName}:${feature.start} // ${locString} (split view)`} - -
  • - ))} -
- {breakpointDialog ? ( - { - setBreakpointDialog(false) +
+ ) +} + +function LaunchBreakpointSplitViewPanel({ + locStrings, + model, + feature, + viewType, +}: { + locStrings: string[] + model: VariantFeatureWidgetModel + feature: SimpleFeatureSerialized + viewType: ViewType +}) { + const session = getSession(model) + const simpleFeature = new SimpleFeature(feature) + return ( +
+ + Launch split views with breakend source and target + +
    + {locStrings.map(locString => ( +
  • + { + event.preventDefault() + session.queueDialog(handleClose => [ + BreakendOptionDialog, + { handleClose, model, feature: simpleFeature, viewType }, + ]) }} - /> - ) : null} -
+ > + {`${feature.refName}:${feature.start} // ${locString} (split view)`} + + + ))} + +
+ ) +} + +export default function BreakendPanel(props: { + locStrings: string[] + model: VariantFeatureWidgetModel + feature: SimpleFeatureSerialized +}) { + const { model, locStrings, feature } = props + const session = getSession(model) + const { pluginManager } = getEnv(session) + let viewType: ViewType | undefined + + try { + viewType = pluginManager.getViewType('BreakpointSplitView') + } catch (e) { + // ignore + } + + return ( + + + {viewType ? ( + ) : null} ) diff --git a/plugins/variants/src/VariantFeatureWidget/VariantFeatureWidget.test.tsx b/plugins/variants/src/VariantFeatureWidget/VariantFeatureWidget.test.tsx index fcb6d7b041..7f15efd849 100644 --- a/plugins/variants/src/VariantFeatureWidget/VariantFeatureWidget.test.tsx +++ b/plugins/variants/src/VariantFeatureWidget/VariantFeatureWidget.test.tsx @@ -5,7 +5,7 @@ import { ConfigurationSchema } from '@jbrowse/core/configuration' import PluginManager from '@jbrowse/core/PluginManager' // locals -import { stateModelFactory } from '.' +import { stateModelFactory } from './stateModelFactory' import VariantFeatureDetails from './VariantFeatureWidget' test('renders with just the required model elements', () => { @@ -38,5 +38,5 @@ test('renders with just the required model elements', () => { }) const { container } = render() - expect(container.firstChild).toMatchSnapshot() + expect(container).toMatchSnapshot() }) diff --git a/plugins/variants/src/VariantFeatureWidget/VariantFeatureWidget.tsx b/plugins/variants/src/VariantFeatureWidget/VariantFeatureWidget.tsx index 93a91d1e1f..cbf9652eb3 100644 --- a/plugins/variants/src/VariantFeatureWidget/VariantFeatureWidget.tsx +++ b/plugins/variants/src/VariantFeatureWidget/VariantFeatureWidget.tsx @@ -1,6 +1,6 @@ import React from 'react' import { observer } from 'mobx-react' -import { Divider, Paper } from '@mui/material' +import { Paper } from '@mui/material' import { FeatureDetails } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail' import { parseBreakend } from '@gmod/vcf' @@ -8,18 +8,8 @@ import { parseBreakend } from '@gmod/vcf' import VariantSampleGrid from './VariantSampleGrid' import BreakendPanel from './BreakendPanel' import VariantAnnotationTable from './VariantAnnotationTable' -import { SimpleFeatureSerialized } from '@jbrowse/core/util' - -const basicDescriptions = { - CHROM: 'chromosome: An identifier from the reference genome', - POS: 'position: The reference position, with the 1st base having position 1', - ID: 'identifier: Semi-colon separated list of unique identifiers where available', - REF: 'reference base(s): Each base must be one of A,C,G,T,N (case insensitive).', - ALT: 'alternate base(s): Comma-separated list of alternate non-reference alleles', - QUAL: 'quality: Phred-scaled quality score for the assertion made in ALT', - FILTER: - 'filter status: PASS if this position has passed all filters, otherwise a semicolon-separated list of codes for filters that fail', -} +import { VariantFeatureWidgetModel } from './stateModelFactory' +import { variantFieldDescriptions } from './variantFieldDescriptions' function AnnPanel({ descriptions, @@ -62,10 +52,7 @@ function CsqPanel({ } const VariantFeatureWidget = observer(function (props: { - model: { - featureData: SimpleFeatureSerialized - descriptions: Record - } + model: VariantFeatureWidgetModel }) { const { model } = props const { featureData, descriptions } = model @@ -76,14 +63,11 @@ const VariantFeatureWidget = observer(function (props: { - - - {feat.type === 'breakend' ? ( +
- - - rs123 - -
-
-
+
- - + +
-
-
-

- Core details -

-
-
- Position -
-
- ctgA:177 -
-
-
- Name -
+ Core details +

- - rs123 - -
-
-
-
- Length -
-
- - 1 - -
-
-
-

- Attributes -

-
-
- REF -
-
- - A - +
+ Position +
+
+ ctgA:177 +
-
-
- ALT +
+ Name +
+
+ + rs123 + +
- - <TRA> - +
+ Length +
+
+ + 1 + +
-
-
+
+

+ Attributes +

- QUAL +
+ REF +
+
+ + A + +
- - 10.4 - +
+ ALT +
+
+ + <TRA> + +
-
-
- INFO.MQ +
+ QUAL +
+
+ + 10.4 + +
- - 5 - +
+ INFO.MQ +
+
+ + 5 + +
-
-
-
+
+ +
@@ -231,14 +233,5 @@ exports[`renders with just the required model elements 1`] = `
-
-
-
`; diff --git a/plugins/variants/src/VariantFeatureWidget/configSchema.ts b/plugins/variants/src/VariantFeatureWidget/configSchema.ts new file mode 100644 index 0000000000..70f00d578f --- /dev/null +++ b/plugins/variants/src/VariantFeatureWidget/configSchema.ts @@ -0,0 +1,3 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' + +export const configSchema = ConfigurationSchema('VariantFeatureWidget', {}) diff --git a/plugins/variants/src/VariantFeatureWidget/index.ts b/plugins/variants/src/VariantFeatureWidget/index.ts index e2f4b8b1e6..7d64f8ebf1 100644 --- a/plugins/variants/src/VariantFeatureWidget/index.ts +++ b/plugins/variants/src/VariantFeatureWidget/index.ts @@ -1,22 +1,8 @@ import { lazy } from 'react' -import { ConfigurationSchema } from '@jbrowse/core/configuration' import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType' import PluginManager from '@jbrowse/core/PluginManager' -import { types } from 'mobx-state-tree' -import { stateModelFactory as baseModelFactory } from '@jbrowse/core/BaseFeatureWidget' - -export const configSchema = ConfigurationSchema('VariantFeatureWidget', {}) - -export function stateModelFactory(pluginManager: PluginManager) { - const baseModel = baseModelFactory(pluginManager) - return types.compose( - baseModel, - types.model('VariantFeatureWidget', { - type: types.literal('VariantFeatureWidget'), - descriptions: types.frozen(), - }), - ) -} +import { stateModelFactory } from './stateModelFactory' +import { configSchema } from './configSchema' export default (pluginManager: PluginManager) => { pluginManager.addWidgetType( @@ -24,7 +10,7 @@ export default (pluginManager: PluginManager) => { new WidgetType({ name: 'VariantFeatureWidget', heading: 'Feature details', - configSchema, + configSchema: configSchema, stateModel: stateModelFactory(pluginManager), ReactComponent: lazy(() => import('./VariantFeatureWidget')), }), diff --git a/plugins/variants/src/VariantFeatureWidget/stateModelFactory.ts b/plugins/variants/src/VariantFeatureWidget/stateModelFactory.ts new file mode 100644 index 0000000000..2ae01c8e24 --- /dev/null +++ b/plugins/variants/src/VariantFeatureWidget/stateModelFactory.ts @@ -0,0 +1,19 @@ +import PluginManager from '@jbrowse/core/PluginManager' +import { Instance, types } from 'mobx-state-tree' +import { stateModelFactory as baseModelFactory } from '@jbrowse/core/BaseFeatureWidget' + +export function stateModelFactory(pluginManager: PluginManager) { + const baseModel = baseModelFactory(pluginManager) + return types.compose( + baseModel, + types.model('VariantFeatureWidget', { + type: types.literal('VariantFeatureWidget'), + descriptions: types.frozen(), + }), + ) +} + +export type VariantFeatureWidgetStateModel = ReturnType< + typeof stateModelFactory +> +export type VariantFeatureWidgetModel = Instance diff --git a/plugins/variants/src/VariantFeatureWidget/variantFieldDescriptions.ts b/plugins/variants/src/VariantFeatureWidget/variantFieldDescriptions.ts new file mode 100644 index 0000000000..0b38539885 --- /dev/null +++ b/plugins/variants/src/VariantFeatureWidget/variantFieldDescriptions.ts @@ -0,0 +1,10 @@ +export const variantFieldDescriptions = { + CHROM: 'chromosome: An identifier from the reference genome', + POS: 'position: The reference position, with the 1st base having position 1', + ID: 'identifier: Semi-colon separated list of unique identifiers where available', + REF: 'reference base(s): Each base must be one of A,C,G,T,N (case insensitive).', + ALT: 'alternate base(s): Comma-separated list of alternate non-reference alleles', + QUAL: 'quality: Phred-scaled quality score for the assertion made in ALT', + FILTER: + 'filter status: PASS if this position has passed all filters, otherwise a semicolon-separated list of codes for filters that fail', +} diff --git a/test_data/config_demo.json b/test_data/config_demo.json index 27a25cd365..9688be84b7 100644 --- a/test_data/config_demo.json +++ b/test_data/config_demo.json @@ -1342,6 +1342,41 @@ } } }, + { + "type": "VariantTrack", + "trackId": "breast_cancer_sniffles_hg19_traonly", + "name": "SKBR3 pacbio (sniffles VCF, TRA only)", + "adapter": { + "type": "VcfAdapter", + "vcfLocation": { + "uri": "https://jbrowse.org/genomes/hg19/SKBR3/reads_lr_skbr3.fa_ngmlr-0.2.3_mapped.bam.sniffles1kb_auto_l8_s5_noalt.filtered.vcf", + "locationType": "UriLocation" + } + }, + "category": ["SKBR3"], + "assemblyNames": ["hg19"] + }, + { + "type": "VariantTrack", + "trackId": "breast_cancer_sniffles_hg19_traonly_tabix", + "name": "SKBR3 pacbio (sniffles VCF, TRA only tabix)", + "adapter": { + "type": "VcfTabixAdapter", + "vcfGzLocation": { + "uri": "https://jbrowse.org/genomes/hg19/SKBR3/reads_lr_skbr3.fa_ngmlr-0.2.3_mapped.bam.sniffles1kb_auto_l8_s5_noalt.filtered.vcf.gz", + "locationType": "UriLocation" + }, + "index": { + "location": { + "uri": "https://jbrowse.org/genomes/hg19/SKBR3/reads_lr_skbr3.fa_ngmlr-0.2.3_mapped.bam.sniffles1kb_auto_l8_s5_noalt.filtered.vcf.gz.tbi", + "locationType": "UriLocation" + }, + "indexType": "TBI" + } + }, + "category": ["SKBR3"], + "assemblyNames": ["hg19"] + }, { "type": "VariantTrack", "trackId": "1000genomes_indels", diff --git a/yarn.lock b/yarn.lock index 84c0c104e7..a7fb7c90e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3259,7 +3259,7 @@ wordwrap "^1.0.0" wrap-ansi "^7.0.0" -"@oclif/core@^3.18.2", "@oclif/core@^3.19.1", "@oclif/core@^3.19.2": +"@oclif/core@^3.18.2", "@oclif/core@^3.19.2": version "3.19.2" resolved "https://registry.yarnpkg.com/@oclif/core/-/core-3.19.2.tgz#1672061e7309f0048f9f9991607b303a9e5082fc" integrity sha512-/kUZZQZO92esToSTn+fnfMQdAj8uDJ/lsMNXBRBCiLQpwnIskHXy8JzYpynxxkzY2wbGREBceQhxCyDV0XpY4g== @@ -14548,13 +14548,13 @@ obuf@^1.0.0, obuf@^1.1.2: integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== oclif@^4.0.0: - version "4.4.15" - resolved "https://registry.yarnpkg.com/oclif/-/oclif-4.4.15.tgz#497debe9acb9cacd8c0c59a5beb1cfc7ef7ef987" - integrity sha512-L2X3I+iTWpuMTzr1McDf2k8YwS57P+C9Yeqvo2jzXZ/sotkhoNW0RwDO4nZFqHvyn6I9Dj4Q8OVGVa85c6M0EQ== + version "4.4.16" + resolved "https://registry.yarnpkg.com/oclif/-/oclif-4.4.16.tgz#30384a03e20b7294a048a5f36ed7a755e988c0fd" + integrity sha512-4qNgN8ZhLZV2k92HXBiIvFGDM2HKgXrB8auVe9EpNQPlZr/PXeW7CTYpkEm3VYrmtohe2p3ma5jIR/Nyuu/v6g== dependencies: "@aws-sdk/client-cloudfront" "^3.511.0" "@aws-sdk/client-s3" "^3.515.0" - "@oclif/core" "^3.19.1" + "@oclif/core" "^3.19.2" "@oclif/plugin-help" "^6.0.12" "@oclif/plugin-not-found" "^3.0.10" "@oclif/plugin-warn-if-update-available" "^3.0.10"