@jbrowse/plugin-linear-comparative-view 3.6.5 → 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.
Files changed (57) hide show
  1. package/dist/LGVSyntenyDisplay/model.d.ts +17 -2
  2. package/dist/LinearComparativeView/components/ColorBySelector.d.ts +5 -0
  3. package/dist/LinearComparativeView/components/ColorBySelector.js +54 -0
  4. package/dist/LinearComparativeView/components/Header.js +7 -2
  5. package/dist/LinearComparativeView/components/MinLengthSlider.d.ts +5 -0
  6. package/dist/LinearComparativeView/components/MinLengthSlider.js +47 -0
  7. package/dist/LinearComparativeView/components/OpacitySlider.d.ts +5 -0
  8. package/dist/LinearComparativeView/components/OpacitySlider.js +46 -0
  9. package/dist/LinearComparativeView/components/RubberbandSpan.js +2 -2
  10. package/dist/LinearComparativeView/components/SliderTooltip.d.ts +2 -0
  11. package/dist/LinearComparativeView/components/SliderTooltip.js +9 -0
  12. package/dist/LinearComparativeView/components/useRangeSelect.js +10 -14
  13. package/dist/LinearComparativeView/model.d.ts +3 -0
  14. package/dist/LinearComparativeView/model.js +4 -0
  15. package/dist/LinearSyntenyDisplay/afterAttach.js +5 -3
  16. package/dist/LinearSyntenyDisplay/components/LinearSyntenyRendering.js +10 -5
  17. package/dist/LinearSyntenyDisplay/components/util.d.ts +2 -1
  18. package/dist/LinearSyntenyDisplay/components/util.js +43 -2
  19. package/dist/LinearSyntenyDisplay/drawSynteny.d.ts +3 -2
  20. package/dist/LinearSyntenyDisplay/drawSynteny.js +284 -45
  21. package/dist/LinearSyntenyDisplay/model.d.ts +6 -0
  22. package/dist/LinearSyntenyDisplay/model.js +12 -0
  23. package/dist/LinearSyntenyView/components/DiagonalizationProgressDialog.d.ts +6 -0
  24. package/dist/LinearSyntenyView/components/DiagonalizationProgressDialog.js +87 -0
  25. package/dist/LinearSyntenyView/model.d.ts +42 -11
  26. package/dist/LinearSyntenyView/model.js +70 -18
  27. package/dist/LinearSyntenyView/util/diagonalize.d.ts +27 -0
  28. package/dist/LinearSyntenyView/util/diagonalize.js +91 -0
  29. package/esm/LGVSyntenyDisplay/model.d.ts +17 -2
  30. package/esm/LinearComparativeView/components/ColorBySelector.d.ts +5 -0
  31. package/esm/LinearComparativeView/components/ColorBySelector.js +49 -0
  32. package/esm/LinearComparativeView/components/Header.js +8 -3
  33. package/esm/LinearComparativeView/components/MinLengthSlider.d.ts +5 -0
  34. package/esm/LinearComparativeView/components/MinLengthSlider.js +42 -0
  35. package/esm/LinearComparativeView/components/OpacitySlider.d.ts +5 -0
  36. package/esm/LinearComparativeView/components/OpacitySlider.js +41 -0
  37. package/esm/LinearComparativeView/components/RubberbandSpan.js +2 -2
  38. package/esm/LinearComparativeView/components/SliderTooltip.d.ts +2 -0
  39. package/esm/LinearComparativeView/components/SliderTooltip.js +6 -0
  40. package/esm/LinearComparativeView/components/useRangeSelect.js +11 -15
  41. package/esm/LinearComparativeView/model.d.ts +3 -0
  42. package/esm/LinearComparativeView/model.js +4 -0
  43. package/esm/LinearSyntenyDisplay/afterAttach.js +6 -4
  44. package/esm/LinearSyntenyDisplay/components/LinearSyntenyRendering.js +10 -5
  45. package/esm/LinearSyntenyDisplay/components/util.d.ts +2 -1
  46. package/esm/LinearSyntenyDisplay/components/util.js +43 -3
  47. package/esm/LinearSyntenyDisplay/drawSynteny.d.ts +3 -2
  48. package/esm/LinearSyntenyDisplay/drawSynteny.js +283 -45
  49. package/esm/LinearSyntenyDisplay/model.d.ts +6 -0
  50. package/esm/LinearSyntenyDisplay/model.js +12 -0
  51. package/esm/LinearSyntenyView/components/DiagonalizationProgressDialog.d.ts +6 -0
  52. package/esm/LinearSyntenyView/components/DiagonalizationProgressDialog.js +85 -0
  53. package/esm/LinearSyntenyView/model.d.ts +42 -11
  54. package/esm/LinearSyntenyView/model.js +70 -18
  55. package/esm/LinearSyntenyView/util/diagonalize.d.ts +27 -0
  56. package/esm/LinearSyntenyView/util/diagonalize.js +88 -0
  57. 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>;
