@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.
Files changed (59) hide show
  1. package/dist/LGVSyntenyDisplay/configSchemaF.js +1 -1
  2. package/dist/LGVSyntenyDisplay/model.d.ts +17 -2
  3. package/dist/LinearComparativeView/components/ColorBySelector.d.ts +5 -0
  4. package/dist/LinearComparativeView/components/ColorBySelector.js +54 -0
  5. package/dist/LinearComparativeView/components/Header.js +7 -2
  6. package/dist/LinearComparativeView/components/MinLengthSlider.d.ts +5 -0
  7. package/dist/LinearComparativeView/components/MinLengthSlider.js +47 -0
  8. package/dist/LinearComparativeView/components/OpacitySlider.d.ts +5 -0
  9. package/dist/LinearComparativeView/components/OpacitySlider.js +46 -0
  10. package/dist/LinearComparativeView/components/RubberbandSpan.js +2 -2
  11. package/dist/LinearComparativeView/components/SliderTooltip.d.ts +2 -0
  12. package/dist/LinearComparativeView/components/SliderTooltip.js +9 -0
  13. package/dist/LinearComparativeView/components/useRangeSelect.js +10 -14
  14. package/dist/LinearComparativeView/model.d.ts +7 -4
  15. package/dist/LinearComparativeView/model.js +4 -0
  16. package/dist/LinearSyntenyDisplay/afterAttach.js +5 -3
  17. package/dist/LinearSyntenyDisplay/components/LinearSyntenyRendering.js +10 -5
  18. package/dist/LinearSyntenyDisplay/components/util.d.ts +2 -1
  19. package/dist/LinearSyntenyDisplay/components/util.js +43 -2
  20. package/dist/LinearSyntenyDisplay/drawSynteny.d.ts +3 -2
  21. package/dist/LinearSyntenyDisplay/drawSynteny.js +284 -45
  22. package/dist/LinearSyntenyDisplay/model.d.ts +6 -0
  23. package/dist/LinearSyntenyDisplay/model.js +12 -0
  24. package/dist/LinearSyntenyView/components/DiagonalizationProgressDialog.d.ts +6 -0
  25. package/dist/LinearSyntenyView/components/DiagonalizationProgressDialog.js +87 -0
  26. package/dist/LinearSyntenyView/model.d.ts +46 -15
  27. package/dist/LinearSyntenyView/model.js +70 -18
  28. package/dist/LinearSyntenyView/util/diagonalize.d.ts +27 -0
  29. package/dist/LinearSyntenyView/util/diagonalize.js +91 -0
  30. package/esm/LGVSyntenyDisplay/configSchemaF.js +1 -1
  31. package/esm/LGVSyntenyDisplay/model.d.ts +17 -2
  32. package/esm/LinearComparativeView/components/ColorBySelector.d.ts +5 -0
  33. package/esm/LinearComparativeView/components/ColorBySelector.js +49 -0
  34. package/esm/LinearComparativeView/components/Header.js +8 -3
  35. package/esm/LinearComparativeView/components/MinLengthSlider.d.ts +5 -0
  36. package/esm/LinearComparativeView/components/MinLengthSlider.js +42 -0
  37. package/esm/LinearComparativeView/components/OpacitySlider.d.ts +5 -0
  38. package/esm/LinearComparativeView/components/OpacitySlider.js +41 -0
  39. package/esm/LinearComparativeView/components/RubberbandSpan.js +2 -2
  40. package/esm/LinearComparativeView/components/SliderTooltip.d.ts +2 -0
  41. package/esm/LinearComparativeView/components/SliderTooltip.js +6 -0
  42. package/esm/LinearComparativeView/components/useRangeSelect.js +11 -15
  43. package/esm/LinearComparativeView/model.d.ts +7 -4
  44. package/esm/LinearComparativeView/model.js +4 -0
  45. package/esm/LinearSyntenyDisplay/afterAttach.js +6 -4
  46. package/esm/LinearSyntenyDisplay/components/LinearSyntenyRendering.js +10 -5
  47. package/esm/LinearSyntenyDisplay/components/util.d.ts +2 -1
  48. package/esm/LinearSyntenyDisplay/components/util.js +43 -3
  49. package/esm/LinearSyntenyDisplay/drawSynteny.d.ts +3 -2
  50. package/esm/LinearSyntenyDisplay/drawSynteny.js +283 -45
  51. package/esm/LinearSyntenyDisplay/model.d.ts +6 -0
  52. package/esm/LinearSyntenyDisplay/model.js +12 -0
  53. package/esm/LinearSyntenyView/components/DiagonalizationProgressDialog.d.ts +6 -0
  54. package/esm/LinearSyntenyView/components/DiagonalizationProgressDialog.js +85 -0
  55. package/esm/LinearSyntenyView/model.d.ts +46 -15
  56. package/esm/LinearSyntenyView/model.js +70 -18
  57. package/esm/LinearSyntenyView/util/diagonalize.d.ts +27 -0
  58. package/esm/LinearSyntenyView/util/diagonalize.js +88 -0
  59. package/package.json +5 -5
