@jbrowse/plugin-circular-view 2.3.1 → 2.3.2
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/CircularView/components/CircularView.js +31 -80
- package/dist/CircularView/components/CircularView.js.map +1 -1
- package/dist/CircularView/components/Controls.d.ts +6 -0
- package/dist/CircularView/components/Controls.js +54 -0
- package/dist/CircularView/components/Controls.js.map +1 -0
- package/dist/CircularView/components/ImportForm.js +7 -5
- package/dist/CircularView/components/ImportForm.js.map +1 -1
- package/dist/CircularView/models/CircularView.d.ts +20 -10
- package/dist/CircularView/models/CircularView.js +26 -23
- package/dist/CircularView/models/CircularView.js.map +1 -1
- package/esm/CircularView/components/CircularView.js +32 -81
- package/esm/CircularView/components/CircularView.js.map +1 -1
- package/esm/CircularView/components/Controls.d.ts +6 -0
- package/esm/CircularView/components/Controls.js +49 -0
- package/esm/CircularView/components/Controls.js.map +1 -0
- package/esm/CircularView/components/ImportForm.js +7 -5
- package/esm/CircularView/components/ImportForm.js.map +1 -1
- package/esm/CircularView/models/CircularView.d.ts +20 -10
- package/esm/CircularView/models/CircularView.js +26 -23
- package/esm/CircularView/models/CircularView.js.map +1 -1
- package/package.json +2 -2
- package/src/CircularView/components/CircularView.tsx +71 -178
- package/src/CircularView/components/Controls.tsx +106 -0
- package/src/CircularView/components/ImportForm.tsx +9 -6
- package/src/CircularView/models/CircularView.ts +442 -426
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getSession,
|
|
20
20
|
clamp,
|
|
21
21
|
isSessionModelWithWidgets,
|
|
22
|
+
Region as IRegion,
|
|
22
23
|
} from '@jbrowse/core/util'
|
|
23
24
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
|
|
24
25
|
import { calculateStaticSlices, sliceIsVisible } from './slices'
|
|
@@ -33,10 +34,10 @@ function stateModelFactory(pluginManager: PluginManager) {
|
|
|
33
34
|
const minHeight = 40
|
|
34
35
|
const minWidth = 100
|
|
35
36
|
const defaultHeight = 400
|
|
36
|
-
return types
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.model('CircularView', {
|
|
37
|
+
return types
|
|
38
|
+
.compose(
|
|
39
|
+
BaseViewModel,
|
|
40
|
+
types.model('CircularView', {
|
|
40
41
|
/**
|
|
41
42
|
* #property
|
|
42
43
|
*/
|
|
@@ -49,7 +50,7 @@ function stateModelFactory(pluginManager: PluginManager) {
|
|
|
49
50
|
/**
|
|
50
51
|
* #property
|
|
51
52
|
*/
|
|
52
|
-
bpPerPx:
|
|
53
|
+
bpPerPx: 200,
|
|
53
54
|
/**
|
|
54
55
|
* #property
|
|
55
56
|
*/
|
|
@@ -102,449 +103,464 @@ function stateModelFactory(pluginManager: PluginManager) {
|
|
|
102
103
|
minimumBlockWidth: 20,
|
|
103
104
|
|
|
104
105
|
trackSelectorType: 'hierarchical',
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*/
|
|
120
|
-
get visibleSection() {
|
|
121
|
-
return viewportVisibleSection(
|
|
122
|
-
[
|
|
123
|
-
self.scrollX,
|
|
124
|
-
self.scrollX + self.width,
|
|
125
|
-
self.scrollY,
|
|
126
|
-
self.scrollY + self.height,
|
|
127
|
-
],
|
|
128
|
-
this.centerXY,
|
|
129
|
-
this.radiusPx,
|
|
130
|
-
)
|
|
131
|
-
},
|
|
132
|
-
/**
|
|
133
|
-
* #getter
|
|
134
|
-
*/
|
|
135
|
-
get circumferencePx() {
|
|
136
|
-
let elidedBp = 0
|
|
137
|
-
for (const r of this.elidedRegions) {
|
|
138
|
-
elidedBp += r.widthBp
|
|
139
|
-
}
|
|
140
|
-
return (
|
|
141
|
-
elidedBp / self.bpPerPx + self.spacingPx * this.elidedRegions.length
|
|
142
|
-
)
|
|
143
|
-
},
|
|
144
|
-
/**
|
|
145
|
-
* #getter
|
|
146
|
-
*/
|
|
147
|
-
get radiusPx() {
|
|
148
|
-
return this.circumferencePx / (2 * Math.PI)
|
|
149
|
-
},
|
|
150
|
-
/**
|
|
151
|
-
* #getter
|
|
152
|
-
*/
|
|
153
|
-
get bpPerRadian() {
|
|
154
|
-
return self.bpPerPx * this.radiusPx
|
|
155
|
-
},
|
|
156
|
-
/**
|
|
157
|
-
* #getter
|
|
158
|
-
*/
|
|
159
|
-
get pxPerRadian() {
|
|
160
|
-
return this.radiusPx
|
|
161
|
-
},
|
|
162
|
-
/**
|
|
163
|
-
* #getter
|
|
164
|
-
*/
|
|
165
|
-
get centerXY(): [number, number] {
|
|
166
|
-
return [
|
|
167
|
-
this.radiusPx + self.paddingPx,
|
|
168
|
-
this.radiusPx + self.paddingPx,
|
|
169
|
-
]
|
|
170
|
-
},
|
|
171
|
-
/**
|
|
172
|
-
* #getter
|
|
173
|
-
*/
|
|
174
|
-
get totalBp() {
|
|
175
|
-
let total = 0
|
|
176
|
-
for (const region of self.displayedRegions) {
|
|
177
|
-
total += region.end - region.start
|
|
178
|
-
}
|
|
179
|
-
return total
|
|
180
|
-
},
|
|
181
|
-
/**
|
|
182
|
-
* #getter
|
|
183
|
-
*/
|
|
184
|
-
get maximumRadiusPx() {
|
|
185
|
-
return self.lockedFitToWindow
|
|
186
|
-
? Math.min(self.width, self.height) / 2 - self.lockedPaddingPx
|
|
187
|
-
: 1000000
|
|
188
|
-
},
|
|
189
|
-
/**
|
|
190
|
-
* #getter
|
|
191
|
-
*/
|
|
192
|
-
get maxBpPerPx() {
|
|
193
|
-
const minCircumferencePx = 2 * Math.PI * self.minimumRadiusPx
|
|
194
|
-
return this.totalBp / minCircumferencePx
|
|
195
|
-
},
|
|
196
|
-
/**
|
|
197
|
-
* #getter
|
|
198
|
-
*/
|
|
199
|
-
get minBpPerPx() {
|
|
200
|
-
// min depends on window dimensions, clamp between old min(0.01) and max
|
|
201
|
-
const maxCircumferencePx = 2 * Math.PI * this.maximumRadiusPx
|
|
202
|
-
return clamp(
|
|
203
|
-
this.totalBp / maxCircumferencePx,
|
|
204
|
-
0.0000000001,
|
|
205
|
-
this.maxBpPerPx,
|
|
106
|
+
}),
|
|
107
|
+
)
|
|
108
|
+
.volatile(() => ({
|
|
109
|
+
volatileWidth: undefined as number | undefined,
|
|
110
|
+
error: undefined as unknown,
|
|
111
|
+
}))
|
|
112
|
+
.views(self => ({
|
|
113
|
+
/**
|
|
114
|
+
* #getter
|
|
115
|
+
*/
|
|
116
|
+
get width() {
|
|
117
|
+
if (self.volatileWidth === undefined) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'wait for view to be initialized first before accessing width',
|
|
206
120
|
)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
121
|
+
}
|
|
122
|
+
return self.volatileWidth
|
|
123
|
+
},
|
|
124
|
+
/**
|
|
125
|
+
* #getter
|
|
126
|
+
*/
|
|
127
|
+
get staticSlices() {
|
|
128
|
+
return calculateStaticSlices(self)
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* #getter
|
|
133
|
+
*/
|
|
134
|
+
get visibleSection() {
|
|
135
|
+
return viewportVisibleSection(
|
|
136
|
+
[
|
|
137
|
+
self.scrollX,
|
|
138
|
+
self.scrollX + self.width,
|
|
139
|
+
self.scrollY,
|
|
140
|
+
self.scrollY + self.height,
|
|
141
|
+
],
|
|
142
|
+
this.centerXY,
|
|
143
|
+
this.radiusPx,
|
|
144
|
+
)
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* #getter
|
|
148
|
+
*/
|
|
149
|
+
get circumferencePx() {
|
|
150
|
+
let elidedBp = 0
|
|
151
|
+
|
|
152
|
+
for (const r of this.elidedRegions) {
|
|
153
|
+
elidedBp += r.widthBp
|
|
154
|
+
}
|
|
155
|
+
return (
|
|
156
|
+
elidedBp / self.bpPerPx + self.spacingPx * this.elidedRegions.length
|
|
157
|
+
)
|
|
158
|
+
},
|
|
159
|
+
/**
|
|
160
|
+
* #getter
|
|
161
|
+
*/
|
|
162
|
+
get radiusPx() {
|
|
163
|
+
return this.circumferencePx / (2 * Math.PI)
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* #getter
|
|
167
|
+
*/
|
|
168
|
+
get bpPerRadian() {
|
|
169
|
+
return self.bpPerPx * this.radiusPx
|
|
170
|
+
},
|
|
171
|
+
/**
|
|
172
|
+
* #getter
|
|
173
|
+
*/
|
|
174
|
+
get pxPerRadian() {
|
|
175
|
+
return this.radiusPx
|
|
176
|
+
},
|
|
177
|
+
/**
|
|
178
|
+
* #getter
|
|
179
|
+
*/
|
|
180
|
+
get centerXY(): [number, number] {
|
|
181
|
+
return [this.radiusPx + self.paddingPx, this.radiusPx + self.paddingPx]
|
|
182
|
+
},
|
|
183
|
+
/**
|
|
184
|
+
* #getter
|
|
185
|
+
*/
|
|
186
|
+
get totalBp() {
|
|
187
|
+
let total = 0
|
|
188
|
+
for (const region of self.displayedRegions) {
|
|
189
|
+
total += region.end - region.start
|
|
190
|
+
}
|
|
191
|
+
return total
|
|
192
|
+
},
|
|
193
|
+
/**
|
|
194
|
+
* #getter
|
|
195
|
+
*/
|
|
196
|
+
get maximumRadiusPx() {
|
|
197
|
+
return self.lockedFitToWindow
|
|
198
|
+
? Math.min(self.width, self.height) / 2 - self.lockedPaddingPx
|
|
199
|
+
: 1000000
|
|
200
|
+
},
|
|
201
|
+
/**
|
|
202
|
+
* #getter
|
|
203
|
+
*/
|
|
204
|
+
get maxBpPerPx() {
|
|
205
|
+
const minCircumferencePx = 2 * Math.PI * self.minimumRadiusPx
|
|
206
|
+
return this.totalBp / minCircumferencePx
|
|
207
|
+
},
|
|
208
|
+
/**
|
|
209
|
+
* #getter
|
|
210
|
+
*/
|
|
211
|
+
get minBpPerPx() {
|
|
212
|
+
// min depends on window dimensions, clamp between old min(0.01) and max
|
|
213
|
+
const maxCircumferencePx = 2 * Math.PI * this.maximumRadiusPx
|
|
214
|
+
return clamp(
|
|
215
|
+
this.totalBp / maxCircumferencePx,
|
|
216
|
+
0.0000000001,
|
|
217
|
+
this.maxBpPerPx,
|
|
218
|
+
)
|
|
219
|
+
},
|
|
220
|
+
/**
|
|
221
|
+
* #getter
|
|
222
|
+
*/
|
|
223
|
+
get atMaxBpPerPx() {
|
|
224
|
+
return self.bpPerPx >= this.maxBpPerPx
|
|
225
|
+
},
|
|
226
|
+
/**
|
|
227
|
+
* #getter
|
|
228
|
+
*/
|
|
229
|
+
get atMinBpPerPx() {
|
|
230
|
+
return self.bpPerPx <= this.minBpPerPx
|
|
231
|
+
},
|
|
232
|
+
/**
|
|
233
|
+
* #getter
|
|
234
|
+
*/
|
|
235
|
+
get tooSmallToLock() {
|
|
236
|
+
return this.minBpPerPx <= 0.0000000001
|
|
237
|
+
},
|
|
238
|
+
/**
|
|
239
|
+
* #getter
|
|
240
|
+
*/
|
|
241
|
+
get figureDimensions(): [number, number] {
|
|
242
|
+
return [
|
|
243
|
+
this.radiusPx * 2 + 2 * self.paddingPx,
|
|
244
|
+
this.radiusPx * 2 + 2 * self.paddingPx,
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
/**
|
|
248
|
+
* #getter
|
|
249
|
+
*/
|
|
250
|
+
get figureWidth() {
|
|
251
|
+
return this.figureDimensions[0]
|
|
252
|
+
},
|
|
253
|
+
/**
|
|
254
|
+
* #getter
|
|
255
|
+
*/
|
|
256
|
+
get figureHeight() {
|
|
257
|
+
return this.figureDimensions[1]
|
|
258
|
+
},
|
|
259
|
+
/**
|
|
260
|
+
* #getter
|
|
261
|
+
* this is displayedRegions, post-processed to
|
|
262
|
+
* elide regions that are too small to see reasonably
|
|
263
|
+
*/
|
|
264
|
+
get elidedRegions() {
|
|
265
|
+
const visible: (
|
|
266
|
+
| {
|
|
267
|
+
elided: true
|
|
268
|
+
widthBp: number
|
|
269
|
+
regions: IRegion[]
|
|
274
270
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
delete v.elided
|
|
282
|
-
visible[i] = { ...v, ...v.regions[0] }
|
|
271
|
+
| {
|
|
272
|
+
elided: false
|
|
273
|
+
widthBp: number
|
|
274
|
+
start: number
|
|
275
|
+
end: number
|
|
276
|
+
refName: string
|
|
283
277
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
278
|
+
)[] = []
|
|
279
|
+
self.displayedRegions.forEach(region => {
|
|
280
|
+
const widthBp = region.end - region.start
|
|
281
|
+
const widthPx = widthBp / self.bpPerPx
|
|
282
|
+
if (widthPx < self.minVisibleWidth) {
|
|
283
|
+
// too small to see, collapse into a single elision region
|
|
284
|
+
const lastVisible = visible[visible.length - 1]
|
|
285
|
+
if (lastVisible?.elided) {
|
|
286
|
+
lastVisible.regions.push({ ...region })
|
|
287
|
+
lastVisible.widthBp += widthBp
|
|
288
|
+
} else {
|
|
289
|
+
visible.push({
|
|
290
|
+
elided: true,
|
|
291
|
+
widthBp,
|
|
292
|
+
regions: [{ ...region }],
|
|
293
|
+
})
|
|
295
294
|
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
*/
|
|
302
|
-
get initialized() {
|
|
303
|
-
const { assemblyManager } = getSession(self)
|
|
304
|
-
return this.assemblyNames.every(
|
|
305
|
-
a => assemblyManager.get(a)?.initialized,
|
|
306
|
-
)
|
|
307
|
-
},
|
|
308
|
-
}))
|
|
309
|
-
.views(self => ({
|
|
310
|
-
/**
|
|
311
|
-
* #getter
|
|
312
|
-
*/
|
|
313
|
-
get visibleStaticSlices() {
|
|
314
|
-
return self.staticSlices.filter(s => sliceIsVisible(self, s))
|
|
315
|
-
},
|
|
316
|
-
}))
|
|
317
|
-
.volatile(() => ({
|
|
318
|
-
error: undefined as unknown,
|
|
319
|
-
}))
|
|
320
|
-
.actions(self => ({
|
|
321
|
-
/**
|
|
322
|
-
* #action
|
|
323
|
-
*/
|
|
324
|
-
setWidth(newWidth: number) {
|
|
325
|
-
self.width = Math.max(newWidth, minWidth)
|
|
326
|
-
return self.width
|
|
327
|
-
},
|
|
328
|
-
/**
|
|
329
|
-
* #action
|
|
330
|
-
*/
|
|
331
|
-
setHeight(newHeight: number) {
|
|
332
|
-
self.height = Math.max(newHeight, minHeight)
|
|
333
|
-
return self.height
|
|
334
|
-
},
|
|
335
|
-
/**
|
|
336
|
-
* #action
|
|
337
|
-
*/
|
|
338
|
-
resizeHeight(distance: number) {
|
|
339
|
-
const oldHeight = self.height
|
|
340
|
-
const newHeight = this.setHeight(self.height + distance)
|
|
341
|
-
this.setModelViewWhenAdjust(!self.tooSmallToLock)
|
|
342
|
-
return newHeight - oldHeight
|
|
343
|
-
},
|
|
344
|
-
/**
|
|
345
|
-
* #action
|
|
346
|
-
*/
|
|
347
|
-
resizeWidth(distance: number) {
|
|
348
|
-
const oldWidth = self.width
|
|
349
|
-
const newWidth = this.setWidth(self.width + distance)
|
|
350
|
-
this.setModelViewWhenAdjust(!self.tooSmallToLock)
|
|
351
|
-
return newWidth - oldWidth
|
|
352
|
-
},
|
|
353
|
-
/**
|
|
354
|
-
* #action
|
|
355
|
-
*/
|
|
356
|
-
rotateClockwiseButton() {
|
|
357
|
-
this.rotateClockwise(Math.PI / 6)
|
|
358
|
-
},
|
|
295
|
+
} else {
|
|
296
|
+
// big enough to see, display it
|
|
297
|
+
visible.push({ ...region, widthBp, elided: false })
|
|
298
|
+
}
|
|
299
|
+
})
|
|
359
300
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
301
|
+
// remove any single-region elisions
|
|
302
|
+
for (let i = 0; i < visible.length; i += 1) {
|
|
303
|
+
const v = visible[i]
|
|
304
|
+
if (v.elided && v.regions.length === 1) {
|
|
305
|
+
visible[i] = { ...v, ...v.regions[0], elided: false }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return visible
|
|
309
|
+
},
|
|
310
|
+
/**
|
|
311
|
+
* #getter
|
|
312
|
+
*/
|
|
313
|
+
get assemblyNames() {
|
|
314
|
+
const assemblyNames: string[] = []
|
|
315
|
+
self.displayedRegions.forEach(displayedRegion => {
|
|
316
|
+
if (!assemblyNames.includes(displayedRegion.assemblyName)) {
|
|
317
|
+
assemblyNames.push(displayedRegion.assemblyName)
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
return assemblyNames
|
|
321
|
+
},
|
|
322
|
+
/**
|
|
323
|
+
* #getter
|
|
324
|
+
*/
|
|
325
|
+
get initialized() {
|
|
326
|
+
const { assemblyManager } = getSession(self)
|
|
327
|
+
return (
|
|
328
|
+
self.volatileWidth !== undefined &&
|
|
329
|
+
this.assemblyNames.every(a => assemblyManager.get(a)?.initialized)
|
|
330
|
+
)
|
|
331
|
+
},
|
|
332
|
+
}))
|
|
333
|
+
.views(self => ({
|
|
334
|
+
/**
|
|
335
|
+
* #getter
|
|
336
|
+
*/
|
|
337
|
+
get visibleStaticSlices() {
|
|
338
|
+
return self.staticSlices.filter(s => sliceIsVisible(self, s))
|
|
339
|
+
},
|
|
340
|
+
}))
|
|
366
341
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
342
|
+
.actions(self => ({
|
|
343
|
+
/**
|
|
344
|
+
* #action
|
|
345
|
+
*/
|
|
346
|
+
setWidth(newWidth: number) {
|
|
347
|
+
self.volatileWidth = Math.max(newWidth, minWidth)
|
|
348
|
+
return self.volatileWidth
|
|
349
|
+
},
|
|
350
|
+
/**
|
|
351
|
+
* #action
|
|
352
|
+
*/
|
|
353
|
+
setHeight(newHeight: number) {
|
|
354
|
+
self.height = Math.max(newHeight, minHeight)
|
|
355
|
+
return self.height
|
|
356
|
+
},
|
|
357
|
+
/**
|
|
358
|
+
* #action
|
|
359
|
+
*/
|
|
360
|
+
resizeHeight(distance: number) {
|
|
361
|
+
const oldHeight = self.height
|
|
362
|
+
const newHeight = this.setHeight(self.height + distance)
|
|
363
|
+
this.setModelViewWhenAdjust(!self.tooSmallToLock)
|
|
364
|
+
return newHeight - oldHeight
|
|
365
|
+
},
|
|
366
|
+
/**
|
|
367
|
+
* #action
|
|
368
|
+
*/
|
|
369
|
+
resizeWidth(distance: number) {
|
|
370
|
+
const oldWidth = self.width
|
|
371
|
+
const newWidth = this.setWidth(self.width + distance)
|
|
372
|
+
this.setModelViewWhenAdjust(!self.tooSmallToLock)
|
|
373
|
+
return newWidth - oldWidth
|
|
374
|
+
},
|
|
375
|
+
/**
|
|
376
|
+
* #action
|
|
377
|
+
*/
|
|
378
|
+
rotateClockwiseButton() {
|
|
379
|
+
this.rotateClockwise(Math.PI / 6)
|
|
380
|
+
},
|
|
373
381
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
382
|
+
/**
|
|
383
|
+
* #action
|
|
384
|
+
*/
|
|
385
|
+
rotateCounterClockwiseButton() {
|
|
386
|
+
this.rotateCounterClockwise(Math.PI / 6)
|
|
387
|
+
},
|
|
380
388
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
389
|
+
/**
|
|
390
|
+
* #action
|
|
391
|
+
*/
|
|
392
|
+
rotateClockwise(distance = 0.17) {
|
|
393
|
+
self.offsetRadians += distance
|
|
394
|
+
},
|
|
387
395
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
396
|
+
/**
|
|
397
|
+
* #action
|
|
398
|
+
*/
|
|
399
|
+
rotateCounterClockwise(distance = 0.17) {
|
|
400
|
+
self.offsetRadians -= distance
|
|
401
|
+
},
|
|
394
402
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
403
|
+
/**
|
|
404
|
+
* #action
|
|
405
|
+
*/
|
|
406
|
+
zoomInButton() {
|
|
407
|
+
this.setBpPerPx(self.bpPerPx / 1.4)
|
|
408
|
+
},
|
|
401
409
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
+
/**
|
|
411
|
+
* #action
|
|
412
|
+
*/
|
|
413
|
+
zoomOutButton() {
|
|
414
|
+
this.setBpPerPx(self.bpPerPx * 1.4)
|
|
415
|
+
},
|
|
410
416
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
},
|
|
417
|
+
/**
|
|
418
|
+
* #action
|
|
419
|
+
*/
|
|
420
|
+
setBpPerPx(newVal: number) {
|
|
421
|
+
self.bpPerPx = clamp(newVal, self.minBpPerPx, self.maxBpPerPx)
|
|
422
|
+
},
|
|
418
423
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
self.
|
|
424
|
+
/**
|
|
425
|
+
* #action
|
|
426
|
+
*/
|
|
427
|
+
setModelViewWhenAdjust(secondCondition: boolean) {
|
|
428
|
+
if (self.lockedFitToWindow && secondCondition) {
|
|
429
|
+
this.setBpPerPx(self.minBpPerPx)
|
|
430
|
+
}
|
|
431
|
+
},
|
|
425
432
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
433
|
+
/**
|
|
434
|
+
* #action
|
|
435
|
+
*/
|
|
436
|
+
closeView() {
|
|
437
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
438
|
+
getParent<any>(self, 2).removeView(self)
|
|
439
|
+
},
|
|
432
440
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (isSessionModelWithWidgets(session)) {
|
|
440
|
-
const selector = session.addWidget(
|
|
441
|
-
'HierarchicalTrackSelectorWidget',
|
|
442
|
-
'hierarchicalTrackSelector',
|
|
443
|
-
{ view: self },
|
|
444
|
-
)
|
|
445
|
-
session.showWidget(selector)
|
|
446
|
-
return selector
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
throw new Error(
|
|
450
|
-
`invalid track selector type ${self.trackSelectorType}`,
|
|
451
|
-
)
|
|
452
|
-
},
|
|
441
|
+
/**
|
|
442
|
+
* #action
|
|
443
|
+
*/
|
|
444
|
+
setDisplayedRegions(regions: SnapshotOrInstance<typeof Region>[]) {
|
|
445
|
+
const previouslyEmpty = self.displayedRegions.length === 0
|
|
446
|
+
self.displayedRegions = cast(regions)
|
|
453
447
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
448
|
+
if (previouslyEmpty) {
|
|
449
|
+
this.setBpPerPx(self.minBpPerPx)
|
|
450
|
+
} else {
|
|
451
|
+
this.setBpPerPx(self.bpPerPx)
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* #action
|
|
457
|
+
*/
|
|
458
|
+
activateTrackSelector() {
|
|
459
|
+
if (self.trackSelectorType === 'hierarchical') {
|
|
460
|
+
const session = getSession(self)
|
|
461
|
+
if (isSessionModelWithWidgets(session)) {
|
|
462
|
+
const selector = session.addWidget(
|
|
463
|
+
'HierarchicalTrackSelectorWidget',
|
|
464
|
+
'hierarchicalTrackSelector',
|
|
465
|
+
{ view: self },
|
|
466
|
+
)
|
|
467
|
+
session.showWidget(selector)
|
|
468
|
+
return selector
|
|
463
469
|
}
|
|
464
|
-
}
|
|
470
|
+
}
|
|
471
|
+
throw new Error(`invalid track selector type ${self.trackSelectorType}`)
|
|
472
|
+
},
|
|
465
473
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
474
|
+
/**
|
|
475
|
+
* #action
|
|
476
|
+
*/
|
|
477
|
+
toggleTrack(trackId: string) {
|
|
478
|
+
// if we have any tracks with that configuration, turn them off
|
|
479
|
+
const hiddenCount = this.hideTrack(trackId)
|
|
480
|
+
// if none had that configuration, turn one on
|
|
481
|
+
if (!hiddenCount) {
|
|
482
|
+
this.showTrack(trackId)
|
|
483
|
+
}
|
|
484
|
+
},
|
|
473
485
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const trackType = pluginManager.getTrackType(conf.type)
|
|
481
|
-
if (!trackType) {
|
|
482
|
-
throw new Error(`unknown track type ${conf.type}`)
|
|
483
|
-
}
|
|
484
|
-
const viewType = pluginManager.getViewType(self.type)
|
|
485
|
-
const supportedDisplays = viewType.displayTypes.map(d => d.name)
|
|
486
|
-
const displayConf = conf.displays.find((d: AnyConfigurationModel) =>
|
|
487
|
-
supportedDisplays.includes(d.type),
|
|
488
|
-
)
|
|
489
|
-
const track = trackType.stateModel.create({
|
|
490
|
-
...initialSnapshot,
|
|
491
|
-
type: conf.type,
|
|
492
|
-
configuration: conf,
|
|
493
|
-
displays: [{ type: displayConf.type, configuration: displayConf }],
|
|
494
|
-
})
|
|
495
|
-
self.tracks.push(track)
|
|
496
|
-
},
|
|
486
|
+
/**
|
|
487
|
+
* #action
|
|
488
|
+
*/
|
|
489
|
+
setError(error: unknown) {
|
|
490
|
+
self.error = error
|
|
491
|
+
},
|
|
497
492
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
displays: [{ type: displayConf.type, configuration: displayConf }],
|
|
522
|
-
})
|
|
523
|
-
self.tracks.push(track)
|
|
524
|
-
},
|
|
493
|
+
/**
|
|
494
|
+
* #action
|
|
495
|
+
*/
|
|
496
|
+
showTrack(trackId: string, initialSnapshot = {}) {
|
|
497
|
+
const schema = pluginManager.pluggableConfigSchemaType('track')
|
|
498
|
+
const conf = resolveIdentifier(schema, getRoot(self), trackId)
|
|
499
|
+
const trackType = pluginManager.getTrackType(conf.type)
|
|
500
|
+
if (!trackType) {
|
|
501
|
+
throw new Error(`unknown track type ${conf.type}`)
|
|
502
|
+
}
|
|
503
|
+
const viewType = pluginManager.getViewType(self.type)
|
|
504
|
+
const supportedDisplays = viewType.displayTypes.map(d => d.name)
|
|
505
|
+
const displayConf = conf.displays.find((d: AnyConfigurationModel) =>
|
|
506
|
+
supportedDisplays.includes(d.type),
|
|
507
|
+
)
|
|
508
|
+
const track = trackType.stateModel.create({
|
|
509
|
+
...initialSnapshot,
|
|
510
|
+
type: conf.type,
|
|
511
|
+
configuration: conf,
|
|
512
|
+
displays: [{ type: displayConf.type, configuration: displayConf }],
|
|
513
|
+
})
|
|
514
|
+
self.tracks.push(track)
|
|
515
|
+
},
|
|
525
516
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
517
|
+
/**
|
|
518
|
+
* #action
|
|
519
|
+
*/
|
|
520
|
+
addTrackConf(configuration: AnyConfigurationModel, initialSnapshot = {}) {
|
|
521
|
+
const { type } = configuration
|
|
522
|
+
const name = readConfObject(configuration, 'name')
|
|
523
|
+
const trackType = pluginManager.getTrackType(type)
|
|
524
|
+
if (!trackType) {
|
|
525
|
+
throw new Error(`unknown track type ${configuration.type}`)
|
|
526
|
+
}
|
|
527
|
+
const viewType = pluginManager.getViewType(self.type)
|
|
528
|
+
const supportedDisplays = viewType.displayTypes.map(d => d.name)
|
|
529
|
+
const displayConf = configuration.displays.find(
|
|
530
|
+
(d: AnyConfigurationModel) => supportedDisplays.includes(d.type),
|
|
531
|
+
)
|
|
532
|
+
const track = trackType.stateModel.create({
|
|
533
|
+
...initialSnapshot,
|
|
534
|
+
name,
|
|
535
|
+
type,
|
|
536
|
+
configuration,
|
|
537
|
+
displays: [{ type: displayConf.type, configuration: displayConf }],
|
|
538
|
+
})
|
|
539
|
+
self.tracks.push(track)
|
|
540
|
+
},
|
|
536
541
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
|
|
542
|
+
/**
|
|
543
|
+
* #action
|
|
544
|
+
*/
|
|
545
|
+
hideTrack(trackId: string) {
|
|
546
|
+
const schema = pluginManager.pluggableConfigSchemaType('track')
|
|
547
|
+
const conf = resolveIdentifier(schema, getRoot(self), trackId)
|
|
548
|
+
const t = self.tracks.filter(t => t.configuration === conf)
|
|
549
|
+
transaction(() => t.forEach(t => self.tracks.remove(t)))
|
|
550
|
+
return t.length
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* #action
|
|
555
|
+
*/
|
|
556
|
+
toggleFitToWindowLock() {
|
|
557
|
+
// when going unlocked -> locked and circle is cut off, set to the
|
|
558
|
+
// locked minBpPerPx
|
|
559
|
+
self.lockedFitToWindow = !self.lockedFitToWindow
|
|
560
|
+
this.setModelViewWhenAdjust(self.atMinBpPerPx)
|
|
561
|
+
return self.lockedFitToWindow
|
|
562
|
+
},
|
|
563
|
+
}))
|
|
548
564
|
}
|
|
549
565
|
|
|
550
566
|
export type CircularViewStateModel = ReturnType<typeof stateModelFactory>
|