@jbrowse/plugin-linear-genome-view 1.7.9 → 2.0.0
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/BaseLinearDisplay/components/BaseLinearDisplay.d.ts +1 -5
- package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js +118 -226
- package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js.map +1 -0
- package/dist/BaseLinearDisplay/components/Block.js +53 -74
- package/dist/BaseLinearDisplay/components/Block.js.map +1 -0
- package/dist/BaseLinearDisplay/components/LinearBlocks.d.ts +11 -1
- package/dist/BaseLinearDisplay/components/LinearBlocks.js +64 -103
- package/dist/BaseLinearDisplay/components/LinearBlocks.js.map +1 -0
- package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +145 -175
- package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -0
- package/dist/BaseLinearDisplay/components/Tooltip.d.ts +8 -0
- package/dist/BaseLinearDisplay/components/Tooltip.js +118 -0
- package/dist/BaseLinearDisplay/components/Tooltip.js.map +1 -0
- package/dist/BaseLinearDisplay/index.js +13 -40
- package/dist/BaseLinearDisplay/index.js.map +1 -0
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +20 -15
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +605 -683
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -0
- package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +15 -22
- package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -0
- package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +7 -8
- package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js +266 -312
- package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -0
- package/dist/LinearBareDisplay/configSchema.js +11 -17
- package/dist/LinearBareDisplay/configSchema.js.map +1 -0
- package/dist/LinearBareDisplay/index.js +7 -20
- package/dist/LinearBareDisplay/index.js.map +1 -0
- package/dist/LinearBareDisplay/model.d.ts +16 -14
- package/dist/LinearBareDisplay/model.js +36 -42
- package/dist/LinearBareDisplay/model.js.map +1 -0
- package/dist/LinearBasicDisplay/components/SetMaxHeight.d.ts +1 -1
- package/dist/LinearBasicDisplay/components/SetMaxHeight.js +76 -85
- package/dist/LinearBasicDisplay/components/SetMaxHeight.js.map +1 -0
- package/dist/LinearBasicDisplay/configSchema.js +15 -23
- package/dist/LinearBasicDisplay/configSchema.js.map +1 -0
- package/dist/LinearBasicDisplay/index.js +10 -22
- package/dist/LinearBasicDisplay/index.js.map +1 -0
- package/dist/LinearBasicDisplay/model.d.ts +20 -15
- package/dist/LinearBasicDisplay/model.js +180 -159
- package/dist/LinearBasicDisplay/model.js.map +1 -0
- package/dist/LinearGenomeView/components/CenterLine.d.ts +2 -8
- package/dist/LinearGenomeView/components/CenterLine.js +60 -74
- package/dist/LinearGenomeView/components/CenterLine.js.map +1 -0
- package/dist/LinearGenomeView/components/ExportSvgDialog.js +141 -141
- package/dist/LinearGenomeView/components/ExportSvgDialog.js.map +1 -0
- package/dist/LinearGenomeView/components/Header.js +70 -123
- package/dist/LinearGenomeView/components/Header.js.map +1 -0
- package/dist/LinearGenomeView/components/HelpDialog.d.ts +0 -1
- package/dist/LinearGenomeView/components/HelpDialog.js +62 -45
- package/dist/LinearGenomeView/components/HelpDialog.js.map +1 -0
- package/dist/LinearGenomeView/components/ImportForm.js +223 -320
- package/dist/LinearGenomeView/components/ImportForm.js.map +1 -0
- package/dist/LinearGenomeView/components/LinearGenomeView.js +64 -120
- package/dist/LinearGenomeView/components/LinearGenomeView.js.map +1 -0
- package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js +198 -337
- package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -0
- package/dist/LinearGenomeView/components/MiniControls.js +64 -78
- package/dist/LinearGenomeView/components/MiniControls.js.map +1 -0
- package/dist/LinearGenomeView/components/OverviewRubberBand.js +226 -293
- package/dist/LinearGenomeView/components/OverviewRubberBand.js.map +1 -0
- package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +24 -8
- package/dist/LinearGenomeView/components/OverviewScaleBar.js +277 -365
- package/dist/LinearGenomeView/components/OverviewScaleBar.js.map +1 -0
- package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +1 -1
- package/dist/LinearGenomeView/components/RefNameAutocomplete.js +237 -324
- package/dist/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -0
- package/dist/LinearGenomeView/components/RubberBand.js +229 -296
- package/dist/LinearGenomeView/components/RubberBand.js.map +1 -0
- package/dist/LinearGenomeView/components/Ruler.js +45 -90
- package/dist/LinearGenomeView/components/Ruler.js.map +1 -0
- package/dist/LinearGenomeView/components/ScaleBar.d.ts +8 -399
- package/dist/LinearGenomeView/components/ScaleBar.js +121 -167
- package/dist/LinearGenomeView/components/ScaleBar.js.map +1 -0
- package/dist/LinearGenomeView/components/SearchBox.js +158 -205
- package/dist/LinearGenomeView/components/SearchBox.js.map +1 -0
- package/dist/LinearGenomeView/components/SearchResultsDialog.d.ts +0 -1
- package/dist/LinearGenomeView/components/SearchResultsDialog.js +105 -150
- package/dist/LinearGenomeView/components/SearchResultsDialog.js.map +1 -0
- package/dist/LinearGenomeView/components/SequenceDialog.js +219 -272
- package/dist/LinearGenomeView/components/SequenceDialog.js.map +1 -0
- package/dist/LinearGenomeView/components/TrackContainer.js +116 -156
- package/dist/LinearGenomeView/components/TrackContainer.js.map +1 -0
- package/dist/LinearGenomeView/components/TrackLabel.d.ts +6 -42
- package/dist/LinearGenomeView/components/TrackLabel.js +115 -150
- package/dist/LinearGenomeView/components/TrackLabel.js.map +1 -0
- package/dist/LinearGenomeView/components/TracksContainer.d.ts +1 -1
- package/dist/LinearGenomeView/components/TracksContainer.js +172 -199
- package/dist/LinearGenomeView/components/TracksContainer.js.map +1 -0
- package/dist/LinearGenomeView/components/VerticalGuides.d.ts +2 -3
- package/dist/LinearGenomeView/components/VerticalGuides.js +66 -104
- package/dist/LinearGenomeView/components/VerticalGuides.js.map +1 -0
- package/dist/LinearGenomeView/components/ZoomControls.js +72 -87
- package/dist/LinearGenomeView/components/ZoomControls.js.map +1 -0
- package/dist/LinearGenomeView/components/util.d.ts +12 -0
- package/dist/LinearGenomeView/components/util.js +95 -13
- package/dist/LinearGenomeView/components/util.js.map +1 -0
- package/dist/LinearGenomeView/index.d.ts +6 -11
- package/dist/LinearGenomeView/index.js +1163 -1387
- package/dist/LinearGenomeView/index.js.map +1 -0
- package/dist/LinearGenomeView/util.js +76 -83
- package/dist/LinearGenomeView/util.js.map +1 -0
- package/dist/index.d.ts +101 -51
- package/dist/index.js +225 -284
- package/dist/index.js.map +1 -0
- package/esm/BaseLinearDisplay/components/BaseLinearDisplay.d.ts +9 -0
- package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js +68 -0
- package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js.map +1 -0
- package/esm/BaseLinearDisplay/components/Block.d.ts +15 -0
- package/esm/BaseLinearDisplay/components/Block.js +46 -0
- package/esm/BaseLinearDisplay/components/Block.js.map +1 -0
- package/esm/BaseLinearDisplay/components/LinearBlocks.d.ts +22 -0
- package/esm/BaseLinearDisplay/components/LinearBlocks.js +62 -0
- package/esm/BaseLinearDisplay/components/LinearBlocks.js.map +1 -0
- package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.d.ts +4 -0
- package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +113 -0
- package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -0
- package/esm/BaseLinearDisplay/components/Tooltip.d.ts +8 -0
- package/esm/BaseLinearDisplay/components/Tooltip.js +64 -0
- package/esm/BaseLinearDisplay/components/Tooltip.js.map +1 -0
- package/esm/BaseLinearDisplay/index.d.ts +5 -0
- package/esm/BaseLinearDisplay/index.js +4 -0
- package/esm/BaseLinearDisplay/index.js.map +1 -0
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +232 -0
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js +541 -0
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -0
- package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.d.ts +1 -0
- package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +14 -0
- package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -0
- package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +96 -0
- package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.js +225 -0
- package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -0
- package/esm/LinearBareDisplay/configSchema.d.ts +2 -0
- package/esm/LinearBareDisplay/configSchema.js +9 -0
- package/esm/LinearBareDisplay/configSchema.js.map +1 -0
- package/esm/LinearBareDisplay/index.d.ts +2 -0
- package/esm/LinearBareDisplay/index.js +3 -0
- package/esm/LinearBareDisplay/index.js.map +1 -0
- package/esm/LinearBareDisplay/model.d.ts +194 -0
- package/esm/LinearBareDisplay/model.js +28 -0
- package/esm/LinearBareDisplay/model.js.map +1 -0
- package/esm/LinearBasicDisplay/components/SetMaxHeight.d.ts +10 -0
- package/esm/LinearBasicDisplay/components/SetMaxHeight.js +40 -0
- package/esm/LinearBasicDisplay/components/SetMaxHeight.js.map +1 -0
- package/esm/LinearBasicDisplay/configSchema.d.ts +2 -0
- package/esm/LinearBasicDisplay/configSchema.js +14 -0
- package/esm/LinearBasicDisplay/configSchema.js.map +1 -0
- package/esm/LinearBasicDisplay/index.d.ts +2 -0
- package/esm/LinearBasicDisplay/index.js +3 -0
- package/esm/LinearBasicDisplay/index.js.map +1 -0
- package/esm/LinearBasicDisplay/model.d.ts +218 -0
- package/esm/LinearBasicDisplay/model.js +127 -0
- package/esm/LinearBasicDisplay/model.js.map +1 -0
- package/esm/LinearGenomeView/components/CenterLine.d.ts +8 -0
- package/esm/LinearGenomeView/components/CenterLine.js +40 -0
- package/esm/LinearGenomeView/components/CenterLine.js.map +1 -0
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +6 -0
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +52 -0
- package/esm/LinearGenomeView/components/ExportSvgDialog.js.map +1 -0
- package/esm/LinearGenomeView/components/Header.d.ts +7 -0
- package/esm/LinearGenomeView/components/Header.js +81 -0
- package/esm/LinearGenomeView/components/Header.js.map +1 -0
- package/esm/LinearGenomeView/components/HelpDialog.d.ts +4 -0
- package/esm/LinearGenomeView/components/HelpDialog.js +58 -0
- package/esm/LinearGenomeView/components/HelpDialog.js.map +1 -0
- package/esm/LinearGenomeView/components/ImportForm.d.ts +7 -0
- package/esm/LinearGenomeView/components/ImportForm.js +141 -0
- package/esm/LinearGenomeView/components/ImportForm.js.map +1 -0
- package/esm/LinearGenomeView/components/LinearGenomeView.d.ts +7 -0
- package/esm/LinearGenomeView/components/LinearGenomeView.js +67 -0
- package/esm/LinearGenomeView/components/LinearGenomeView.js.map +1 -0
- package/esm/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +4 -0
- package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js +132 -0
- package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -0
- package/esm/LinearGenomeView/components/MiniControls.d.ts +6 -0
- package/esm/LinearGenomeView/components/MiniControls.js +25 -0
- package/esm/LinearGenomeView/components/MiniControls.js.map +1 -0
- package/esm/LinearGenomeView/components/OverviewRubberBand.d.ts +22 -0
- package/esm/LinearGenomeView/components/OverviewRubberBand.js +194 -0
- package/esm/LinearGenomeView/components/OverviewRubberBand.js.map +1 -0
- package/esm/LinearGenomeView/components/OverviewScaleBar.d.ts +148 -0
- package/esm/LinearGenomeView/components/OverviewScaleBar.js +287 -0
- package/esm/LinearGenomeView/components/OverviewScaleBar.js.map +1 -0
- package/esm/LinearGenomeView/components/RefNameAutocomplete.d.ts +22 -0
- package/esm/LinearGenomeView/components/RefNameAutocomplete.js +136 -0
- package/esm/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -0
- package/esm/LinearGenomeView/components/RubberBand.d.ts +9 -0
- package/esm/LinearGenomeView/components/RubberBand.js +198 -0
- package/esm/LinearGenomeView/components/RubberBand.js.map +1 -0
- package/esm/LinearGenomeView/components/Ruler.d.ts +27 -0
- package/esm/LinearGenomeView/components/Ruler.js +50 -0
- package/esm/LinearGenomeView/components/Ruler.js.map +1 -0
- package/esm/LinearGenomeView/components/ScaleBar.d.ts +10 -0
- package/esm/LinearGenomeView/components/ScaleBar.js +112 -0
- package/esm/LinearGenomeView/components/ScaleBar.js.map +1 -0
- package/esm/LinearGenomeView/components/SearchBox.d.ts +8 -0
- package/esm/LinearGenomeView/components/SearchBox.js +94 -0
- package/esm/LinearGenomeView/components/SearchBox.js.map +1 -0
- package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +7 -0
- package/esm/LinearGenomeView/components/SearchResultsDialog.js +107 -0
- package/esm/LinearGenomeView/components/SearchResultsDialog.js.map +1 -0
- package/esm/LinearGenomeView/components/SequenceDialog.d.ts +8 -0
- package/esm/LinearGenomeView/components/SequenceDialog.js +147 -0
- package/esm/LinearGenomeView/components/SequenceDialog.js.map +1 -0
- package/esm/LinearGenomeView/components/TrackContainer.d.ts +9 -0
- package/esm/LinearGenomeView/components/TrackContainer.js +109 -0
- package/esm/LinearGenomeView/components/TrackContainer.js.map +1 -0
- package/esm/LinearGenomeView/components/TrackLabel.d.ts +8 -0
- package/esm/LinearGenomeView/components/TrackLabel.js +89 -0
- package/esm/LinearGenomeView/components/TrackLabel.js.map +1 -0
- package/esm/LinearGenomeView/components/TracksContainer.d.ts +10 -0
- package/esm/LinearGenomeView/components/TracksContainer.js +142 -0
- package/esm/LinearGenomeView/components/TracksContainer.js.map +1 -0
- package/esm/LinearGenomeView/components/VerticalGuides.d.ts +8 -0
- package/esm/LinearGenomeView/components/VerticalGuides.js +71 -0
- package/esm/LinearGenomeView/components/VerticalGuides.js.map +1 -0
- package/esm/LinearGenomeView/components/ZoomControls.d.ts +7 -0
- package/esm/LinearGenomeView/components/ZoomControls.js +32 -0
- package/esm/LinearGenomeView/components/ZoomControls.js.map +1 -0
- package/esm/LinearGenomeView/components/util.d.ts +14 -0
- package/esm/LinearGenomeView/components/util.js +17 -0
- package/esm/LinearGenomeView/components/util.js.map +1 -0
- package/esm/LinearGenomeView/index.d.ts +288 -0
- package/esm/LinearGenomeView/index.js +1124 -0
- package/esm/LinearGenomeView/index.js.map +1 -0
- package/esm/LinearGenomeView/util.d.ts +14 -0
- package/esm/LinearGenomeView/util.js +62 -0
- package/esm/LinearGenomeView/util.js.map +1 -0
- package/esm/index.d.ts +615 -0
- package/esm/index.js +127 -0
- package/esm/index.js.map +1 -0
- package/package.json +21 -13
- package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +6 -90
- package/src/BaseLinearDisplay/components/Block.tsx +5 -5
- package/src/BaseLinearDisplay/components/LinearBlocks.tsx +4 -4
- package/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx +7 -8
- package/src/BaseLinearDisplay/components/Tooltip.tsx +107 -0
- package/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +16 -8
- package/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts +1 -1
- package/src/LinearBasicDisplay/components/SetMaxHeight.tsx +10 -7
- package/src/LinearBasicDisplay/model.ts +21 -15
- package/src/LinearGenomeView/components/CenterLine.tsx +6 -11
- package/src/LinearGenomeView/components/ExportSvgDialog.tsx +5 -5
- package/src/LinearGenomeView/components/Header.tsx +12 -16
- package/src/LinearGenomeView/components/HelpDialog.tsx +10 -9
- package/src/LinearGenomeView/components/ImportForm.tsx +24 -37
- package/src/LinearGenomeView/components/LinearGenomeView.test.js +18 -8
- package/src/LinearGenomeView/components/LinearGenomeView.tsx +20 -13
- package/src/LinearGenomeView/components/MiniControls.tsx +29 -40
- package/src/LinearGenomeView/components/OverviewRubberBand.tsx +6 -10
- package/src/LinearGenomeView/components/OverviewScaleBar.tsx +103 -93
- package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +13 -44
- package/src/LinearGenomeView/components/RubberBand.tsx +12 -17
- package/src/LinearGenomeView/components/Ruler.tsx +5 -11
- package/src/LinearGenomeView/components/ScaleBar.tsx +27 -34
- package/src/LinearGenomeView/components/SearchBox.tsx +22 -32
- package/src/LinearGenomeView/components/SearchResultsDialog.tsx +7 -7
- package/src/LinearGenomeView/components/SequenceDialog.tsx +9 -9
- package/src/LinearGenomeView/components/TrackContainer.tsx +12 -12
- package/src/LinearGenomeView/components/TrackLabel.tsx +37 -50
- package/src/LinearGenomeView/components/TracksContainer.tsx +8 -13
- package/src/LinearGenomeView/components/VerticalGuides.tsx +9 -11
- package/src/LinearGenomeView/components/ZoomControls.tsx +12 -13
- package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +504 -568
- package/src/LinearGenomeView/components/util.ts +43 -0
- package/src/LinearGenomeView/index.test.ts +21 -5
- package/src/LinearGenomeView/index.tsx +101 -88
- package/src/index.ts +36 -31
- package/dist/LinearBareDisplay/index.test.js +0 -33
- package/dist/LinearGenomeView/components/LinearGenomeView.test.js +0 -234
- package/dist/LinearGenomeView/components/ScaleBar.test.js +0 -180
- package/dist/LinearGenomeView/index.test.js +0 -1170
- package/dist/LinearGenomeView/util.test.js +0 -78
|
@@ -0,0 +1,1124 @@
|
|
|
1
|
+
import { getConf } from '@jbrowse/core/configuration';
|
|
2
|
+
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models';
|
|
3
|
+
import { ElementId, Region as MUIRegion } from '@jbrowse/core/util/types/mst';
|
|
4
|
+
import { ReturnToImportFormDialog } from '@jbrowse/core/ui';
|
|
5
|
+
import { assembleLocString, clamp, findLastIndex, getContainingView, getSession, isViewContainer, isSessionModelWithWidgets, measureText, parseLocString, springAnimate, viewBpToPx, } from '@jbrowse/core/util';
|
|
6
|
+
import calculateDynamicBlocks from '@jbrowse/core/util/calculateDynamicBlocks';
|
|
7
|
+
import calculateStaticBlocks from '@jbrowse/core/util/calculateStaticBlocks';
|
|
8
|
+
import { getParentRenderProps } from '@jbrowse/core/util/tracks';
|
|
9
|
+
import { transaction, autorun } from 'mobx';
|
|
10
|
+
import { addDisposer, cast, getSnapshot, getRoot, resolveIdentifier, types, } from 'mobx-state-tree';
|
|
11
|
+
import Base1DView from '@jbrowse/core/util/Base1DViewModel';
|
|
12
|
+
import clone from 'clone';
|
|
13
|
+
import { saveAs } from 'file-saver';
|
|
14
|
+
// icons
|
|
15
|
+
import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons';
|
|
16
|
+
import SyncAltIcon from '@mui/icons-material/SyncAlt';
|
|
17
|
+
import VisibilityIcon from '@mui/icons-material/Visibility';
|
|
18
|
+
import LabelIcon from '@mui/icons-material/Label';
|
|
19
|
+
import FolderOpenIcon from '@mui/icons-material/FolderOpen';
|
|
20
|
+
import PhotoCameraIcon from '@mui/icons-material/PhotoCamera';
|
|
21
|
+
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
|
22
|
+
import MenuOpenIcon from '@mui/icons-material/MenuOpen';
|
|
23
|
+
// locals
|
|
24
|
+
import { renderToSvg } from './components/LinearGenomeViewSvg';
|
|
25
|
+
import RefNameAutocomplete from './components/RefNameAutocomplete';
|
|
26
|
+
import SearchBox from './components/SearchBox';
|
|
27
|
+
import ExportSvgDlg from './components/ExportSvgDialog';
|
|
28
|
+
function calculateVisibleLocStrings(contentBlocks) {
|
|
29
|
+
if (!contentBlocks.length) {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
const isSingleAssemblyName = contentBlocks.every(block => block.assemblyName === contentBlocks[0].assemblyName);
|
|
33
|
+
const locs = contentBlocks.map(block => assembleLocString({
|
|
34
|
+
...block,
|
|
35
|
+
start: Math.round(block.start),
|
|
36
|
+
end: Math.round(block.end),
|
|
37
|
+
assemblyName: isSingleAssemblyName ? undefined : block.assemblyName,
|
|
38
|
+
}));
|
|
39
|
+
return locs.join(' ');
|
|
40
|
+
}
|
|
41
|
+
export const HEADER_BAR_HEIGHT = 48;
|
|
42
|
+
export const HEADER_OVERVIEW_HEIGHT = 20;
|
|
43
|
+
export const SCALE_BAR_HEIGHT = 17;
|
|
44
|
+
export const RESIZE_HANDLE_HEIGHT = 3;
|
|
45
|
+
export const INTER_REGION_PADDING_WIDTH = 2;
|
|
46
|
+
export const SPACING = 7;
|
|
47
|
+
export const WIDGET_HEIGHT = 32;
|
|
48
|
+
function localStorageGetItem(item) {
|
|
49
|
+
return typeof localStorage !== 'undefined'
|
|
50
|
+
? localStorage.getItem(item)
|
|
51
|
+
: undefined;
|
|
52
|
+
}
|
|
53
|
+
export function stateModelFactory(pluginManager) {
|
|
54
|
+
return types
|
|
55
|
+
.compose(BaseViewModel, types.model('LinearGenomeView', {
|
|
56
|
+
id: ElementId,
|
|
57
|
+
type: types.literal('LinearGenomeView'),
|
|
58
|
+
offsetPx: 0,
|
|
59
|
+
bpPerPx: 1,
|
|
60
|
+
displayedRegions: types.array(MUIRegion),
|
|
61
|
+
// we use an array for the tracks because the tracks are displayed in a
|
|
62
|
+
// specific order that we need to keep.
|
|
63
|
+
tracks: types.array(pluginManager.pluggableMstType('track', 'stateModel')),
|
|
64
|
+
hideHeader: false,
|
|
65
|
+
hideHeaderOverview: false,
|
|
66
|
+
hideNoTracksActive: false,
|
|
67
|
+
trackSelectorType: types.optional(types.enumeration(['hierarchical']), 'hierarchical'),
|
|
68
|
+
trackLabels: types.optional(types.string, () => localStorageGetItem('lgv-trackLabels') || 'overlapping'),
|
|
69
|
+
showCenterLine: types.optional(types.boolean, () => {
|
|
70
|
+
const setting = localStorageGetItem('lgv-showCenterLine');
|
|
71
|
+
return setting !== undefined && setting !== null ? !!+setting : false;
|
|
72
|
+
}),
|
|
73
|
+
showCytobandsSetting: types.optional(types.boolean, () => {
|
|
74
|
+
const setting = localStorageGetItem('lgv-showCytobands');
|
|
75
|
+
return setting !== undefined && setting !== null ? !!+setting : true;
|
|
76
|
+
}),
|
|
77
|
+
}))
|
|
78
|
+
.volatile(() => ({
|
|
79
|
+
volatileWidth: undefined,
|
|
80
|
+
minimumBlockWidth: 3,
|
|
81
|
+
draggingTrackId: undefined,
|
|
82
|
+
volatileError: undefined,
|
|
83
|
+
// array of callbacks to run after the next set of the displayedRegions,
|
|
84
|
+
// which is basically like an onLoad
|
|
85
|
+
afterDisplayedRegionsSetCallbacks: [],
|
|
86
|
+
scaleFactor: 1,
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
88
|
+
trackRefs: {},
|
|
89
|
+
coarseDynamicBlocks: [],
|
|
90
|
+
coarseTotalBp: 0,
|
|
91
|
+
leftOffset: undefined,
|
|
92
|
+
rightOffset: undefined,
|
|
93
|
+
searchResults: undefined,
|
|
94
|
+
searchQuery: undefined,
|
|
95
|
+
seqDialogDisplayed: false,
|
|
96
|
+
}))
|
|
97
|
+
.views(self => ({
|
|
98
|
+
get width() {
|
|
99
|
+
if (self.volatileWidth === undefined) {
|
|
100
|
+
throw new Error('width undefined, make sure to check for model.initialized');
|
|
101
|
+
}
|
|
102
|
+
return self.volatileWidth;
|
|
103
|
+
},
|
|
104
|
+
get interRegionPaddingWidth() {
|
|
105
|
+
return INTER_REGION_PADDING_WIDTH;
|
|
106
|
+
},
|
|
107
|
+
get assemblyNames() {
|
|
108
|
+
return [
|
|
109
|
+
...new Set(self.displayedRegions.map(region => region.assemblyName)),
|
|
110
|
+
];
|
|
111
|
+
},
|
|
112
|
+
}))
|
|
113
|
+
.views(self => ({
|
|
114
|
+
get assemblyErrors() {
|
|
115
|
+
const { assemblyManager } = getSession(self);
|
|
116
|
+
const { assemblyNames } = self;
|
|
117
|
+
return assemblyNames
|
|
118
|
+
.map(a => { var _a; return (_a = assemblyManager.get(a)) === null || _a === void 0 ? void 0 : _a.error; })
|
|
119
|
+
.filter(f => !!f)
|
|
120
|
+
.join(', ');
|
|
121
|
+
},
|
|
122
|
+
get assembliesInitialized() {
|
|
123
|
+
const { assemblyManager } = getSession(self);
|
|
124
|
+
const { assemblyNames } = self;
|
|
125
|
+
return assemblyNames.every(a => { var _a; return (_a = assemblyManager.get(a)) === null || _a === void 0 ? void 0 : _a.initialized; });
|
|
126
|
+
},
|
|
127
|
+
get initialized() {
|
|
128
|
+
return self.volatileWidth !== undefined && this.assembliesInitialized;
|
|
129
|
+
},
|
|
130
|
+
get hasDisplayedRegions() {
|
|
131
|
+
return self.displayedRegions.length > 0;
|
|
132
|
+
},
|
|
133
|
+
get isSearchDialogDisplayed() {
|
|
134
|
+
return self.searchResults !== undefined;
|
|
135
|
+
},
|
|
136
|
+
get scaleBarHeight() {
|
|
137
|
+
return SCALE_BAR_HEIGHT + RESIZE_HANDLE_HEIGHT;
|
|
138
|
+
},
|
|
139
|
+
get headerHeight() {
|
|
140
|
+
if (self.hideHeader) {
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
if (self.hideHeaderOverview) {
|
|
144
|
+
return HEADER_BAR_HEIGHT;
|
|
145
|
+
}
|
|
146
|
+
return HEADER_BAR_HEIGHT + HEADER_OVERVIEW_HEIGHT;
|
|
147
|
+
},
|
|
148
|
+
get trackHeights() {
|
|
149
|
+
return self.tracks
|
|
150
|
+
.map(t => t.displays[0].height)
|
|
151
|
+
.reduce((a, b) => a + b, 0);
|
|
152
|
+
},
|
|
153
|
+
get trackHeightsWithResizeHandles() {
|
|
154
|
+
return this.trackHeights + self.tracks.length * RESIZE_HANDLE_HEIGHT;
|
|
155
|
+
},
|
|
156
|
+
get height() {
|
|
157
|
+
return (this.trackHeightsWithResizeHandles +
|
|
158
|
+
this.headerHeight +
|
|
159
|
+
this.scaleBarHeight);
|
|
160
|
+
},
|
|
161
|
+
get totalBp() {
|
|
162
|
+
return self.displayedRegions.reduce((a, b) => a + b.end - b.start, 0);
|
|
163
|
+
},
|
|
164
|
+
get maxBpPerPx() {
|
|
165
|
+
return this.totalBp / (self.width * 0.9);
|
|
166
|
+
},
|
|
167
|
+
get minBpPerPx() {
|
|
168
|
+
return 1 / 50;
|
|
169
|
+
},
|
|
170
|
+
get error() {
|
|
171
|
+
return self.volatileError || this.assemblyErrors;
|
|
172
|
+
},
|
|
173
|
+
get maxOffset() {
|
|
174
|
+
// objectively determined to keep the linear genome on the main screen
|
|
175
|
+
const leftPadding = 10;
|
|
176
|
+
return this.displayedRegionsTotalPx - leftPadding;
|
|
177
|
+
},
|
|
178
|
+
get minOffset() {
|
|
179
|
+
// objectively determined to keep the linear genome on the main screen
|
|
180
|
+
const rightPadding = 30;
|
|
181
|
+
return -self.width + rightPadding;
|
|
182
|
+
},
|
|
183
|
+
get displayedRegionsTotalPx() {
|
|
184
|
+
return this.totalBp / self.bpPerPx;
|
|
185
|
+
},
|
|
186
|
+
renderProps() {
|
|
187
|
+
return {
|
|
188
|
+
...getParentRenderProps(self),
|
|
189
|
+
bpPerPx: self.bpPerPx,
|
|
190
|
+
highResolutionScaling: getConf(getSession(self), 'highResolutionScaling'),
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
searchScope(assemblyName) {
|
|
194
|
+
return {
|
|
195
|
+
assemblyName,
|
|
196
|
+
includeAggregateIndexes: true,
|
|
197
|
+
tracks: self.tracks,
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
bpToPx({ refName, coord, regionNumber, }) {
|
|
201
|
+
return viewBpToPx({ refName, coord, regionNumber, self });
|
|
202
|
+
},
|
|
203
|
+
/**
|
|
204
|
+
*
|
|
205
|
+
* @param px - px in the view area, return value is the displayed regions
|
|
206
|
+
* @returns BpOffset of the displayed region that it lands in
|
|
207
|
+
*/
|
|
208
|
+
pxToBp(px) {
|
|
209
|
+
let bpSoFar = 0;
|
|
210
|
+
const bp = (self.offsetPx + px) * self.bpPerPx;
|
|
211
|
+
const n = self.displayedRegions.length;
|
|
212
|
+
if (bp < 0) {
|
|
213
|
+
const region = self.displayedRegions[0];
|
|
214
|
+
const offset = bp;
|
|
215
|
+
const snap = getSnapshot(region);
|
|
216
|
+
return {
|
|
217
|
+
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
218
|
+
...snap,
|
|
219
|
+
oob: true,
|
|
220
|
+
coord: region.reversed
|
|
221
|
+
? Math.floor(region.end - offset) + 1
|
|
222
|
+
: Math.floor(region.start + offset) + 1,
|
|
223
|
+
offset,
|
|
224
|
+
index: 0,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const interRegionPaddingBp = self.interRegionPaddingWidth * self.bpPerPx;
|
|
228
|
+
const minimumBlockBp = self.minimumBlockWidth * self.bpPerPx;
|
|
229
|
+
for (let index = 0; index < self.displayedRegions.length; index += 1) {
|
|
230
|
+
const region = self.displayedRegions[index];
|
|
231
|
+
const len = region.end - region.start;
|
|
232
|
+
const offset = bp - bpSoFar;
|
|
233
|
+
if (len + bpSoFar > bp && bpSoFar <= bp) {
|
|
234
|
+
const snap = getSnapshot(region);
|
|
235
|
+
return {
|
|
236
|
+
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
237
|
+
...snap,
|
|
238
|
+
oob: false,
|
|
239
|
+
offset,
|
|
240
|
+
coord: region.reversed
|
|
241
|
+
? Math.floor(region.end - offset) + 1
|
|
242
|
+
: Math.floor(region.start + offset) + 1,
|
|
243
|
+
index,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// add the interRegionPaddingWidth if the boundary is in the screen
|
|
247
|
+
// e.g. offset>0 && offset<width
|
|
248
|
+
if (region.end - region.start > minimumBlockBp &&
|
|
249
|
+
offset / self.bpPerPx > 0 &&
|
|
250
|
+
offset / self.bpPerPx < self.width) {
|
|
251
|
+
bpSoFar += len + interRegionPaddingBp;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
bpSoFar += len;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (bp >= bpSoFar) {
|
|
258
|
+
const region = self.displayedRegions[n - 1];
|
|
259
|
+
const len = region.end - region.start;
|
|
260
|
+
const offset = bp - bpSoFar + len;
|
|
261
|
+
const snap = getSnapshot(region);
|
|
262
|
+
return {
|
|
263
|
+
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
264
|
+
...snap,
|
|
265
|
+
oob: true,
|
|
266
|
+
offset,
|
|
267
|
+
coord: region.reversed
|
|
268
|
+
? Math.floor(region.end - offset) + 1
|
|
269
|
+
: Math.floor(region.start + offset) + 1,
|
|
270
|
+
index: n - 1,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
coord: 0,
|
|
275
|
+
index: 0,
|
|
276
|
+
refName: '',
|
|
277
|
+
oob: true,
|
|
278
|
+
assemblyName: '',
|
|
279
|
+
offset: 0,
|
|
280
|
+
start: 0,
|
|
281
|
+
end: 0,
|
|
282
|
+
reversed: false,
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
getTrack(id) {
|
|
286
|
+
return self.tracks.find(t => t.configuration.trackId === id);
|
|
287
|
+
},
|
|
288
|
+
rankSearchResults(results) {
|
|
289
|
+
// order of rank
|
|
290
|
+
const openTrackIds = self.tracks.map(track => track.configuration.trackId);
|
|
291
|
+
results.forEach(result => {
|
|
292
|
+
if (openTrackIds.includes(result.trackId)) {
|
|
293
|
+
result.updateScore(result.getScore() + 1);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
return results;
|
|
297
|
+
},
|
|
298
|
+
// modifies view menu action onClick to apply to all tracks of same type
|
|
299
|
+
rewriteOnClicks(trackType, viewMenuActions) {
|
|
300
|
+
viewMenuActions.forEach(action => {
|
|
301
|
+
// go to lowest level menu
|
|
302
|
+
if ('subMenu' in action) {
|
|
303
|
+
this.rewriteOnClicks(trackType, action.subMenu);
|
|
304
|
+
}
|
|
305
|
+
if ('onClick' in action) {
|
|
306
|
+
const holdOnClick = action.onClick;
|
|
307
|
+
action.onClick = (...args) => {
|
|
308
|
+
self.tracks.forEach(track => {
|
|
309
|
+
if (track.type === trackType) {
|
|
310
|
+
holdOnClick.apply(track, [track, ...args]);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
},
|
|
317
|
+
get trackTypeActions() {
|
|
318
|
+
const allActions = new Map();
|
|
319
|
+
self.tracks.forEach(track => {
|
|
320
|
+
const trackInMap = allActions.get(track.type);
|
|
321
|
+
if (!trackInMap) {
|
|
322
|
+
const viewMenuActions = clone(track.viewMenuActions);
|
|
323
|
+
this.rewriteOnClicks(track.type, viewMenuActions);
|
|
324
|
+
allActions.set(track.type, viewMenuActions);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
return allActions;
|
|
328
|
+
},
|
|
329
|
+
get centerLineInfo() {
|
|
330
|
+
return self.displayedRegions.length
|
|
331
|
+
? this.pxToBp(self.width / 2)
|
|
332
|
+
: undefined;
|
|
333
|
+
},
|
|
334
|
+
}))
|
|
335
|
+
.actions(self => ({
|
|
336
|
+
setShowCytobands(flag) {
|
|
337
|
+
self.showCytobandsSetting = flag;
|
|
338
|
+
localStorage.setItem('lgv-showCytobands', `${+flag}`);
|
|
339
|
+
},
|
|
340
|
+
setWidth(newWidth) {
|
|
341
|
+
self.volatileWidth = newWidth;
|
|
342
|
+
},
|
|
343
|
+
setError(error) {
|
|
344
|
+
self.volatileError = error;
|
|
345
|
+
},
|
|
346
|
+
toggleHeader() {
|
|
347
|
+
self.hideHeader = !self.hideHeader;
|
|
348
|
+
},
|
|
349
|
+
toggleHeaderOverview() {
|
|
350
|
+
self.hideHeaderOverview = !self.hideHeaderOverview;
|
|
351
|
+
},
|
|
352
|
+
toggleNoTracksActive() {
|
|
353
|
+
self.hideNoTracksActive = !self.hideNoTracksActive;
|
|
354
|
+
},
|
|
355
|
+
scrollTo(offsetPx) {
|
|
356
|
+
const newOffsetPx = clamp(offsetPx, self.minOffset, self.maxOffset);
|
|
357
|
+
self.offsetPx = newOffsetPx;
|
|
358
|
+
return newOffsetPx;
|
|
359
|
+
},
|
|
360
|
+
zoomTo(bpPerPx) {
|
|
361
|
+
const newBpPerPx = clamp(bpPerPx, self.minBpPerPx, self.maxBpPerPx);
|
|
362
|
+
if (newBpPerPx === self.bpPerPx) {
|
|
363
|
+
return newBpPerPx;
|
|
364
|
+
}
|
|
365
|
+
const oldBpPerPx = self.bpPerPx;
|
|
366
|
+
self.bpPerPx = newBpPerPx;
|
|
367
|
+
if (Math.abs(oldBpPerPx - newBpPerPx) < 0.000001) {
|
|
368
|
+
console.warn('zoomTo bpPerPx rounding error');
|
|
369
|
+
return oldBpPerPx;
|
|
370
|
+
}
|
|
371
|
+
// tweak the offset so that the center of the view remains at the same coordinate
|
|
372
|
+
const viewWidth = self.width;
|
|
373
|
+
this.scrollTo(Math.round(((self.offsetPx + viewWidth / 2) * oldBpPerPx) / newBpPerPx -
|
|
374
|
+
viewWidth / 2));
|
|
375
|
+
return newBpPerPx;
|
|
376
|
+
},
|
|
377
|
+
setOffsets(left, right) {
|
|
378
|
+
// sets offsets used in the get sequence dialog
|
|
379
|
+
self.leftOffset = left;
|
|
380
|
+
self.rightOffset = right;
|
|
381
|
+
},
|
|
382
|
+
setSearchResults(results, query) {
|
|
383
|
+
self.searchResults = results;
|
|
384
|
+
self.searchQuery = query;
|
|
385
|
+
},
|
|
386
|
+
setSequenceDialogOpen(open) {
|
|
387
|
+
self.seqDialogDisplayed = open;
|
|
388
|
+
},
|
|
389
|
+
setNewView(bpPerPx, offsetPx) {
|
|
390
|
+
this.zoomTo(bpPerPx);
|
|
391
|
+
this.scrollTo(offsetPx);
|
|
392
|
+
},
|
|
393
|
+
horizontallyFlip() {
|
|
394
|
+
self.displayedRegions = cast(self.displayedRegions
|
|
395
|
+
.slice()
|
|
396
|
+
.reverse()
|
|
397
|
+
.map(region => ({ ...region, reversed: !region.reversed })));
|
|
398
|
+
this.scrollTo(self.totalBp / self.bpPerPx - self.offsetPx - self.width);
|
|
399
|
+
},
|
|
400
|
+
showTrack(trackId, initialSnapshot = {}, displayInitialSnapshot = {}) {
|
|
401
|
+
const schema = pluginManager.pluggableConfigSchemaType('track');
|
|
402
|
+
const conf = resolveIdentifier(schema, getRoot(self), trackId);
|
|
403
|
+
if (!conf) {
|
|
404
|
+
throw new Error(`Could not resolve identifier "${trackId}"`);
|
|
405
|
+
}
|
|
406
|
+
const trackType = pluginManager.getTrackType(conf === null || conf === void 0 ? void 0 : conf.type);
|
|
407
|
+
if (!trackType) {
|
|
408
|
+
throw new Error(`Unknown track type ${conf.type}`);
|
|
409
|
+
}
|
|
410
|
+
const viewType = pluginManager.getViewType(self.type);
|
|
411
|
+
const supportedDisplays = viewType.displayTypes.map(d => d.name);
|
|
412
|
+
const displayConf = conf.displays.find((d) => supportedDisplays.includes(d.type));
|
|
413
|
+
if (!displayConf) {
|
|
414
|
+
throw new Error(`Could not find a compatible display for view type ${self.type}`);
|
|
415
|
+
}
|
|
416
|
+
const t = self.tracks.filter(t => t.configuration === conf);
|
|
417
|
+
if (t.length === 0) {
|
|
418
|
+
const track = trackType.stateModel.create({
|
|
419
|
+
...initialSnapshot,
|
|
420
|
+
type: conf.type,
|
|
421
|
+
configuration: conf,
|
|
422
|
+
displays: [
|
|
423
|
+
{
|
|
424
|
+
type: displayConf.type,
|
|
425
|
+
configuration: displayConf,
|
|
426
|
+
...displayInitialSnapshot,
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
});
|
|
430
|
+
self.tracks.push(track);
|
|
431
|
+
return track;
|
|
432
|
+
}
|
|
433
|
+
return t[0];
|
|
434
|
+
},
|
|
435
|
+
hideTrack(trackId) {
|
|
436
|
+
const schema = pluginManager.pluggableConfigSchemaType('track');
|
|
437
|
+
const conf = resolveIdentifier(schema, getRoot(self), trackId);
|
|
438
|
+
const t = self.tracks.filter(t => t.configuration === conf);
|
|
439
|
+
transaction(() => t.forEach(t => self.tracks.remove(t)));
|
|
440
|
+
return t.length;
|
|
441
|
+
},
|
|
442
|
+
}))
|
|
443
|
+
.actions(self => ({
|
|
444
|
+
moveTrack(movingId, targetId) {
|
|
445
|
+
const oldIndex = self.tracks.findIndex(track => track.id === movingId);
|
|
446
|
+
if (oldIndex === -1) {
|
|
447
|
+
throw new Error(`Track ID ${movingId} not found`);
|
|
448
|
+
}
|
|
449
|
+
const newIndex = self.tracks.findIndex(track => track.id === targetId);
|
|
450
|
+
if (newIndex === -1) {
|
|
451
|
+
throw new Error(`Track ID ${targetId} not found`);
|
|
452
|
+
}
|
|
453
|
+
const track = getSnapshot(self.tracks[oldIndex]);
|
|
454
|
+
self.tracks.splice(oldIndex, 1);
|
|
455
|
+
self.tracks.splice(newIndex, 0, track);
|
|
456
|
+
},
|
|
457
|
+
closeView() {
|
|
458
|
+
const parent = getContainingView(self);
|
|
459
|
+
if (parent) {
|
|
460
|
+
// I am embedded in a some other view
|
|
461
|
+
if (isViewContainer(parent)) {
|
|
462
|
+
parent.removeView(self);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
// I am part of a session
|
|
467
|
+
getSession(self).removeView(self);
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
toggleTrack(trackId) {
|
|
471
|
+
// if we have any tracks with that configuration, turn them off
|
|
472
|
+
const hiddenCount = self.hideTrack(trackId);
|
|
473
|
+
// if none had that configuration, turn one on
|
|
474
|
+
if (!hiddenCount) {
|
|
475
|
+
self.showTrack(trackId);
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
setTrackLabels(setting) {
|
|
479
|
+
self.trackLabels = setting;
|
|
480
|
+
localStorage.setItem('lgv-trackLabels', setting);
|
|
481
|
+
},
|
|
482
|
+
toggleCenterLine() {
|
|
483
|
+
self.showCenterLine = !self.showCenterLine;
|
|
484
|
+
localStorage.setItem('lgv-showCenterLine', `${+self.showCenterLine}`);
|
|
485
|
+
},
|
|
486
|
+
setDisplayedRegions(regions) {
|
|
487
|
+
self.displayedRegions = cast(regions);
|
|
488
|
+
self.zoomTo(self.bpPerPx);
|
|
489
|
+
},
|
|
490
|
+
activateTrackSelector() {
|
|
491
|
+
if (self.trackSelectorType === 'hierarchical') {
|
|
492
|
+
const session = getSession(self);
|
|
493
|
+
if (isSessionModelWithWidgets(session)) {
|
|
494
|
+
const selector = session.addWidget('HierarchicalTrackSelectorWidget', 'hierarchicalTrackSelector', { view: self });
|
|
495
|
+
session.showWidget(selector);
|
|
496
|
+
return selector;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
throw new Error(`invalid track selector type ${self.trackSelectorType}`);
|
|
500
|
+
},
|
|
501
|
+
navToLocString(locString, optAssemblyName) {
|
|
502
|
+
const { assemblyNames } = self;
|
|
503
|
+
const { assemblyManager } = getSession(self);
|
|
504
|
+
const { isValidRefName } = assemblyManager;
|
|
505
|
+
const assemblyName = optAssemblyName || assemblyNames[0];
|
|
506
|
+
let parsedLocStrings;
|
|
507
|
+
const inputs = locString
|
|
508
|
+
.split(/(\s+)/)
|
|
509
|
+
.map(f => f.trim())
|
|
510
|
+
.filter(f => !!f);
|
|
511
|
+
// first try interpreting as a whitespace-separated sequence of
|
|
512
|
+
// multiple locstrings
|
|
513
|
+
try {
|
|
514
|
+
parsedLocStrings = inputs.map(l => parseLocString(l, ref => isValidRefName(ref, assemblyName)));
|
|
515
|
+
}
|
|
516
|
+
catch (e) {
|
|
517
|
+
// if this fails, try interpreting as a whitespace-separated refname,
|
|
518
|
+
// start, end if start and end are integer inputs
|
|
519
|
+
const [refName, start, end] = inputs;
|
|
520
|
+
if (`${e}`.match(/Unknown reference sequence/) &&
|
|
521
|
+
Number.isInteger(+start) &&
|
|
522
|
+
Number.isInteger(+end)) {
|
|
523
|
+
parsedLocStrings = [
|
|
524
|
+
parseLocString(refName + ':' + start + '..' + end, ref => isValidRefName(ref, assemblyName)),
|
|
525
|
+
];
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
throw e;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const locations = parsedLocStrings === null || parsedLocStrings === void 0 ? void 0 : parsedLocStrings.map(region => {
|
|
532
|
+
const asmName = region.assemblyName || assemblyName;
|
|
533
|
+
const asm = assemblyManager.get(asmName);
|
|
534
|
+
const { refName } = region;
|
|
535
|
+
if (!asm) {
|
|
536
|
+
throw new Error(`assembly ${asmName} not found`);
|
|
537
|
+
}
|
|
538
|
+
const { regions } = asm;
|
|
539
|
+
if (!regions) {
|
|
540
|
+
throw new Error(`regions not loaded yet for ${asmName}`);
|
|
541
|
+
}
|
|
542
|
+
const canonicalRefName = asm.getCanonicalRefName(region.refName);
|
|
543
|
+
if (!canonicalRefName) {
|
|
544
|
+
throw new Error(`Could not find refName ${refName} in ${asm.name}`);
|
|
545
|
+
}
|
|
546
|
+
const parentRegion = regions.find(region => region.refName === canonicalRefName);
|
|
547
|
+
if (!parentRegion) {
|
|
548
|
+
throw new Error(`Could not find refName ${refName} in ${asmName}`);
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
...region,
|
|
552
|
+
assemblyName: asmName,
|
|
553
|
+
parentRegion,
|
|
554
|
+
};
|
|
555
|
+
});
|
|
556
|
+
if (locations.length === 1) {
|
|
557
|
+
const loc = locations[0];
|
|
558
|
+
this.setDisplayedRegions([
|
|
559
|
+
{ reversed: loc.reversed, ...loc.parentRegion },
|
|
560
|
+
]);
|
|
561
|
+
const { start, end, parentRegion } = loc;
|
|
562
|
+
this.navTo({
|
|
563
|
+
...loc,
|
|
564
|
+
start: clamp(start !== null && start !== void 0 ? start : 0, 0, parentRegion.end),
|
|
565
|
+
end: clamp(end !== null && end !== void 0 ? end : parentRegion.end, 0, parentRegion.end),
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
this.setDisplayedRegions(
|
|
570
|
+
// @ts-ignore
|
|
571
|
+
locations.map(r => (r.start === undefined ? r.parentRegion : r)));
|
|
572
|
+
this.showAllRegions();
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
/**
|
|
576
|
+
* Navigate to a location based on its refName and optionally start, end,
|
|
577
|
+
* and assemblyName. Can handle if there are multiple displayedRegions
|
|
578
|
+
* from same refName. Only navigates to a location if it is entirely
|
|
579
|
+
* within a displayedRegion. Navigates to the first matching location
|
|
580
|
+
* encountered.
|
|
581
|
+
*
|
|
582
|
+
* Throws an error if navigation was unsuccessful
|
|
583
|
+
*
|
|
584
|
+
* @param location - a proposed location to navigate to
|
|
585
|
+
*/
|
|
586
|
+
navTo(query) {
|
|
587
|
+
this.navToMultiple([query]);
|
|
588
|
+
},
|
|
589
|
+
navToMultiple(locations) {
|
|
590
|
+
const firstLocation = locations[0];
|
|
591
|
+
let { refName } = firstLocation;
|
|
592
|
+
const { start, end, assemblyName = self.assemblyNames[0], } = firstLocation;
|
|
593
|
+
if (start !== undefined && end !== undefined && start > end) {
|
|
594
|
+
throw new Error(`start "${start + 1}" is greater than end "${end}"`);
|
|
595
|
+
}
|
|
596
|
+
const session = getSession(self);
|
|
597
|
+
const { assemblyManager } = session;
|
|
598
|
+
const assembly = assemblyManager.get(assemblyName);
|
|
599
|
+
if (assembly) {
|
|
600
|
+
const canonicalRefName = assembly.getCanonicalRefName(refName);
|
|
601
|
+
if (canonicalRefName) {
|
|
602
|
+
refName = canonicalRefName;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
let s = start;
|
|
606
|
+
let e = end;
|
|
607
|
+
let refNameMatched = false;
|
|
608
|
+
const predicate = (r) => {
|
|
609
|
+
if (refName === r.refName) {
|
|
610
|
+
refNameMatched = true;
|
|
611
|
+
if (s === undefined) {
|
|
612
|
+
s = r.start;
|
|
613
|
+
}
|
|
614
|
+
if (e === undefined) {
|
|
615
|
+
e = r.end;
|
|
616
|
+
}
|
|
617
|
+
if (s >= r.start && s <= r.end && e <= r.end && e >= r.start) {
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
s = start;
|
|
621
|
+
e = end;
|
|
622
|
+
}
|
|
623
|
+
return false;
|
|
624
|
+
};
|
|
625
|
+
const lastIndex = findLastIndex(self.displayedRegions, predicate);
|
|
626
|
+
let index;
|
|
627
|
+
while (index !== lastIndex) {
|
|
628
|
+
try {
|
|
629
|
+
const previousIndex = index;
|
|
630
|
+
index = self.displayedRegions
|
|
631
|
+
.slice(previousIndex === undefined ? 0 : previousIndex + 1)
|
|
632
|
+
.findIndex(predicate);
|
|
633
|
+
if (previousIndex !== undefined) {
|
|
634
|
+
index += previousIndex + 1;
|
|
635
|
+
}
|
|
636
|
+
if (!refNameMatched) {
|
|
637
|
+
throw new Error(`could not find a region with refName "${refName}"`);
|
|
638
|
+
}
|
|
639
|
+
if (s === undefined) {
|
|
640
|
+
throw new Error(`could not find a region with refName "${refName}" that contained an end position ${e}`);
|
|
641
|
+
}
|
|
642
|
+
if (e === undefined) {
|
|
643
|
+
throw new Error(`could not find a region with refName "${refName}" that contained a start position ${s + 1}`);
|
|
644
|
+
}
|
|
645
|
+
if (index === -1) {
|
|
646
|
+
throw new Error(`could not find a region that completely contained "${assembleLocString(firstLocation)}"`);
|
|
647
|
+
}
|
|
648
|
+
if (locations.length === 1) {
|
|
649
|
+
const f = self.displayedRegions[index];
|
|
650
|
+
this.moveTo({ index, offset: f.reversed ? f.end - e : s - f.start }, { index, offset: f.reversed ? f.end - s : e - f.start });
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
let locationIndex = 0;
|
|
654
|
+
let locationStart = 0;
|
|
655
|
+
let locationEnd = 0;
|
|
656
|
+
for (locationIndex; locationIndex < locations.length; locationIndex++) {
|
|
657
|
+
const location = locations[locationIndex];
|
|
658
|
+
const region = self.displayedRegions[index + locationIndex];
|
|
659
|
+
locationStart = location.start || region.start;
|
|
660
|
+
locationEnd = location.end || region.end;
|
|
661
|
+
if (location.refName !== region.refName) {
|
|
662
|
+
throw new Error(`Entered location ${assembleLocString(location)} does not match with displayed regions`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
locationIndex -= 1;
|
|
666
|
+
const startDisplayedRegion = self.displayedRegions[index];
|
|
667
|
+
const endDisplayedRegion = self.displayedRegions[index + locationIndex];
|
|
668
|
+
this.moveTo({
|
|
669
|
+
index,
|
|
670
|
+
offset: startDisplayedRegion.reversed
|
|
671
|
+
? startDisplayedRegion.end - e
|
|
672
|
+
: s - startDisplayedRegion.start,
|
|
673
|
+
}, {
|
|
674
|
+
index: index + locationIndex,
|
|
675
|
+
offset: endDisplayedRegion.reversed
|
|
676
|
+
? endDisplayedRegion.end - locationStart
|
|
677
|
+
: locationEnd - endDisplayedRegion.start,
|
|
678
|
+
});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
if (index === lastIndex) {
|
|
683
|
+
throw error;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
/**
|
|
689
|
+
* Navigate to a location based on user clicking and dragging on the
|
|
690
|
+
* overview scale bar to select a region to zoom into.
|
|
691
|
+
* Can handle if there are multiple displayedRegions from same refName.
|
|
692
|
+
* Only navigates to a location if it is entirely within a displayedRegion.
|
|
693
|
+
*
|
|
694
|
+
* @param leftPx- `object as {start, end, index, offset}`, offset = start of user drag
|
|
695
|
+
* @param rightPx- `object as {start, end, index, offset}`, offset = end of user drag
|
|
696
|
+
*/
|
|
697
|
+
zoomToDisplayedRegions(leftPx, rightPx) {
|
|
698
|
+
if (leftPx === undefined || rightPx === undefined) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const singleRefSeq = leftPx.refName === rightPx.refName && leftPx.index === rightPx.index;
|
|
702
|
+
// zooming into one displayed Region
|
|
703
|
+
if ((singleRefSeq && rightPx.offset < leftPx.offset) ||
|
|
704
|
+
leftPx.index > rightPx.index) {
|
|
705
|
+
;
|
|
706
|
+
[leftPx, rightPx] = [rightPx, leftPx];
|
|
707
|
+
}
|
|
708
|
+
const startOffset = {
|
|
709
|
+
start: leftPx.start,
|
|
710
|
+
end: leftPx.end,
|
|
711
|
+
index: leftPx.index,
|
|
712
|
+
offset: leftPx.offset,
|
|
713
|
+
};
|
|
714
|
+
const endOffset = {
|
|
715
|
+
start: rightPx.start,
|
|
716
|
+
end: rightPx.end,
|
|
717
|
+
index: rightPx.index,
|
|
718
|
+
offset: rightPx.offset,
|
|
719
|
+
};
|
|
720
|
+
if (startOffset && endOffset) {
|
|
721
|
+
this.moveTo(startOffset, endOffset);
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
const session = getSession(self);
|
|
725
|
+
session.notify('No regions found to navigate to', 'warning');
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
/**
|
|
729
|
+
* Helper method for the fetchSequence.
|
|
730
|
+
* Retrieves the corresponding regions that were selected by the rubberband
|
|
731
|
+
*
|
|
732
|
+
* @param leftOffset - `object as {start, end, index, offset}`, offset = start of user drag
|
|
733
|
+
* @param rightOffset - `object as {start, end, index, offset}`, offset = end of user drag
|
|
734
|
+
* @returns array of Region[]
|
|
735
|
+
*/
|
|
736
|
+
getSelectedRegions(leftOffset, rightOffset) {
|
|
737
|
+
const snap = getSnapshot(self);
|
|
738
|
+
const simView = Base1DView.create({
|
|
739
|
+
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
740
|
+
...snap,
|
|
741
|
+
interRegionPaddingWidth: self.interRegionPaddingWidth,
|
|
742
|
+
});
|
|
743
|
+
simView.setVolatileWidth(self.width);
|
|
744
|
+
simView.zoomToDisplayedRegions(leftOffset, rightOffset);
|
|
745
|
+
return simView.dynamicBlocks.contentBlocks.map(region => ({
|
|
746
|
+
...region,
|
|
747
|
+
start: Math.floor(region.start),
|
|
748
|
+
end: Math.ceil(region.end),
|
|
749
|
+
}));
|
|
750
|
+
},
|
|
751
|
+
// schedule something to be run after the next time displayedRegions is set
|
|
752
|
+
afterDisplayedRegionsSet(cb) {
|
|
753
|
+
self.afterDisplayedRegionsSetCallbacks.push(cb);
|
|
754
|
+
},
|
|
755
|
+
/**
|
|
756
|
+
* offset is the base-pair-offset in the displayed region, index is the index of the
|
|
757
|
+
* displayed region in the linear genome view
|
|
758
|
+
*
|
|
759
|
+
* @param start - object as `{start, end, offset, index}`
|
|
760
|
+
* @param end - object as `{start, end, offset, index}`
|
|
761
|
+
*/
|
|
762
|
+
moveTo(start, end) {
|
|
763
|
+
// find locations in the modellist
|
|
764
|
+
let bpSoFar = 0;
|
|
765
|
+
if (start.index === end.index) {
|
|
766
|
+
bpSoFar += end.offset - start.offset;
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
const s = self.displayedRegions[start.index];
|
|
770
|
+
bpSoFar += s.end - s.start - start.offset;
|
|
771
|
+
if (end.index - start.index >= 2) {
|
|
772
|
+
for (let i = start.index + 1; i < end.index; i += 1) {
|
|
773
|
+
bpSoFar +=
|
|
774
|
+
self.displayedRegions[i].end - self.displayedRegions[i].start;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
bpSoFar += end.offset;
|
|
778
|
+
}
|
|
779
|
+
const targetBpPerPx = bpSoFar /
|
|
780
|
+
(self.width -
|
|
781
|
+
self.interRegionPaddingWidth * (end.index - start.index));
|
|
782
|
+
const newBpPerPx = self.zoomTo(targetBpPerPx);
|
|
783
|
+
// If our target bpPerPx was smaller than the allowed minBpPerPx, adjust
|
|
784
|
+
// the scroll so the requested range is in the middle of the screen
|
|
785
|
+
let extraBp = 0;
|
|
786
|
+
if (targetBpPerPx < newBpPerPx) {
|
|
787
|
+
extraBp = ((newBpPerPx - targetBpPerPx) * self.width) / 2;
|
|
788
|
+
}
|
|
789
|
+
let bpToStart = -extraBp;
|
|
790
|
+
for (let i = 0; i < self.displayedRegions.length; i += 1) {
|
|
791
|
+
const region = self.displayedRegions[i];
|
|
792
|
+
if (start.index === i) {
|
|
793
|
+
bpToStart += start.offset;
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
bpToStart += region.end - region.start;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
self.scrollTo(Math.round(bpToStart / self.bpPerPx) +
|
|
801
|
+
self.interRegionPaddingWidth * start.index);
|
|
802
|
+
},
|
|
803
|
+
horizontalScroll(distance) {
|
|
804
|
+
const oldOffsetPx = self.offsetPx;
|
|
805
|
+
// newOffsetPx is the actual offset after the scroll is clamped
|
|
806
|
+
const newOffsetPx = self.scrollTo(self.offsetPx + distance);
|
|
807
|
+
return newOffsetPx - oldOffsetPx;
|
|
808
|
+
},
|
|
809
|
+
/**
|
|
810
|
+
* scrolls the view to center on the given bp. if that is not in any
|
|
811
|
+
* of the displayed regions, does nothing
|
|
812
|
+
* @param bp - basepair at which you want to center the view
|
|
813
|
+
* @param refName - refName of the displayedRegion you are centering at
|
|
814
|
+
* @param regionIndex - index of the displayedRegion
|
|
815
|
+
*/
|
|
816
|
+
centerAt(bp, refName, regionIndex) {
|
|
817
|
+
const centerPx = self.bpToPx({
|
|
818
|
+
refName,
|
|
819
|
+
coord: bp,
|
|
820
|
+
regionNumber: regionIndex,
|
|
821
|
+
});
|
|
822
|
+
if (centerPx) {
|
|
823
|
+
self.scrollTo(Math.round(centerPx.offsetPx - self.width / 2));
|
|
824
|
+
}
|
|
825
|
+
},
|
|
826
|
+
center() {
|
|
827
|
+
const centerBp = self.totalBp / 2;
|
|
828
|
+
self.scrollTo(Math.round(centerBp / self.bpPerPx - self.width / 2));
|
|
829
|
+
},
|
|
830
|
+
showAllRegions() {
|
|
831
|
+
self.zoomTo(self.maxBpPerPx);
|
|
832
|
+
this.center();
|
|
833
|
+
},
|
|
834
|
+
showAllRegionsInAssembly(assemblyName) {
|
|
835
|
+
const session = getSession(self);
|
|
836
|
+
const { assemblyManager } = session;
|
|
837
|
+
if (!assemblyName) {
|
|
838
|
+
const assemblyNames = [
|
|
839
|
+
...new Set(self.displayedRegions.map(region => region.assemblyName)),
|
|
840
|
+
];
|
|
841
|
+
if (assemblyNames.length > 1) {
|
|
842
|
+
session.notify(`Can't perform this with multiple assemblies currently`);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
;
|
|
846
|
+
[assemblyName] = assemblyNames;
|
|
847
|
+
}
|
|
848
|
+
const assembly = assemblyManager.get(assemblyName);
|
|
849
|
+
if (assembly) {
|
|
850
|
+
const { regions } = assembly;
|
|
851
|
+
if (regions) {
|
|
852
|
+
this.setDisplayedRegions(regions);
|
|
853
|
+
self.zoomTo(self.maxBpPerPx);
|
|
854
|
+
this.center();
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
setDraggingTrackId(idx) {
|
|
859
|
+
self.draggingTrackId = idx;
|
|
860
|
+
},
|
|
861
|
+
setScaleFactor(factor) {
|
|
862
|
+
self.scaleFactor = factor;
|
|
863
|
+
},
|
|
864
|
+
}))
|
|
865
|
+
.actions(self => {
|
|
866
|
+
let cancelLastAnimation = () => { };
|
|
867
|
+
function slide(viewWidths) {
|
|
868
|
+
const [animate, cancelAnimation] = springAnimate(self.offsetPx, self.offsetPx + self.width * viewWidths, self.scrollTo);
|
|
869
|
+
cancelLastAnimation();
|
|
870
|
+
cancelLastAnimation = cancelAnimation;
|
|
871
|
+
animate();
|
|
872
|
+
}
|
|
873
|
+
return { slide };
|
|
874
|
+
})
|
|
875
|
+
.actions(self => {
|
|
876
|
+
let cancelLastAnimation = () => { };
|
|
877
|
+
function zoom(targetBpPerPx) {
|
|
878
|
+
self.zoomTo(self.bpPerPx);
|
|
879
|
+
if (
|
|
880
|
+
// already zoomed all the way in
|
|
881
|
+
(targetBpPerPx < self.bpPerPx && self.bpPerPx === self.minBpPerPx) ||
|
|
882
|
+
// already zoomed all the way out
|
|
883
|
+
(targetBpPerPx > self.bpPerPx && self.bpPerPx === self.maxBpPerPx)) {
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
const factor = self.bpPerPx / targetBpPerPx;
|
|
887
|
+
const [animate, cancelAnimation] = springAnimate(1, factor, self.setScaleFactor, () => {
|
|
888
|
+
self.zoomTo(targetBpPerPx);
|
|
889
|
+
self.setScaleFactor(1);
|
|
890
|
+
});
|
|
891
|
+
cancelLastAnimation();
|
|
892
|
+
cancelLastAnimation = cancelAnimation;
|
|
893
|
+
animate();
|
|
894
|
+
}
|
|
895
|
+
return { zoom };
|
|
896
|
+
})
|
|
897
|
+
.views(self => ({
|
|
898
|
+
get canShowCytobands() {
|
|
899
|
+
return self.displayedRegions.length === 1 && this.anyCytobandsExist;
|
|
900
|
+
},
|
|
901
|
+
get showCytobands() {
|
|
902
|
+
return this.canShowCytobands && self.showCytobandsSetting;
|
|
903
|
+
},
|
|
904
|
+
get anyCytobandsExist() {
|
|
905
|
+
const { assemblyManager } = getSession(self);
|
|
906
|
+
const { assemblyNames } = self;
|
|
907
|
+
return assemblyNames.some(asm => { var _a, _b; return (_b = (_a = assemblyManager.get(asm)) === null || _a === void 0 ? void 0 : _a.cytobands) === null || _b === void 0 ? void 0 : _b.length; });
|
|
908
|
+
},
|
|
909
|
+
get cytobandOffset() {
|
|
910
|
+
return this.showCytobands
|
|
911
|
+
? measureText(self.displayedRegions[0].refName, 12) + 15
|
|
912
|
+
: 0;
|
|
913
|
+
},
|
|
914
|
+
}))
|
|
915
|
+
.views(self => ({
|
|
916
|
+
menuItems() {
|
|
917
|
+
const { canShowCytobands, showCytobands } = self;
|
|
918
|
+
const menuItems = [
|
|
919
|
+
{
|
|
920
|
+
label: 'Return to import form',
|
|
921
|
+
onClick: () => {
|
|
922
|
+
getSession(self).queueDialog(handleClose => [
|
|
923
|
+
ReturnToImportFormDialog,
|
|
924
|
+
{ model: self, handleClose },
|
|
925
|
+
]);
|
|
926
|
+
},
|
|
927
|
+
icon: FolderOpenIcon,
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
label: 'Export SVG',
|
|
931
|
+
icon: PhotoCameraIcon,
|
|
932
|
+
onClick: () => {
|
|
933
|
+
getSession(self).queueDialog(handleClose => [
|
|
934
|
+
ExportSvgDlg,
|
|
935
|
+
{ model: self, handleClose },
|
|
936
|
+
]);
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
label: 'Open track selector',
|
|
941
|
+
onClick: self.activateTrackSelector,
|
|
942
|
+
icon: TrackSelectorIcon,
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
label: 'Horizontally flip',
|
|
946
|
+
icon: SyncAltIcon,
|
|
947
|
+
onClick: self.horizontallyFlip,
|
|
948
|
+
},
|
|
949
|
+
{ type: 'divider' },
|
|
950
|
+
{
|
|
951
|
+
label: 'Show all regions in assembly',
|
|
952
|
+
icon: VisibilityIcon,
|
|
953
|
+
onClick: self.showAllRegionsInAssembly,
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
label: 'Show center line',
|
|
957
|
+
icon: VisibilityIcon,
|
|
958
|
+
type: 'checkbox',
|
|
959
|
+
checked: self.showCenterLine,
|
|
960
|
+
onClick: self.toggleCenterLine,
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
label: 'Show header',
|
|
964
|
+
icon: VisibilityIcon,
|
|
965
|
+
type: 'checkbox',
|
|
966
|
+
checked: !self.hideHeader,
|
|
967
|
+
onClick: self.toggleHeader,
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
label: 'Show header overview',
|
|
971
|
+
icon: VisibilityIcon,
|
|
972
|
+
type: 'checkbox',
|
|
973
|
+
checked: !self.hideHeaderOverview,
|
|
974
|
+
onClick: self.toggleHeaderOverview,
|
|
975
|
+
disabled: self.hideHeader,
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
label: 'Show no tracks active button',
|
|
979
|
+
icon: VisibilityIcon,
|
|
980
|
+
type: 'checkbox',
|
|
981
|
+
checked: !self.hideNoTracksActive,
|
|
982
|
+
onClick: self.toggleNoTracksActive,
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
label: 'Track labels',
|
|
986
|
+
icon: LabelIcon,
|
|
987
|
+
subMenu: [
|
|
988
|
+
{
|
|
989
|
+
label: 'Overlapping',
|
|
990
|
+
icon: VisibilityIcon,
|
|
991
|
+
type: 'radio',
|
|
992
|
+
checked: self.trackLabels === 'overlapping',
|
|
993
|
+
onClick: () => self.setTrackLabels('overlapping'),
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
label: 'Offset',
|
|
997
|
+
icon: VisibilityIcon,
|
|
998
|
+
type: 'radio',
|
|
999
|
+
checked: self.trackLabels === 'offset',
|
|
1000
|
+
onClick: () => self.setTrackLabels('offset'),
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
label: 'Hidden',
|
|
1004
|
+
icon: VisibilityIcon,
|
|
1005
|
+
type: 'radio',
|
|
1006
|
+
checked: self.trackLabels === 'hidden',
|
|
1007
|
+
onClick: () => self.setTrackLabels('hidden'),
|
|
1008
|
+
},
|
|
1009
|
+
],
|
|
1010
|
+
},
|
|
1011
|
+
...(canShowCytobands
|
|
1012
|
+
? [
|
|
1013
|
+
{
|
|
1014
|
+
label: showCytobands ? 'Hide ideogram' : 'Show ideograms',
|
|
1015
|
+
onClick: () => {
|
|
1016
|
+
self.setShowCytobands(!showCytobands);
|
|
1017
|
+
},
|
|
1018
|
+
},
|
|
1019
|
+
]
|
|
1020
|
+
: []),
|
|
1021
|
+
];
|
|
1022
|
+
// add track's view level menu options
|
|
1023
|
+
for (const [key, value] of self.trackTypeActions.entries()) {
|
|
1024
|
+
if (value.length) {
|
|
1025
|
+
menuItems.push({ type: 'divider' }, { type: 'subHeader', label: key });
|
|
1026
|
+
value.forEach(action => {
|
|
1027
|
+
menuItems.push(action);
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return menuItems;
|
|
1032
|
+
},
|
|
1033
|
+
}))
|
|
1034
|
+
.views(self => {
|
|
1035
|
+
let currentlyCalculatedStaticBlocks;
|
|
1036
|
+
let stringifiedCurrentlyCalculatedStaticBlocks = '';
|
|
1037
|
+
return {
|
|
1038
|
+
get staticBlocks() {
|
|
1039
|
+
const ret = calculateStaticBlocks(self);
|
|
1040
|
+
const sret = JSON.stringify(ret);
|
|
1041
|
+
if (stringifiedCurrentlyCalculatedStaticBlocks !== sret) {
|
|
1042
|
+
currentlyCalculatedStaticBlocks = ret;
|
|
1043
|
+
stringifiedCurrentlyCalculatedStaticBlocks = sret;
|
|
1044
|
+
}
|
|
1045
|
+
return currentlyCalculatedStaticBlocks;
|
|
1046
|
+
},
|
|
1047
|
+
get dynamicBlocks() {
|
|
1048
|
+
return calculateDynamicBlocks(self);
|
|
1049
|
+
},
|
|
1050
|
+
get roundedDynamicBlocks() {
|
|
1051
|
+
return this.dynamicBlocks.contentBlocks.map(block => {
|
|
1052
|
+
return {
|
|
1053
|
+
...block,
|
|
1054
|
+
start: Math.floor(block.start),
|
|
1055
|
+
end: Math.ceil(block.end),
|
|
1056
|
+
};
|
|
1057
|
+
});
|
|
1058
|
+
},
|
|
1059
|
+
get visibleLocStrings() {
|
|
1060
|
+
return calculateVisibleLocStrings(this.dynamicBlocks.contentBlocks);
|
|
1061
|
+
},
|
|
1062
|
+
get coarseVisibleLocStrings() {
|
|
1063
|
+
return calculateVisibleLocStrings(self.coarseDynamicBlocks);
|
|
1064
|
+
},
|
|
1065
|
+
};
|
|
1066
|
+
})
|
|
1067
|
+
.actions(self => ({
|
|
1068
|
+
// this "clears the view" and makes the view return to the import form
|
|
1069
|
+
clearView() {
|
|
1070
|
+
self.setDisplayedRegions([]);
|
|
1071
|
+
self.tracks.clear();
|
|
1072
|
+
// it is necessary to run these after setting displayed regions empty
|
|
1073
|
+
// or else model.offsetPx gets set to Infinity and breaks
|
|
1074
|
+
// mobx-state-tree snapshot
|
|
1075
|
+
self.scrollTo(0);
|
|
1076
|
+
self.zoomTo(10);
|
|
1077
|
+
},
|
|
1078
|
+
setCoarseDynamicBlocks(blocks) {
|
|
1079
|
+
self.coarseDynamicBlocks = blocks.contentBlocks;
|
|
1080
|
+
self.coarseTotalBp = blocks.totalBp;
|
|
1081
|
+
},
|
|
1082
|
+
afterAttach() {
|
|
1083
|
+
addDisposer(self, autorun(() => {
|
|
1084
|
+
if (self.initialized) {
|
|
1085
|
+
this.setCoarseDynamicBlocks(self.dynamicBlocks);
|
|
1086
|
+
}
|
|
1087
|
+
}, { delay: 150 }));
|
|
1088
|
+
},
|
|
1089
|
+
}))
|
|
1090
|
+
.actions(self => ({
|
|
1091
|
+
async exportSvg(opts = {}) {
|
|
1092
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1093
|
+
const html = await renderToSvg(self, opts);
|
|
1094
|
+
const blob = new Blob([html], { type: 'image/svg+xml' });
|
|
1095
|
+
saveAs(blob, opts.filename || 'image.svg');
|
|
1096
|
+
},
|
|
1097
|
+
}))
|
|
1098
|
+
.views(self => ({
|
|
1099
|
+
rubberBandMenuItems() {
|
|
1100
|
+
return [
|
|
1101
|
+
{
|
|
1102
|
+
label: 'Zoom to region',
|
|
1103
|
+
icon: ZoomInIcon,
|
|
1104
|
+
onClick: () => {
|
|
1105
|
+
const { leftOffset, rightOffset } = self;
|
|
1106
|
+
if (leftOffset && rightOffset) {
|
|
1107
|
+
self.moveTo(leftOffset, rightOffset);
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
label: 'Get sequence',
|
|
1113
|
+
icon: MenuOpenIcon,
|
|
1114
|
+
onClick: () => {
|
|
1115
|
+
self.setSequenceDialogOpen(true);
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
];
|
|
1119
|
+
},
|
|
1120
|
+
}));
|
|
1121
|
+
}
|
|
1122
|
+
export { renderToSvg, RefNameAutocomplete, SearchBox };
|
|
1123
|
+
export { default as ReactComponent } from './components/LinearGenomeView';
|
|
1124
|
+
//# sourceMappingURL=index.js.map
|