1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package org.utgenome.gwt.utgb.client.canvas;
26
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.Comparator;
30 import java.util.HashSet;
31 import java.util.List;
32
33 import org.utgenome.gwt.utgb.client.bio.Interval;
34 import org.utgenome.gwt.utgb.client.bio.OnGenome;
35 import org.utgenome.gwt.utgb.client.bio.OnGenomeDataVisitorBase;
36 import org.utgenome.gwt.utgb.client.bio.ReadCoverage;
37 import org.utgenome.gwt.utgb.client.bio.SAMReadLight;
38 import org.utgenome.gwt.utgb.client.bio.SAMReadPair;
39 import org.utgenome.gwt.utgb.client.bio.SAMReadPairFragment;
40 import org.utgenome.gwt.utgb.client.track.TrackWindow;
41
42
43
44
45
46
47
48 public class IntervalLayout {
49
50 private boolean keepSpaceForLabels = true;
51 private boolean hasEnoughHeightForLabels = false;
52 private boolean allowPairedReadsOverlaop = true;
53 private PrioritySearchTree<OnGenome> globalLayout = new PrioritySearchTree<OnGenome>();
54 private PrioritySearchTree<LocusLayout> localLayoutInView = new PrioritySearchTree<LocusLayout>();
55
56 private TrackWindow w;
57
58 public static class LocusLayout {
59 private OnGenome locus;
60 private int yOffset;
61 private int height = 1;
62
63 public LocusLayout(OnGenome locus, int yOffset) {
64 this.locus = locus;
65 this.yOffset = yOffset;
66 }
67
68 public LocusLayout(OnGenome locus, int yOffset, int height) {
69 this.locus = locus;
70 this.yOffset = yOffset;
71 this.height = height;
72 }
73
74 public int getHeight() {
75 return height;
76 }
77
78 public OnGenome getLocus() {
79 return locus;
80 }
81
82 public int getYOffset() {
83 return yOffset;
84 }
85
86 public int scaledHeight(int scale) {
87 return scaledHeight(yOffset, scale);
88 }
89
90 public static int scaledHeight(int y, int scale) {
91 return y * scale;
92 }
93
94 }
95
96 public IntervalLayout() {
97 }
98
99 public void setTrackWindow(TrackWindow window) {
100 this.w = window;
101 }
102
103 public static int estimiateLabelWidth(OnGenome l, int geneHeight) {
104 String name = l.getName();
105 int labelWidth = name != null ? (int) (name.length() * geneHeight * 0.9) : 0;
106 if (labelWidth > 150)
107 labelWidth = 150;
108 return labelWidth;
109 }
110
111 public List<OnGenome> activeReads() {
112 final ArrayList<OnGenome> activeData = new ArrayList<OnGenome>();
113 globalLayout.depthFirstSearch(new PrioritySearchTree.Visitor<OnGenome>() {
114 public void visit(OnGenome l) {
115 if (w.overlapWith(l))
116 activeData.add(l);
117 }
118 });
119 return activeData;
120 }
121
122
123
124
125
126
127
128 public static class IntervalRetriever extends OnGenomeDataVisitorBase {
129
130 public boolean allowPEOverlap = false;
131 public int start = -1;
132 public int end = -1;
133 public int height = 1;
134 public boolean isDefined = false;
135
136 public void clear() {
137 isDefined = false;
138 height = 1;
139 }
140
141 @Override
142 public void visitInterval(Interval interval) {
143 start = interval.getStart();
144 end = interval.getEnd();
145 isDefined = true;
146 }
147
148 @Override
149 public void visitSAMReadLight(SAMReadLight r) {
150
151 start = r.unclippedStart;
152 end = r.unclippedEnd;
153
154 isDefined = true;
155 }
156
157 @Override
158 public void visitReadCoverage(ReadCoverage readCoverage) {
159 this.height = Math.abs(readCoverage.maxHeight - readCoverage.minHeight);
160 visitInterval(readCoverage);
161 }
162
163 @Override
164 public void visitSAMReadPairFragment(SAMReadPairFragment fragment) {
165 start = fragment.getStart();
166 end = fragment.getEnd();
167 isDefined = true;
168 }
169
170 @Override
171 public void visitSAMReadPair(SAMReadPair pair) {
172 start = pair.getStart();
173 end = pair.getEnd();
174 isDefined = true;
175
176 if (!allowPEOverlap && pair.getFirst().unclippedSequenceHasOverlapWith(pair.getSecond())) {
177 height = 2;
178 }
179 }
180
181 }
182
183 private static class DepthMeasure implements PrioritySearchTree.ResultHandler<LocusLayout> {
184 int maxDepth = 0;
185
186 public void handle(LocusLayout l) {
187 if (maxDepth < l.getYOffset()) {
188 maxDepth = l.getYOffset();
189 }
190 }
191
192 public boolean toContinue() {
193 return true;
194 }
195 }
196
197 public int maxDepth(TrackWindow view) {
198 int x1 = pixelPositionOnWindow(view.getStartOnGenome());
199 int x2 = pixelPositionOnWindow(view.getEndOnGenome());
200 int maxDepth = 0;
201
202 DepthMeasure dm = new DepthMeasure();
203 localLayoutInView.rangeQuery(x1, Integer.MAX_VALUE, x2, dm);
204 return dm.maxDepth;
205 }
206
207
208
209
210
211
212
213
214
215 public <T extends OnGenome> int reset(List<T> intervalList, int geneHeight) {
216
217 globalLayout.clear();
218 IntervalRetriever ir = new IntervalRetriever();
219 for (OnGenome l : intervalList) {
220 ir.clear();
221 l.accept(ir);
222 if (!ir.isDefined)
223 continue;
224 globalLayout.insert(l, ir.end, ir.start);
225 }
226 return createLocalLayout(geneHeight);
227 }
228
229 private class LayoutGenerator {
230
231 private final int geneHeight;
232
233 IntervalRetriever ir = new IntervalRetriever();
234
235 private boolean toContinue = true;
236 boolean showLabelsFlag = keepSpaceForLabels;
237 boolean needRelayout = false;
238 int maxYOffset = 0;
239
240 public LayoutGenerator(int geneHeight) {
241 this.geneHeight = geneHeight;
242 ir.allowPEOverlap = allowPairedReadsOverlaop;
243 reset();
244 }
245
246
247
248
249 public void reset() {
250 toContinue = true;
251 needRelayout = false;
252 maxYOffset = 0;
253 }
254
255 public void handle(OnGenome l) {
256 ir.clear();
257 l.accept(ir);
258 if (!ir.isDefined)
259 return;
260
261 int x1 = pixelPositionOnWindow(ir.start);
262 int x2 = pixelPositionOnWindow(ir.end);
263
264 if (showLabelsFlag) {
265 int labelWidth = estimiateLabelWidth(l, geneHeight);
266 if (x1 - labelWidth > 0)
267 x1 -= labelWidth;
268 else
269 x2 += labelWidth;
270 }
271
272 List<LocusLayout> activeLocus = localLayoutInView.rangeQuery(x1, Integer.MAX_VALUE, x2);
273
274 HashSet<Integer> filledY = new HashSet<Integer>();
275
276 for (LocusLayout al : activeLocus) {
277 for (int i = 0; i < al.getHeight(); i++)
278 filledY.add(al.yOffset + i);
279 }
280
281 int blankY = 0;
282 for (; filledY.contains(blankY); blankY++) {
283 }
284
285 localLayoutInView.insert(new LocusLayout(l, blankY, ir.height), x2, x1);
286 if (blankY > maxYOffset) {
287 maxYOffset = blankY;
288 if (showLabelsFlag && maxYOffset > 30) {
289 showLabelsFlag = false;
290
291 needRelayout = true;
292 toContinue = false;
293 }
294 }
295
296 }
297
298 public boolean toContinue() {
299 return toContinue;
300 }
301
302 }
303
304
305
306
307
308
309
310
311
312 public <T extends OnGenome> int createLocalLayout(final int geneHeight) {
313
314
315
316 List<OnGenome> readsInRange = globalLayout.rangeQuery(w.getStartOnGenome(), Integer.MAX_VALUE, w.getEndOnGenome());
317
318 Collections.sort(readsInRange, new Comparator<OnGenome>() {
319 public int compare(OnGenome o1, OnGenome o2) {
320 int diff = o1.getStart() - o2.getStart();
321 if (diff == 0)
322 return o1.length() - o2.length();
323 else
324 return diff;
325 }
326 });
327
328 LayoutGenerator layoutGenerator = new LayoutGenerator(geneHeight);
329 boolean toContinue = false;
330 do {
331 localLayoutInView.clear();
332 layoutGenerator.reset();
333
334 for (OnGenome each : readsInRange) {
335 layoutGenerator.handle(each);
336 toContinue = layoutGenerator.needRelayout;
337 if (!layoutGenerator.toContinue())
338 break;
339 }
340 }
341 while (toContinue);
342
343 int maxYOffset = layoutGenerator.maxYOffset;
344 if (maxYOffset <= 0)
345 maxYOffset = 1;
346
347 hasEnoughHeightForLabels = layoutGenerator.showLabelsFlag;
348 return maxYOffset;
349 }
350
351 public void setKeepSpaceForLabels(boolean keep) {
352 this.keepSpaceForLabels = keep;
353 }
354
355 public boolean keepSpaceForLabels() {
356 return this.keepSpaceForLabels;
357 }
358
359 public boolean hasEnoughHeightForLabels() {
360 return hasEnoughHeightForLabels;
361 }
362
363 public void depthFirstSearch(PrioritySearchTree.Visitor<LocusLayout> visitor) {
364 localLayoutInView.depthFirstSearch(visitor);
365 }
366
367
368
369
370
371
372
373
374
375
376
377 private static class PairedEndTargetResolver extends OnGenomeDataVisitorBase {
378 private OnGenome target;
379 private final int start;
380 private final int yOffset;
381
382 public PairedEndTargetResolver(OnGenome g, int start, int yOffset) {
383 this.target = g;
384 this.start = start;
385 this.yOffset = yOffset;
386 }
387
388 @Override
389 public void visitSAMReadPair(SAMReadPair pair) {
390 boolean overlapWithFirst = pair.getFirst().unclippedSequenceContains(start);
391 boolean overlapWithSecond = pair.getSecond().unclippedSequenceContains(start);
392
393 if (overlapWithFirst) {
394 if (overlapWithSecond) {
395 this.target = yOffset == 0 ? pair.getFirst() : pair.getSecond();
396 }
397 else
398 this.target = pair.getFirst();
399 }
400 else if (overlapWithSecond)
401 this.target = pair.getSecond();
402
403 }
404
405 public static OnGenome resolve(OnGenome g, int start, int yOffset) {
406 PairedEndTargetResolver pe = new PairedEndTargetResolver(g, start, yOffset);
407 g.accept(pe);
408 return pe.target;
409 }
410 }
411
412
413
414
415
416
417
418
419 public OnGenome overlappedInterval(int x, int y, int xBorder, int geneHeight) {
420
421 for (LocusLayout gl : localLayoutInView.rangeQuery(x, Integer.MAX_VALUE, x)) {
422 OnGenome g = gl.getLocus();
423 int y1 = gl.getYOffset() * geneHeight;
424 int y2 = y1 + geneHeight * gl.getHeight();
425
426 if (y1 <= y && y <= y2) {
427 int x1 = pixelPositionOnWindow(g.getStart()) - xBorder;
428 int x2 = pixelPositionOnWindow(g.getStart() + g.length()) + xBorder;
429
430 if (hasEnoughHeightForLabels) {
431 int labelWidth = estimiateLabelWidth(g, geneHeight);
432 if (x1 - labelWidth > 0)
433 x1 -= labelWidth;
434 else
435 x2 += labelWidth;
436 }
437
438 if (x1 <= x && x <= x2) {
439 int start = w.convertToGenomePosition(x);
440 g = PairedEndTargetResolver.resolve(g, start, (y - y1) / geneHeight);
441 return g;
442 }
443 }
444 }
445 return null;
446
447 }
448
449 public int pixelPositionOnWindow(int indexOnGenome) {
450 return w.convertToPixelX(indexOnGenome);
451 }
452
453 public void clear() {
454 localLayoutInView.clear();
455 globalLayout.clear();
456 }
457
458 public void setAllowOverlapPairedReads(boolean allow) {
459 this.allowPairedReadsOverlaop = allow;
460 }
461
462 }