@jbrowse/plugin-circular-view 2.3.1 → 2.3.3

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.
@@ -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.compose(
37
- BaseViewModel,
38
- types
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: 2000000,
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
- .volatile(() => ({
107
- width: 0,
108
- }))
109
- .views(self => ({
110
- /**
111
- * #getter
112
- */
113
- get staticSlices() {
114
- return calculateStaticSlices(self)
115
- },
116
-
117
- /**
118
- * #getter
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
- * #getter
210
- */
211
- get atMaxBpPerPx() {
212
- return self.bpPerPx >= this.maxBpPerPx
213
- },
214
- /**
215
- * #getter
216
- */
217
- get atMinBpPerPx() {
218
- return self.bpPerPx <= this.minBpPerPx
219
- },
220
- /**
221
- * #getter
222
- */
223
- get tooSmallToLock() {
224
- return this.minBpPerPx <= 0.0000000001
225
- },
226
- /**
227
- * #getter
228
- */
229
- get figureDimensions(): [number, number] {
230
- return [
231
- this.radiusPx * 2 + 2 * self.paddingPx,
232
- this.radiusPx * 2 + 2 * self.paddingPx,
233
- ]
234
- },
235
- /**
236
- * #getter
237
- */
238
- get figureWidth() {
239
- return this.figureDimensions[0]
240
- },
241
- /**
242
- * #getter
243
- */
244
- get figureHeight() {
245
- return this.figureDimensions[1]
246
- },
247
- /**
248
- * #getter
249
- * this is displayedRegions, post-processed to
250
- * elide regions that are too small to see reasonably
251
- */
252
- get elidedRegions() {
253
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
- const visible: any[] = []
255
- self.displayedRegions.forEach(region => {
256
- const widthBp = region.end - region.start
257
- const widthPx = widthBp / self.bpPerPx
258
- if (widthPx < self.minVisibleWidth) {
259
- // too small to see, collapse into a single elision region
260
- const lastVisible = visible[visible.length - 1]
261
- if (lastVisible && lastVisible.elided) {
262
- lastVisible.regions.push({ ...region })
263
- lastVisible.widthBp += widthBp
264
- } else {
265
- visible.push({
266
- elided: true,
267
- widthBp,
268
- regions: [{ ...region }],
269
- })
270
- }
271
- } else {
272
- // big enough to see, display it
273
- visible.push({ ...region, widthBp })
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
- // remove any single-region elisions
278
- for (let i = 0; i < visible.length; i += 1) {
279
- const v = visible[i]
280
- if (v.elided && v.regions.length === 1) {
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
- return visible
286
- },
287
- /**
288
- * #getter
289
- */
290
- get assemblyNames() {
291
- const assemblyNames: string[] = []
292
- self.displayedRegions.forEach(displayedRegion => {
293
- if (!assemblyNames.includes(displayedRegion.assemblyName)) {
294
- assemblyNames.push(displayedRegion.assemblyName)
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
- return assemblyNames
298
- },
299
- /**
300
- * #getter
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
- * #action
362
- */
363
- rotateCounterClockwiseButton() {
364
- this.rotateCounterClockwise(Math.PI / 6)
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
- * #action
369
- */
370
- rotateClockwise(distance = 0.17) {
371
- self.offsetRadians += distance
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
- * #action
376
- */
377
- rotateCounterClockwise(distance = 0.17) {
378
- self.offsetRadians -= distance
379
- },
382
+ /**
383
+ * #action
384
+ */
385
+ rotateCounterClockwiseButton() {
386
+ this.rotateCounterClockwise(Math.PI / 6)
387
+ },
380
388
 
381
- /**
382
- * #action
383
- */
384
- zoomInButton() {
385
- this.setBpPerPx(self.bpPerPx / 1.4)
386
- },
389
+ /**
390
+ * #action
391
+ */
392
+ rotateClockwise(distance = 0.17) {
393
+ self.offsetRadians += distance
394
+ },
387
395
 
388
- /**
389
- * #action
390
- */
391
- zoomOutButton() {
392
- this.setBpPerPx(self.bpPerPx * 1.4)
393
- },
396
+ /**
397
+ * #action
398
+ */
399
+ rotateCounterClockwise(distance = 0.17) {
400
+ self.offsetRadians -= distance
401
+ },
394
402
 
395
- /**
396
- * #action
397
- */
398
- setBpPerPx(newVal: number) {
399
- self.bpPerPx = clamp(newVal, self.minBpPerPx, self.maxBpPerPx)
400
- },
403
+ /**
404
+ * #action
405
+ */
406
+ zoomInButton() {
407
+ this.setBpPerPx(self.bpPerPx / 1.4)
408
+ },
401
409
 
402
- /**
403
- * #action
404
- */
405
- setModelViewWhenAdjust(secondCondition: boolean) {
406
- if (self.lockedFitToWindow && secondCondition) {
407
- this.setBpPerPx(self.minBpPerPx)
408
- }
409
- },
410
+ /**
411
+ * #action
412
+ */
413
+ zoomOutButton() {
414
+ this.setBpPerPx(self.bpPerPx * 1.4)
415
+ },
410
416
 
411
- /**
412
- * #action
413
- */
414
- closeView() {
415
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
416
- getParent<any>(self, 2).removeView(self)
417
- },
417
+ /**
418
+ * #action
419
+ */
420
+ setBpPerPx(newVal: number) {
421
+ self.bpPerPx = clamp(newVal, self.minBpPerPx, self.maxBpPerPx)
422
+ },
418
423
 
419
- /**
420
- * #action
421
- */
422
- setDisplayedRegions(regions: SnapshotOrInstance<typeof Region>[]) {
423
- const previouslyEmpty = self.displayedRegions.length === 0
424
- self.displayedRegions = cast(regions)
424
+ /**
425
+ * #action
426
+ */
427
+ setModelViewWhenAdjust(secondCondition: boolean) {
428
+ if (self.lockedFitToWindow && secondCondition) {
429
+ this.setBpPerPx(self.minBpPerPx)
430
+ }
431
+ },
425
432
 
426
- if (previouslyEmpty) {
427
- this.setBpPerPx(self.minBpPerPx)
428
- } else {
429
- this.setBpPerPx(self.bpPerPx)
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
- * #action
435
- */
436
- activateTrackSelector() {
437
- if (self.trackSelectorType === 'hierarchical') {
438
- const session = getSession(self)
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
- * #action
456
- */
457
- toggleTrack(trackId: string) {
458
- // if we have any tracks with that configuration, turn them off
459
- const hiddenCount = this.hideTrack(trackId)
460
- // if none had that configuration, turn one on
461
- if (!hiddenCount) {
462
- this.showTrack(trackId)
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
- * #action
468
- */
469
- setError(error: unknown) {
470
- console.error(error)
471
- self.error = error
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
- * #action
476
- */
477
- showTrack(trackId: string, initialSnapshot = {}) {
478
- const schema = pluginManager.pluggableConfigSchemaType('track')
479
- const conf = resolveIdentifier(schema, getRoot(self), trackId)
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
- * #action
500
- */
501
- addTrackConf(
502
- configuration: AnyConfigurationModel,
503
- initialSnapshot = {},
504
- ) {
505
- const { type } = configuration
506
- const name = readConfObject(configuration, 'name')
507
- const trackType = pluginManager.getTrackType(type)
508
- if (!trackType) {
509
- throw new Error(`unknown track type ${configuration.type}`)
510
- }
511
- const viewType = pluginManager.getViewType(self.type)
512
- const supportedDisplays = viewType.displayTypes.map(d => d.name)
513
- const displayConf = configuration.displays.find(
514
- (d: AnyConfigurationModel) => supportedDisplays.includes(d.type),
515
- )
516
- const track = trackType.stateModel.create({
517
- ...initialSnapshot,
518
- name,
519
- type,
520
- configuration,
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
- * #action
528
- */
529
- hideTrack(trackId: string) {
530
- const schema = pluginManager.pluggableConfigSchemaType('track')
531
- const conf = resolveIdentifier(schema, getRoot(self), trackId)
532
- const t = self.tracks.filter(t => t.configuration === conf)
533
- transaction(() => t.forEach(t => self.tracks.remove(t)))
534
- return t.length
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
- * #action
539
- */
540
- toggleFitToWindowLock() {
541
- // when going unlocked -> locked and circle is cut off, set to the locked minBpPerPx
542
- self.lockedFitToWindow = !self.lockedFitToWindow
543
- this.setModelViewWhenAdjust(self.atMinBpPerPx)
544
- return self.lockedFitToWindow
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>