@jbrowse/plugin-circular-view 2.16.1 → 2.18.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/BaseChordDisplay/components/BaseChordDisplay.js +5 -4
- package/dist/BaseChordDisplay/components/Loading.js +1 -2
- package/dist/BaseChordDisplay/{models/configSchema.d.ts → configSchema.d.ts} +0 -3
- package/dist/BaseChordDisplay/{models/configSchema.js → configSchema.js} +1 -10
- package/dist/BaseChordDisplay/index.d.ts +2 -2
- package/dist/BaseChordDisplay/index.js +2 -2
- package/{esm/BaseChordDisplay/models → dist/BaseChordDisplay}/model.d.ts +11 -52
- package/dist/BaseChordDisplay/{models/model.js → model.js} +29 -89
- package/dist/BaseChordDisplay/renderReaction.d.ts +27 -0
- package/dist/BaseChordDisplay/{models/renderReaction.js → renderReaction.js} +5 -13
- package/dist/CircularView/components/CircularView.d.ts +1 -1
- package/dist/CircularView/components/CircularView.js +2 -3
- package/dist/CircularView/components/Controls.d.ts +1 -1
- package/dist/CircularView/components/Controls.js +11 -12
- package/dist/CircularView/components/ExportSvgDialog.d.ts +1 -1
- package/dist/CircularView/components/ExportSvgDialog.js +2 -4
- package/dist/CircularView/components/ImportForm.d.ts +1 -1
- package/dist/CircularView/components/ImportForm.js +3 -3
- package/dist/CircularView/components/Ruler.d.ts +2 -2
- package/dist/CircularView/components/Ruler.js +1 -7
- package/dist/CircularView/index.d.ts +1 -1
- package/dist/CircularView/index.js +1 -1
- package/dist/CircularView/{models/model.d.ts → model.d.ts} +9 -158
- package/dist/CircularView/{models/model.js → model.js} +8 -217
- package/dist/CircularView/{models/slices.d.ts → slices.d.ts} +1 -1
- package/dist/CircularView/svgcomponents/SVGBackground.js +1 -1
- package/dist/CircularView/svgcomponents/SVGCircularView.d.ts +1 -1
- package/dist/CircularView/svgcomponents/SVGCircularView.js +4 -9
- package/dist/CircularView/{models/viewportVisibleRegion.js → viewportVisibleRegion.js} +0 -70
- package/dist/LaunchCircularView/index.d.ts +1 -1
- package/dist/LaunchCircularView/index.js +1 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -7
- package/esm/BaseChordDisplay/components/BaseChordDisplay.js +5 -4
- package/esm/BaseChordDisplay/components/Loading.js +2 -3
- package/esm/BaseChordDisplay/{models/configSchema.d.ts → configSchema.d.ts} +0 -3
- package/esm/BaseChordDisplay/{models/configSchema.js → configSchema.js} +1 -10
- package/esm/BaseChordDisplay/index.d.ts +2 -2
- package/esm/BaseChordDisplay/index.js +2 -2
- package/{dist/BaseChordDisplay/models → esm/BaseChordDisplay}/model.d.ts +11 -52
- package/esm/BaseChordDisplay/{models/model.js → model.js} +30 -90
- package/esm/BaseChordDisplay/renderReaction.d.ts +27 -0
- package/esm/BaseChordDisplay/{models/renderReaction.js → renderReaction.js} +5 -10
- package/esm/CircularView/components/CircularView.d.ts +1 -1
- package/esm/CircularView/components/CircularView.js +2 -3
- package/esm/CircularView/components/Controls.d.ts +1 -1
- package/esm/CircularView/components/Controls.js +11 -12
- package/esm/CircularView/components/ExportSvgDialog.d.ts +1 -1
- package/esm/CircularView/components/ExportSvgDialog.js +2 -4
- package/esm/CircularView/components/ImportForm.d.ts +1 -1
- package/esm/CircularView/components/ImportForm.js +3 -3
- package/esm/CircularView/components/Ruler.d.ts +2 -2
- package/esm/CircularView/components/Ruler.js +2 -8
- package/esm/CircularView/index.d.ts +1 -1
- package/esm/CircularView/index.js +1 -1
- package/esm/CircularView/{models/model.d.ts → model.d.ts} +9 -158
- package/esm/CircularView/{models/model.js → model.js} +9 -218
- package/esm/CircularView/{models/slices.d.ts → slices.d.ts} +1 -1
- package/esm/CircularView/{models/slices.js → slices.js} +1 -1
- package/esm/CircularView/svgcomponents/SVGBackground.js +1 -1
- package/esm/CircularView/svgcomponents/SVGCircularView.d.ts +1 -1
- package/esm/CircularView/svgcomponents/SVGCircularView.js +4 -9
- package/esm/CircularView/{models/viewportVisibleRegion.js → viewportVisibleRegion.js} +0 -70
- package/esm/LaunchCircularView/index.d.ts +1 -1
- package/esm/LaunchCircularView/index.js +1 -3
- package/esm/index.d.ts +3 -3
- package/esm/index.js +3 -5
- package/package.json +2 -3
- package/dist/BaseChordDisplay/models/renderReaction.d.ts +0 -45
- package/esm/BaseChordDisplay/models/renderReaction.d.ts +0 -45
- /package/dist/CircularView/{models/slices.js → slices.js} +0 -0
- /package/dist/CircularView/{models/viewportVisibleRegion.d.ts → viewportVisibleRegion.d.ts} +0 -0
- /package/esm/CircularView/{models/viewportVisibleRegion.d.ts → viewportVisibleRegion.d.ts} +0 -0
|
@@ -1,107 +1,40 @@
|
|
|
1
1
|
import { lazy } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { Region } from '@jbrowse/core/util/types/mst';
|
|
4
|
-
import { transaction } from 'mobx';
|
|
5
|
-
import { saveAs } from 'file-saver';
|
|
6
|
-
import { readConfObject, } from '@jbrowse/core/configuration';
|
|
7
|
-
import { getSession, clamp, isSessionModelWithWidgets, } from '@jbrowse/core/util';
|
|
2
|
+
import { readConfObject } from '@jbrowse/core/configuration';
|
|
8
3
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models';
|
|
9
|
-
// icons
|
|
10
4
|
import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons';
|
|
5
|
+
import { clamp, getSession, isSessionModelWithWidgets, } from '@jbrowse/core/util';
|
|
11
6
|
import FolderOpenIcon from '@mui/icons-material/FolderOpen';
|
|
12
7
|
import PhotoCameraIcon from '@mui/icons-material/PhotoCamera';
|
|
13
|
-
|
|
8
|
+
import { saveAs } from 'file-saver';
|
|
9
|
+
import { transaction } from 'mobx';
|
|
10
|
+
import { cast, getRoot, resolveIdentifier, types } from 'mobx-state-tree';
|
|
14
11
|
import { calculateStaticSlices, sliceIsVisible } from './slices';
|
|
15
12
|
import { viewportVisibleSection } from './viewportVisibleRegion';
|
|
16
|
-
|
|
17
|
-
const ExportSvgDialog = lazy(() => import('../components/ExportSvgDialog'));
|
|
18
|
-
/**
|
|
19
|
-
* #stateModel CircularView
|
|
20
|
-
* extends
|
|
21
|
-
* - [BaseViewModel](../baseviewmodel)
|
|
22
|
-
*/
|
|
13
|
+
const ExportSvgDialog = lazy(() => import('./components/ExportSvgDialog'));
|
|
23
14
|
function stateModelFactory(pluginManager) {
|
|
24
15
|
const minHeight = 40;
|
|
25
16
|
const minWidth = 100;
|
|
26
17
|
const defaultHeight = 400;
|
|
27
18
|
return types
|
|
28
19
|
.compose('CircularView', BaseViewModel, types.model({
|
|
29
|
-
/**
|
|
30
|
-
* #property
|
|
31
|
-
*/
|
|
32
20
|
type: types.literal('CircularView'),
|
|
33
|
-
/**
|
|
34
|
-
* #property
|
|
35
|
-
* similar to offsetPx in linear genome view
|
|
36
|
-
*/
|
|
37
21
|
offsetRadians: -Math.PI / 2,
|
|
38
|
-
/**
|
|
39
|
-
* #property
|
|
40
|
-
*/
|
|
41
22
|
bpPerPx: 200,
|
|
42
|
-
/**
|
|
43
|
-
* #property
|
|
44
|
-
*/
|
|
45
23
|
tracks: types.array(pluginManager.pluggableMstType('track', 'stateModel')),
|
|
46
|
-
/**
|
|
47
|
-
* #property
|
|
48
|
-
*/
|
|
49
24
|
hideVerticalResizeHandle: false,
|
|
50
|
-
/**
|
|
51
|
-
* #property
|
|
52
|
-
*/
|
|
53
25
|
hideTrackSelectorButton: false,
|
|
54
|
-
/**
|
|
55
|
-
* #property
|
|
56
|
-
*/
|
|
57
26
|
lockedFitToWindow: true,
|
|
58
|
-
/**
|
|
59
|
-
* #property
|
|
60
|
-
*/
|
|
61
27
|
disableImportForm: false,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
*/
|
|
65
|
-
height: types.optional(types.refinement('trackHeight', types.number, n => n >= minHeight), defaultHeight),
|
|
66
|
-
/**
|
|
67
|
-
* #property
|
|
68
|
-
*/
|
|
69
|
-
displayedRegions: types.array(Region),
|
|
70
|
-
/**
|
|
71
|
-
* #property
|
|
72
|
-
*/
|
|
28
|
+
height: types.optional(types.number, defaultHeight),
|
|
29
|
+
displayedRegions: types.optional(types.frozen(), []),
|
|
73
30
|
scrollX: 0,
|
|
74
|
-
/**
|
|
75
|
-
* #property
|
|
76
|
-
*/
|
|
77
31
|
scrollY: 0,
|
|
78
|
-
/**
|
|
79
|
-
* #property
|
|
80
|
-
*/
|
|
81
32
|
minimumRadiusPx: 25,
|
|
82
|
-
/**
|
|
83
|
-
* #property
|
|
84
|
-
*/
|
|
85
33
|
spacingPx: 10,
|
|
86
|
-
/**
|
|
87
|
-
* #property
|
|
88
|
-
*/
|
|
89
34
|
paddingPx: 80,
|
|
90
|
-
/**
|
|
91
|
-
* #property
|
|
92
|
-
*/
|
|
93
35
|
lockedPaddingPx: 100,
|
|
94
|
-
/**
|
|
95
|
-
* #property
|
|
96
|
-
*/
|
|
97
36
|
minVisibleWidth: 6,
|
|
98
|
-
/**
|
|
99
|
-
* #property
|
|
100
|
-
*/
|
|
101
37
|
minimumBlockWidth: 20,
|
|
102
|
-
/**
|
|
103
|
-
* #property
|
|
104
|
-
*/
|
|
105
38
|
trackSelectorType: 'hierarchical',
|
|
106
39
|
}))
|
|
107
40
|
.volatile(() => ({
|
|
@@ -109,25 +42,16 @@ function stateModelFactory(pluginManager) {
|
|
|
109
42
|
error: undefined,
|
|
110
43
|
}))
|
|
111
44
|
.views(self => ({
|
|
112
|
-
/**
|
|
113
|
-
* #getter
|
|
114
|
-
*/
|
|
115
45
|
get width() {
|
|
116
46
|
if (self.volatileWidth === undefined) {
|
|
117
47
|
throw new Error('wait for view to be initialized first before accessing width');
|
|
118
48
|
}
|
|
119
49
|
return self.volatileWidth;
|
|
120
50
|
},
|
|
121
|
-
/**
|
|
122
|
-
* #getter
|
|
123
|
-
*/
|
|
124
51
|
get visibleSection() {
|
|
125
52
|
const { scrollX, scrollY, width, height } = self;
|
|
126
53
|
return viewportVisibleSection([scrollX, scrollX + width, scrollY, scrollY + height], this.centerXY, this.radiusPx);
|
|
127
54
|
},
|
|
128
|
-
/**
|
|
129
|
-
* #getter
|
|
130
|
-
*/
|
|
131
55
|
get circumferencePx() {
|
|
132
56
|
let elidedBp = 0;
|
|
133
57
|
for (const r of this.elidedRegions) {
|
|
@@ -135,33 +59,18 @@ function stateModelFactory(pluginManager) {
|
|
|
135
59
|
}
|
|
136
60
|
return (elidedBp / self.bpPerPx + self.spacingPx * this.elidedRegions.length);
|
|
137
61
|
},
|
|
138
|
-
/**
|
|
139
|
-
* #getter
|
|
140
|
-
*/
|
|
141
62
|
get radiusPx() {
|
|
142
63
|
return this.circumferencePx / (2 * Math.PI);
|
|
143
64
|
},
|
|
144
|
-
/**
|
|
145
|
-
* #getter
|
|
146
|
-
*/
|
|
147
65
|
get bpPerRadian() {
|
|
148
66
|
return self.bpPerPx * this.radiusPx;
|
|
149
67
|
},
|
|
150
|
-
/**
|
|
151
|
-
* #getter
|
|
152
|
-
*/
|
|
153
68
|
get pxPerRadian() {
|
|
154
69
|
return this.radiusPx;
|
|
155
70
|
},
|
|
156
|
-
/**
|
|
157
|
-
* #getter
|
|
158
|
-
*/
|
|
159
71
|
get centerXY() {
|
|
160
72
|
return [this.radiusPx + self.paddingPx, this.radiusPx + self.paddingPx];
|
|
161
73
|
},
|
|
162
|
-
/**
|
|
163
|
-
* #getter
|
|
164
|
-
*/
|
|
165
74
|
get totalBp() {
|
|
166
75
|
let total = 0;
|
|
167
76
|
for (const region of self.displayedRegions) {
|
|
@@ -169,80 +78,46 @@ function stateModelFactory(pluginManager) {
|
|
|
169
78
|
}
|
|
170
79
|
return total;
|
|
171
80
|
},
|
|
172
|
-
/**
|
|
173
|
-
* #getter
|
|
174
|
-
*/
|
|
175
81
|
get maximumRadiusPx() {
|
|
176
82
|
return self.lockedFitToWindow
|
|
177
83
|
? Math.min(self.width, self.height) / 2 - self.lockedPaddingPx
|
|
178
84
|
: 1000000;
|
|
179
85
|
},
|
|
180
|
-
/**
|
|
181
|
-
* #getter
|
|
182
|
-
*/
|
|
183
86
|
get maxBpPerPx() {
|
|
184
87
|
const minCircumferencePx = 2 * Math.PI * self.minimumRadiusPx;
|
|
185
88
|
return this.totalBp / minCircumferencePx;
|
|
186
89
|
},
|
|
187
|
-
/**
|
|
188
|
-
* #getter
|
|
189
|
-
*/
|
|
190
90
|
get minBpPerPx() {
|
|
191
|
-
// min depends on window dimensions, clamp between old min(0.01) and max
|
|
192
91
|
const maxCircumferencePx = 2 * Math.PI * this.maximumRadiusPx;
|
|
193
92
|
return clamp(this.totalBp / maxCircumferencePx, 0.0000000001, this.maxBpPerPx);
|
|
194
93
|
},
|
|
195
|
-
/**
|
|
196
|
-
* #getter
|
|
197
|
-
*/
|
|
198
94
|
get atMaxBpPerPx() {
|
|
199
95
|
return self.bpPerPx >= this.maxBpPerPx;
|
|
200
96
|
},
|
|
201
|
-
/**
|
|
202
|
-
* #getter
|
|
203
|
-
*/
|
|
204
97
|
get atMinBpPerPx() {
|
|
205
98
|
return self.bpPerPx <= this.minBpPerPx;
|
|
206
99
|
},
|
|
207
|
-
/**
|
|
208
|
-
* #getter
|
|
209
|
-
*/
|
|
210
100
|
get tooSmallToLock() {
|
|
211
101
|
return this.minBpPerPx <= 0.0000000001;
|
|
212
102
|
},
|
|
213
|
-
/**
|
|
214
|
-
* #getter
|
|
215
|
-
*/
|
|
216
103
|
get figureDimensions() {
|
|
217
104
|
return [
|
|
218
105
|
this.radiusPx * 2 + 2 * self.paddingPx,
|
|
219
106
|
this.radiusPx * 2 + 2 * self.paddingPx,
|
|
220
107
|
];
|
|
221
108
|
},
|
|
222
|
-
/**
|
|
223
|
-
* #getter
|
|
224
|
-
*/
|
|
225
109
|
get figureWidth() {
|
|
226
110
|
return this.figureDimensions[0];
|
|
227
111
|
},
|
|
228
|
-
/**
|
|
229
|
-
* #getter
|
|
230
|
-
*/
|
|
231
112
|
get figureHeight() {
|
|
232
113
|
return this.figureDimensions[1];
|
|
233
114
|
},
|
|
234
|
-
/**
|
|
235
|
-
* #getter
|
|
236
|
-
* this is displayedRegions, post-processed to elide regions that are too
|
|
237
|
-
* small to see reasonably
|
|
238
|
-
*/
|
|
239
115
|
get elidedRegions() {
|
|
240
116
|
const visible = [];
|
|
241
117
|
self.displayedRegions.forEach(region => {
|
|
242
118
|
const widthBp = region.end - region.start;
|
|
243
119
|
const widthPx = widthBp / self.bpPerPx;
|
|
244
120
|
if (widthPx < self.minVisibleWidth) {
|
|
245
|
-
// too small to see, collapse into a single elision region
|
|
246
121
|
const lastVisible = visible.at(-1);
|
|
247
122
|
if (lastVisible === null || lastVisible === void 0 ? void 0 : lastVisible.elided) {
|
|
248
123
|
lastVisible.regions.push({ ...region });
|
|
@@ -257,11 +132,9 @@ function stateModelFactory(pluginManager) {
|
|
|
257
132
|
}
|
|
258
133
|
}
|
|
259
134
|
else {
|
|
260
|
-
// big enough to see, display it
|
|
261
135
|
visible.push({ ...region, widthBp, elided: false });
|
|
262
136
|
}
|
|
263
137
|
});
|
|
264
|
-
// remove any single-region elisions
|
|
265
138
|
for (let i = 0; i < visible.length; i += 1) {
|
|
266
139
|
const v = visible[i];
|
|
267
140
|
if (v.elided && v.regions.length === 1) {
|
|
@@ -270,9 +143,6 @@ function stateModelFactory(pluginManager) {
|
|
|
270
143
|
}
|
|
271
144
|
return visible;
|
|
272
145
|
},
|
|
273
|
-
/**
|
|
274
|
-
* #getter
|
|
275
|
-
*/
|
|
276
146
|
get assemblyNames() {
|
|
277
147
|
const assemblyNames = [];
|
|
278
148
|
self.displayedRegions.forEach(displayedRegion => {
|
|
@@ -282,9 +152,6 @@ function stateModelFactory(pluginManager) {
|
|
|
282
152
|
});
|
|
283
153
|
return assemblyNames;
|
|
284
154
|
},
|
|
285
|
-
/**
|
|
286
|
-
* #getter
|
|
287
|
-
*/
|
|
288
155
|
get initialized() {
|
|
289
156
|
const { assemblyManager } = getSession(self);
|
|
290
157
|
return (self.volatileWidth !== undefined &&
|
|
@@ -292,107 +159,62 @@ function stateModelFactory(pluginManager) {
|
|
|
292
159
|
},
|
|
293
160
|
}))
|
|
294
161
|
.views(self => ({
|
|
295
|
-
/**
|
|
296
|
-
* #getter
|
|
297
|
-
*/
|
|
298
162
|
get staticSlices() {
|
|
299
163
|
return calculateStaticSlices(self);
|
|
300
164
|
},
|
|
301
165
|
}))
|
|
302
166
|
.views(self => ({
|
|
303
|
-
/**
|
|
304
|
-
* #getter
|
|
305
|
-
*/
|
|
306
167
|
get visibleStaticSlices() {
|
|
307
168
|
return self.staticSlices.filter(s => sliceIsVisible(self, s));
|
|
308
169
|
},
|
|
309
170
|
}))
|
|
310
171
|
.actions(self => ({
|
|
311
|
-
/**
|
|
312
|
-
* #action
|
|
313
|
-
*/
|
|
314
172
|
setWidth(newWidth) {
|
|
315
173
|
self.volatileWidth = Math.max(newWidth, minWidth);
|
|
316
174
|
return self.volatileWidth;
|
|
317
175
|
},
|
|
318
|
-
/**
|
|
319
|
-
* #action
|
|
320
|
-
*/
|
|
321
176
|
setHeight(newHeight) {
|
|
322
177
|
self.height = Math.max(newHeight, minHeight);
|
|
323
178
|
return self.height;
|
|
324
179
|
},
|
|
325
|
-
/**
|
|
326
|
-
* #action
|
|
327
|
-
*/
|
|
328
180
|
resizeHeight(distance) {
|
|
329
181
|
const oldHeight = self.height;
|
|
330
182
|
const newHeight = this.setHeight(self.height + distance);
|
|
331
183
|
this.setModelViewWhenAdjust(!self.tooSmallToLock);
|
|
332
184
|
return newHeight - oldHeight;
|
|
333
185
|
},
|
|
334
|
-
/**
|
|
335
|
-
* #action
|
|
336
|
-
*/
|
|
337
186
|
resizeWidth(distance) {
|
|
338
187
|
const oldWidth = self.width;
|
|
339
188
|
const newWidth = this.setWidth(self.width + distance);
|
|
340
189
|
this.setModelViewWhenAdjust(!self.tooSmallToLock);
|
|
341
190
|
return newWidth - oldWidth;
|
|
342
191
|
},
|
|
343
|
-
/**
|
|
344
|
-
* #action
|
|
345
|
-
*/
|
|
346
192
|
rotateClockwiseButton() {
|
|
347
193
|
this.rotateClockwise(Math.PI / 6);
|
|
348
194
|
},
|
|
349
|
-
/**
|
|
350
|
-
* #action
|
|
351
|
-
*/
|
|
352
195
|
rotateCounterClockwiseButton() {
|
|
353
196
|
this.rotateCounterClockwise(Math.PI / 6);
|
|
354
197
|
},
|
|
355
|
-
/**
|
|
356
|
-
* #action
|
|
357
|
-
*/
|
|
358
198
|
rotateClockwise(distance = 0.17) {
|
|
359
199
|
self.offsetRadians += distance;
|
|
360
200
|
},
|
|
361
|
-
/**
|
|
362
|
-
* #action
|
|
363
|
-
*/
|
|
364
201
|
rotateCounterClockwise(distance = 0.17) {
|
|
365
202
|
self.offsetRadians -= distance;
|
|
366
203
|
},
|
|
367
|
-
/**
|
|
368
|
-
* #action
|
|
369
|
-
*/
|
|
370
204
|
zoomInButton() {
|
|
371
205
|
this.setBpPerPx(self.bpPerPx / 1.4);
|
|
372
206
|
},
|
|
373
|
-
/**
|
|
374
|
-
* #action
|
|
375
|
-
*/
|
|
376
207
|
zoomOutButton() {
|
|
377
208
|
this.setBpPerPx(self.bpPerPx * 1.4);
|
|
378
209
|
},
|
|
379
|
-
/**
|
|
380
|
-
* #action
|
|
381
|
-
*/
|
|
382
210
|
setBpPerPx(newVal) {
|
|
383
211
|
self.bpPerPx = clamp(newVal, self.minBpPerPx, self.maxBpPerPx);
|
|
384
212
|
},
|
|
385
|
-
/**
|
|
386
|
-
* #action
|
|
387
|
-
*/
|
|
388
213
|
setModelViewWhenAdjust(secondCondition) {
|
|
389
214
|
if (self.lockedFitToWindow && secondCondition) {
|
|
390
215
|
this.setBpPerPx(self.minBpPerPx);
|
|
391
216
|
}
|
|
392
217
|
},
|
|
393
|
-
/**
|
|
394
|
-
* #action
|
|
395
|
-
*/
|
|
396
218
|
setDisplayedRegions(regions) {
|
|
397
219
|
const previouslyEmpty = self.displayedRegions.length === 0;
|
|
398
220
|
self.displayedRegions = cast(regions);
|
|
@@ -403,9 +225,6 @@ function stateModelFactory(pluginManager) {
|
|
|
403
225
|
this.setBpPerPx(self.bpPerPx);
|
|
404
226
|
}
|
|
405
227
|
},
|
|
406
|
-
/**
|
|
407
|
-
* #action
|
|
408
|
-
*/
|
|
409
228
|
activateTrackSelector() {
|
|
410
229
|
if (self.trackSelectorType === 'hierarchical') {
|
|
411
230
|
const session = getSession(self);
|
|
@@ -417,9 +236,6 @@ function stateModelFactory(pluginManager) {
|
|
|
417
236
|
}
|
|
418
237
|
throw new Error(`invalid track selector type ${self.trackSelectorType}`);
|
|
419
238
|
},
|
|
420
|
-
/**
|
|
421
|
-
* #action
|
|
422
|
-
*/
|
|
423
239
|
toggleTrack(trackId) {
|
|
424
240
|
const hiddenCount = this.hideTrack(trackId);
|
|
425
241
|
if (!hiddenCount) {
|
|
@@ -428,15 +244,9 @@ function stateModelFactory(pluginManager) {
|
|
|
428
244
|
}
|
|
429
245
|
return false;
|
|
430
246
|
},
|
|
431
|
-
/**
|
|
432
|
-
* #action
|
|
433
|
-
*/
|
|
434
247
|
setError(error) {
|
|
435
248
|
self.error = error;
|
|
436
249
|
},
|
|
437
|
-
/**
|
|
438
|
-
* #action
|
|
439
|
-
*/
|
|
440
250
|
showTrack(trackId, initialSnapshot = {}) {
|
|
441
251
|
const schema = pluginManager.pluggableConfigSchemaType('track');
|
|
442
252
|
const conf = resolveIdentifier(schema, getRoot(self), trackId);
|
|
@@ -455,9 +265,6 @@ function stateModelFactory(pluginManager) {
|
|
|
455
265
|
});
|
|
456
266
|
self.tracks.push(track);
|
|
457
267
|
},
|
|
458
|
-
/**
|
|
459
|
-
* #action
|
|
460
|
-
*/
|
|
461
268
|
addTrackConf(configuration, initialSnapshot = {}) {
|
|
462
269
|
const { type } = configuration;
|
|
463
270
|
const name = readConfObject(configuration, 'name');
|
|
@@ -476,9 +283,6 @@ function stateModelFactory(pluginManager) {
|
|
|
476
283
|
displays: [{ type: displayConf.type, configuration: displayConf }],
|
|
477
284
|
}));
|
|
478
285
|
},
|
|
479
|
-
/**
|
|
480
|
-
* #action
|
|
481
|
-
*/
|
|
482
286
|
hideTrack(trackId) {
|
|
483
287
|
const schema = pluginManager.pluggableConfigSchemaType('track');
|
|
484
288
|
const conf = resolveIdentifier(schema, getRoot(self), trackId);
|
|
@@ -488,32 +292,19 @@ function stateModelFactory(pluginManager) {
|
|
|
488
292
|
});
|
|
489
293
|
return t.length;
|
|
490
294
|
},
|
|
491
|
-
/**
|
|
492
|
-
* #action
|
|
493
|
-
*/
|
|
494
295
|
toggleFitToWindowLock() {
|
|
495
|
-
// when going unlocked -> locked and circle is cut off, set to the
|
|
496
|
-
// locked minBpPerPx
|
|
497
296
|
self.lockedFitToWindow = !self.lockedFitToWindow;
|
|
498
297
|
this.setModelViewWhenAdjust(self.atMinBpPerPx);
|
|
499
298
|
return self.lockedFitToWindow;
|
|
500
299
|
},
|
|
501
|
-
/**
|
|
502
|
-
* #action
|
|
503
|
-
* creates an svg export and save using FileSaver
|
|
504
|
-
*/
|
|
505
300
|
async exportSvg(opts = {}) {
|
|
506
|
-
const { renderToSvg } = await import('
|
|
301
|
+
const { renderToSvg } = await import('./svgcomponents/SVGCircularView');
|
|
507
302
|
const html = await renderToSvg(self, opts);
|
|
508
303
|
const blob = new Blob([html], { type: 'image/svg+xml' });
|
|
509
304
|
saveAs(blob, opts.filename || 'image.svg');
|
|
510
305
|
},
|
|
511
306
|
}))
|
|
512
307
|
.views(self => ({
|
|
513
|
-
/**
|
|
514
|
-
* #method
|
|
515
|
-
* return the view menu items
|
|
516
|
-
*/
|
|
517
308
|
menuItems() {
|
|
518
309
|
return [
|
|
519
310
|
{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assembleLocString, polarToCartesian } from '@jbrowse/core/util';
|
|
2
2
|
import { thetaRangesOverlap } from './viewportVisibleRegion';
|
|
3
3
|
export class Slice {
|
|
4
4
|
constructor(view, region, offsetRadians, radianWidth) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { useTheme } from '@mui/material';
|
|
3
2
|
import { stripAlpha } from '@jbrowse/core/util';
|
|
3
|
+
import { useTheme } from '@mui/material';
|
|
4
4
|
export default function SVGBackground({ width, height, shift, }) {
|
|
5
5
|
const theme = useTheme();
|
|
6
6
|
return (React.createElement("rect", { width: width + shift * 2, height: height, fill: stripAlpha(theme.palette.background.default) }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CircularViewModel, ExportSvgOptions } from '../model';
|
|
2
2
|
type CGV = CircularViewModel;
|
|
3
3
|
export declare function renderToSvg(model: CGV, opts: ExportSvgOptions): Promise<string>;
|
|
4
4
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { createJBrowseTheme } from '@jbrowse/core/ui';
|
|
3
|
+
import { getSession, radToDeg, renderToStaticMarkup } from '@jbrowse/core/util';
|
|
2
4
|
import { ThemeProvider } from '@mui/material';
|
|
3
5
|
import { when } from 'mobx';
|
|
4
|
-
import { getSession, radToDeg, renderToStaticMarkup } from '@jbrowse/core/util';
|
|
5
|
-
import { createJBrowseTheme } from '@jbrowse/core/ui';
|
|
6
6
|
import { getRoot } from 'mobx-state-tree';
|
|
7
7
|
import SVGBackground from './SVGBackground';
|
|
8
8
|
import Ruler from '../components/Ruler';
|
|
@@ -22,16 +22,11 @@ export async function renderToSvg(model, opts) {
|
|
|
22
22
|
}));
|
|
23
23
|
const { staticSlices, offsetRadians, centerXY } = model;
|
|
24
24
|
const deg = radToDeg(offsetRadians);
|
|
25
|
-
// the xlink namespace is used for rendering <image> tag
|
|
26
25
|
return renderToStaticMarkup(React.createElement(ThemeProvider, { theme: createJBrowseTheme(theme) },
|
|
27
26
|
React.createElement(Wrapper, null,
|
|
28
27
|
React.createElement("svg", { width: width, height: height, xmlns: "http://www.w3.org/2000/svg", xmlnsXlink: "http://www.w3.org/1999/xlink", viewBox: [0, 0, width + shift * 2, height].toString() },
|
|
29
28
|
React.createElement(SVGBackground, { width: width, height: height, shift: shift }),
|
|
30
29
|
React.createElement("g", { transform: `translate(${centerXY}) rotate(${deg})` },
|
|
31
|
-
staticSlices.map((slice, i) => (
|
|
32
|
-
|
|
33
|
-
React.createElement(Ruler, { key: i, model: model, slice: slice }))),
|
|
34
|
-
displayResults.map(({ result }, i) => (
|
|
35
|
-
/* biome-ignore lint/suspicious/noArrayIndexKey: */
|
|
36
|
-
React.createElement(React.Fragment, { key: i }, result))))))), createRootFn);
|
|
30
|
+
staticSlices.map((slice, i) => (React.createElement(Ruler, { key: i, model: model, slice: slice }))),
|
|
31
|
+
displayResults.map(({ result }, i) => (React.createElement(React.Fragment, { key: i }, result))))))), createRootFn);
|
|
37
32
|
}
|
|
@@ -48,26 +48,21 @@ export function thetaRangesOverlap(r1start, r1length, r2start, r2length) {
|
|
|
48
48
|
if (r1length + 0.0001 >= twoPi || r2length + 0.0001 >= twoPi) {
|
|
49
49
|
return true;
|
|
50
50
|
}
|
|
51
|
-
// put both range starts between 2π and 4π
|
|
52
51
|
r1start = (((r1start % twoPi) + twoPi) % twoPi) + twoPi;
|
|
53
52
|
r2start = (((r2start % twoPi) + twoPi) % twoPi) + twoPi;
|
|
54
53
|
if (r1start < r2start + r2length && r1start + r1length > r2start) {
|
|
55
54
|
return true;
|
|
56
55
|
}
|
|
57
|
-
// move r2 2π to the left and check
|
|
58
56
|
r2start -= twoPi;
|
|
59
57
|
if (r1start < r2start + r2length && r1start + r1length > r2start) {
|
|
60
58
|
return true;
|
|
61
59
|
}
|
|
62
|
-
// move it 2π to the right and check
|
|
63
60
|
r2start += twoPi + twoPi;
|
|
64
61
|
return r1start < r2start + r2length && r1start + r1length > r2start;
|
|
65
62
|
}
|
|
66
|
-
// return which arc range has any part of the circle visible in the viewport
|
|
67
63
|
export function viewportVisibleSection(viewSides, circleCenter, circleRadius) {
|
|
68
64
|
let [viewL, viewR, viewT, viewB] = viewSides;
|
|
69
65
|
const [cx, cy] = circleCenter;
|
|
70
|
-
// transform coordinate system to center of circle
|
|
71
66
|
viewL -= cx;
|
|
72
67
|
viewR -= cx;
|
|
73
68
|
viewT -= cy;
|
|
@@ -92,61 +87,6 @@ export function viewportVisibleSection(viewSides, circleCenter, circleRadius) {
|
|
|
92
87
|
theta: [0, 2 * Math.PI],
|
|
93
88
|
};
|
|
94
89
|
}
|
|
95
|
-
// const viewportCompletelyContainsCircle =
|
|
96
|
-
// circleCenter[0] - viewL >= circleRadius &&
|
|
97
|
-
// viewR - circleCenter[0] >= circleRadius &&
|
|
98
|
-
// circleCenter[1] - viewT >= circleRadius &&
|
|
99
|
-
// viewB - circleCenter[1] >= circleRadius
|
|
100
|
-
// if (viewportCompletelyContainsCircle) {
|
|
101
|
-
// return [0, 2 * Math.PI]
|
|
102
|
-
// }
|
|
103
|
-
// const distToCenterSquared = ([x, y]) => {
|
|
104
|
-
// const [cx, cy] = circleCenter
|
|
105
|
-
// const sq = n => n * n
|
|
106
|
-
// return sq(x - cx) + sq(y - cy)
|
|
107
|
-
// }
|
|
108
|
-
// const circleRadiusSquared = circleRadius * circleRadius
|
|
109
|
-
// const tlInside = distToCenterSquared([viewL, viewT]) <= circleRadiusSquared
|
|
110
|
-
// const trInside = distToCenterSquared([viewR, viewT]) <= circleRadiusSquared
|
|
111
|
-
// const blInside = distToCenterSquared([viewL, viewB]) <= circleRadiusSquared
|
|
112
|
-
// const brInside = distToCenterSquared([viewR, viewB]) <= circleRadiusSquared
|
|
113
|
-
// const noIntersection = !tlInside && !trInside && !blInside && !brInside
|
|
114
|
-
// if (noIntersection) return undefined
|
|
115
|
-
// const circleCompletelyContainsViewport =
|
|
116
|
-
// tlInside && trInside && blInside && brInside
|
|
117
|
-
// if (circleCompletelyContainsViewport) {
|
|
118
|
-
// // viewport is in the circle, but the center is not in it, so take max
|
|
119
|
-
// // and min of thetas to the center
|
|
120
|
-
// const thetas = [
|
|
121
|
-
// Math.atan(viewT / viewL),
|
|
122
|
-
// Math.atan(viewT / viewR),
|
|
123
|
-
// Math.atan(viewB / viewL),
|
|
124
|
-
// Math.atan(viewB / viewR),
|
|
125
|
-
// ]
|
|
126
|
-
// return [Math.min(...thetas), Math.max(...thetas)]
|
|
127
|
-
// }
|
|
128
|
-
// if we get here, the viewport is partly in, partly out of the circle
|
|
129
|
-
// const viewLIntersects = Math.abs(viewL - circleCenter[0]) <= circleRadius
|
|
130
|
-
// const viewRIntersects = Math.abs(viewR - circleCenter[0]) <= circleRadius
|
|
131
|
-
// const viewTIntersects = Math.abs(viewT - circleCenter[1]) <= circleRadius
|
|
132
|
-
// const viewBIntersects = Math.abs(viewB - circleCenter[1]) <= circleRadius
|
|
133
|
-
// const numIntersectingSides =
|
|
134
|
-
// Number(viewLIntersects) +
|
|
135
|
-
// Number(viewRIntersects) +
|
|
136
|
-
// Number(viewTIntersects) +
|
|
137
|
-
// Number(viewBIntersects)
|
|
138
|
-
// if (numIntersectingSides === 4) return [0, 2 * Math.PI]
|
|
139
|
-
// if (numIntersectingSides === 3) {
|
|
140
|
-
// // TODO calculate the thetas of the
|
|
141
|
-
// } else if (numIntersectingSides === 2) {
|
|
142
|
-
// // TODO calculate the thetas of the 2 intersection points
|
|
143
|
-
// } else if (numIntersectingSides === 1) {
|
|
144
|
-
// // TODO calculate the thetas of the 1-2 intersection points of the line, and the angle between
|
|
145
|
-
// }
|
|
146
|
-
// make a list of vertices-of-interest that lie inside both shapes to examine
|
|
147
|
-
// to determine the range covered by their intersection
|
|
148
|
-
// transform coordinates to have the circle as the origin and find the intersections
|
|
149
|
-
// of the circle and the view rectangle
|
|
150
90
|
const vertices = [
|
|
151
91
|
[viewL, viewT],
|
|
152
92
|
[viewR, viewT],
|
|
@@ -157,7 +97,6 @@ export function viewportVisibleSection(viewSides, circleCenter, circleRadius) {
|
|
|
157
97
|
findCircleIntersectionY(viewR, 0, 0, circleRadius, vertices);
|
|
158
98
|
findCircleIntersectionX(viewT, 0, 0, circleRadius, vertices);
|
|
159
99
|
findCircleIntersectionX(viewB, 0, 0, circleRadius, vertices);
|
|
160
|
-
// for each edge, also look at the closest point to center if it is inside the circle
|
|
161
100
|
if (-viewL < circleRadius) {
|
|
162
101
|
vertices.push([viewL, 0]);
|
|
163
102
|
}
|
|
@@ -170,24 +109,15 @@ export function viewportVisibleSection(viewSides, circleCenter, circleRadius) {
|
|
|
170
109
|
if (viewB < circleRadius) {
|
|
171
110
|
vertices.push([0, viewB]);
|
|
172
111
|
}
|
|
173
|
-
// const verticesOriginal = vertices.map(([x, y]) => [x + cx, y + cy])
|
|
174
|
-
// now convert them all to polar and take the max and min of rho and theta
|
|
175
|
-
// const viewportCenterTheta = cartesianToTheta(viewR + viewL, viewT + viewB)
|
|
176
112
|
const reflect = viewL >= 0 ? -1 : 1;
|
|
177
|
-
// viewportCenterTheta < Math.PI / 2 || viewportCenterTheta > 1.5 * Math.PI
|
|
178
|
-
// ? -1
|
|
179
|
-
// : 1
|
|
180
113
|
let rhoMin = Number.POSITIVE_INFINITY;
|
|
181
114
|
let rhoMax = Number.NEGATIVE_INFINITY;
|
|
182
115
|
let thetaMin = Number.POSITIVE_INFINITY;
|
|
183
116
|
let thetaMax = Number.NEGATIVE_INFINITY;
|
|
184
117
|
for (const [vx, vy] of vertices) {
|
|
185
|
-
// ignore vertex if outside the viewport
|
|
186
118
|
if (vx >= viewL && vx <= viewR && vy >= viewT && vy <= viewB) {
|
|
187
119
|
const [rho, theta] = cartesianToPolar(vx * reflect, vy * reflect);
|
|
188
|
-
// ignore vertex if outside the circle
|
|
189
120
|
if (rho <= circleRadius + 0.001) {
|
|
190
|
-
// ignore theta if rho is 0
|
|
191
121
|
if (theta < thetaMin && rho > 0.0001) {
|
|
192
122
|
thetaMin = theta;
|
|
193
123
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import PluginManager from '@jbrowse/core/PluginManager';
|
|
1
|
+
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
2
2
|
export default function LaunchCircularViewF(pluginManager: PluginManager): void;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { when } from 'mobx';
|
|
2
2
|
export default function LaunchCircularViewF(pluginManager) {
|
|
3
|
-
pluginManager.addToExtensionPoint('LaunchView-CircularView',
|
|
4
|
-
// @ts-expect-error
|
|
5
|
-
async ({ session, assembly, tracks = [], }) => {
|
|
3
|
+
pluginManager.addToExtensionPoint('LaunchView-CircularView', async ({ session, assembly, tracks = [], }) => {
|
|
6
4
|
const { assemblyManager } = session;
|
|
7
5
|
const view = session.addView('CircularView', {});
|
|
8
6
|
await when(() => view.initialized);
|