@jbrowse/plugin-linear-genome-view 2.1.7 → 2.2.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/LinearBlocks.d.ts +2 -2
- package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +2 -22
- package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +146 -1
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +600 -464
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -1
- package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +19 -1
- package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -1
- package/dist/BasicTrack/configSchema.d.ts +3 -0
- package/dist/BasicTrack/configSchema.js +18 -0
- package/dist/BasicTrack/configSchema.js.map +1 -0
- package/dist/BasicTrack/index.d.ts +3 -0
- package/dist/BasicTrack/index.js +18 -0
- package/dist/BasicTrack/index.js.map +1 -0
- package/dist/FeatureTrack/configSchema.d.ts +3 -0
- package/dist/FeatureTrack/configSchema.js +21 -0
- package/dist/FeatureTrack/configSchema.js.map +1 -0
- package/dist/FeatureTrack/index.d.ts +3 -0
- package/dist/FeatureTrack/index.js +18 -0
- package/dist/FeatureTrack/index.js.map +1 -0
- package/dist/LinearBareDisplay/configSchema.d.ts +5 -1
- package/dist/LinearBareDisplay/configSchema.js +12 -1
- package/dist/LinearBareDisplay/configSchema.js.map +1 -1
- package/dist/LinearBareDisplay/model.d.ts +16 -0
- package/dist/LinearBareDisplay/model.js +16 -0
- package/dist/LinearBareDisplay/model.js.map +1 -1
- package/dist/LinearBasicDisplay/configSchema.d.ts +5 -1
- package/dist/LinearBasicDisplay/configSchema.js +16 -1
- package/dist/LinearBasicDisplay/configSchema.js.map +1 -1
- package/dist/LinearBasicDisplay/model.d.ts +77 -6
- package/dist/LinearBasicDisplay/model.js +167 -111
- package/dist/LinearBasicDisplay/model.js.map +1 -1
- package/dist/LinearGenomeView/components/ImportForm.js +34 -28
- package/dist/LinearGenomeView/components/ImportForm.js.map +1 -1
- package/dist/LinearGenomeView/components/LinearGenomeView.js +1 -21
- package/dist/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
- package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js +7 -5
- package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
- package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +26 -34
- package/dist/LinearGenomeView/index.d.ts +189 -15
- package/dist/LinearGenomeView/index.js +266 -27
- package/dist/LinearGenomeView/index.js.map +1 -1
- package/dist/index.d.ts +12 -84
- package/dist/index.js +4 -25
- package/dist/index.js.map +1 -1
- package/esm/BaseLinearDisplay/components/LinearBlocks.d.ts +2 -2
- package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +2 -22
- package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +146 -1
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js +600 -464
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -1
- package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +19 -1
- package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -1
- package/esm/BasicTrack/configSchema.d.ts +3 -0
- package/esm/BasicTrack/configSchema.js +16 -0
- package/esm/BasicTrack/configSchema.js.map +1 -0
- package/esm/BasicTrack/index.d.ts +3 -0
- package/esm/BasicTrack/index.js +13 -0
- package/esm/BasicTrack/index.js.map +1 -0
- package/esm/FeatureTrack/configSchema.d.ts +3 -0
- package/esm/FeatureTrack/configSchema.js +19 -0
- package/esm/FeatureTrack/configSchema.js.map +1 -0
- package/esm/FeatureTrack/index.d.ts +3 -0
- package/esm/FeatureTrack/index.js +13 -0
- package/esm/FeatureTrack/index.js.map +1 -0
- package/esm/LinearBareDisplay/configSchema.d.ts +5 -1
- package/esm/LinearBareDisplay/configSchema.js +14 -2
- package/esm/LinearBareDisplay/configSchema.js.map +1 -1
- package/esm/LinearBareDisplay/model.d.ts +16 -0
- package/esm/LinearBareDisplay/model.js +16 -0
- package/esm/LinearBareDisplay/model.js.map +1 -1
- package/esm/LinearBasicDisplay/configSchema.d.ts +5 -1
- package/esm/LinearBasicDisplay/configSchema.js +18 -2
- package/esm/LinearBasicDisplay/configSchema.js.map +1 -1
- package/esm/LinearBasicDisplay/model.d.ts +77 -6
- package/esm/LinearBasicDisplay/model.js +167 -111
- package/esm/LinearBasicDisplay/model.js.map +1 -1
- package/esm/LinearGenomeView/components/ImportForm.js +36 -30
- package/esm/LinearGenomeView/components/ImportForm.js.map +1 -1
- package/esm/LinearGenomeView/components/LinearGenomeView.js +2 -22
- package/esm/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
- package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js +7 -5
- package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
- package/esm/LinearGenomeView/components/OverviewScaleBar.d.ts +26 -34
- package/esm/LinearGenomeView/index.d.ts +189 -15
- package/esm/LinearGenomeView/index.js +266 -27
- package/esm/LinearGenomeView/index.js.map +1 -1
- package/esm/index.d.ts +12 -84
- package/esm/index.js +4 -25
- package/esm/index.js.map +1 -1
- package/package.json +2 -2
- package/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx +2 -24
- package/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +695 -555
- package/src/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.ts +20 -1
- package/src/BasicTrack/configSchema.ts +23 -0
- package/src/BasicTrack/index.ts +22 -0
- package/src/FeatureTrack/configSchema.ts +27 -0
- package/src/FeatureTrack/index.ts +21 -0
- package/src/LinearBareDisplay/configSchema.ts +15 -2
- package/src/LinearBareDisplay/model.ts +16 -0
- package/src/LinearBasicDisplay/configSchema.ts +19 -2
- package/src/LinearBasicDisplay/model.ts +63 -11
- package/src/LinearGenomeView/components/ImportForm.tsx +79 -63
- package/src/LinearGenomeView/components/LinearGenomeView.tsx +2 -26
- package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +21 -18
- package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap +204 -204
- package/src/LinearGenomeView/index.test.ts +33 -26
- package/src/LinearGenomeView/index.tsx +317 -60
- package/src/index.ts +6 -46
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import TooLargeMessage from './TooLargeMessage'
|
|
4
3
|
import { BaseDisplay } from '@jbrowse/core/pluggableElementTypes/models'
|
|
5
4
|
import { getConf } from '@jbrowse/core/configuration'
|
|
6
5
|
import { MenuItem } from '@jbrowse/core/ui'
|
|
@@ -24,9 +23,12 @@ import {
|
|
|
24
23
|
} from '@jbrowse/core/util/tracks'
|
|
25
24
|
import { autorun } from 'mobx'
|
|
26
25
|
import { addDisposer, isAlive, types, Instance } from 'mobx-state-tree'
|
|
26
|
+
|
|
27
27
|
// icons
|
|
28
28
|
import MenuOpenIcon from '@mui/icons-material/MenuOpen'
|
|
29
29
|
|
|
30
|
+
// locals
|
|
31
|
+
import TooLargeMessage from './TooLargeMessage'
|
|
30
32
|
import { LinearGenomeViewModel, ExportSvgOptions } from '../../LinearGenomeView'
|
|
31
33
|
import { Tooltip } from '../components/BaseLinearDisplay'
|
|
32
34
|
import BlockState, { renderBlockData } from './serverSideRenderedBlock'
|
|
@@ -64,598 +66,736 @@ function getDisplayStr(totalBytes: number) {
|
|
|
64
66
|
const minDisplayHeight = 20
|
|
65
67
|
const defaultDisplayHeight = 100
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
/**
|
|
70
|
+
* #stateModel BaseLinearDisplay
|
|
71
|
+
* extends `BaseDisplay`
|
|
72
|
+
*/
|
|
73
|
+
function stateModelFactory() {
|
|
74
|
+
return types
|
|
75
|
+
.compose(
|
|
76
|
+
'BaseLinearDisplay',
|
|
77
|
+
BaseDisplay,
|
|
78
|
+
types.model({
|
|
79
|
+
/**
|
|
80
|
+
* #property
|
|
81
|
+
*/
|
|
82
|
+
height: types.optional(
|
|
83
|
+
types.refinement(
|
|
84
|
+
'displayHeight',
|
|
85
|
+
types.number,
|
|
86
|
+
n => n >= minDisplayHeight,
|
|
87
|
+
),
|
|
88
|
+
defaultDisplayHeight,
|
|
77
89
|
),
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return Tooltip as unknown as React.FC
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* returns a string feature ID if the globally-selected object
|
|
122
|
-
* is probably a feature
|
|
123
|
-
*/
|
|
124
|
-
get selectedFeatureId() {
|
|
125
|
-
if (isAlive(self)) {
|
|
126
|
-
const { selection } = getSession(self)
|
|
127
|
-
// does it quack like a feature?
|
|
128
|
-
if (isFeature(selection)) {
|
|
129
|
-
return selection.id()
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return undefined
|
|
133
|
-
},
|
|
134
|
-
/**
|
|
135
|
-
* if a display-level message should be displayed instead of the blocks,
|
|
136
|
-
* make this return a react component
|
|
137
|
-
*/
|
|
138
|
-
get DisplayMessageComponent() {
|
|
139
|
-
return undefined as undefined | React.FC<any>
|
|
140
|
-
},
|
|
141
|
-
}))
|
|
142
|
-
.views(self => ({
|
|
143
|
-
/**
|
|
144
|
-
* a CompositeMap of `featureId -> feature obj` that
|
|
145
|
-
* just looks in all the block data for that feature
|
|
146
|
-
*/
|
|
147
|
-
get features() {
|
|
148
|
-
const featureMaps = []
|
|
149
|
-
for (const block of self.blockState.values()) {
|
|
150
|
-
if (block && block.features) {
|
|
151
|
-
featureMaps.push(block.features)
|
|
90
|
+
/**
|
|
91
|
+
* #property
|
|
92
|
+
* updated via autorun
|
|
93
|
+
*/
|
|
94
|
+
blockState: types.map(BlockState),
|
|
95
|
+
/**
|
|
96
|
+
* #property
|
|
97
|
+
*/
|
|
98
|
+
userBpPerPxLimit: types.maybe(types.number),
|
|
99
|
+
/**
|
|
100
|
+
* #property
|
|
101
|
+
*/
|
|
102
|
+
userByteSizeLimit: types.maybe(types.number),
|
|
103
|
+
}),
|
|
104
|
+
)
|
|
105
|
+
.volatile(() => ({
|
|
106
|
+
currBpPerPx: 0,
|
|
107
|
+
message: '',
|
|
108
|
+
featureIdUnderMouse: undefined as undefined | string,
|
|
109
|
+
contextMenuFeature: undefined as undefined | Feature,
|
|
110
|
+
scrollTop: 0,
|
|
111
|
+
estimatedRegionStatsP: undefined as undefined | Promise<Stats>,
|
|
112
|
+
estimatedRegionStats: undefined as undefined | Stats,
|
|
113
|
+
}))
|
|
114
|
+
.views(self => ({
|
|
115
|
+
/**
|
|
116
|
+
* #getter
|
|
117
|
+
*/
|
|
118
|
+
get blockType(): 'staticBlocks' | 'dynamicBlocks' {
|
|
119
|
+
return 'staticBlocks'
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* #getter
|
|
123
|
+
*/
|
|
124
|
+
get blockDefinitions() {
|
|
125
|
+
const { blockType } = this
|
|
126
|
+
const view = getContainingView(self) as LGV
|
|
127
|
+
if (!view.initialized) {
|
|
128
|
+
throw new Error('view not initialized yet')
|
|
152
129
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
130
|
+
return view[blockType]
|
|
131
|
+
},
|
|
132
|
+
}))
|
|
133
|
+
.views(self => ({
|
|
134
|
+
/**
|
|
135
|
+
* #getter
|
|
136
|
+
* how many milliseconds to wait for the display to
|
|
137
|
+
* "settle" before re-rendering a block
|
|
138
|
+
*/
|
|
139
|
+
get renderDelay() {
|
|
140
|
+
return 50
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* #getter
|
|
145
|
+
*/
|
|
146
|
+
get TooltipComponent(): React.FC<any> {
|
|
147
|
+
return Tooltip as unknown as React.FC
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* #getter
|
|
152
|
+
* returns a string feature ID if the globally-selected object
|
|
153
|
+
* is probably a feature
|
|
154
|
+
*/
|
|
155
|
+
get selectedFeatureId() {
|
|
156
|
+
if (isAlive(self)) {
|
|
157
|
+
const { selection } = getSession(self)
|
|
158
|
+
// does it quack like a feature?
|
|
159
|
+
if (isFeature(selection)) {
|
|
160
|
+
return selection.id()
|
|
161
|
+
}
|
|
177
162
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
self.estimatedRegionStats?.fetchSizeLimit ||
|
|
202
|
-
(getConf(self, 'fetchSizeLimit') as number)
|
|
203
|
-
)
|
|
204
|
-
},
|
|
205
|
-
}))
|
|
206
|
-
.actions(self => ({
|
|
207
|
-
// base display reload does nothing, see specialized displays for details
|
|
208
|
-
setMessage(message: string) {
|
|
209
|
-
self.message = message
|
|
210
|
-
},
|
|
211
|
-
|
|
212
|
-
afterAttach() {
|
|
213
|
-
// watch the parent's blocks to update our block state when they change,
|
|
214
|
-
// then we recreate the blocks on our own model (creating and deleting to
|
|
215
|
-
// match the parent blocks)
|
|
216
|
-
const blockWatchDisposer = autorun(() => {
|
|
217
|
-
const blocksPresent: { [key: string]: boolean } = {}
|
|
218
|
-
const view = getContainingView(self) as LGV
|
|
219
|
-
if (view.initialized) {
|
|
220
|
-
self.blockDefinitions.contentBlocks.forEach(block => {
|
|
221
|
-
blocksPresent[block.key] = true
|
|
222
|
-
if (!self.blockState.has(block.key)) {
|
|
223
|
-
this.addBlock(block.key, block)
|
|
224
|
-
}
|
|
225
|
-
})
|
|
226
|
-
self.blockState.forEach((_, key) => {
|
|
227
|
-
if (!blocksPresent[key]) {
|
|
228
|
-
this.deleteBlock(key)
|
|
229
|
-
}
|
|
230
|
-
})
|
|
163
|
+
return undefined
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* #getter
|
|
167
|
+
* if a display-level message should be displayed instead of the blocks,
|
|
168
|
+
* make this return a react component
|
|
169
|
+
*/
|
|
170
|
+
get DisplayMessageComponent() {
|
|
171
|
+
return undefined as undefined | React.FC<any>
|
|
172
|
+
},
|
|
173
|
+
}))
|
|
174
|
+
.views(self => ({
|
|
175
|
+
/**
|
|
176
|
+
* #getter
|
|
177
|
+
* a CompositeMap of `featureId -> feature obj` that
|
|
178
|
+
* just looks in all the block data for that feature
|
|
179
|
+
*/
|
|
180
|
+
get features() {
|
|
181
|
+
const featureMaps = []
|
|
182
|
+
for (const block of self.blockState.values()) {
|
|
183
|
+
if (block && block.features) {
|
|
184
|
+
featureMaps.push(block.features)
|
|
185
|
+
}
|
|
231
186
|
}
|
|
232
|
-
|
|
187
|
+
return new CompositeMap(featureMaps)
|
|
188
|
+
},
|
|
233
189
|
|
|
234
|
-
|
|
235
|
-
|
|
190
|
+
/**
|
|
191
|
+
* #getter
|
|
192
|
+
*/
|
|
193
|
+
get featureUnderMouse() {
|
|
194
|
+
const feat = self.featureIdUnderMouse
|
|
195
|
+
return feat ? this.features.get(feat) : undefined
|
|
196
|
+
},
|
|
236
197
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
filters?: string[]
|
|
198
|
+
/**
|
|
199
|
+
* #getter
|
|
200
|
+
*/
|
|
201
|
+
getFeatureOverlapping(blockKey: string, x: number, y: number) {
|
|
202
|
+
return self.blockState.get(blockKey)?.layout?.getByCoord(x, y)
|
|
243
203
|
},
|
|
244
|
-
) {
|
|
245
|
-
if (self.estimatedRegionStatsP) {
|
|
246
|
-
return self.estimatedRegionStatsP
|
|
247
|
-
}
|
|
248
204
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (
|
|
264
|
-
|
|
205
|
+
/**
|
|
206
|
+
* #getter
|
|
207
|
+
*/
|
|
208
|
+
getFeatureByID(blockKey: string, id: string): LayoutRecord | undefined {
|
|
209
|
+
return self.blockState.get(blockKey)?.layout?.getByID(id)
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* #getter
|
|
214
|
+
*/
|
|
215
|
+
searchFeatureByID(id: string): LayoutRecord | undefined {
|
|
216
|
+
let ret
|
|
217
|
+
self.blockState.forEach(block => {
|
|
218
|
+
const val = block?.layout?.getByID(id)
|
|
219
|
+
if (val) {
|
|
220
|
+
ret = val
|
|
265
221
|
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
}
|
|
222
|
+
})
|
|
223
|
+
return ret
|
|
224
|
+
},
|
|
269
225
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return self.estimatedRegionStatsP
|
|
278
|
-
},
|
|
279
|
-
setRegionStatsP(p?: Promise<Stats>) {
|
|
280
|
-
self.estimatedRegionStatsP = p
|
|
281
|
-
},
|
|
282
|
-
setRegionStats(estimatedRegionStats?: Stats) {
|
|
283
|
-
self.estimatedRegionStats = estimatedRegionStats
|
|
284
|
-
},
|
|
285
|
-
clearRegionStats() {
|
|
286
|
-
self.estimatedRegionStatsP = undefined
|
|
287
|
-
self.estimatedRegionStats = undefined
|
|
288
|
-
},
|
|
289
|
-
setHeight(displayHeight: number) {
|
|
290
|
-
if (displayHeight > minDisplayHeight) {
|
|
291
|
-
self.height = displayHeight
|
|
292
|
-
} else {
|
|
293
|
-
self.height = minDisplayHeight
|
|
294
|
-
}
|
|
295
|
-
return self.height
|
|
296
|
-
},
|
|
297
|
-
resizeHeight(distance: number) {
|
|
298
|
-
const oldHeight = self.height
|
|
299
|
-
const newHeight = this.setHeight(self.height + distance)
|
|
300
|
-
return newHeight - oldHeight
|
|
301
|
-
},
|
|
302
|
-
|
|
303
|
-
setScrollTop(scrollTop: number) {
|
|
304
|
-
self.scrollTop = scrollTop
|
|
305
|
-
},
|
|
306
|
-
|
|
307
|
-
updateStatsLimit(stats: Stats) {
|
|
308
|
-
const view = getContainingView(self) as LGV
|
|
309
|
-
if (stats.bytes) {
|
|
310
|
-
self.userByteSizeLimit = stats.bytes
|
|
311
|
-
} else {
|
|
312
|
-
self.userBpPerPxLimit = view.bpPerPx
|
|
313
|
-
}
|
|
314
|
-
},
|
|
226
|
+
/**
|
|
227
|
+
* #getter
|
|
228
|
+
*/
|
|
229
|
+
get currentBytesRequested() {
|
|
230
|
+
return self.estimatedRegionStats?.bytes || 0
|
|
231
|
+
},
|
|
315
232
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
233
|
+
/**
|
|
234
|
+
* #getter
|
|
235
|
+
*/
|
|
236
|
+
get currentFeatureScreenDensity() {
|
|
237
|
+
const view = getContainingView(self) as LGV
|
|
238
|
+
return (self.estimatedRegionStats?.featureDensity || 0) * view.bpPerPx
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* #getter
|
|
243
|
+
*/
|
|
244
|
+
get maxFeatureScreenDensity() {
|
|
245
|
+
return getConf(self, 'maxFeatureScreenDensity')
|
|
246
|
+
},
|
|
247
|
+
/**
|
|
248
|
+
* #getter
|
|
249
|
+
*/
|
|
250
|
+
get estimatedStatsReady() {
|
|
251
|
+
return !!self.estimatedRegionStats
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* #getter
|
|
256
|
+
*/
|
|
257
|
+
get maxAllowableBytes() {
|
|
258
|
+
return (
|
|
259
|
+
self.userByteSizeLimit ||
|
|
260
|
+
self.estimatedRegionStats?.fetchSizeLimit ||
|
|
261
|
+
(getConf(self, 'fetchSizeLimit') as number)
|
|
342
262
|
)
|
|
263
|
+
},
|
|
264
|
+
}))
|
|
265
|
+
.actions(self => ({
|
|
266
|
+
/**
|
|
267
|
+
* #action
|
|
268
|
+
*/
|
|
269
|
+
setMessage(message: string) {
|
|
270
|
+
self.message = message
|
|
271
|
+
},
|
|
343
272
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
// - and bytes > max allowed bytes || curr density>max density
|
|
369
|
-
get regionTooLarge() {
|
|
370
|
-
const view = getContainingView(self) as LGV
|
|
371
|
-
if (!self.estimatedStatsReady || view.dynamicBlocks.totalBp < 20_000) {
|
|
372
|
-
return false
|
|
373
|
-
}
|
|
374
|
-
const bpLimitOrDensity = self.userBpPerPxLimit
|
|
375
|
-
? view.bpPerPx > self.userBpPerPxLimit
|
|
376
|
-
: self.currentFeatureScreenDensity > self.maxFeatureScreenDensity
|
|
377
|
-
|
|
378
|
-
return (
|
|
379
|
-
self.currentBytesRequested > self.maxAllowableBytes || bpLimitOrDensity
|
|
380
|
-
)
|
|
381
|
-
},
|
|
382
|
-
|
|
383
|
-
// only shows a message of bytes requested is defined, the feature density
|
|
384
|
-
// based stats don't produce any helpful message besides to zoom in
|
|
385
|
-
get regionTooLargeReason() {
|
|
386
|
-
const req = self.currentBytesRequested
|
|
387
|
-
const max = self.maxAllowableBytes
|
|
388
|
-
|
|
389
|
-
return req && req > max
|
|
390
|
-
? `Requested too much data (${getDisplayStr(req)})`
|
|
391
|
-
: ''
|
|
392
|
-
},
|
|
393
|
-
}))
|
|
394
|
-
.actions(self => {
|
|
395
|
-
const { reload: superReload } = self
|
|
396
|
-
|
|
397
|
-
return {
|
|
398
|
-
async reload() {
|
|
399
|
-
self.setError()
|
|
400
|
-
const aborter = new AbortController()
|
|
401
|
-
const view = getContainingView(self) as LGV
|
|
273
|
+
afterAttach() {
|
|
274
|
+
// watch the parent's blocks to update our block state when they change,
|
|
275
|
+
// then we recreate the blocks on our own model (creating and deleting to
|
|
276
|
+
// match the parent blocks)
|
|
277
|
+
const blockWatchDisposer = autorun(() => {
|
|
278
|
+
const blocksPresent: { [key: string]: boolean } = {}
|
|
279
|
+
const view = getContainingView(self) as LGV
|
|
280
|
+
if (view.initialized) {
|
|
281
|
+
self.blockDefinitions.contentBlocks.forEach(block => {
|
|
282
|
+
blocksPresent[block.key] = true
|
|
283
|
+
if (!self.blockState.has(block.key)) {
|
|
284
|
+
this.addBlock(block.key, block)
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
self.blockState.forEach((_, key) => {
|
|
288
|
+
if (!blocksPresent[key]) {
|
|
289
|
+
this.deleteBlock(key)
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
addDisposer(self, blockWatchDisposer)
|
|
296
|
+
},
|
|
402
297
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
298
|
+
/**
|
|
299
|
+
* #action
|
|
300
|
+
*/
|
|
301
|
+
estimateRegionsStats(
|
|
302
|
+
regions: Region[],
|
|
303
|
+
opts: {
|
|
304
|
+
headers?: Record<string, string>
|
|
305
|
+
signal?: AbortSignal
|
|
306
|
+
filters?: string[]
|
|
307
|
+
},
|
|
308
|
+
) {
|
|
309
|
+
if (self.estimatedRegionStatsP) {
|
|
310
|
+
return self.estimatedRegionStatsP
|
|
407
311
|
}
|
|
408
312
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
313
|
+
const { rpcManager } = getSession(self)
|
|
314
|
+
const { adapterConfig } = self
|
|
315
|
+
if (!adapterConfig) {
|
|
316
|
+
// A track extending the base track might not have an adapter config
|
|
317
|
+
// e.g. Apollo tracks don't use adapters
|
|
318
|
+
return Promise.resolve({})
|
|
319
|
+
}
|
|
320
|
+
const sessionId = getRpcSessionId(self)
|
|
321
|
+
|
|
322
|
+
const params = {
|
|
323
|
+
sessionId,
|
|
324
|
+
regions,
|
|
325
|
+
adapterConfig,
|
|
326
|
+
statusCallback: (message: string) => {
|
|
327
|
+
if (isAlive(self)) {
|
|
328
|
+
this.setMessage(message)
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
...opts,
|
|
332
|
+
}
|
|
415
333
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
334
|
+
self.estimatedRegionStatsP = rpcManager
|
|
335
|
+
.call(sessionId, 'CoreEstimateRegionStats', params)
|
|
336
|
+
.catch(e => {
|
|
337
|
+
this.setRegionStatsP(undefined)
|
|
338
|
+
throw e
|
|
339
|
+
}) as Promise<Stats>
|
|
340
|
+
|
|
341
|
+
return self.estimatedRegionStatsP
|
|
342
|
+
},
|
|
343
|
+
/**
|
|
344
|
+
* #action
|
|
345
|
+
*/
|
|
346
|
+
setRegionStatsP(p?: Promise<Stats>) {
|
|
347
|
+
self.estimatedRegionStatsP = p
|
|
348
|
+
},
|
|
349
|
+
/**
|
|
350
|
+
* #action
|
|
351
|
+
*/
|
|
352
|
+
setRegionStats(estimatedRegionStats?: Stats) {
|
|
353
|
+
self.estimatedRegionStats = estimatedRegionStats
|
|
354
|
+
},
|
|
355
|
+
/**
|
|
356
|
+
* #action
|
|
357
|
+
*/
|
|
358
|
+
clearRegionStats() {
|
|
359
|
+
self.estimatedRegionStatsP = undefined
|
|
360
|
+
self.estimatedRegionStats = undefined
|
|
361
|
+
},
|
|
362
|
+
/**
|
|
363
|
+
* #action
|
|
364
|
+
*/
|
|
365
|
+
setHeight(displayHeight: number) {
|
|
366
|
+
if (displayHeight > minDisplayHeight) {
|
|
367
|
+
self.height = displayHeight
|
|
368
|
+
} else {
|
|
369
|
+
self.height = minDisplayHeight
|
|
425
370
|
}
|
|
371
|
+
return self.height
|
|
372
|
+
},
|
|
373
|
+
/**
|
|
374
|
+
* #action
|
|
375
|
+
*/
|
|
376
|
+
resizeHeight(distance: number) {
|
|
377
|
+
const oldHeight = self.height
|
|
378
|
+
const newHeight = this.setHeight(self.height + distance)
|
|
379
|
+
return newHeight - oldHeight
|
|
426
380
|
},
|
|
427
|
-
afterAttach() {
|
|
428
|
-
// this autorun performs stats estimation
|
|
429
|
-
//
|
|
430
|
-
// the chain of events calls estimateRegionsStats against the data
|
|
431
|
-
// adapter which by default uses featureDensity, but can also respond
|
|
432
|
-
// with a byte size estimate and fetch size limit (data adapter can
|
|
433
|
-
// define what is too much data)
|
|
434
|
-
addDisposer(
|
|
435
|
-
self,
|
|
436
|
-
autorun(
|
|
437
|
-
async () => {
|
|
438
|
-
try {
|
|
439
|
-
const aborter = new AbortController()
|
|
440
|
-
const view = getContainingView(self) as LGV
|
|
441
|
-
|
|
442
|
-
// extra check for contentBlocks.length
|
|
443
|
-
// https://github.com/GMOD/jbrowse-components/issues/2694
|
|
444
|
-
if (
|
|
445
|
-
!view.initialized ||
|
|
446
|
-
!view.staticBlocks.contentBlocks.length
|
|
447
|
-
) {
|
|
448
|
-
return
|
|
449
|
-
}
|
|
450
381
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
382
|
+
/**
|
|
383
|
+
* #action
|
|
384
|
+
*/
|
|
385
|
+
setScrollTop(scrollTop: number) {
|
|
386
|
+
self.scrollTop = scrollTop
|
|
387
|
+
},
|
|
457
388
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
389
|
+
/**
|
|
390
|
+
* #action
|
|
391
|
+
*/
|
|
392
|
+
updateStatsLimit(stats: Stats) {
|
|
393
|
+
const view = getContainingView(self) as LGV
|
|
394
|
+
if (stats.bytes) {
|
|
395
|
+
self.userByteSizeLimit = stats.bytes
|
|
396
|
+
} else {
|
|
397
|
+
self.userBpPerPxLimit = view.bpPerPx
|
|
398
|
+
}
|
|
399
|
+
},
|
|
462
400
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
self.setRegionStats(estimatedRegionStats)
|
|
474
|
-
}
|
|
475
|
-
} catch (e) {
|
|
476
|
-
if (!isAbortException(e) && isAlive(self)) {
|
|
477
|
-
console.error(e)
|
|
478
|
-
self.setError(e)
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
},
|
|
482
|
-
{ delay: 500 },
|
|
483
|
-
),
|
|
401
|
+
/**
|
|
402
|
+
* #action
|
|
403
|
+
*/
|
|
404
|
+
addBlock(key: string, block: BaseBlock) {
|
|
405
|
+
self.blockState.set(
|
|
406
|
+
key,
|
|
407
|
+
BlockState.create({
|
|
408
|
+
key,
|
|
409
|
+
region: block.toRegion(),
|
|
410
|
+
}),
|
|
484
411
|
)
|
|
485
412
|
},
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
},
|
|
508
|
-
|
|
509
|
-
contextMenuItems() {
|
|
510
|
-
return self.contextMenuFeature
|
|
511
|
-
? [
|
|
413
|
+
/**
|
|
414
|
+
* #action
|
|
415
|
+
*/
|
|
416
|
+
setCurrBpPerPx(n: number) {
|
|
417
|
+
self.currBpPerPx = n
|
|
418
|
+
},
|
|
419
|
+
/**
|
|
420
|
+
* #action
|
|
421
|
+
*/
|
|
422
|
+
deleteBlock(key: string) {
|
|
423
|
+
self.blockState.delete(key)
|
|
424
|
+
},
|
|
425
|
+
/**
|
|
426
|
+
* #action
|
|
427
|
+
*/
|
|
428
|
+
selectFeature(feature: Feature) {
|
|
429
|
+
const session = getSession(self)
|
|
430
|
+
if (isSessionModelWithWidgets(session)) {
|
|
431
|
+
const featureWidget = session.addWidget(
|
|
432
|
+
'BaseFeatureWidget',
|
|
433
|
+
'baseFeature',
|
|
512
434
|
{
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (self.contextMenuFeature) {
|
|
517
|
-
self.selectFeature(self.contextMenuFeature)
|
|
518
|
-
}
|
|
519
|
-
},
|
|
435
|
+
view: getContainingView(self),
|
|
436
|
+
track: getContainingTrack(self),
|
|
437
|
+
featureData: feature.toJSON(),
|
|
520
438
|
},
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
session.showWidget(featureWidget)
|
|
442
|
+
}
|
|
443
|
+
if (isSelectionContainer(session)) {
|
|
444
|
+
session.setSelection(feature)
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
/**
|
|
448
|
+
* #action
|
|
449
|
+
*/
|
|
450
|
+
clearFeatureSelection() {
|
|
451
|
+
const session = getSession(self)
|
|
452
|
+
session.clearSelection()
|
|
453
|
+
},
|
|
454
|
+
/**
|
|
455
|
+
* #action
|
|
456
|
+
*/
|
|
457
|
+
setFeatureIdUnderMouse(feature: string | undefined) {
|
|
458
|
+
self.featureIdUnderMouse = feature
|
|
459
|
+
},
|
|
460
|
+
/**
|
|
461
|
+
* #action
|
|
462
|
+
*/
|
|
463
|
+
reload() {
|
|
464
|
+
;[...self.blockState.values()].map(val => val.doReload())
|
|
465
|
+
},
|
|
466
|
+
/**
|
|
467
|
+
* #action
|
|
468
|
+
*/
|
|
469
|
+
setContextMenuFeature(feature?: Feature) {
|
|
470
|
+
self.contextMenuFeature = feature
|
|
471
|
+
},
|
|
472
|
+
}))
|
|
473
|
+
.views(self => ({
|
|
474
|
+
/**
|
|
475
|
+
* #getter
|
|
476
|
+
* region is too large if:
|
|
477
|
+
* - stats are ready
|
|
478
|
+
* - region is greater than 20kb (don't warn when zoomed in less than that)
|
|
479
|
+
* - and bytes is greater than max allowed bytes or density greater than max density
|
|
480
|
+
*/
|
|
481
|
+
get regionTooLarge() {
|
|
482
|
+
const view = getContainingView(self) as LGV
|
|
483
|
+
if (!self.estimatedStatsReady || view.dynamicBlocks.totalBp < 20_000) {
|
|
484
|
+
return false
|
|
485
|
+
}
|
|
486
|
+
const bpLimitOrDensity = self.userBpPerPxLimit
|
|
487
|
+
? view.bpPerPx > self.userBpPerPxLimit
|
|
488
|
+
: self.currentFeatureScreenDensity > self.maxFeatureScreenDensity
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
self.currentBytesRequested > self.maxAllowableBytes ||
|
|
492
|
+
bpLimitOrDensity
|
|
493
|
+
)
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* #getter
|
|
498
|
+
* only shows a message of bytes requested is defined, the feature density
|
|
499
|
+
* based stats don't produce any helpful message besides to zoom in
|
|
500
|
+
*/
|
|
501
|
+
get regionTooLargeReason() {
|
|
502
|
+
const req = self.currentBytesRequested
|
|
503
|
+
const max = self.maxAllowableBytes
|
|
504
|
+
|
|
505
|
+
return req && req > max
|
|
506
|
+
? `Requested too much data (${getDisplayStr(req)})`
|
|
507
|
+
: ''
|
|
508
|
+
},
|
|
509
|
+
}))
|
|
510
|
+
.actions(self => {
|
|
511
|
+
const { reload: superReload } = self
|
|
512
|
+
|
|
526
513
|
return {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
514
|
+
/**
|
|
515
|
+
* #action
|
|
516
|
+
*/
|
|
517
|
+
async reload() {
|
|
518
|
+
self.setError()
|
|
519
|
+
const aborter = new AbortController()
|
|
520
|
+
const view = getContainingView(self) as LGV
|
|
521
|
+
|
|
522
|
+
// extra check for contentBlocks.length
|
|
523
|
+
// https://github.com/GMOD/jbrowse-components/issues/2694
|
|
524
|
+
if (!view.initialized || !view.staticBlocks.contentBlocks.length) {
|
|
525
|
+
return
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
self.estimatedRegionStatsP = self.estimateRegionsStats(
|
|
530
|
+
view.staticBlocks.contentBlocks,
|
|
531
|
+
{ signal: aborter.signal },
|
|
532
|
+
)
|
|
533
|
+
const estimatedRegionStats = await self.estimatedRegionStatsP
|
|
534
|
+
|
|
535
|
+
if (isAlive(self)) {
|
|
536
|
+
self.setRegionStats(estimatedRegionStats)
|
|
537
|
+
superReload()
|
|
538
|
+
} else {
|
|
539
|
+
return
|
|
540
540
|
}
|
|
541
|
+
} catch (e) {
|
|
542
|
+
console.error(e)
|
|
543
|
+
self.setError(e)
|
|
541
544
|
}
|
|
542
545
|
},
|
|
543
|
-
|
|
544
|
-
|
|
546
|
+
afterAttach() {
|
|
547
|
+
// this autorun performs stats estimation
|
|
548
|
+
//
|
|
549
|
+
// the chain of events calls estimateRegionsStats against the data
|
|
550
|
+
// adapter which by default uses featureDensity, but can also respond
|
|
551
|
+
// with a byte size estimate and fetch size limit (data adapter can
|
|
552
|
+
// define what is too much data)
|
|
553
|
+
addDisposer(
|
|
554
|
+
self,
|
|
555
|
+
autorun(
|
|
556
|
+
async () => {
|
|
557
|
+
try {
|
|
558
|
+
const aborter = new AbortController()
|
|
559
|
+
const view = getContainingView(self) as LGV
|
|
560
|
+
|
|
561
|
+
// extra check for contentBlocks.length
|
|
562
|
+
// https://github.com/GMOD/jbrowse-components/issues/2694
|
|
563
|
+
if (
|
|
564
|
+
!view.initialized ||
|
|
565
|
+
!view.staticBlocks.contentBlocks.length
|
|
566
|
+
) {
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// don't re-estimate featureDensity even if zoom level changes,
|
|
571
|
+
// jbrowse1-style assume it's sort of representative
|
|
572
|
+
if (self.estimatedRegionStats?.featureDensity !== undefined) {
|
|
573
|
+
self.setCurrBpPerPx(view.bpPerPx)
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// we estimate stats once at a given zoom level
|
|
578
|
+
if (view.bpPerPx === self.currBpPerPx) {
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
self.clearRegionStats()
|
|
583
|
+
self.setCurrBpPerPx(view.bpPerPx)
|
|
584
|
+
const statsP = self.estimateRegionsStats(
|
|
585
|
+
view.staticBlocks.contentBlocks,
|
|
586
|
+
{ signal: aborter.signal },
|
|
587
|
+
)
|
|
588
|
+
self.setRegionStatsP(statsP)
|
|
589
|
+
const estimatedRegionStats = await statsP
|
|
590
|
+
|
|
591
|
+
if (isAlive(self)) {
|
|
592
|
+
self.setRegionStats(estimatedRegionStats)
|
|
593
|
+
}
|
|
594
|
+
} catch (e) {
|
|
595
|
+
if (!isAbortException(e) && isAlive(self)) {
|
|
596
|
+
console.error(e)
|
|
597
|
+
self.setError(e)
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
{ delay: 500 },
|
|
602
|
+
),
|
|
603
|
+
)
|
|
545
604
|
},
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
605
|
+
}
|
|
606
|
+
})
|
|
607
|
+
.views(self => ({
|
|
608
|
+
/**
|
|
609
|
+
* #method
|
|
610
|
+
*/
|
|
611
|
+
regionCannotBeRenderedText(_region: Region) {
|
|
612
|
+
return self.regionTooLarge ? 'Force load to see features' : ''
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* #method
|
|
617
|
+
* @param region -
|
|
618
|
+
* @returns falsy if the region is fine to try rendering. Otherwise,
|
|
619
|
+
* return a react node + string of text.
|
|
620
|
+
* string of text describes why it cannot be rendered
|
|
621
|
+
* react node allows user to force load at current setting
|
|
622
|
+
*/
|
|
623
|
+
regionCannotBeRendered(_region: Region) {
|
|
624
|
+
const { regionTooLarge } = self
|
|
625
|
+
return regionTooLarge ? <TooLargeMessage model={self} /> : null
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* #method
|
|
630
|
+
*/
|
|
631
|
+
trackMenuItems(): MenuItem[] {
|
|
632
|
+
return []
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* #method
|
|
637
|
+
*/
|
|
638
|
+
contextMenuItems() {
|
|
639
|
+
return self.contextMenuFeature
|
|
640
|
+
? [
|
|
641
|
+
{
|
|
642
|
+
label: 'Open feature details',
|
|
643
|
+
icon: MenuOpenIcon,
|
|
644
|
+
onClick: () => {
|
|
645
|
+
if (self.contextMenuFeature) {
|
|
646
|
+
self.selectFeature(self.contextMenuFeature)
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
]
|
|
651
|
+
: []
|
|
652
|
+
},
|
|
653
|
+
/**
|
|
654
|
+
* #method
|
|
655
|
+
*/
|
|
656
|
+
renderProps() {
|
|
657
|
+
const view = getContainingView(self) as LGV
|
|
658
|
+
return {
|
|
659
|
+
...(getParentRenderProps(self) as any),
|
|
660
|
+
notReady:
|
|
661
|
+
self.currBpPerPx !== view.bpPerPx || !self.estimatedRegionStats,
|
|
662
|
+
rpcDriverName: self.rpcDriverName,
|
|
663
|
+
displayModel: self,
|
|
664
|
+
onFeatureClick(_: unknown, featureId?: string) {
|
|
665
|
+
const f = featureId || self.featureIdUnderMouse
|
|
666
|
+
if (!f) {
|
|
667
|
+
self.clearFeatureSelection()
|
|
668
|
+
} else {
|
|
669
|
+
const feature = self.features.get(f)
|
|
670
|
+
if (feature) {
|
|
671
|
+
self.selectFeature(feature)
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
onClick() {
|
|
550
676
|
self.clearFeatureSelection()
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
677
|
+
},
|
|
678
|
+
// similar to click but opens a menu with further options
|
|
679
|
+
onFeatureContextMenu(_: unknown, featureId?: string) {
|
|
680
|
+
const f = featureId || self.featureIdUnderMouse
|
|
681
|
+
if (!f) {
|
|
682
|
+
self.clearFeatureSelection()
|
|
683
|
+
} else {
|
|
684
|
+
// feature id under mouse passed to context menu
|
|
685
|
+
self.setContextMenuFeature(self.features.get(f))
|
|
686
|
+
}
|
|
687
|
+
},
|
|
556
688
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
689
|
+
onMouseMove(_: unknown, featureId?: string) {
|
|
690
|
+
self.setFeatureIdUnderMouse(featureId)
|
|
691
|
+
},
|
|
560
692
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
693
|
+
onMouseLeave(_: unknown) {
|
|
694
|
+
self.setFeatureIdUnderMouse(undefined)
|
|
695
|
+
},
|
|
564
696
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
roundedDynamicBlocks
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
697
|
+
onContextMenu() {
|
|
698
|
+
self.setContextMenuFeature(undefined)
|
|
699
|
+
self.clearFeatureSelection()
|
|
700
|
+
},
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
}))
|
|
704
|
+
.actions(self => ({
|
|
705
|
+
/**
|
|
706
|
+
* #method
|
|
707
|
+
*/
|
|
708
|
+
async renderSvg(opts: ExportSvgOptions & { overrideHeight: number }) {
|
|
709
|
+
const { height, id } = self
|
|
710
|
+
const { overrideHeight } = opts
|
|
711
|
+
const view = getContainingView(self) as LGV
|
|
712
|
+
const { offsetPx: viewOffsetPx, roundedDynamicBlocks, width } = view
|
|
713
|
+
|
|
714
|
+
const renderings = await Promise.all(
|
|
715
|
+
roundedDynamicBlocks.map(block => {
|
|
716
|
+
const blockState = BlockState.create({
|
|
717
|
+
key: block.key,
|
|
718
|
+
region: block,
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
// regionCannotBeRendered can return jsx so look for plaintext
|
|
722
|
+
// version, or just get the default if none available
|
|
723
|
+
const cannotBeRenderedReason =
|
|
724
|
+
self.regionCannotBeRenderedText(block) ||
|
|
725
|
+
self.regionCannotBeRendered(block)
|
|
726
|
+
|
|
727
|
+
if (cannotBeRenderedReason) {
|
|
728
|
+
return {
|
|
729
|
+
reactElement: (
|
|
730
|
+
<>
|
|
731
|
+
<rect x={0} y={0} width={width} height={20} fill="#aaa" />
|
|
732
|
+
<text x={0} y={15}>
|
|
733
|
+
{cannotBeRenderedReason}
|
|
734
|
+
</text>
|
|
735
|
+
</>
|
|
736
|
+
),
|
|
737
|
+
}
|
|
602
738
|
}
|
|
603
|
-
}
|
|
604
739
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
740
|
+
const { rpcManager, renderArgs, renderProps, rendererType } =
|
|
741
|
+
renderBlockData(blockState, self)
|
|
742
|
+
|
|
743
|
+
return rendererType.renderInClient(rpcManager, {
|
|
744
|
+
...renderArgs,
|
|
745
|
+
...renderProps,
|
|
746
|
+
viewParams: getViewParams(self, true),
|
|
747
|
+
exportSVG: opts,
|
|
748
|
+
})
|
|
749
|
+
}),
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
return (
|
|
753
|
+
<>
|
|
754
|
+
{renderings.map((rendering, index) => {
|
|
755
|
+
const { offsetPx } = roundedDynamicBlocks[index]
|
|
756
|
+
const offset = offsetPx - viewOffsetPx
|
|
757
|
+
const clipid = getId(id, index)
|
|
758
|
+
|
|
759
|
+
return (
|
|
760
|
+
<React.Fragment key={`frag-${index}`}>
|
|
761
|
+
<defs>
|
|
762
|
+
<clipPath id={clipid}>
|
|
763
|
+
<rect
|
|
764
|
+
x={0}
|
|
765
|
+
y={0}
|
|
766
|
+
width={width}
|
|
767
|
+
height={overrideHeight || height}
|
|
768
|
+
/>
|
|
769
|
+
</clipPath>
|
|
770
|
+
</defs>
|
|
771
|
+
<g transform={`translate(${offset} 0)`}>
|
|
772
|
+
<g clipPath={`url(#${clipid})`}>
|
|
773
|
+
{React.isValidElement(rendering.reactElement) ? (
|
|
774
|
+
rendering.reactElement
|
|
775
|
+
) : (
|
|
776
|
+
<g
|
|
777
|
+
/* eslint-disable-next-line react/no-danger */
|
|
778
|
+
dangerouslySetInnerHTML={{ __html: rendering.html }}
|
|
779
|
+
/>
|
|
780
|
+
)}
|
|
781
|
+
</g>
|
|
644
782
|
</g>
|
|
645
|
-
</
|
|
646
|
-
|
|
647
|
-
)
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
783
|
+
</React.Fragment>
|
|
784
|
+
)
|
|
785
|
+
})}
|
|
786
|
+
</>
|
|
787
|
+
)
|
|
788
|
+
},
|
|
789
|
+
}))
|
|
790
|
+
.postProcessSnapshot(self => {
|
|
791
|
+
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
792
|
+
const r = self as Omit<typeof self, symbol>
|
|
793
|
+
const { blockState, ...rest } = r
|
|
794
|
+
return rest
|
|
795
|
+
})
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export const BaseLinearDisplay = stateModelFactory()
|
|
659
799
|
|
|
660
800
|
export type BaseLinearDisplayStateModel = typeof BaseLinearDisplay
|
|
661
801
|
export type BaseLinearDisplayModel = Instance<BaseLinearDisplayStateModel>
|