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.
Files changed (28) hide show
  1. package/dist/ProteinView/components/HeaderStructureRow.js +10 -1
  2. package/dist/ProteinView/components/PlddtLegend.js +21 -0
  3. package/dist/ProteinView/components/ProteinAlignment.js +6 -3
  4. package/dist/ProteinView/components/ProteinViewHeader.js +2 -6
  5. package/dist/ProteinView/constants.js +15 -1
  6. package/dist/ProteinView/hooks/useProteinFeatureTrackData.js +3 -3
  7. package/dist/ProteinView/model.js +37 -0
  8. package/dist/ProteinView/storedSettings.js +1 -0
  9. package/dist/ProteinView/structureModel.js +14 -1
  10. package/dist/jbrowse-plugin-protein3d.umd.production.min.js +15 -15
  11. package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +4 -4
  12. package/dist/version.js +1 -1
  13. package/package.json +1 -1
  14. package/src/ProteinView/components/HeaderStructureRow.tsx +27 -0
  15. package/src/ProteinView/components/PlddtLegend.tsx +41 -0
  16. package/src/ProteinView/components/ProteinAlignment.tsx +6 -10
  17. package/src/ProteinView/components/ProteinViewHeader.tsx +7 -18
  18. package/src/ProteinView/constants.ts +16 -1
  19. package/src/ProteinView/hooks/useProteinFeatureTrackData.ts +3 -3
  20. package/src/ProteinView/model.test.ts +48 -0
  21. package/src/ProteinView/model.ts +39 -0
  22. package/src/ProteinView/proteinViewSpec.ts +1 -0
  23. package/src/ProteinView/storedSettings.ts +1 -0
  24. package/src/ProteinView/structureModel.ts +15 -0
  25. package/src/ProteinView/withStoredSettings.test.ts +1 -0
  26. package/src/version.ts +1 -1
  27. package/dist/ProteinView/components/TrackLegend.js +0 -33
  28. 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
- return (React.createElement("div", { "data-testid": "structure-row", "data-label": label, style: { display: 'flex', alignItems: 'center', gap: 8, minHeight: 24 } },
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, hydrophobicityCells, } = model;
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 ? (React.createElement(TrackLegend, { featureTypes: featureData?.visibleGroups.map(group => group.type) ?? [], showConfidence: confidenceCells.length > 0 })) : null));
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 { structures, showAlignment } = model;
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 = 50;
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, hiddenFeatureTypes, structurePositionToAlignmentMap, } = model;
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 (!hiddenFeatureTypes.has(feature.type)) {
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
- hiddenFeatureTypes,
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',
@@ -13,6 +13,7 @@ export const PERSISTED_SETTINGS = [
13
13
  'showControls',
14
14
  'autoScrollAlignment',
15
15
  'compactTracks',
16
+ 'showAllFeatureTracks',
16
17
  ];
17
18
  /**
18
19
  * A stored preference fills in only what the snapshot leaves unsaid. Comparing
@@ -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