@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.
- 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 +3 -0
- 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 +42 -11
- 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/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 +3 -0
- 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 +42 -11
- 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
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { category10 } from '@jbrowse/core/ui/colors';
|
|
1
2
|
import { doesIntersect2, getContainingView } from '@jbrowse/core/util';
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
222
|
+
let colorKey = 'default';
|
|
223
|
+
if (useStrandColorThin) {
|
|
224
|
+
const strand = f.get('strand');
|
|
225
|
+
colorKey = strand === -1 ? 'neg' : 'pos';
|
|
57
226
|
}
|
|
58
|
-
else {
|
|
59
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
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 =
|
|
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
|
|
101
|
-
const d2 = len
|
|
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
|
-
|
|
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(
|
|
127
|
-
|
|
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(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
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;
|