jbrowse-plugin-protein3d 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ProteinView/components/HeaderStructureRow.js +10 -1
- package/dist/ProteinView/components/PlddtLegend.js +21 -0
- package/dist/ProteinView/components/ProteinAlignment.js +6 -3
- package/dist/ProteinView/components/ProteinViewHeader.js +2 -6
- package/dist/ProteinView/constants.js +15 -1
- package/dist/ProteinView/hooks/useProteinFeatureTrackData.js +3 -3
- package/dist/ProteinView/model.js +37 -0
- package/dist/ProteinView/storedSettings.js +1 -0
- package/dist/ProteinView/structureModel.js +14 -1
- package/dist/jbrowse-plugin-protein3d.umd.production.min.js +15 -15
- package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +4 -4
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/ProteinView/components/HeaderStructureRow.tsx +27 -0
- package/src/ProteinView/components/PlddtLegend.tsx +41 -0
- package/src/ProteinView/components/ProteinAlignment.tsx +6 -10
- package/src/ProteinView/components/ProteinViewHeader.tsx +7 -18
- package/src/ProteinView/constants.ts +16 -1
- package/src/ProteinView/hooks/useProteinFeatureTrackData.ts +3 -3
- package/src/ProteinView/model.test.ts +48 -0
- package/src/ProteinView/model.ts +39 -0
- package/src/ProteinView/proteinViewSpec.ts +1 -0
- package/src/ProteinView/storedSettings.ts +1 -0
- package/src/ProteinView/structureModel.ts +15 -0
- package/src/ProteinView/withStoredSettings.test.ts +1 -0
- package/src/version.ts +1 -1
- package/dist/ProteinView/components/TrackLegend.js +0 -33
- package/src/ProteinView/components/TrackLegend.tsx +0 -68
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
2
3
|
import CloseIcon from '@mui/icons-material/Close';
|
|
4
|
+
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
3
5
|
import Chip from '@mui/material/Chip';
|
|
4
6
|
import IconButton from '@mui/material/IconButton';
|
|
5
7
|
import Tooltip from '@mui/material/Tooltip';
|
|
@@ -10,7 +12,14 @@ const LOW_SIMILARITY_EXPLANATION = `Under ${Math.round(LOW_IDENTITY_OVER_SHORTER
|
|
|
10
12
|
const StructureRow = observer(function StructureRow({ model, structure, }) {
|
|
11
13
|
const { label, alignmentQuality: quality, statusMessage } = structure;
|
|
12
14
|
const coveredRange = quality ? describeCoveredRange(quality) : undefined;
|
|
13
|
-
|
|
15
|
+
const switchable = model.showAlignment && model.structures.length > 1;
|
|
16
|
+
const open = model.alignmentStructure === structure;
|
|
17
|
+
return (React.createElement("div", { "data-testid": "structure-row", "data-label": label, "data-open": switchable ? open : undefined, style: { display: 'flex', alignItems: 'center', gap: 8, minHeight: 24 } },
|
|
18
|
+
switchable ? (React.createElement(Tooltip, { title: open ? 'Alignment shown below' : 'Show alignment' },
|
|
19
|
+
React.createElement("span", null,
|
|
20
|
+
React.createElement(IconButton, { size: "small", "aria-label": `Show ${label} alignment`, "aria-expanded": open, disabled: open, onClick: () => {
|
|
21
|
+
model.openAlignmentOf(structure);
|
|
22
|
+
}, sx: { p: 0 } }, open ? (React.createElement(ExpandMoreIcon, { fontSize: "small" })) : (React.createElement(ChevronRightIcon, { fontSize: "small" })))))) : null,
|
|
14
23
|
React.createElement(Typography, { variant: "caption", sx: { fontWeight: 'bold' } }, label),
|
|
15
24
|
statusMessage ? (React.createElement(Typography, { variant: "caption", color: "error", "data-testid": "structure-status" }, statusMessage)) : null,
|
|
16
25
|
quality ? (React.createElement(Typography, { variant: "caption", color: "textSecondary", "data-testid": "header-alignment-quality" },
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Typography from '@mui/material/Typography';
|
|
3
|
+
import { PLDDT_BINS, plddtColor } from '../residueTracks';
|
|
4
|
+
export default function PlddtLegend() {
|
|
5
|
+
return (React.createElement(Typography, { variant: "caption", color: "textSecondary", component: "div", "data-testid": "track-legend", style: {
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexWrap: 'wrap',
|
|
8
|
+
gap: 6,
|
|
9
|
+
fontSize: 9,
|
|
10
|
+
paddingLeft: 8,
|
|
11
|
+
} },
|
|
12
|
+
"pLDDT:",
|
|
13
|
+
PLDDT_BINS.map(bin => (React.createElement("span", { key: bin.label, style: { display: 'inline-flex', alignItems: 'center', gap: 3 } },
|
|
14
|
+
React.createElement("span", { style: {
|
|
15
|
+
width: 8,
|
|
16
|
+
height: 8,
|
|
17
|
+
background: plddtColor(bin.score),
|
|
18
|
+
border: '1px solid rgba(0,0,0,0.3)',
|
|
19
|
+
} }),
|
|
20
|
+
bin.label)))));
|
|
21
|
+
}
|
|
@@ -7,11 +7,11 @@ import { makeStyles } from 'tss-react/mui';
|
|
|
7
7
|
import AlignmentRuler from './AlignmentRuler';
|
|
8
8
|
import ChainSelect from './ChainSelect';
|
|
9
9
|
import HoverMarker from './HoverMarker';
|
|
10
|
+
import PlddtLegend from './PlddtLegend';
|
|
10
11
|
import ProteinAlignmentHelpButton from './ProteinAlignmentHelpButton';
|
|
11
12
|
import { ProteinFeatureTrackContent, ProteinFeatureTrackLabels, } from './ProteinFeatureTrack';
|
|
12
13
|
import ResidueValueTrack from './ResidueValueTrack';
|
|
13
14
|
import SplitString, { AlignmentHighlights } from './SplitString';
|
|
14
|
-
import TrackLegend from './TrackLegend';
|
|
15
15
|
import ExternalLink from '../../components/ExternalLink';
|
|
16
16
|
import { largeJumpScrollTarget, offScreenCenterTarget } from '../autoScroll';
|
|
17
17
|
import { CHAR_WIDTH, LABEL_WIDTH, ROW_HEIGHT } from '../constants';
|
|
@@ -66,7 +66,10 @@ function GutterLabel({ label, title, height, }) {
|
|
|
66
66
|
} }, label)));
|
|
67
67
|
}
|
|
68
68
|
const ProteinAlignment = observer(function ProteinAlignment({ model, }) {
|
|
69
|
-
const { alignment: pairwiseAlignment, alignmentQuality: quality, showHighlight, showProteinTracks, label, confidenceCells,
|
|
69
|
+
const { alignment: pairwiseAlignment, alignmentQuality: quality, showHighlight, showProteinTracks, showAllFeatureTracks, label, confidenceCells, } = model;
|
|
70
|
+
const hydrophobicityCells = showAllFeatureTracks
|
|
71
|
+
? model.hydrophobicityCells
|
|
72
|
+
: [];
|
|
70
73
|
const { classes } = useStyles();
|
|
71
74
|
const containerRef = useRef(null);
|
|
72
75
|
const lastScrolledSelectionRef = useRef(undefined);
|
|
@@ -206,6 +209,6 @@ const ProteinAlignment = observer(function ProteinAlignment({ model, }) {
|
|
|
206
209
|
confidenceCells.length > 0 ? (React.createElement(ResidueValueTrack, { cells: confidenceCells, colorFor: plddtColor, formatValue: v => `pLDDT ${v.toFixed(0)}`, sequenceLength: a0.length, model: model })) : null,
|
|
207
210
|
hydrophobicityCells.length > 0 ? (React.createElement(ResidueValueTrack, { cells: hydrophobicityCells, colorFor: hydrophobicityColor, formatValue: v => `Kyte-Doolittle ${v.toFixed(1)}`, sequenceLength: a0.length, model: model })) : null,
|
|
208
211
|
React.createElement(HoverMarker, { model: model }))) : null)),
|
|
209
|
-
showProteinTracks
|
|
212
|
+
showProteinTracks && confidenceCells.length > 0 ? React.createElement(PlddtLegend, null) : null));
|
|
210
213
|
});
|
|
211
214
|
export default ProteinAlignment;
|
|
@@ -51,7 +51,7 @@ const DisplaySettingsMenu = observer(function DisplaySettingsMenu({ model, }) {
|
|
|
51
51
|
model.behaviorToggles.map(toggle => (React.createElement(ToggleMenuItem, { key: toggle.label, checked: toggle.checked, label: toggle.label, onToggle: toggle.toggle }))))));
|
|
52
52
|
});
|
|
53
53
|
const ProteinViewHeader = observer(function ProteinViewHeader({ model, }) {
|
|
54
|
-
const {
|
|
54
|
+
const { alignmentStructure, showAlignment } = model;
|
|
55
55
|
return (React.createElement("div", null,
|
|
56
56
|
React.createElement("div", { style: {
|
|
57
57
|
display: 'flex',
|
|
@@ -68,11 +68,7 @@ const ProteinViewHeader = observer(function ProteinViewHeader({ model, }) {
|
|
|
68
68
|
React.createElement(ColorSchemeSelect, { model: model }),
|
|
69
69
|
React.createElement(DisplaySettingsMenu, { model: model }))),
|
|
70
70
|
React.createElement(HeaderStructureRows, { model: model }),
|
|
71
|
-
showAlignment
|
|
72
|
-
? structures
|
|
73
|
-
.filter(s => s.pairwiseAlignment || s.alignmentPending)
|
|
74
|
-
.map((structure, idx) => (React.createElement("div", { key: idx }, structure.pairwiseAlignment ? (React.createElement(ProteinAlignment, { model: structure })) : (React.createElement(LoadingEllipses, { message: "Loading pairwise alignment" })))))
|
|
75
|
-
: null,
|
|
71
|
+
showAlignment && alignmentStructure?.pairwiseAlignment ? (React.createElement(ProteinAlignment, { model: alignmentStructure })) : showAlignment && alignmentStructure?.alignmentPending ? (React.createElement(LoadingEllipses, { message: "Loading pairwise alignment" })) : null,
|
|
76
72
|
React.createElement(AddStructureDialog, { model: model })));
|
|
77
73
|
});
|
|
78
74
|
export default ProteinViewHeader;
|
|
@@ -4,7 +4,21 @@ export const COMPACT_TRACK_HEIGHT = 8;
|
|
|
4
4
|
export const COMPACT_TRACK_GAP = 1;
|
|
5
5
|
export const NORMAL_TRACK_HEIGHT = 12;
|
|
6
6
|
export const NORMAL_TRACK_GAP = 2;
|
|
7
|
-
export const LABEL_WIDTH =
|
|
7
|
+
export const LABEL_WIDTH = 124;
|
|
8
|
+
export const MINOR_FEATURE_TYPES = new Set([
|
|
9
|
+
'Chain',
|
|
10
|
+
'Helix',
|
|
11
|
+
'Turn',
|
|
12
|
+
'Beta strand',
|
|
13
|
+
'Compositional bias',
|
|
14
|
+
'Modified residue',
|
|
15
|
+
'Glycosylation',
|
|
16
|
+
'Lipidation',
|
|
17
|
+
'Cross-link',
|
|
18
|
+
'Alternative sequence',
|
|
19
|
+
'Sequence conflict',
|
|
20
|
+
'Initiator methionine',
|
|
21
|
+
]);
|
|
8
22
|
export const HOVER_MARKER_COLOR = 'rgba(255, 105, 180, 0.5)';
|
|
9
23
|
export const SELECTED_BORDER = '2px solid #333';
|
|
10
24
|
export const HOVERED_BORDER = '1px solid black';
|
|
@@ -58,14 +58,14 @@ export function packLanes(layouts) {
|
|
|
58
58
|
}
|
|
59
59
|
export default function useProteinFeatureTrackData(model, uniprotId, mapUniProtPosition) {
|
|
60
60
|
const { features, isLoading, error } = useUniProtFeatures(uniprotId);
|
|
61
|
-
const { alignment: pairwiseAlignment,
|
|
61
|
+
const { alignment: pairwiseAlignment, omittedFeatureTypes, structurePositionToAlignmentMap, } = model;
|
|
62
62
|
const data = useMemo(() => {
|
|
63
63
|
if (!features || !pairwiseAlignment || !structurePositionToAlignmentMap) {
|
|
64
64
|
return undefined;
|
|
65
65
|
}
|
|
66
66
|
const groups = new Map();
|
|
67
67
|
for (const feature of features) {
|
|
68
|
-
if (!
|
|
68
|
+
if (!omittedFeatureTypes.has(feature.type)) {
|
|
69
69
|
const layout = layoutFeature(feature, structurePositionToAlignmentMap, mapUniProtPosition);
|
|
70
70
|
if (layout) {
|
|
71
71
|
const list = groups.get(feature.type);
|
|
@@ -90,7 +90,7 @@ export default function useProteinFeatureTrackData(model, uniprotId, mapUniProtP
|
|
|
90
90
|
}, [
|
|
91
91
|
features,
|
|
92
92
|
pairwiseAlignment,
|
|
93
|
-
|
|
93
|
+
omittedFeatureTypes,
|
|
94
94
|
structurePositionToAlignmentMap,
|
|
95
95
|
mapUniProtPosition,
|
|
96
96
|
]);
|
|
@@ -79,6 +79,18 @@ function stateModelFactory() {
|
|
|
79
79
|
* render the feature/residue tracks at reduced height
|
|
80
80
|
*/
|
|
81
81
|
compactTracks: true,
|
|
82
|
+
/**
|
|
83
|
+
* #property
|
|
84
|
+
* also draw the feature types in MINOR_FEATURE_TYPES and the
|
|
85
|
+
* hydrophobicity track
|
|
86
|
+
*/
|
|
87
|
+
showAllFeatureTracks: false,
|
|
88
|
+
/**
|
|
89
|
+
* #property
|
|
90
|
+
* which structure's alignment panel is open; unset opens the seeded
|
|
91
|
+
* structure, else the first
|
|
92
|
+
*/
|
|
93
|
+
alignmentStructureIndex: types.maybe(types.number),
|
|
82
94
|
/**
|
|
83
95
|
* #property
|
|
84
96
|
*/
|
|
@@ -199,6 +211,12 @@ function stateModelFactory() {
|
|
|
199
211
|
structure.setSelectedFeatureId(undefined);
|
|
200
212
|
}
|
|
201
213
|
},
|
|
214
|
+
/**
|
|
215
|
+
* #action
|
|
216
|
+
*/
|
|
217
|
+
openAlignmentOf(structure) {
|
|
218
|
+
self.alignmentStructureIndex = self.structures.indexOf(structure);
|
|
219
|
+
},
|
|
202
220
|
}))
|
|
203
221
|
.actions(self => ({
|
|
204
222
|
/**
|
|
@@ -215,6 +233,12 @@ function stateModelFactory() {
|
|
|
215
233
|
removeStructure(structure) {
|
|
216
234
|
const plugin = self.molstarPluginContext;
|
|
217
235
|
const molstarStructure = structure.molstarStructure;
|
|
236
|
+
const removed = self.structures.indexOf(structure);
|
|
237
|
+
const open = self.alignmentStructureIndex;
|
|
238
|
+
if (open !== undefined) {
|
|
239
|
+
self.alignmentStructureIndex =
|
|
240
|
+
removed === open ? undefined : removed < open ? open - 1 : open;
|
|
241
|
+
}
|
|
218
242
|
self.structures.remove(structure);
|
|
219
243
|
self.setSuperposedCount(0);
|
|
220
244
|
if (plugin) {
|
|
@@ -311,6 +335,18 @@ function stateModelFactory() {
|
|
|
311
335
|
? []
|
|
312
336
|
: [{ id: getPath(s), message: s.loadingMessage }]);
|
|
313
337
|
},
|
|
338
|
+
/**
|
|
339
|
+
* #getter
|
|
340
|
+
* The one structure whose alignment panel is open.
|
|
341
|
+
*/
|
|
342
|
+
get alignmentStructure() {
|
|
343
|
+
const { structures, alignmentStructureIndex } = self;
|
|
344
|
+
return ((alignmentStructureIndex === undefined
|
|
345
|
+
? undefined
|
|
346
|
+
: structures[alignmentStructureIndex]) ??
|
|
347
|
+
structures.find(s => s.seededSelection) ??
|
|
348
|
+
structures[0]);
|
|
349
|
+
},
|
|
314
350
|
/**
|
|
315
351
|
* #getter
|
|
316
352
|
* What the header's Tune menu offers: the layout choices, remembered for
|
|
@@ -321,6 +357,7 @@ function stateModelFactory() {
|
|
|
321
357
|
return [
|
|
322
358
|
['showAlignment', 'Show alignment'],
|
|
323
359
|
['showProteinTracks', 'Show feature tracks'],
|
|
360
|
+
['showAllFeatureTracks', 'Show all feature tracks'],
|
|
324
361
|
['compactTracks', 'Compact tracks'],
|
|
325
362
|
[
|
|
326
363
|
'autoScrollAlignment',
|
|
@@ -4,7 +4,7 @@ import { autorun, when } from 'mobx';
|
|
|
4
4
|
import { alignTranscriptToEntity, alignmentCol, alignmentQuality, chooseMappedEntity, codonGenomeSpan, entityLabel, fetchUniProtStructureMappings, fusionPartnerPositions, getPdbIdFromUrl, getUniprotIdFromAlphaFoldTarget, interactionMatchesMappedEntity, looksLikePlddt, makeCoordinateMapper, makeLabelSeqIdIndex, mappedStructureIdentity, rangeToLabelSeqIds, residueNumber, residueRangeToPositions, resolveStructureUrl, stripStopCodon, structureDisplayLabel, structurePos, toLabelSeqIds, transcriptRangeToStructureRange, unmapStructurePositions, } from 'p2s_mapper';
|
|
5
5
|
import { attachStructureInteractions } from './attachStructureInteractions';
|
|
6
6
|
import { connectedHoverTranscriptPos } from './connectedHover';
|
|
7
|
-
import { COMPACT_TRACK_GAP, COMPACT_TRACK_HEIGHT, NORMAL_TRACK_GAP, NORMAL_TRACK_HEIGHT, } from './constants';
|
|
7
|
+
import { COMPACT_TRACK_GAP, COMPACT_TRACK_HEIGHT, MINOR_FEATURE_TYPES, NORMAL_TRACK_GAP, NORMAL_TRACK_HEIGHT, } from './constants';
|
|
8
8
|
import { proteinAbbreviationMapping } from './proteinAbbreviationMapping';
|
|
9
9
|
import { clickProteinToGenome, structureRangeToGenomeRegions, } from './proteinToGenomeMapping';
|
|
10
10
|
import { kyteDoolittleScores, mapResidueValuesToColumns } from './residueTracks';
|
|
@@ -870,6 +870,19 @@ const Structure = types
|
|
|
870
870
|
get showProteinTracks() {
|
|
871
871
|
return this.parentView.showProteinTracks;
|
|
872
872
|
},
|
|
873
|
+
get showAllFeatureTracks() {
|
|
874
|
+
return this.parentView.showAllFeatureTracks;
|
|
875
|
+
},
|
|
876
|
+
/**
|
|
877
|
+
* #getter
|
|
878
|
+
* The feature types left undrawn: the ones hidden by hand, and the minor
|
|
879
|
+
* ones unless every track is shown.
|
|
880
|
+
*/
|
|
881
|
+
get omittedFeatureTypes() {
|
|
882
|
+
return this.showAllFeatureTracks
|
|
883
|
+
? self.hiddenFeatureTypes
|
|
884
|
+
: new Set([...MINOR_FEATURE_TYPES, ...self.hiddenFeatureTypes]);
|
|
885
|
+
},
|
|
873
886
|
get trackHeight() {
|
|
874
887
|
return this.parentView.compactTracks
|
|
875
888
|
? COMPACT_TRACK_HEIGHT
|