@jbrowse/plugin-linear-comparative-view 3.6.4 → 3.7.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/LGVSyntenyDisplay/configSchemaF.js +1 -1
- package/dist/LGVSyntenyDisplay/model.d.ts +17 -2
- package/dist/LinearComparativeView/components/ColorBySelector.d.ts +5 -0
- package/dist/LinearComparativeView/components/ColorBySelector.js +54 -0
- package/dist/LinearComparativeView/components/Header.js +7 -2
- package/dist/LinearComparativeView/components/MinLengthSlider.d.ts +5 -0
- package/dist/LinearComparativeView/components/MinLengthSlider.js +47 -0
- package/dist/LinearComparativeView/components/OpacitySlider.d.ts +5 -0
- package/dist/LinearComparativeView/components/OpacitySlider.js +46 -0
- package/dist/LinearComparativeView/components/RubberbandSpan.js +2 -2
- package/dist/LinearComparativeView/components/SliderTooltip.d.ts +2 -0
- package/dist/LinearComparativeView/components/SliderTooltip.js +9 -0
- package/dist/LinearComparativeView/components/useRangeSelect.js +10 -14
- package/dist/LinearComparativeView/model.d.ts +7 -4
- package/dist/LinearComparativeView/model.js +4 -0
- package/dist/LinearSyntenyDisplay/afterAttach.js +5 -3
- package/dist/LinearSyntenyDisplay/components/LinearSyntenyRendering.js +10 -5
- package/dist/LinearSyntenyDisplay/components/util.d.ts +2 -1
- package/dist/LinearSyntenyDisplay/components/util.js +43 -2
- package/dist/LinearSyntenyDisplay/drawSynteny.d.ts +3 -2
- package/dist/LinearSyntenyDisplay/drawSynteny.js +284 -45
- package/dist/LinearSyntenyDisplay/model.d.ts +6 -0
- package/dist/LinearSyntenyDisplay/model.js +12 -0
- package/dist/LinearSyntenyView/components/DiagonalizationProgressDialog.d.ts +6 -0
- package/dist/LinearSyntenyView/components/DiagonalizationProgressDialog.js +87 -0
- package/dist/LinearSyntenyView/model.d.ts +46 -15
- package/dist/LinearSyntenyView/model.js +70 -18
- package/dist/LinearSyntenyView/util/diagonalize.d.ts +27 -0
- package/dist/LinearSyntenyView/util/diagonalize.js +91 -0
- package/esm/LGVSyntenyDisplay/configSchemaF.js +1 -1
- package/esm/LGVSyntenyDisplay/model.d.ts +17 -2
- package/esm/LinearComparativeView/components/ColorBySelector.d.ts +5 -0
- package/esm/LinearComparativeView/components/ColorBySelector.js +49 -0
- package/esm/LinearComparativeView/components/Header.js +8 -3
- package/esm/LinearComparativeView/components/MinLengthSlider.d.ts +5 -0
- package/esm/LinearComparativeView/components/MinLengthSlider.js +42 -0
- package/esm/LinearComparativeView/components/OpacitySlider.d.ts +5 -0
- package/esm/LinearComparativeView/components/OpacitySlider.js +41 -0
- package/esm/LinearComparativeView/components/RubberbandSpan.js +2 -2
- package/esm/LinearComparativeView/components/SliderTooltip.d.ts +2 -0
- package/esm/LinearComparativeView/components/SliderTooltip.js +6 -0
- package/esm/LinearComparativeView/components/useRangeSelect.js +11 -15
- package/esm/LinearComparativeView/model.d.ts +7 -4
- package/esm/LinearComparativeView/model.js +4 -0
- package/esm/LinearSyntenyDisplay/afterAttach.js +6 -4
- package/esm/LinearSyntenyDisplay/components/LinearSyntenyRendering.js +10 -5
- package/esm/LinearSyntenyDisplay/components/util.d.ts +2 -1
- package/esm/LinearSyntenyDisplay/components/util.js +43 -3
- package/esm/LinearSyntenyDisplay/drawSynteny.d.ts +3 -2
- package/esm/LinearSyntenyDisplay/drawSynteny.js +283 -45
- package/esm/LinearSyntenyDisplay/model.d.ts +6 -0
- package/esm/LinearSyntenyDisplay/model.js +12 -0
- package/esm/LinearSyntenyView/components/DiagonalizationProgressDialog.d.ts +6 -0
- package/esm/LinearSyntenyView/components/DiagonalizationProgressDialog.js +85 -0
- package/esm/LinearSyntenyView/model.d.ts +46 -15
- package/esm/LinearSyntenyView/model.js +70 -18
- package/esm/LinearSyntenyView/util/diagonalize.d.ts +27 -0
- package/esm/LinearSyntenyView/util/diagonalize.js +88 -0
- package/package.json +5 -5
|
@@ -13,6 +13,7 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
13
13
|
showIntraviewLinks: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
14
14
|
linkViews: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
15
15
|
interactiveOverlay: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
16
|
+
showDynamicControls: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
16
17
|
levels: import("mobx-state-tree").IArrayType<import("mobx-state-tree").IModelType<{
|
|
17
18
|
id: import("mobx-state-tree").IOptionalIType<import("mobx-state-tree").ISimpleType<string>, [undefined]>;
|
|
18
19
|
type: import("mobx-state-tree").IType<string | undefined, string, string>;
|
|
@@ -78,8 +79,8 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
78
79
|
readonly interRegionPaddingWidth: number;
|
|
79
80
|
readonly assemblyNames: string[];
|
|
80
81
|
readonly assemblyDisplayNames: string[];
|
|
81
|
-
readonly isTopLevelView:
|
|
82
|
-
readonly stickyViewHeaders: boolean
|
|
82
|
+
readonly isTopLevelView: boolean;
|
|
83
|
+
readonly stickyViewHeaders: boolean;
|
|
83
84
|
readonly rubberbandTop: number;
|
|
84
85
|
readonly pinnedTracksTop: number;
|
|
85
86
|
} & {
|
|
@@ -285,6 +286,7 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
285
286
|
drawCIGAR: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
286
287
|
drawCIGARMatchesOnly: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
287
288
|
drawCurves: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
289
|
+
drawLocationMarkers: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
288
290
|
}, {
|
|
289
291
|
width: number;
|
|
290
292
|
} & {
|
|
@@ -308,6 +310,7 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
308
310
|
removeView(view: import("@jbrowse/plugin-linear-genome-view").LinearGenomeViewModel): void;
|
|
309
311
|
setLevelHeight(newHeight: number, level?: number): number;
|
|
310
312
|
setLinkViews(arg: boolean): void;
|
|
313
|
+
setShowDynamicControls(arg: boolean): void;
|
|
311
314
|
activateTrackSelector(level: number): import("@jbrowse/core/util").Widget;
|
|
312
315
|
toggleTrack(trackId: string, level?: number): void;
|
|
313
316
|
showTrack(trackId: string, level?: number, initialSnapshot?: {}): void;
|
|
@@ -333,6 +336,7 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
333
336
|
setDrawCurves(arg: boolean): void;
|
|
334
337
|
setDrawCIGAR(arg: boolean): void;
|
|
335
338
|
setDrawCIGARMatchesOnly(arg: boolean): void;
|
|
339
|
+
setDrawLocationMarkers(arg: boolean): void;
|
|
336
340
|
showAllRegions(): void;
|
|
337
341
|
} & {
|
|
338
342
|
exportSvg(opts: ExportSvgOptions): Promise<void>;
|
|
@@ -344,40 +348,66 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
344
348
|
icon: import("@mui/material/OverridableComponent").OverridableComponent<import("@mui/material").SvgIconTypeMap<{}, "svg">> & {
|
|
345
349
|
muiName: string;
|
|
346
350
|
};
|
|
347
|
-
|
|
351
|
+
helpText: string;
|
|
348
352
|
type?: undefined;
|
|
353
|
+
checked?: undefined;
|
|
354
|
+
subMenu?: undefined;
|
|
349
355
|
} | {
|
|
350
356
|
label: string;
|
|
351
|
-
checked: boolean;
|
|
352
357
|
type: string;
|
|
353
|
-
|
|
358
|
+
checked: boolean;
|
|
354
359
|
onClick: () => void;
|
|
360
|
+
helpText: string;
|
|
361
|
+
description?: undefined;
|
|
355
362
|
icon?: undefined;
|
|
363
|
+
subMenu?: undefined;
|
|
356
364
|
} | {
|
|
357
365
|
label: string;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
366
|
+
subMenu: ({
|
|
367
|
+
label: string;
|
|
368
|
+
checked: boolean;
|
|
369
|
+
type: string;
|
|
370
|
+
description: string;
|
|
371
|
+
onClick: () => void;
|
|
372
|
+
helpText: string;
|
|
373
|
+
icon?: undefined;
|
|
374
|
+
} | {
|
|
375
|
+
label: string;
|
|
376
|
+
type: string;
|
|
377
|
+
checked: boolean;
|
|
378
|
+
icon: typeof Curves;
|
|
379
|
+
onClick: () => void;
|
|
380
|
+
helpText: string;
|
|
381
|
+
description?: undefined;
|
|
382
|
+
})[];
|
|
383
|
+
onClick?: undefined;
|
|
364
384
|
description?: undefined;
|
|
385
|
+
icon?: undefined;
|
|
386
|
+
helpText?: undefined;
|
|
387
|
+
type?: undefined;
|
|
388
|
+
checked?: undefined;
|
|
365
389
|
} | {
|
|
366
390
|
label: string;
|
|
367
391
|
type: string;
|
|
368
392
|
checked: boolean;
|
|
369
|
-
icon:
|
|
393
|
+
icon: import("@mui/material/OverridableComponent").OverridableComponent<import("@mui/material").SvgIconTypeMap<{}, "svg">> & {
|
|
394
|
+
muiName: string;
|
|
395
|
+
};
|
|
370
396
|
onClick: () => void;
|
|
397
|
+
helpText: string;
|
|
371
398
|
description?: undefined;
|
|
399
|
+
subMenu?: undefined;
|
|
372
400
|
} | {
|
|
373
401
|
label: string;
|
|
374
402
|
icon: import("@mui/material/OverridableComponent").OverridableComponent<import("@mui/material").SvgIconTypeMap<{}, "svg">> & {
|
|
375
403
|
muiName: string;
|
|
376
404
|
};
|
|
377
405
|
onClick: () => void;
|
|
406
|
+
helpText: string;
|
|
378
407
|
description?: undefined;
|
|
379
|
-
checked?: undefined;
|
|
380
408
|
type?: undefined;
|
|
409
|
+
checked?: undefined;
|
|
410
|
+
subMenu?: undefined;
|
|
381
411
|
})[];
|
|
382
412
|
menuItems(): (import("@jbrowse/core/ui").MenuDivider | import("@jbrowse/core/ui").MenuSubHeader | import("@jbrowse/core/ui").NormalMenuItem | import("@jbrowse/core/ui").CheckboxMenuItem | import("@jbrowse/core/ui").RadioMenuItem | import("@jbrowse/core/ui").SubMenuItem | {
|
|
383
413
|
label: string;
|
|
@@ -399,6 +429,7 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
399
429
|
showIntraviewLinks: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
400
430
|
linkViews: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
401
431
|
interactiveOverlay: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
432
|
+
showDynamicControls: import("mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
402
433
|
levels: import("mobx-state-tree").IArrayType<import("mobx-state-tree").IModelType<{
|
|
403
434
|
id: import("mobx-state-tree").IOptionalIType<import("mobx-state-tree").ISimpleType<string>, [undefined]>;
|
|
404
435
|
type: import("mobx-state-tree").IType<string | undefined, string, string>;
|
|
@@ -464,8 +495,8 @@ export default function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
464
495
|
readonly interRegionPaddingWidth: number;
|
|
465
496
|
readonly assemblyNames: string[];
|
|
466
497
|
readonly assemblyDisplayNames: string[];
|
|
467
|
-
readonly isTopLevelView:
|
|
468
|
-
readonly stickyViewHeaders: boolean
|
|
498
|
+
readonly isTopLevelView: boolean;
|
|
499
|
+
readonly stickyViewHeaders: boolean;
|
|
469
500
|
readonly rubberbandTop: number;
|
|
470
501
|
readonly pinnedTracksTop: number;
|
|
471
502
|
} & {
|
|
@@ -42,6 +42,7 @@ const util_1 = require("@jbrowse/core/util");
|
|
|
42
42
|
const CropFree_1 = __importDefault(require("@mui/icons-material/CropFree"));
|
|
43
43
|
const Link_1 = __importDefault(require("@mui/icons-material/Link"));
|
|
44
44
|
const PhotoCamera_1 = __importDefault(require("@mui/icons-material/PhotoCamera"));
|
|
45
|
+
const Shuffle_1 = __importDefault(require("@mui/icons-material/Shuffle"));
|
|
45
46
|
const Visibility_1 = __importDefault(require("@mui/icons-material/Visibility"));
|
|
46
47
|
const file_saver_1 = require("file-saver");
|
|
47
48
|
const mobx_1 = require("mobx");
|
|
@@ -49,6 +50,7 @@ const mobx_state_tree_1 = require("mobx-state-tree");
|
|
|
49
50
|
const Icons_1 = require("./components/Icons");
|
|
50
51
|
const model_1 = __importDefault(require("../LinearComparativeView/model"));
|
|
51
52
|
const ExportSvgDialog = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./components/ExportSvgDialog'))));
|
|
53
|
+
const DiagonalizationProgressDialog = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./components/DiagonalizationProgressDialog'))));
|
|
52
54
|
function stateModelFactory(pluginManager) {
|
|
53
55
|
return mobx_state_tree_1.types
|
|
54
56
|
.compose('LinearSyntenyView', (0, model_1.default)(pluginManager), mobx_state_tree_1.types.model({
|
|
@@ -56,6 +58,7 @@ function stateModelFactory(pluginManager) {
|
|
|
56
58
|
drawCIGAR: true,
|
|
57
59
|
drawCIGARMatchesOnly: false,
|
|
58
60
|
drawCurves: false,
|
|
61
|
+
drawLocationMarkers: false,
|
|
59
62
|
}))
|
|
60
63
|
.volatile(() => ({
|
|
61
64
|
importFormSyntenyTrackSelections: mobx_1.observable.array(),
|
|
@@ -79,6 +82,9 @@ function stateModelFactory(pluginManager) {
|
|
|
79
82
|
setDrawCIGARMatchesOnly(arg) {
|
|
80
83
|
self.drawCIGARMatchesOnly = arg;
|
|
81
84
|
},
|
|
85
|
+
setDrawLocationMarkers(arg) {
|
|
86
|
+
self.drawLocationMarkers = arg;
|
|
87
|
+
},
|
|
82
88
|
showAllRegions() {
|
|
83
89
|
(0, mobx_1.transaction)(() => {
|
|
84
90
|
for (const view of self.views) {
|
|
@@ -107,30 +113,83 @@ function stateModelFactory(pluginManager) {
|
|
|
107
113
|
onClick: self.squareView,
|
|
108
114
|
description: 'Makes both views use the same zoom level, adjusting to the average of each',
|
|
109
115
|
icon: CropFree_1.default,
|
|
116
|
+
helpText: 'Square view synchronizes the zoom levels of both genome views by calculating the average zoom level and applying it to both panels. This helps ensure features are displayed at comparable scales, making it easier to compare syntenic regions visually.',
|
|
110
117
|
},
|
|
111
118
|
{
|
|
112
119
|
label: 'Show all regions',
|
|
113
120
|
onClick: self.showAllRegions,
|
|
114
121
|
description: 'Show entire genome assemblies',
|
|
115
122
|
icon: Visibility_1.default,
|
|
123
|
+
helpText: 'This command will zoom out all views to display the entire genome assemblies. This is useful when you want to get a high-level overview of syntenic relationships across whole genomes or when you need to reset the view after zooming into specific regions.',
|
|
116
124
|
},
|
|
117
125
|
{
|
|
118
|
-
label: '
|
|
119
|
-
checked: self.drawCIGAR,
|
|
120
|
-
type: 'checkbox',
|
|
121
|
-
description: 'If disabled, only draws the broad scale CIGAR match',
|
|
126
|
+
label: 'Re-order chromosomes',
|
|
122
127
|
onClick: () => {
|
|
123
|
-
|
|
128
|
+
(0, util_1.getSession)(self).queueDialog(handleClose => [
|
|
129
|
+
DiagonalizationProgressDialog,
|
|
130
|
+
{
|
|
131
|
+
handleClose,
|
|
132
|
+
model: self,
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
124
135
|
},
|
|
136
|
+
icon: Shuffle_1.default,
|
|
137
|
+
description: "Reorder and reorient query regions to minimize crossing lines, also known as 'diagonalizing'",
|
|
138
|
+
helpText: "This operation 'diagonalizes' the data which algorithmically reorders and reorients chromosomes to minimize crossing synteny lines, creating a more diagonal pattern. This makes it easier to identify large-scale genomic rearrangements, inversions, and translocations. The process may take a few moments for large genomes.",
|
|
125
139
|
},
|
|
126
140
|
{
|
|
127
|
-
label: '
|
|
128
|
-
checked: self.drawCIGARMatchesOnly,
|
|
141
|
+
label: 'Show dynamic controls',
|
|
129
142
|
type: 'checkbox',
|
|
130
|
-
|
|
143
|
+
checked: self.showDynamicControls,
|
|
131
144
|
onClick: () => {
|
|
132
|
-
self.
|
|
145
|
+
self.setShowDynamicControls(!self.showDynamicControls);
|
|
133
146
|
},
|
|
147
|
+
helpText: 'Toggle visibility of dynamic controls like opacity and minimum length sliders. These controls allow you to adjust synteny visualization parameters in real-time.',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
label: 'Draw',
|
|
151
|
+
subMenu: [
|
|
152
|
+
{
|
|
153
|
+
label: 'Draw CIGAR',
|
|
154
|
+
checked: self.drawCIGAR,
|
|
155
|
+
type: 'checkbox',
|
|
156
|
+
description: 'If disabled, only draws the broad scale CIGAR match',
|
|
157
|
+
onClick: () => {
|
|
158
|
+
self.setDrawCIGAR(!self.drawCIGAR);
|
|
159
|
+
},
|
|
160
|
+
helpText: 'CIGAR strings encode detailed alignment information including matches, insertions, and deletions. When enabled, this option visualizes the fine-scale variations in syntenic alignments. Disable this for a cleaner view that shows only broad syntenic blocks.',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
label: 'Draw only CIGAR matches',
|
|
164
|
+
checked: self.drawCIGARMatchesOnly,
|
|
165
|
+
type: 'checkbox',
|
|
166
|
+
description: 'If enabled, it hides the insertions and deletions in the CIGAR strings, helps with divergent',
|
|
167
|
+
onClick: () => {
|
|
168
|
+
self.setDrawCIGARMatchesOnly(!self.drawCIGARMatchesOnly);
|
|
169
|
+
},
|
|
170
|
+
helpText: 'When comparing divergent genomes, showing all insertions and deletions can clutter the view. This option filters the CIGAR visualization to show only the matching regions, providing a cleaner view of conserved syntenic blocks while hiding small-scale indels.',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
label: 'Draw curved lines',
|
|
174
|
+
type: 'checkbox',
|
|
175
|
+
checked: self.drawCurves,
|
|
176
|
+
icon: Icons_1.Curves,
|
|
177
|
+
onClick: () => {
|
|
178
|
+
self.setDrawCurves(!self.drawCurves);
|
|
179
|
+
},
|
|
180
|
+
helpText: 'Toggle between straight lines and smooth bezier curves for synteny connections. Curved lines can make the visualization more aesthetically pleasing and may help reduce visual clutter when many syntenic regions are displayed. Straight lines provide a more direct representation.',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
label: 'Draw location markers',
|
|
184
|
+
type: 'checkbox',
|
|
185
|
+
checked: self.drawLocationMarkers,
|
|
186
|
+
description: 'Draw periodic markers to show location within large matches',
|
|
187
|
+
onClick: () => {
|
|
188
|
+
self.setDrawLocationMarkers(!self.drawLocationMarkers);
|
|
189
|
+
},
|
|
190
|
+
helpText: 'Location markers add periodic visual indicators along long syntenic blocks, helping you track position and scale within large conserved regions. This is particularly useful when examining very long syntenic matches where it can be difficult to gauge relative position.',
|
|
191
|
+
},
|
|
192
|
+
],
|
|
134
193
|
},
|
|
135
194
|
{
|
|
136
195
|
label: 'Link views',
|
|
@@ -140,15 +199,7 @@ function stateModelFactory(pluginManager) {
|
|
|
140
199
|
onClick: () => {
|
|
141
200
|
self.setLinkViews(!self.linkViews);
|
|
142
201
|
},
|
|
143
|
-
|
|
144
|
-
{
|
|
145
|
-
label: 'Use curved lines',
|
|
146
|
-
type: 'checkbox',
|
|
147
|
-
checked: self.drawCurves,
|
|
148
|
-
icon: Icons_1.Curves,
|
|
149
|
-
onClick: () => {
|
|
150
|
-
self.setDrawCurves(!self.drawCurves);
|
|
151
|
-
},
|
|
202
|
+
helpText: 'When linked, panning and zooming in one genome view will automatically adjust the other view to maintain the correspondence shown by synteny lines. This makes it easier to explore syntenic regions interactively. Unlink views to navigate each genome independently.',
|
|
152
203
|
},
|
|
153
204
|
{
|
|
154
205
|
label: 'Export SVG',
|
|
@@ -162,6 +213,7 @@ function stateModelFactory(pluginManager) {
|
|
|
162
213
|
},
|
|
163
214
|
]);
|
|
164
215
|
},
|
|
216
|
+
helpText: 'Export the current synteny view as a scalable vector graphics (SVG) file. SVG format preserves quality at any zoom level and can be edited in vector graphics software like Inkscape or Adobe Illustrator. Perfect for creating publication-quality figures.',
|
|
165
217
|
},
|
|
166
218
|
];
|
|
167
219
|
},
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface AlignmentData {
|
|
2
|
+
queryRefName: string;
|
|
3
|
+
refRefName: string;
|
|
4
|
+
queryStart: number;
|
|
5
|
+
queryEnd: number;
|
|
6
|
+
refStart: number;
|
|
7
|
+
refEnd: number;
|
|
8
|
+
strand: number;
|
|
9
|
+
}
|
|
10
|
+
export interface Region {
|
|
11
|
+
refName: string;
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
reversed?: boolean;
|
|
15
|
+
assemblyName: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DiagonalizationResult {
|
|
18
|
+
newRegions: Region[];
|
|
19
|
+
stats: {
|
|
20
|
+
totalAlignments: number;
|
|
21
|
+
regionsProcessed: number;
|
|
22
|
+
regionsReordered: number;
|
|
23
|
+
regionsReversed: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export type ProgressCallback = (progress: number, message: string) => void | Promise<void>;
|
|
27
|
+
export declare function diagonalizeRegions(alignments: AlignmentData[], currentRegions: Region[], progressCallback?: ProgressCallback): Promise<DiagonalizationResult>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diagonalizeRegions = diagonalizeRegions;
|
|
4
|
+
async function diagonalizeRegions(alignments, currentRegions, progressCallback) {
|
|
5
|
+
const updateProgress = async (progress, message) => {
|
|
6
|
+
if (progressCallback) {
|
|
7
|
+
await progressCallback(progress, message);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
await updateProgress(20, `Grouping ${alignments.length} alignments...`);
|
|
11
|
+
const queryGroups = new Map();
|
|
12
|
+
for (const aln of alignments) {
|
|
13
|
+
const targetRefName = aln.refRefName;
|
|
14
|
+
if (!queryGroups.has(targetRefName)) {
|
|
15
|
+
queryGroups.set(targetRefName, {
|
|
16
|
+
refAlignments: new Map(),
|
|
17
|
+
strandWeightedSum: 0,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const group = queryGroups.get(targetRefName);
|
|
21
|
+
const alnLength = Math.abs(aln.queryEnd - aln.queryStart);
|
|
22
|
+
if (!group.refAlignments.has(aln.queryRefName)) {
|
|
23
|
+
group.refAlignments.set(aln.queryRefName, {
|
|
24
|
+
bases: 0,
|
|
25
|
+
positions: [],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const refData = group.refAlignments.get(aln.queryRefName);
|
|
29
|
+
refData.bases += alnLength;
|
|
30
|
+
refData.positions.push((aln.queryStart + aln.queryEnd) / 2);
|
|
31
|
+
const direction = aln.strand >= 0 ? 1 : -1;
|
|
32
|
+
group.strandWeightedSum += direction * alnLength;
|
|
33
|
+
}
|
|
34
|
+
await updateProgress(50, 'Determining optimal ordering and orientation...');
|
|
35
|
+
const queryOrdering = [];
|
|
36
|
+
for (const [targetRefName, group] of queryGroups) {
|
|
37
|
+
let bestRefName = '';
|
|
38
|
+
let maxBases = 0;
|
|
39
|
+
let bestPositions = [];
|
|
40
|
+
for (const [firstViewRefName, data] of group.refAlignments) {
|
|
41
|
+
if (data.bases > maxBases) {
|
|
42
|
+
maxBases = data.bases;
|
|
43
|
+
bestRefName = firstViewRefName;
|
|
44
|
+
bestPositions = data.positions;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const bestRefPos = bestPositions.reduce((a, b) => a + b, 0) / bestPositions.length;
|
|
48
|
+
const shouldReverse = group.strandWeightedSum < 0;
|
|
49
|
+
queryOrdering.push({
|
|
50
|
+
refName: targetRefName,
|
|
51
|
+
bestRefName,
|
|
52
|
+
bestRefPos,
|
|
53
|
+
shouldReverse,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
await updateProgress(70, `Sorting ${queryOrdering.length} query regions...`);
|
|
57
|
+
queryOrdering.sort((a, b) => {
|
|
58
|
+
if (a.bestRefName !== b.bestRefName) {
|
|
59
|
+
return a.bestRefName.localeCompare(b.bestRefName);
|
|
60
|
+
}
|
|
61
|
+
return a.bestRefPos - b.bestRefPos;
|
|
62
|
+
});
|
|
63
|
+
await updateProgress(85, 'Building new region layout...');
|
|
64
|
+
const newQueryRegions = [];
|
|
65
|
+
let regionsReversed = 0;
|
|
66
|
+
for (const { refName, shouldReverse } of queryOrdering) {
|
|
67
|
+
const region = currentRegions.find(r => r.refName === refName);
|
|
68
|
+
if (region) {
|
|
69
|
+
newQueryRegions.push({
|
|
70
|
+
...region,
|
|
71
|
+
reversed: shouldReverse,
|
|
72
|
+
});
|
|
73
|
+
if (shouldReverse !== region.reversed) {
|
|
74
|
+
regionsReversed++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.warn(`Could not find region for refName: ${refName}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
await updateProgress(100, 'Diagonalization complete!');
|
|
82
|
+
return {
|
|
83
|
+
newRegions: newQueryRegions,
|
|
84
|
+
stats: {
|
|
85
|
+
totalAlignments: alignments.length,
|
|
86
|
+
regionsProcessed: queryOrdering.length,
|
|
87
|
+
regionsReordered: newQueryRegions.length,
|
|
88
|
+
regionsReversed,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -8,7 +8,7 @@ function configSchemaF(pluginManager) {
|
|
|
8
8
|
const l2name = (mate === null || mate === void 0 ? void 0 : mate.name) || (mate === null || mate === void 0 ? void 0 : mate.id);
|
|
9
9
|
return [
|
|
10
10
|
l1name ? `Name1: ${l1name}` : '',
|
|
11
|
-
l2name ? `
|
|
11
|
+
l2name ? `Name2: ${l2name}` : '',
|
|
12
12
|
`Loc1: ${assembleLocString({
|
|
13
13
|
refName: f.get('refName'),
|
|
14
14
|
start: f.get('start'),
|
|
@@ -182,6 +182,7 @@ declare function stateModelFactory(schema: AnyConfigurationSchemaType): import("
|
|
|
182
182
|
regionCannotBeRenderedText(_region: import("@jbrowse/core/util").Region): "" | "Force load to see features";
|
|
183
183
|
regionCannotBeRendered(_region: import("@jbrowse/core/util").Region): import("react/jsx-runtime").JSX.Element | null;
|
|
184
184
|
} & {
|
|
185
|
+
mouseoverExtraInformation: string | undefined;
|
|
185
186
|
featureIdUnderMouse: undefined | string;
|
|
186
187
|
contextMenuFeature: undefined | Feature;
|
|
187
188
|
} & {
|
|
@@ -196,9 +197,22 @@ declare function stateModelFactory(schema: AnyConfigurationSchemaType): import("
|
|
|
196
197
|
} & {
|
|
197
198
|
readonly features: import("@jbrowse/core/util/compositeMap").default<string, Feature>;
|
|
198
199
|
readonly featureUnderMouse: Feature | undefined;
|
|
200
|
+
readonly layoutFeatures: import("@jbrowse/core/util/compositeMap").default<string, [number, number, number, number] | [number, number, number, number, {
|
|
201
|
+
label?: string;
|
|
202
|
+
description?: string;
|
|
203
|
+
refName: string;
|
|
204
|
+
}]>;
|
|
199
205
|
getFeatureOverlapping(blockKey: string, x: number, y: number): string | undefined;
|
|
200
|
-
getFeatureByID(blockKey: string, id: string): [number, number, number, number] |
|
|
201
|
-
|
|
206
|
+
getFeatureByID(blockKey: string, id: string): ([number, number, number, number] | [number, number, number, number, {
|
|
207
|
+
label?: string;
|
|
208
|
+
description?: string;
|
|
209
|
+
refName: string;
|
|
210
|
+
}]) | undefined;
|
|
211
|
+
searchFeatureByID(id: string): ([number, number, number, number] | [number, number, number, number, {
|
|
212
|
+
label?: string;
|
|
213
|
+
description?: string;
|
|
214
|
+
refName: string;
|
|
215
|
+
}]) | undefined;
|
|
202
216
|
} & {
|
|
203
217
|
addBlock(key: string, block: import("@jbrowse/core/util/blockTypes").BaseBlock): void;
|
|
204
218
|
deleteBlock(key: string): void;
|
|
@@ -207,6 +221,7 @@ declare function stateModelFactory(schema: AnyConfigurationSchemaType): import("
|
|
|
207
221
|
clearFeatureSelection(): void;
|
|
208
222
|
setFeatureIdUnderMouse(feature?: string): void;
|
|
209
223
|
setContextMenuFeature(feature?: Feature): void;
|
|
224
|
+
setMouseoverExtraInformation(extra?: string): void;
|
|
210
225
|
} & {
|
|
211
226
|
reload(): Promise<void>;
|
|
212
227
|
} & {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
|
|
3
|
+
import PaletteIcon from '@mui/icons-material/Palette';
|
|
4
|
+
import { observer } from 'mobx-react';
|
|
5
|
+
const ColorBySelector = observer(function ({ model, }) {
|
|
6
|
+
var _a, _b, _c;
|
|
7
|
+
const firstDisplay = (_b = (_a = model.levels[0]) === null || _a === void 0 ? void 0 : _a.tracks[0]) === null || _b === void 0 ? void 0 : _b.displays[0];
|
|
8
|
+
const colorBy = (_c = firstDisplay === null || firstDisplay === void 0 ? void 0 : firstDisplay.colorBy) !== null && _c !== void 0 ? _c : 'default';
|
|
9
|
+
const setColorBy = (value) => {
|
|
10
|
+
for (const level of model.levels) {
|
|
11
|
+
for (const track of level.tracks) {
|
|
12
|
+
for (const display of track.displays) {
|
|
13
|
+
;
|
|
14
|
+
display.setColorBy(value);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
return (_jsx(CascadingMenuButton, { menuItems: [
|
|
20
|
+
{
|
|
21
|
+
label: 'Default',
|
|
22
|
+
type: 'radio',
|
|
23
|
+
checked: colorBy === 'default',
|
|
24
|
+
onClick: () => {
|
|
25
|
+
setColorBy('default');
|
|
26
|
+
},
|
|
27
|
+
helpText: 'Use the default color scheme with CIGAR operation coloring. Insertions, deletions, matches, and mismatches are shown in different colors with transparency.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: 'Strand',
|
|
31
|
+
type: 'radio',
|
|
32
|
+
checked: colorBy === 'strand',
|
|
33
|
+
onClick: () => {
|
|
34
|
+
setColorBy('strand');
|
|
35
|
+
},
|
|
36
|
+
helpText: 'Color alignments by strand orientation. Forward strand alignments and reverse strand alignments are shown in different colors, making it easy to identify inversions and strand-specific patterns.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
label: 'Query',
|
|
40
|
+
type: 'radio',
|
|
41
|
+
checked: colorBy === 'query',
|
|
42
|
+
onClick: () => {
|
|
43
|
+
setColorBy('query');
|
|
44
|
+
},
|
|
45
|
+
helpText: 'Color alignments by query sequence name. Each unique query sequence is assigned a consistent color based on its name, making it easy to visually distinguish between different sequences.',
|
|
46
|
+
},
|
|
47
|
+
], children: _jsx(PaletteIcon, {}) }));
|
|
48
|
+
});
|
|
49
|
+
export default ColorBySelector;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
|
|
4
4
|
import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons';
|
|
@@ -7,17 +7,22 @@ import SearchIcon from '@mui/icons-material/Search';
|
|
|
7
7
|
import { FormGroup } from '@mui/material';
|
|
8
8
|
import { observer } from 'mobx-react';
|
|
9
9
|
import { makeStyles } from 'tss-react/mui';
|
|
10
|
+
import ColorBySelector from './ColorBySelector';
|
|
10
11
|
import HeaderSearchBoxes from './HeaderSearchBoxes';
|
|
12
|
+
import MinLengthSlider from './MinLengthSlider';
|
|
13
|
+
import OpacitySlider from './OpacitySlider';
|
|
11
14
|
const useStyles = makeStyles()({
|
|
12
15
|
inline: {
|
|
13
16
|
display: 'inline-flex',
|
|
14
17
|
},
|
|
15
18
|
});
|
|
16
19
|
const Header = observer(function ({ model, }) {
|
|
20
|
+
var _a, _b;
|
|
17
21
|
const { classes } = useStyles();
|
|
18
|
-
const { views } = model;
|
|
22
|
+
const { views, levels, showDynamicControls } = model;
|
|
19
23
|
const [showSearchBoxes, setShowSearchBoxes] = useState(views.length <= 3);
|
|
20
24
|
const [sideBySide, setSideBySide] = useState(views.length <= 3);
|
|
25
|
+
const hasDisplays = (_b = (_a = levels[0]) === null || _a === void 0 ? void 0 : _a.tracks[0]) === null || _b === void 0 ? void 0 : _b.displays[0];
|
|
21
26
|
return (_jsxs(FormGroup, { row: true, children: [_jsx(CascadingMenuButton, { menuItems: [
|
|
22
27
|
{
|
|
23
28
|
label: 'Synteny track selectors',
|
|
@@ -72,6 +77,6 @@ const Header = observer(function ({ model, }) {
|
|
|
72
77
|
setSideBySide(!sideBySide);
|
|
73
78
|
},
|
|
74
79
|
},
|
|
75
|
-
], children: _jsx(SearchIcon, {}) }), showSearchBoxes ? (_jsx("span", { className: sideBySide ? classes.inline : undefined, children: views.map(view => (_jsx(HeaderSearchBoxes, { view: view }, view.id))) })) : null] }));
|
|
80
|
+
], children: _jsx(SearchIcon, {}) }), hasDisplays && showDynamicControls ? (_jsxs(_Fragment, { children: [_jsx(ColorBySelector, { model: model }), _jsx(OpacitySlider, { model: model }), _jsx(MinLengthSlider, { model: model })] })) : null, showSearchBoxes ? (_jsx("span", { className: sideBySide ? classes.inline : undefined, children: views.map(view => (_jsx(HeaderSearchBoxes, { view: view }, view.id))) })) : null] }));
|
|
76
81
|
});
|
|
77
82
|
export default Header;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Box, Slider, Typography } from '@mui/material';
|
|
4
|
+
import { observer } from 'mobx-react';
|
|
5
|
+
import { makeStyles } from 'tss-react/mui';
|
|
6
|
+
import SliderTooltip from './SliderTooltip';
|
|
7
|
+
const useStyles = makeStyles()({
|
|
8
|
+
container: {
|
|
9
|
+
display: 'flex',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
marginLeft: 16,
|
|
12
|
+
marginRight: 16,
|
|
13
|
+
minWidth: 150,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const MinLengthSlider = observer(function ({ model, }) {
|
|
17
|
+
var _a, _b, _c;
|
|
18
|
+
const { classes } = useStyles();
|
|
19
|
+
const { levels } = model;
|
|
20
|
+
const firstDisplay = (_b = (_a = levels[0]) === null || _a === void 0 ? void 0 : _a.tracks[0]) === null || _b === void 0 ? void 0 : _b.displays[0];
|
|
21
|
+
const minAlignmentLength = (_c = firstDisplay === null || firstDisplay === void 0 ? void 0 : firstDisplay.minAlignmentLength) !== null && _c !== void 0 ? _c : 0;
|
|
22
|
+
const [minLengthValue, setMinLengthValue] = useState(Math.log2(Math.max(1, minAlignmentLength)) * 100);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setMinLengthValue(Math.log2(Math.max(1, minAlignmentLength)) * 100);
|
|
25
|
+
}, [minAlignmentLength]);
|
|
26
|
+
return (_jsxs(Box, { className: classes.container, children: [_jsx(Typography, { variant: "body2", style: { marginRight: 8 }, children: "Min length:" }), _jsx(Slider, { value: minLengthValue, onChange: (_, val) => {
|
|
27
|
+
setMinLengthValue(val);
|
|
28
|
+
}, onChangeCommitted: () => {
|
|
29
|
+
const newMinLength = Math.round(2 ** (minLengthValue / 100));
|
|
30
|
+
for (const level of levels) {
|
|
31
|
+
for (const track of level.tracks) {
|
|
32
|
+
for (const display of track.displays) {
|
|
33
|
+
;
|
|
34
|
+
display.setMinAlignmentLength(newMinLength);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, min: 0, max: Math.log2(1000000) * 100, valueLabelDisplay: "auto", valueLabelFormat: newValue => Math.round(2 ** (newValue / 100)).toLocaleString(), size: "small", style: { minWidth: 100 }, slots: {
|
|
39
|
+
valueLabel: SliderTooltip,
|
|
40
|
+
} })] }));
|
|
41
|
+
});
|
|
42
|
+
export default MinLengthSlider;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Slider, Typography } from '@mui/material';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
import { makeStyles } from 'tss-react/mui';
|
|
5
|
+
import SliderTooltip from './SliderTooltip';
|
|
6
|
+
const useStyles = makeStyles()({
|
|
7
|
+
container: {
|
|
8
|
+
display: 'flex',
|
|
9
|
+
alignItems: 'center',
|
|
10
|
+
marginLeft: 16,
|
|
11
|
+
marginRight: 16,
|
|
12
|
+
minWidth: 150,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
const OpacitySlider = observer(function ({ model, }) {
|
|
16
|
+
var _a, _b, _c;
|
|
17
|
+
const { classes } = useStyles();
|
|
18
|
+
const { levels } = model;
|
|
19
|
+
const firstDisplay = (_b = (_a = levels[0]) === null || _a === void 0 ? void 0 : _a.tracks[0]) === null || _b === void 0 ? void 0 : _b.displays[0];
|
|
20
|
+
const alpha = (_c = firstDisplay === null || firstDisplay === void 0 ? void 0 : firstDisplay.alpha) !== null && _c !== void 0 ? _c : 1;
|
|
21
|
+
const exponent = 3;
|
|
22
|
+
const alphaToSlider = (a) => Math.pow(a, 1 / exponent);
|
|
23
|
+
const sliderToAlpha = (s) => Math.pow(s, exponent);
|
|
24
|
+
const sliderValue = alphaToSlider(alpha);
|
|
25
|
+
const handleAlphaChange = (_event, value) => {
|
|
26
|
+
const sliderVal = typeof value === 'number' ? value : value[0];
|
|
27
|
+
const newAlpha = sliderToAlpha(sliderVal);
|
|
28
|
+
for (const level of levels) {
|
|
29
|
+
for (const track of level.tracks) {
|
|
30
|
+
for (const display of track.displays) {
|
|
31
|
+
;
|
|
32
|
+
display.setAlpha(newAlpha);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
return (_jsxs(Box, { className: classes.container, children: [_jsx(Typography, { variant: "body2", style: { marginRight: 8 }, children: "Opacity:" }), _jsx(Slider, { value: sliderValue, onChange: handleAlphaChange, min: 0, max: 1, step: 0.01, valueLabelDisplay: "auto", size: "small", style: { minWidth: 100 }, slots: {
|
|
38
|
+
valueLabel: SliderTooltip,
|
|
39
|
+
}, valueLabelFormat: (value) => sliderToAlpha(value).toFixed(3) })] }));
|
|
40
|
+
});
|
|
41
|
+
export default OpacitySlider;
|