@@ -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
- checked?: undefined;
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
- description: string;
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
- type: string;
359
- checked: boolean;
360
- icon: import("@mui/material/OverridableComponent").OverridableComponent<import("@mui/material").SvgIconTypeMap<{}, "svg">> & {
361
- muiName: string;
362
- };
363
- onClick: () => void;
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: typeof Curves;
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>;
@@ -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: 'Draw CIGAR',
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
- self.setDrawCIGAR(!self.drawCIGAR);
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: 'Draw only CIGAR matches',
128
- checked: self.drawCIGARMatchesOnly,
141
+ label: 'Show dynamic controls',
129
142
  type: 'checkbox',
130
- description: 'If enabled, it hides the insertions and deletions in the CIGAR strings, helps with divergent',
143
+ checked: self.showDynamicControls,
131
144
  onClick: () => {
132
- self.setDrawCIGARMatchesOnly(!self.drawCIGARMatchesOnly);
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
+ }
@@ -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] | undefined;
201
- searchFeatureByID(id: string): [number, number, number, number] | undefined;
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,5 @@
1
+ import type { LinearComparativeViewModel } from '../model';
2
+ declare const ColorBySelector: ({ model, }: {
3
+ model: LinearComparativeViewModel;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ export default ColorBySelector;
@@ -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,5 @@
1
+ import type { LinearComparativeViewModel } from '../model';
2
+ declare const MinLengthSlider: ({ model, }: {
3
+ model: LinearComparativeViewModel;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ export default MinLengthSlider;
@@ -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,5 @@
1
+ import type { LinearComparativeViewModel } from '../model';
2
+ declare const OpacitySlider: ({ model, }: {
3
+ model: LinearComparativeViewModel;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ export default OpacitySlider;
@@ -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;
@@ -23,10 +23,10 @@ const useStyles = makeStyles()(theme => {
23
23
  export default function RubberbandSpan({ leftBpOffset, rightBpOffset, numOfBpSelected, left, width, top = 0, sticky = false, }) {
24
24
  const { classes } = useStyles();
25
25
  const [anchorEl, setAnchorEl] = useState(null);
26
- return (_jsxs(_Fragment, { children: [anchorEl ? (_jsxs(_Fragment, { children: [_jsx(RubberbandTooltip, { side: "left", anchorEl: anchorEl, text: leftBpOffset.map((l, idx) => (_jsx("div", { children: stringify(l, true) }, `JSON.stringify(l)-${idx}`))) }), _jsx(RubberbandTooltip, { side: "right", anchorEl: anchorEl, text: rightBpOffset.map((l, idx) => (_jsx("div", { children: stringify(l, true) }, `JSON.stringify(l)-${idx}`))) })] })) : null, _jsx("div", { className: classes.rubberband, style: { left, width }, children: numOfBpSelected ? (_jsx(Typography, { ref: el => {
26
+ return (_jsxs(_Fragment, { children: [anchorEl ? (_jsxs(_Fragment, { children: [_jsx(RubberbandTooltip, { side: "left", anchorEl: anchorEl, text: leftBpOffset.map((l, idx) => (_jsx("div", { children: stringify(l, true) }, `${JSON.stringify(l)}-left-${idx}`))) }), _jsx(RubberbandTooltip, { side: "right", anchorEl: anchorEl, text: rightBpOffset.map((l, idx) => (_jsx("div", { children: stringify(l, true) }, `${JSON.stringify(l)}-right-${idx}`))) })] })) : null, _jsx("div", { className: classes.rubberband, style: { left, width }, children: numOfBpSelected ? (_jsx(Typography, { ref: el => {
27
27
  setAnchorEl(el);
28
28
  }, variant: "h6", className: classes.rubberbandText, style: {
29
29
  top,
30
30
  position: sticky ? 'sticky' : undefined,
31
- }, children: numOfBpSelected.map((n, i) => (_jsx("div", { children: getBpDisplayStr(n) }, i))) })) : null })] }));
31
+ }, children: numOfBpSelected.map((n, i) => (_jsx("div", { children: getBpDisplayStr(n) }, `bpSelectedRow-${i}`))) })) : null })] }));
32
32
  }
@@ -0,0 +1,2 @@
1
+ import type { SliderValueLabelProps } from '@mui/material';
2
+ export default function SliderTooltip(props: SliderValueLabelProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Tooltip } from '@mui/material';
3
+ export default function SliderTooltip(props) {
4
+ const { children, open, value } = props;
5
+ return (_jsx(Tooltip, { open: open, enterTouchDelay: 0, placement: "top", title: value, arrow: true, children: children }));
6
+ }