@@ -1,5 +1,7 @@
1
+ import { category10 } from '@jbrowse/core/ui/colors';
1
2
  import { doesIntersect2, getContainingView } from '@jbrowse/core/util';
2
- import { draw, drawMatchSimple } from './components/util';
3
+ import { colord } from '@jbrowse/core/util/colord';
4
+ import { draw, drawLocationMarkers, drawMatchSimple } from './components/util';
3
5
  export const MAX_COLOR_RANGE = 255 * 255 * 255;
4
6
  function makeColor(idx) {
5
7
  const r = Math.floor(idx / (255 * 255)) % 255;
@@ -7,7 +9,20 @@ function makeColor(idx) {
7
9
  const b = idx % 255;
8
10
  return `rgb(${r},${g},${b})`;
9
11
  }
10
- const colorMap = {
12
+ function hashString(str) {
13
+ let hash = 0;
14
+ for (let i = 0; i < str.length; i++) {
15
+ const char = str.charCodeAt(i);
16
+ hash = (hash << 5) - hash + char;
17
+ hash = hash & hash;
18
+ }
19
+ return Math.abs(hash);
20
+ }
21
+ function getQueryColor(queryName) {
22
+ const hash = hashString(queryName);
23
+ return category10[hash % category10.length];
24
+ }
25
+ const defaultCigarColors = {
11
26
  I: '#ff03',
12
27
  N: '#0a03',
13
28
  D: '#00f3',
@@ -15,13 +30,39 @@ const colorMap = {
15
30
  M: '#f003',
16
31
  '=': '#f003',
17
32
  };
33
+ const strandCigarColors = {
34
+ I: '#ff03',
35
+ N: '#a020f0',
36
+ D: '#a020f0',
37
+ X: 'brown',
38
+ M: '#f003',
39
+ '=': '#f003',
40
+ };
41
+ const colorSchemes = {
42
+ default: {
43
+ cigarColors: defaultCigarColors,
44
+ },
45
+ strand: {
46
+ posColor: 'red',
47
+ negColor: 'blue',
48
+ cigarColors: strandCigarColors,
49
+ },
50
+ query: {
51
+ cigarColors: defaultCigarColors,
52
+ },
53
+ };
54
+ function applyAlpha(color, alpha) {
55
+ if (alpha === 1) {
56
+ return color;
57
+ }
58
+ return colord(color).alpha(alpha).toHex();
59
+ }
18
60
  const lineLimit = 3;
19
61
  const oobLimit = 1600;
20
62
  export function getId(r, g, b, unitMultiplier) {
21
63
  return Math.floor((r * 255 * 255 + g * 255 + b - 1) / unitMultiplier);
22
64
  }
23
- export function drawRef(model, ctx1, ctx3) {
24
- var _a;
65
+ export function drawCigarClickMap(model, cigarClickMapCanvas) {
25
66
  const view = getContainingView(model);
26
67
  const drawCurves = view.drawCurves;
27
68
  const drawCIGAR = view.drawCIGAR;
@@ -29,55 +70,226 @@ export function drawRef(model, ctx1, ctx3) {
29
70
  const { level, height, featPositions } = model;
30
71
  const width = view.width;
31
72
  const bpPerPxs = view.views.map(v => v.bpPerPx);
32
- if (ctx3) {
33
- ctx3.imageSmoothingEnabled = false;
34
- }
35
- ctx1.beginPath();
73
+ cigarClickMapCanvas.imageSmoothingEnabled = false;
74
+ cigarClickMapCanvas.clearRect(0, 0, width, height);
36
75
  const offsets = view.views.map(v => v.offsetPx);
37
- const unitMultiplier = Math.floor(MAX_COLOR_RANGE / featPositions.length);
38
- ctx1.fillStyle = colorMap.M;
39
- ctx1.strokeStyle = colorMap.M;
40
- for (const { p11, p12, p21, p22 } of featPositions) {
76
+ const bpPerPxInv0 = 1 / bpPerPxs[level];
77
+ const bpPerPxInv1 = 1 / bpPerPxs[level + 1];
78
+ for (const { p11, p12, p21, p22, f, cigar } of featPositions) {
41
79
  const x11 = p11.offsetPx - offsets[level];
42
80
  const x12 = p12.offsetPx - offsets[level];
43
81
  const x21 = p21.offsetPx - offsets[level + 1];
44
82
  const x22 = p22.offsetPx - offsets[level + 1];
45
83
  const l1 = Math.abs(x12 - x11);
46
84
  const l2 = Math.abs(x22 - x21);
85
+ const minX = Math.min(x21, x22);
86
+ const maxX = Math.max(x21, x22);
47
87
  const y1 = 0;
48
88
  const y2 = height;
49
89
  const mid = (y2 - y1) / 2;
90
+ if (!(l1 <= lineLimit && l2 <= lineLimit) &&
91
+ doesIntersect2(minX, maxX, -oobLimit, view.width + oobLimit)) {
92
+ const s1 = f.get('strand');
93
+ const k1 = s1 === -1 ? x12 : x11;
94
+ const k2 = s1 === -1 ? x11 : x12;
95
+ const rev1 = k1 < k2 ? 1 : -1;
96
+ const rev2 = (x21 < x22 ? 1 : -1) * s1;
97
+ let cx1 = k1;
98
+ let cx2 = s1 === -1 ? x22 : x21;
99
+ if (cigar.length && drawCIGAR) {
100
+ let continuingFlag = false;
101
+ let px1 = 0;
102
+ let px2 = 0;
103
+ const unitMultiplier2 = Math.floor(MAX_COLOR_RANGE / cigar.length);
104
+ for (let j = 0; j < cigar.length; j += 2) {
105
+ const len = +cigar[j];
106
+ const op = cigar[j + 1];
107
+ if (!continuingFlag) {
108
+ px1 = cx1;
109
+ px2 = cx2;
110
+ }
111
+ const d1 = len * bpPerPxInv0;
112
+ const d2 = len * bpPerPxInv1;
113
+ if (op === 'M' || op === '=' || op === 'X') {
114
+ cx1 += d1 * rev1;
115
+ cx2 += d2 * rev2;
116
+ }
117
+ else if (op === 'D' || op === 'N') {
118
+ cx1 += d1 * rev1;
119
+ }
120
+ else if (op === 'I') {
121
+ cx2 += d2 * rev2;
122
+ }
123
+ if (!(Math.max(px1, px2, cx1, cx2) < 0 ||
124
+ Math.min(px1, px2, cx1, cx2) > width)) {
125
+ const isNotLast = j < cigar.length - 2;
126
+ if (Math.abs(cx1 - px1) <= 1 &&
127
+ Math.abs(cx2 - px2) <= 1 &&
128
+ isNotLast) {
129
+ continuingFlag = true;
130
+ }
131
+ else {
132
+ continuingFlag = false;
133
+ const shouldDraw = !drawCIGARMatchesOnly ||
134
+ ((op === 'M' || op === '=' || op === 'X') &&
135
+ Math.abs(cx1 - px1) > 1 &&
136
+ Math.abs(cx2 - px2) > 1);
137
+ if (shouldDraw) {
138
+ const idx = j * unitMultiplier2 + 1;
139
+ cigarClickMapCanvas.fillStyle = makeColor(idx);
140
+ draw(cigarClickMapCanvas, px1, cx1, y1, cx2, px2, y2, mid, drawCurves);
141
+ cigarClickMapCanvas.fill();
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ export function drawRef(model, mainCanvas) {
151
+ var _a;
152
+ const view = getContainingView(model);
153
+ const drawCurves = view.drawCurves;
154
+ const drawCIGAR = view.drawCIGAR;
155
+ const drawCIGARMatchesOnly = view.drawCIGARMatchesOnly;
156
+ const drawLocationMarkersEnabled = view.drawLocationMarkers;
157
+ const { level, height, featPositions, alpha, minAlignmentLength, colorBy } = model;
158
+ const width = view.width;
159
+ const bpPerPxs = view.views.map(v => v.bpPerPx);
160
+ const queryTotalLengths = new Map();
161
+ if (minAlignmentLength > 0) {
162
+ for (const { f } of featPositions) {
163
+ const queryName = f.get('name') || f.get('id') || f.id();
164
+ const alignmentLength = Math.abs(f.get('end') - f.get('start'));
165
+ const currentTotal = queryTotalLengths.get(queryName) || 0;
166
+ queryTotalLengths.set(queryName, currentTotal + alignmentLength);
167
+ }
168
+ }
169
+ const schemeConfig = colorSchemes[colorBy] || colorSchemes.default;
170
+ const activeColorMap = schemeConfig.cigarColors;
171
+ const posColor = colorBy === 'strand' ? colorSchemes.strand.posColor : 'red';
172
+ const negColor = colorBy === 'strand' ? colorSchemes.strand.negColor : 'blue';
173
+ const colorMapWithAlpha = {
174
+ I: applyAlpha(activeColorMap.I, alpha),
175
+ N: applyAlpha(activeColorMap.N, alpha),
176
+ D: applyAlpha(activeColorMap.D, alpha),
177
+ X: applyAlpha(activeColorMap.X, alpha),
178
+ M: applyAlpha(activeColorMap.M, alpha),
179
+ '=': applyAlpha(activeColorMap['='], alpha),
180
+ };
181
+ const posColorWithAlpha = applyAlpha(posColor, alpha);
182
+ const negColorWithAlpha = applyAlpha(negColor, alpha);
183
+ const queryColorCache = new Map();
184
+ const getQueryColorWithAlpha = (queryName) => {
185
+ if (!queryColorCache.has(queryName)) {
186
+ const color = getQueryColor(queryName);
187
+ queryColorCache.set(queryName, applyAlpha(color, alpha));
188
+ }
189
+ return queryColorCache.get(queryName);
190
+ };
191
+ mainCanvas.beginPath();
192
+ const offsets = view.views.map(v => v.offsetPx);
193
+ const offsetsL0 = offsets[level];
194
+ const offsetsL1 = offsets[level + 1];
195
+ const unitMultiplier = Math.floor(MAX_COLOR_RANGE / featPositions.length);
196
+ const y1 = 0;
197
+ const y2 = height;
198
+ const mid = (y2 - y1) / 2;
199
+ const useStrandColorThin = colorBy === 'strand';
200
+ const useQueryColorThin = colorBy === 'query';
201
+ mainCanvas.fillStyle = colorMapWithAlpha.M;
202
+ mainCanvas.strokeStyle = colorMapWithAlpha.M;
203
+ const thinLinesByColor = new Map();
204
+ for (const { p11, p12, p21, p22, f } of featPositions) {
205
+ if (minAlignmentLength > 0) {
206
+ const queryName = f.get('name') || f.get('id') || f.id();
207
+ const totalLength = queryTotalLengths.get(queryName) || 0;
208
+ if (totalLength < minAlignmentLength) {
209
+ continue;
210
+ }
211
+ }
212
+ const x11 = p11.offsetPx - offsetsL0;
213
+ const x12 = p12.offsetPx - offsetsL0;
214
+ const x21 = p21.offsetPx - offsetsL1;
215
+ const x22 = p22.offsetPx - offsetsL1;
216
+ const l1 = Math.abs(x12 - x11);
217
+ const l2 = Math.abs(x22 - x21);
50
218
  if (l1 <= lineLimit &&
51
219
  l2 <= lineLimit &&
52
220
  x21 < width + oobLimit &&
53
221
  x21 > -oobLimit) {
54
- ctx1.moveTo(x11, y1);
55
- if (drawCurves) {
56
- ctx1.bezierCurveTo(x11, mid, x21, mid, x21, y2);
222
+ let colorKey = 'default';
223
+ if (useStrandColorThin) {
224
+ const strand = f.get('strand');
225
+ colorKey = strand === -1 ? 'neg' : 'pos';
57
226
  }
58
- else {
59
- ctx1.lineTo(x21, y2);
227
+ else if (useQueryColorThin) {
228
+ colorKey = f.get('refName');
229
+ }
230
+ if (!thinLinesByColor.has(colorKey)) {
231
+ thinLinesByColor.set(colorKey, []);
60
232
  }
233
+ thinLinesByColor.get(colorKey).push({ x11, x21, y1, y2, mid });
61
234
  }
62
235
  }
63
- ctx1.stroke();
64
- ctx1.fillStyle = colorMap.M;
65
- ctx1.strokeStyle = colorMap.M;
236
+ for (const [colorKey, lines] of thinLinesByColor) {
237
+ if (colorKey === 'pos') {
238
+ mainCanvas.strokeStyle = posColorWithAlpha;
239
+ }
240
+ else if (colorKey === 'neg') {
241
+ mainCanvas.strokeStyle = negColorWithAlpha;
242
+ }
243
+ else if (colorKey !== 'default') {
244
+ mainCanvas.strokeStyle = getQueryColorWithAlpha(colorKey);
245
+ }
246
+ else {
247
+ mainCanvas.strokeStyle = colorMapWithAlpha.M;
248
+ }
249
+ mainCanvas.beginPath();
250
+ if (drawCurves) {
251
+ for (const { x11, x21, y1, y2, mid } of lines) {
252
+ mainCanvas.moveTo(x11, y1);
253
+ mainCanvas.bezierCurveTo(x11, mid, x21, mid, x21, y2);
254
+ }
255
+ }
256
+ else {
257
+ for (const { x11, x21, y1, y2 } of lines) {
258
+ mainCanvas.moveTo(x11, y1);
259
+ mainCanvas.lineTo(x21, y2);
260
+ }
261
+ }
262
+ mainCanvas.stroke();
263
+ }
264
+ const bpPerPx0 = bpPerPxs[level];
265
+ const bpPerPx1 = bpPerPxs[level + 1];
266
+ const bpPerPxInv0 = 1 / bpPerPx0;
267
+ const bpPerPxInv1 = 1 / bpPerPx1;
268
+ const useStrandColor = colorBy === 'strand';
269
+ const useQueryColor = colorBy === 'query';
270
+ mainCanvas.fillStyle = colorMapWithAlpha.M;
271
+ mainCanvas.strokeStyle = colorMapWithAlpha.M;
66
272
  for (const { p11, p12, p21, p22, f, cigar } of featPositions) {
67
- const x11 = p11.offsetPx - offsets[level];
68
- const x12 = p12.offsetPx - offsets[level];
69
- const x21 = p21.offsetPx - offsets[level + 1];
70
- const x22 = p22.offsetPx - offsets[level + 1];
273
+ const strand = f.get('strand');
274
+ const refName = f.get('refName');
275
+ if (minAlignmentLength > 0) {
276
+ const queryName = f.get('name') || f.get('id') || f.id();
277
+ const totalLength = queryTotalLengths.get(queryName) || 0;
278
+ if (totalLength < minAlignmentLength) {
279
+ continue;
280
+ }
281
+ }
282
+ const x11 = p11.offsetPx - offsetsL0;
283
+ const x12 = p12.offsetPx - offsetsL0;
284
+ const x21 = p21.offsetPx - offsetsL1;
285
+ const x22 = p22.offsetPx - offsetsL1;
71
286
  const l1 = Math.abs(x12 - x11);
72
287
  const l2 = Math.abs(x22 - x21);
73
288
  const minX = Math.min(x21, x22);
74
289
  const maxX = Math.max(x21, x22);
75
- const y1 = 0;
76
- const y2 = height;
77
- const mid = (y2 - y1) / 2;
78
290
  if (!(l1 <= lineLimit && l2 <= lineLimit) &&
79
291
  doesIntersect2(minX, maxX, -oobLimit, view.width + oobLimit)) {
80
- const s1 = f.get('strand');
292
+ const s1 = strand;
81
293
  const k1 = s1 === -1 ? x12 : x11;
82
294
  const k2 = s1 === -1 ? x11 : x12;
83
295
  const rev1 = k1 < k2 ? 1 : -1;
@@ -88,17 +300,15 @@ export function drawRef(model, ctx1, ctx3) {
88
300
  let continuingFlag = false;
89
301
  let px1 = 0;
90
302
  let px2 = 0;
91
- const unitMultiplier2 = Math.floor(MAX_COLOR_RANGE / cigar.length);
92
303
  for (let j = 0; j < cigar.length; j += 2) {
93
- const idx = j * unitMultiplier2 + 1;
94
304
  const len = +cigar[j];
95
305
  const op = cigar[j + 1];
96
306
  if (!continuingFlag) {
97
307
  px1 = cx1;
98
308
  px2 = cx2;
99
309
  }
100
- const d1 = len / bpPerPxs[level];
101
- const d2 = len / bpPerPxs[level + 1];
310
+ const d1 = len * bpPerPxInv0;
311
+ const d2 = len * bpPerPxInv1;
102
312
  if (op === 'M' || op === '=' || op === 'X') {
103
313
  cx1 += d1 * rev1;
104
314
  cx2 += d2 * rev2;
@@ -119,30 +329,51 @@ export function drawRef(model, ctx1, ctx3) {
119
329
  }
120
330
  else {
121
331
  const letter = (continuingFlag && d1 > 1) || d2 > 1 ? op : 'M';
122
- ctx1.fillStyle = colorMap[letter];
332
+ const isInsertionOrDeletion = letter === 'I' || letter === 'D' || letter === 'N';
333
+ if (useStrandColor && !isInsertionOrDeletion) {
334
+ mainCanvas.fillStyle =
335
+ strand === -1 ? negColorWithAlpha : posColorWithAlpha;
336
+ }
337
+ else if (useQueryColor && !isInsertionOrDeletion) {
338
+ mainCanvas.fillStyle = getQueryColorWithAlpha(refName);
339
+ }
340
+ else {
341
+ mainCanvas.fillStyle = colorMapWithAlpha[letter];
342
+ }
123
343
  continuingFlag = false;
124
344
  if (drawCIGARMatchesOnly) {
125
345
  if (letter === 'M') {
126
- draw(ctx1, px1, cx1, y1, cx2, px2, y2, mid, drawCurves);
127
- ctx1.fill();
346
+ draw(mainCanvas, px1, cx1, y1, cx2, px2, y2, mid, drawCurves);
347
+ mainCanvas.fill();
348
+ if (drawLocationMarkersEnabled) {
349
+ drawLocationMarkers(mainCanvas, px1, cx1, y1, cx2, px2, y2, mid, bpPerPx0, bpPerPx1, drawCurves);
350
+ }
128
351
  }
129
352
  }
130
353
  else {
131
- draw(ctx1, px1, cx1, y1, cx2, px2, y2, mid, drawCurves);
132
- ctx1.fill();
133
- }
134
- if (ctx3) {
135
- ctx3.fillStyle = makeColor(idx);
136
- draw(ctx3, px1, cx1, y1, cx2, px2, y2, mid, drawCurves);
137
- ctx3.fill();
354
+ draw(mainCanvas, px1, cx1, y1, cx2, px2, y2, mid, drawCurves);
355
+ mainCanvas.fill();
356
+ if (drawLocationMarkersEnabled) {
357
+ drawLocationMarkers(mainCanvas, px1, cx1, y1, cx2, px2, y2, mid, bpPerPx0, bpPerPx1, drawCurves);
358
+ }
138
359
  }
139
360
  }
140
361
  }
141
362
  }
142
363
  }
143
364
  else {
144
- draw(ctx1, x11, x12, y1, x22, x21, y2, mid, drawCurves);
145
- ctx1.fill();
365
+ if (useStrandColor) {
366
+ mainCanvas.fillStyle =
367
+ strand === -1 ? negColorWithAlpha : posColorWithAlpha;
368
+ }
369
+ else if (useQueryColor) {
370
+ mainCanvas.fillStyle = getQueryColorWithAlpha(refName);
371
+ }
372
+ draw(mainCanvas, x11, x12, y1, x22, x21, y2, mid, drawCurves);
373
+ mainCanvas.fill();
374
+ if (useStrandColor || useQueryColor) {
375
+ mainCanvas.fillStyle = colorMapWithAlpha.M;
376
+ }
146
377
  }
147
378
  }
148
379
  }
@@ -154,6 +385,13 @@ export function drawRef(model, ctx1, ctx3) {
154
385
  ctx2.clearRect(0, 0, width, height);
155
386
  for (let i = 0; i < featPositions.length; i++) {
156
387
  const feature = featPositions[i];
388
+ if (minAlignmentLength > 0) {
389
+ const queryName = feature.f.get('name') || feature.f.get('id') || feature.f.id();
390
+ const totalLength = queryTotalLengths.get(queryName) || 0;
391
+ if (totalLength < minAlignmentLength) {
392
+ continue;
393
+ }
394
+ }
157
395
  const idx = i * unitMultiplier + 1;
158
396
  ctx2.fillStyle = makeColor(idx);
159
397
  drawMatchSimple({
@@ -172,7 +410,7 @@ export function drawRef(model, ctx1, ctx3) {
172
410
  });
173
411
  }
174
412
  }
175
- export function drawMouseoverSynteny(model) {
413
+ export function drawMouseoverClickMap(model) {
176
414
  var _a;
177
415
  const { level, clickId, mouseoverId } = model;
178
416
  const highResolutionScaling = 1;
@@ -22,6 +22,7 @@ declare function stateModelFactory(configSchema: AnyConfigurationSchemaType): im
22
22
  } & {
23
23
  type: import("mobx-state-tree").ISimpleType<"LinearSyntenyDisplay">;
24
24
  configuration: AnyConfigurationSchemaType;
25
+ colorBy: import("mobx-state-tree").IOptionalIType<import("mobx-state-tree").ISimpleType<string>, [undefined]>;
25
26
  }, {
26
27
  rendererTypeName: string;
27
28
  error: unknown;
@@ -260,6 +261,8 @@ declare function stateModelFactory(configSchema: AnyConfigurationSchemaType): im
260
261
  mouseoverId: string | undefined;
261
262
  clickId: string | undefined;
262
263
  cigarMouseoverId: number;
264
+ alpha: number;
265
+ minAlignmentLength: number;
263
266
  } & {
264
267
  setFeatPositions(arg: FeatPos[]): void;
265
268
  setMainCanvasRef(ref: HTMLCanvasElement | null): void;
@@ -269,6 +272,9 @@ declare function stateModelFactory(configSchema: AnyConfigurationSchemaType): im
269
272
  setMouseoverId(arg?: string): void;
270
273
  setCigarMouseoverId(arg: number): void;
271
274
  setClickId(arg?: string): void;
275
+ setAlpha(value: number): void;
276
+ setMinAlignmentLength(value: number): void;
277
+ setColorBy(value: string): void;
272
278
  } & {
273
279
  readonly adapterConfig: any;
274
280
  readonly trackIds: string[];
@@ -6,6 +6,7 @@ function stateModelFactory(configSchema) {
6
6
  .compose('LinearSyntenyDisplay', baseModelFactory(configSchema), types.model({
7
7
  type: types.literal('LinearSyntenyDisplay'),
8
8
  configuration: ConfigurationReference(configSchema),
9
+ colorBy: types.optional(types.string, 'default'),
9
10
  }))
10
11
  .volatile(() => ({
11
12
  mainCanvas: null,
@@ -16,6 +17,8 @@ function stateModelFactory(configSchema) {
16
17
  mouseoverId: undefined,
17
18
  clickId: undefined,
18
19
  cigarMouseoverId: -1,
20
+ alpha: 0.2,
21
+ minAlignmentLength: 0,
19
22
  }))
20
23
  .actions(self => ({
21
24
  setFeatPositions(arg) {
@@ -42,6 +45,15 @@ function stateModelFactory(configSchema) {
42
45
  setClickId(arg) {
43
46
  self.clickId = arg;
44
47
  },
48
+ setAlpha(value) {
49
+ self.alpha = value;
50
+ },
51
+ setMinAlignmentLength(value) {
52
+ self.minAlignmentLength = value;
53
+ },
54
+ setColorBy(value) {
55
+ self.colorBy = value;
56
+ },
45
57
  }))
46
58
  .views(self => ({
47
59
  get adapterConfig() {
@@ -0,0 +1,6 @@
1
+ import type { LinearSyntenyViewModel } from '../model';
2
+ declare const DiagonalizationProgressDialog: ({ handleClose, model, }: {
3
+ handleClose: () => void;
4
+ model: LinearSyntenyViewModel;
5
+ }) => import("react/jsx-runtime").JSX.Element;
6
+ export default DiagonalizationProgressDialog;
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { Dialog } from '@jbrowse/core/ui';
4
+ import { getSession } from '@jbrowse/core/util';
5
+ import { Button, DialogActions, DialogContent, LinearProgress, Typography, } from '@mui/material';
6
+ import { transaction } from 'mobx';
7
+ import { observer } from 'mobx-react';
8
+ import { diagonalizeRegions } from '../util/diagonalize';
9
+ const DiagonalizationProgressDialog = observer(function ({ handleClose, model, }) {
10
+ const [progress, setProgress] = useState(0);
11
+ const [message, setMessage] = useState('Initializing...');
12
+ const [error, setError] = useState();
13
+ useEffect(() => {
14
+ const runDiagonalization = async () => {
15
+ const session = getSession(model);
16
+ try {
17
+ if (model.views.length !== 2) {
18
+ setError('Diagonalization requires exactly 2 views');
19
+ setProgress(100);
20
+ session.notify('Diagonalization requires exactly 2 views', 'warning');
21
+ return;
22
+ }
23
+ const queryView = model.views[1];
24
+ setProgress(5);
25
+ setMessage('Collecting alignment data...');
26
+ const alignments = [];
27
+ for (const level of model.levels) {
28
+ for (const track of level.tracks) {
29
+ for (const display of track.displays) {
30
+ const { featPositions } = display;
31
+ for (const { f } of featPositions) {
32
+ const mate = f.get('mate');
33
+ alignments.push({
34
+ queryRefName: f.get('refName'),
35
+ refRefName: mate.refName,
36
+ queryStart: f.get('start'),
37
+ queryEnd: f.get('end'),
38
+ refStart: mate.start,
39
+ refEnd: mate.end,
40
+ strand: f.get('strand') || 1,
41
+ });
42
+ }
43
+ }
44
+ }
45
+ }
46
+ if (alignments.length === 0) {
47
+ setError('No alignments found');
48
+ setProgress(100);
49
+ session.notify('No alignments found to diagonalize', 'warning');
50
+ return;
51
+ }
52
+ const result = await diagonalizeRegions(alignments, queryView.displayedRegions, async (prog, msg) => {
53
+ setProgress(prog);
54
+ setMessage(msg);
55
+ });
56
+ if (result.newRegions.length > 0) {
57
+ setProgress(95);
58
+ setMessage('Applying new layout...');
59
+ transaction(() => {
60
+ queryView.setDisplayedRegions(result.newRegions);
61
+ });
62
+ setProgress(100);
63
+ setMessage('Diagonalization complete');
64
+ setTimeout(() => {
65
+ handleClose();
66
+ }, 1500);
67
+ }
68
+ else {
69
+ setError('No regions to reorder');
70
+ setProgress(100);
71
+ session.notify('No query regions found to reorder', 'warning');
72
+ }
73
+ }
74
+ catch (err) {
75
+ console.error('Diagonalization error:', err);
76
+ setError(`Error: ${err}`);
77
+ setProgress(100);
78
+ session.notify(`Diagonalization failed: ${err}`, 'error');
79
+ }
80
+ };
81
+ runDiagonalization();
82
+ }, [model, handleClose]);
83
+ return (_jsxs(Dialog, { open: true, title: "Diagonalizing", onClose: handleClose, children: [_jsxs(DialogContent, { style: { minWidth: 400 }, children: [_jsx(Typography, { variant: "body1", gutterBottom: true, color: error ? 'error' : 'inherit', children: error || message }), _jsx(LinearProgress, { variant: "determinate", value: progress, style: { marginTop: 16 }, color: error ? 'error' : 'primary' }), _jsxs(Typography, { variant: "caption", color: "textSecondary", style: { marginTop: 8, display: 'block' }, children: [Math.round(progress), "% complete"] })] }), _jsx(DialogActions, { children: _jsx(Button, { onClick: handleClose, color: "primary", disabled: progress < 100, children: progress < 100 ? 'Processing...' : 'Done' }) })] }));
84
+ });
85
+ export default DiagonalizationProgressDialog;