1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.utgenome.gwt.utgb.client.canvas;
24
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import org.utgenome.gwt.ipad.client.TouchableComposite;
29 import org.utgenome.gwt.ipad.event.Touch;
30 import org.utgenome.gwt.ipad.event.TouchCancelEvent;
31 import org.utgenome.gwt.ipad.event.TouchCancelHandler;
32 import org.utgenome.gwt.ipad.event.TouchEndEvent;
33 import org.utgenome.gwt.ipad.event.TouchEndHandler;
34 import org.utgenome.gwt.ipad.event.TouchMoveEvent;
35 import org.utgenome.gwt.ipad.event.TouchMoveHandler;
36 import org.utgenome.gwt.ipad.event.TouchStartEvent;
37 import org.utgenome.gwt.ipad.event.TouchStartHandler;
38 import org.utgenome.gwt.utgb.client.UTGBClientException;
39 import org.utgenome.gwt.utgb.client.bio.CDS;
40 import org.utgenome.gwt.utgb.client.bio.CIGAR;
41 import org.utgenome.gwt.utgb.client.bio.Exon;
42 import org.utgenome.gwt.utgb.client.bio.Gap;
43 import org.utgenome.gwt.utgb.client.bio.Gene;
44 import org.utgenome.gwt.utgb.client.bio.GraphData;
45 import org.utgenome.gwt.utgb.client.bio.InfoSilkGenerator;
46 import org.utgenome.gwt.utgb.client.bio.Interval;
47 import org.utgenome.gwt.utgb.client.bio.OnGenome;
48 import org.utgenome.gwt.utgb.client.bio.OnGenomeDataVisitor;
49 import org.utgenome.gwt.utgb.client.bio.OnGenomeDataVisitorBase;
50 import org.utgenome.gwt.utgb.client.bio.Read;
51 import org.utgenome.gwt.utgb.client.bio.ReadCoverage;
52 import org.utgenome.gwt.utgb.client.bio.ReadList;
53 import org.utgenome.gwt.utgb.client.bio.ReferenceSequence;
54 import org.utgenome.gwt.utgb.client.bio.SAMReadLight;
55 import org.utgenome.gwt.utgb.client.bio.SAMReadPair;
56 import org.utgenome.gwt.utgb.client.bio.SAMReadPairFragment;
57 import org.utgenome.gwt.utgb.client.canvas.GWTGraphCanvas.GraphStyle;
58 import org.utgenome.gwt.utgb.client.canvas.IntervalLayout.IntervalRetriever;
59 import org.utgenome.gwt.utgb.client.canvas.IntervalLayout.LocusLayout;
60 import org.utgenome.gwt.utgb.client.track.TrackGroup;
61 import org.utgenome.gwt.utgb.client.track.TrackWindow;
62 import org.utgenome.gwt.utgb.client.ui.FixedWidthLabel;
63 import org.utgenome.gwt.utgb.client.ui.RoundCornerFrame;
64 import org.utgenome.gwt.utgb.client.util.Optional;
65 import org.utgenome.gwt.widget.client.Style;
66
67 import com.google.gwt.core.client.GWT;
68 import com.google.gwt.dom.client.ImageElement;
69 import com.google.gwt.user.client.DOM;
70 import com.google.gwt.user.client.Event;
71 import com.google.gwt.user.client.Timer;
72 import com.google.gwt.user.client.Window;
73 import com.google.gwt.user.client.ui.AbsolutePanel;
74 import com.google.gwt.user.client.ui.FlexTable;
75 import com.google.gwt.user.client.ui.Label;
76 import com.google.gwt.user.client.ui.PopupPanel;
77 import com.google.gwt.user.client.ui.VerticalPanel;
78 import com.google.gwt.user.client.ui.Widget;
79 import com.google.gwt.widgetideas.graphics.client.Color;
80 import com.google.gwt.widgetideas.graphics.client.GWTCanvas;
81 import com.google.gwt.widgetideas.graphics.client.ImageLoader;
82 import com.google.gwt.widgetideas.graphics.client.ImageLoader.CallBack;
83
84
85
86
87
88
89
90 public class GWTGenomeCanvas extends TouchableComposite {
91
92 private ReadDisplayStyle style = new ReadDisplayStyle();
93
94
95
96 private int geneHeight = style.readHeight;
97 private int geneMargin = 2;
98
99 private boolean reverse = false;
100
101
102 private GWTCanvas canvas = new GWTCanvas();
103 private AbsolutePanel basePanel = new AbsolutePanel();
104 private AbsolutePanel panel = new AbsolutePanel();
105 private static PopupInfo popupLabel = new PopupInfo();
106 private LocusClickHandler clickHandler = null;
107
108 private IntervalLayout intervalLayout = new IntervalLayout();
109 private TrackWindow trackWindow;
110
111 public static enum CoverageStyle {
112 DEFAULT, SMOOTH
113 };
114
115 private CoverageStyle coverageStyle = CoverageStyle.DEFAULT;
116
117 private List<Widget> readLabels = new ArrayList<Widget>();
118
119 private TrackGroup trackGroup;
120
121 public GWTGenomeCanvas() {
122
123 initWidget();
124 }
125
126 static class PopupInfo extends PopupPanel {
127
128 OnGenome locus;
129 private FlexTable infoTable = new FlexTable();
130
131 public PopupInfo() {
132 super(true);
133
134 RoundCornerFrame infoFrame = new RoundCornerFrame("336699", 0.7f, 4);
135 infoFrame.setWidget(infoTable);
136 this.setWidget(infoFrame);
137 }
138
139 public void setLocus(OnGenome g) {
140 if (this.locus == g)
141 return;
142
143 this.locus = g;
144
145 }
146
147 public void update() {
148 if (locus == null)
149 return;
150
151 InfoSilkGenerator silk = new InfoSilkGenerator();
152 locus.accept(silk);
153 infoTable.clear();
154 final int numRowsInCol = 15;
155 final List<String> lines = silk.getLines();
156 final int cols = lines.size() / numRowsInCol + (lines.size() % numRowsInCol != 0 ? 1 : 0);
157 for (int col = 0; col < cols; col++) {
158 VerticalPanel p = new VerticalPanel();
159 Style.padding(p, Style.LEFT | Style.RIGHT, 5);
160 Style.fontColor(p, "white");
161 Style.fontSize(p, 14);
162 Style.margin(p, 0);
163 Style.preserveWhiteSpace(p);
164
165 for (int i = 0; i < numRowsInCol; i++) {
166 int index = numRowsInCol * col + i;
167 if (index >= lines.size())
168 break;
169 p.add(new Label(lines.get(index)));
170 }
171 infoTable.setWidget(0, col, p);
172
173 }
174 }
175
176 }
177
178 public void setLocusClickHandler(LocusClickHandler handler) {
179 this.clickHandler = handler;
180 }
181
182 @Override
183 public void onBrowserEvent(Event event) {
184
185 super.onBrowserEvent(event);
186
187 int type = DOM.eventGetType(event);
188 switch (type) {
189 case Event.ONMOUSEOVER:
190
191 break;
192 case Event.ONMOUSEMOVE: {
193 moveDrag(event);
194 break;
195 }
196 case Event.ONMOUSEOUT: {
197 resetDrag(event);
198 break;
199 }
200 case Event.ONMOUSEDOWN: {
201
202 startDrag(event);
203 break;
204 }
205 case Event.ONMOUSEUP: {
206 resetDrag(event);
207 break;
208 }
209 }
210
211 }
212
213 private void startDrag(Event event) {
214 if (startDrag(getXOnCanvas(event), getYOnCanvas(event))) {
215 event.preventDefault();
216 }
217 else {
218 Event.setCapture(this.getElement());
219 }
220 }
221
222 private boolean startDrag(int clientX, int clientY) {
223 OnGenome g = overlappedInterval(clientX, clientY, 2);
224 if (g != null) {
225 if (clickHandler != null)
226 clickHandler.onClick(clientX, clientY, g);
227 return true;
228 }
229 else {
230 dragStartPoint.set(new DragPoint(clientX, clientY));
231 Style.cursor(canvas, Style.CURSOR_RESIZE_E);
232 }
233 return false;
234 }
235
236 private void moveDrag(Event e) {
237 moveDrag(getXOnCanvas(e), getYOnCanvas(e));
238 }
239
240 private void moveDrag(int clientX, int clientY) {
241
242 OnGenome g = overlappedInterval(clientX, clientY, 2);
243 if (g != null) {
244 if (popupLabel.locus != g) {
245
246 Style.cursor(canvas, Style.CURSOR_POINTER);
247 displayInfo(clientX, clientY, g);
248 }
249 }
250 else {
251 if (dragStartPoint.isDefined() && trackWindow != null) {
252 DragPoint p = dragStartPoint.get();
253 final int x_origin = trackWindow.convertToGenomePosition(p.x);
254 int startDiff = trackWindow.convertToGenomePosition(clientX) - x_origin;
255 if (startDiff != 0) {
256 int newStart = trackWindow.getStartOnGenome() - startDiff;
257 if (newStart < 1)
258 newStart = 1;
259 int newEnd = newStart + trackWindow.getSequenceLength();
260 TrackWindow newWindow = trackWindow.newWindow(newStart, newEnd);
261 if (trackGroup != null)
262 trackGroup.setTrackWindow(newWindow);
263 dragStartPoint.set(new DragPoint(clientX, p.y));
264 }
265 }
266 else {
267 Style.cursor(canvas, Style.CURSOR_AUTO);
268 popupLabel.setLocus(null);
269 }
270 }
271 }
272
273 private void resetDrag(Event event) {
274 resetDrag(getXOnCanvas(event), getYOnCanvas(event));
275 DOM.releaseCapture(this.getElement());
276 event.preventDefault();
277 }
278
279 private void resetDrag(int clientX, int clientY) {
280 if (dragStartPoint.isDefined() && trackWindow != null) {
281 DragPoint p = dragStartPoint.get();
282 final int x_origin = trackWindow.convertToGenomePosition(p.x);
283 int startDiff = trackWindow.convertToGenomePosition(clientX) - x_origin;
284 if (startDiff != 0) {
285 int newStart = trackWindow.getStartOnGenome() - startDiff;
286 if (newStart < 1)
287 newStart = 1;
288 int newEnd = newStart + trackWindow.getSequenceLength();
289 TrackWindow newWindow = trackWindow.newWindow(newStart, newEnd);
290 if (trackGroup != null)
291 trackGroup.setTrackWindow(newWindow);
292 }
293 }
294 dragStartPoint.reset();
295 Style.cursor(canvas, Style.CURSOR_AUTO);
296 }
297
298 public static class DragPoint {
299 public final int x;
300 public final int y;
301
302 public DragPoint(int x, int y) {
303 this.x = x;
304 this.y = y;
305 }
306 }
307
308 private Optional<DragPoint> dragStartPoint = new Optional<DragPoint>();
309
310 public void displayInfo(final int clientX, final int clientY, final OnGenome g) {
311 if (popupLabel == null)
312 popupLabel = new PopupInfo();
313
314 popupLabel.setLocus(g);
315
316 Timer timer = new Timer() {
317 @Override
318 public void run() {
319 popupLabel.removeFromParent();
320 if (popupLabel.locus == g) {
321
322 int x = clientX + 10 + getAbsoluteLeft();
323 int y = clientY + 3 + getAbsoluteTop();
324 final int w = Window.getClientWidth();
325 final int h = Window.getClientHeight();
326 final int xMax = w - 300;
327
328
329 if (x > xMax)
330 x = xMax;
331
332
333
334 popupLabel.setPopupPosition(x, y);
335 popupLabel.update();
336 popupLabel.show();
337
338 }
339 }
340 };
341
342 timer.schedule(100);
343 }
344
345 public void setAllowOverlapPairedReads(boolean allow) {
346 }
347
348
349
350
351
352
353
354
355 private OnGenome overlappedInterval(Event event, int xBorder) {
356 return overlappedInterval(getXOnCanvas(event), getYOnCanvas(event), xBorder);
357 }
358
359 private OnGenome overlappedInterval(int clientX, int clientY, int xBorder) {
360 int h = getReadHeight();
361 int x = drawPosition(clientX);
362 int y = clientY;
363 OnGenome g = intervalLayout.overlappedInterval(x, y, xBorder, h);
364 return g;
365 }
366
367 public int getXOnCanvas(Event event) {
368 return getXOnCanvas(DOM.eventGetClientX(event));
369 }
370
371 public int getXOnCanvas(int clientX) {
372 return clientX + Window.getScrollLeft() - basePanel.getAbsoluteLeft();
373 }
374
375 public int getYOnCanvas(int clientY) {
376 return clientY + Window.getScrollTop() - basePanel.getAbsoluteTop();
377 }
378
379 public int getYOnCanvas(Event event) {
380 return getYOnCanvas(DOM.eventGetClientY(event));
381 }
382
383 private void initWidget() {
384 super.initWidget(basePanel);
385
386 panel.add(canvas, 0, 0);
387 basePanel.add(panel, 0, 0);
388 sinkEvents(Event.ONMOUSEMOVE | Event.ONMOUSEOVER | Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONMOUSEOUT);
389
390
391 this.addTouchStartHandler(new TouchStartHandler() {
392 public void onTouchStart(TouchStartEvent event) {
393 Touch touch = event.touches().get(0);
394 event.preventDefault();
395 startDrag(touch.getClientX(), touch.getClientY());
396 DOM.setCapture(GWTGenomeCanvas.this.getElement());
397 }
398 });
399
400 this.addTouchMoveHandler(new TouchMoveHandler() {
401 public void onTouchMove(TouchMoveEvent event) {
402 event.preventDefault();
403 Touch touch = event.touches().get(0);
404 moveDrag(touch.getClientX(), touch.getClientY());
405 }
406 });
407
408 this.addTouchEndHandler(new TouchEndHandler() {
409 public void onTouchEnd(TouchEndEvent event) {
410 Touch touch = event.touches().get(0);
411 resetDrag(touch.getClientX(), touch.getClientY());
412 DOM.releaseCapture(GWTGenomeCanvas.this.getElement());
413
414 }
415 });
416
417 this.addTouchCancelHandler(new TouchCancelHandler() {
418 public void onTouchCancel(TouchCancelEvent event) {
419 Touch touch = event.touches().get(0);
420 resetDrag(touch.getClientX(), touch.getClientY());
421 DOM.releaseCapture(GWTGenomeCanvas.this.getElement());
422
423 }
424 });
425 }
426
427 private boolean hasCache = false;
428 private TrackWindow prefetchWindow;
429
430 public boolean hasCacheCovering(TrackWindow newWindow) {
431 return prefetchWindow != null && hasCache && prefetchWindow.contains(newWindow);
432 }
433
434 public TrackWindow getPrefetchWindow() {
435 return prefetchWindow;
436 }
437
438 private float DEFAULT_PREFETCH_FACTOR = 1.0f;
439 private float PREFETCH_FACTOR = DEFAULT_PREFETCH_FACTOR;
440
441 public void resetPrefetchFactor() {
442 this.PREFETCH_FACTOR = DEFAULT_PREFETCH_FACTOR;
443 }
444
445 public float getPrefetchFactor() {
446 return this.PREFETCH_FACTOR;
447 }
448
449 public void setPrefetchFactor(float factor) {
450 if (factor <= 0.3f)
451 factor = 0.3f;
452 this.PREFETCH_FACTOR = factor;
453 }
454
455
456
457
458 public void setTrackWindow(TrackWindow newWindow, boolean resetPrefetchWindow) {
459
460 if (resetPrefetchWindow || !hasCacheCovering(newWindow)) {
461 int prefetchStart = newWindow.getStartOnGenome() - (int) (newWindow.getSequenceLength() * PREFETCH_FACTOR);
462 int prefetchEnd = newWindow.getEndOnGenome() + (int) (newWindow.getSequenceLength() * PREFETCH_FACTOR);
463 if (prefetchStart <= 0) {
464 prefetchStart = 1;
465 prefetchEnd = newWindow.getEndOnGenome() + (int) (newWindow.getSequenceLength() * PREFETCH_FACTOR * 2);
466 }
467 int prefetchPixelSize = (int) (newWindow.getPixelWidth() * (1 + PREFETCH_FACTOR * 2));
468 prefetchWindow = new TrackWindow(prefetchPixelSize, prefetchStart, prefetchEnd);
469 hasCache = false;
470 }
471
472 if (trackWindow != null) {
473 int newX = newWindow.convertToPixelX(trackWindow.getStartOnGenome());
474 if (!trackWindow.hasSameScaleWith(newWindow)) {
475 int newPixelWidth = newWindow.convertToPixelLength(trackWindow.getSequenceLength());
476 Style.scaleXwithAnimation(canvas, (double) newPixelWidth / trackWindow.getPixelWidth(), newX, 0.5);
477 imageACGT = null;
478 }
479 else {
480 Style.scrollX(canvas, newX, 0.5);
481 }
482 }
483
484 this.trackWindow = newWindow;
485 reverse = newWindow.isReverseStrand();
486
487 intervalLayout.setTrackWindow(newWindow);
488 }
489
490 public void setTrackGroup(TrackGroup trackGroup) {
491 this.trackGroup = trackGroup;
492 }
493
494 public int pixelPositionOnWindow(int indexOnGenome) {
495 return trackWindow.convertToPixelX(indexOnGenome);
496 }
497
498 public void clearWidgets() {
499 canvas.clear();
500
501
502 if (popupLabel != null)
503 popupLabel.removeFromParent();
504
505 scale.removeFromParent();
506
507 for (Widget w : readLabels) {
508 w.removeFromParent();
509 }
510 readLabels.clear();
511 Style.scaleXwithAnimation(canvas, 1, 0.0);
512 panel.setWidgetPosition(canvas, 0, 0);
513 basePanel.setWidgetPosition(panel, 0, 0);
514 }
515
516 public void clear() {
517 clearWidgets();
518 prefetchWindow = null;
519 hasCache = false;
520 intervalLayout.clear();
521 }
522
523 public static int width(int x1, int x2) {
524 return (x1 < x2) ? x2 - x1 : x1 - x2;
525 }
526
527 private final int FONT_WIDTH = 7;
528
529 private static final String DEFAULT_COLOR_A = "#50B6E8";
530 private static final String DEFAULT_COLOR_C = "#E7846E";
531 private static final String DEFAULT_COLOR_G = "#84AB51";
532 private static final String DEFAULT_COLOR_T = "#FFA930";
533 private static final String DEFAULT_COLOR_N = "#FFFFFF";
534
535 private static final float repeatColorAlpha = 0.3f;
536 private static final Color[] colors = { getColor(DEFAULT_COLOR_A, 1.0f), getColor(DEFAULT_COLOR_C, 1.0f), getColor(DEFAULT_COLOR_G, 1.0f),
537 getColor(DEFAULT_COLOR_T, 1.0f), getColor(DEFAULT_COLOR_A, repeatColorAlpha), getColor(DEFAULT_COLOR_C, repeatColorAlpha),
538 getColor(DEFAULT_COLOR_G, repeatColorAlpha), getColor(DEFAULT_COLOR_T, repeatColorAlpha), getColor(DEFAULT_COLOR_N, 1.0f) };
539
540 private int getReadHeight() {
541 return geneHeight + geneMargin;
542 }
543
544
545
546
547
548
549
550 class ReadPainter extends OnGenomeDataVisitorBase {
551
552 private LocusLayout gl;
553 private int h = getReadHeight();
554
555 public void setLayoutInfo(LocusLayout layout) {
556 this.gl = layout;
557 }
558
559 public int getYPos() {
560 return gl.scaledHeight(h);
561 }
562
563 public int getYPos(int y) {
564 return LocusLayout.scaledHeight(y, h);
565 }
566
567 @Override
568 public void visitGene(Gene g) {
569 int gx1 = pixelPositionOnWindow(g.getStart());
570 int gx2 = pixelPositionOnWindow(g.getEnd());
571
572 int geneWidth = gx2 - gx1;
573 if (geneWidth <= 10) {
574 draw(g, getYPos());
575 }
576 else {
577 CDS cds = g.getCDS().size() > 0 ? g.getCDS().get(0) : null;
578 draw(g, g.getExon(), cds, getYPos());
579 }
580
581 drawLabel(g);
582 }
583
584 public void drawBases(int startOnGenome, int y, String seq, String qual) {
585
586 int pixelWidthOfBase = (int) (trackWindow.getPixelLengthPerBase() + 0.1d);
587
588 if (imageACGT == null) {
589 GWT.log("font image is not loaded");
590 return;
591 }
592
593 for (int i = 0; i < seq.length(); i++) {
594 int baseIndex = 8;
595 switch (seq.charAt(i)) {
596 case 'A':
597 baseIndex = 0;
598 break;
599 case 'C':
600 baseIndex = 1;
601 break;
602 case 'G':
603 baseIndex = 2;
604 break;
605 case 'T':
606 baseIndex = 3;
607 break;
608 case 'a':
609 baseIndex = 4;
610 break;
611 case 'c':
612 baseIndex = 5;
613 break;
614 case 'g':
615 baseIndex = 6;
616 break;
617 case 't':
618 baseIndex = 7;
619 break;
620 case 'N':
621 baseIndex = 8;
622 break;
623 default:
624 continue;
625 }
626
627 double x1 = trackWindow.convertToPixelXDouble(startOnGenome + i);
628 double x2 = trackWindow.convertToPixelXDouble(startOnGenome + i + 1);
629
630 int h = imageACGT.getHeight();
631 if (h >= geneHeight)
632 h = geneHeight;
633
634 if (qual == null || h < 5 || !style.showBaseQuality) {
635 canvas.drawImage(imageACGT, pixelWidthOfBase * baseIndex, 0, pixelWidthOfBase, h, (int) x1, y, pixelWidthOfBase, h);
636 }
637 else {
638 canvas.saveContext();
639 final int threshold = 40;
640 if (i < qual.length()) {
641 int qv = qual.charAt(i) - 33;
642 if (qv > threshold)
643 qv = threshold;
644 if (qv < 0)
645 qv = 0;
646 float ratio = (float) qv / threshold;
647 float height = h * ratio;
648
649 canvas.setFillStyle(colors[baseIndex]);
650 canvas.fillRect(x1, y, x2 - x1, geneHeight);
651
652 canvas.setFillStyle(new Color(255, 255, 255, 0.7f));
653 canvas.fillRect((int) x1, y, pixelWidthOfBase, h * (1 - ratio));
654
655 canvas.drawImage(imageACGT, pixelWidthOfBase * baseIndex, h, pixelWidthOfBase, h, (int) x1, y, pixelWidthOfBase, h);
656 }
657 canvas.restoreContext();
658 }
659 }
660
661 }
662
663 private void drawLabel(OnGenome r, int y) {
664 if (!intervalLayout.hasEnoughHeightForLabels())
665 return;
666
667 IntervalRetriever ir = new IntervalRetriever();
668 ir.allowPEOverlap = style.overlapPairedReads;
669 r.accept(ir);
670
671 int gx1 = pixelPositionOnWindow(ir.start);
672 int gx2 = pixelPositionOnWindow(ir.end);
673
674 String n = r.getName();
675 if (n != null) {
676 int textWidth = IntervalLayout.estimiateLabelWidth(r, geneHeight);
677
678 Widget label = new FixedWidthLabel(n, textWidth);
679 Style.fontSize(label, geneHeight);
680 Style.fontColor(label, getExonColorText(r));
681
682 Style.verticalAlign(label, "middle");
683
684 int yPos = y - 1;
685
686 if (gx1 - textWidth < 0) {
687 if (reverse) {
688 Style.textAlign(label, "right");
689 panel.add(label, drawPosition(gx2) - textWidth - 1, yPos);
690 }
691 else {
692 Style.textAlign(label, "left");
693 panel.add(label, drawPosition(gx2) + 1, yPos);
694 }
695 }
696 else {
697 if (reverse) {
698 Style.textAlign(label, "left");
699 panel.add(label, drawPosition(gx1) + 1, yPos);
700 }
701 else {
702 Style.textAlign(label, "right");
703 panel.add(label, drawPosition(gx1) - textWidth - 1, yPos);
704 }
705 }
706
707 readLabels.add(label);
708 }
709
710 }
711
712 @Override
713 public void visitGap(Gap p) {
714 drawPadding(pixelPositionOnWindow(p.getStart()), pixelPositionOnWindow(p.getEnd()), getYPos(), getColor("#666666", 1.0f), true);
715 }
716
717 private void drawLabel(OnGenome r) {
718 drawLabel(r, getYPos());
719 }
720
721 @Override
722 public void visitInterval(Interval interval) {
723 draw(interval, getYPos());
724 }
725
726 @Override
727 public void visitRead(Read r) {
728 draw(r, getYPos());
729 drawLabel(r);
730 }
731
732 @Override
733 public void visitSAMReadPair(SAMReadPair pair) {
734
735 SAMReadLight first = pair.getFirst();
736 SAMReadLight second = pair.getSecond();
737
738 int y1 = getYPos();
739 int y2 = y1;
740
741 if (!style.overlapPairedReads && first.unclippedSequenceHasOverlapWith(second)) {
742 if (first.unclippedStart > second.unclippedStart) {
743 SAMReadLight tmp = first;
744 first = second;
745 second = tmp;
746 }
747 y2 = getYPos(gl.getYOffset() + 1);
748 }
749 else {
750 visitGap(pair.getGap());
751 }
752
753 drawLabel(pair);
754 drawSAMRead(first, y1, false);
755 drawSAMRead(second, y2, false);
756 }
757
758 @Override
759 public void visitSAMReadPairFragment(SAMReadPairFragment fragment) {
760 drawLabel(fragment);
761 visitGap(fragment.getGap());
762 drawSAMRead(fragment.oneEnd, getYPos(), false);
763 }
764
765 @Override
766 public void visitSAMReadLight(SAMReadLight r) {
767 drawSAMRead(r, getYPos(), true);
768 }
769
770 class PostponedInsertion {
771 final int pixelX;
772 final String subseq;
773
774 public PostponedInsertion(int pixelX, String subseq) {
775 this.pixelX = pixelX;
776 this.subseq = subseq;
777 }
778
779 }
780
781 public void drawSAMRead(SAMReadLight r, int y, boolean drawLabel) {
782
783 try {
784 int cx1 = pixelPositionOnWindow(r.unclippedStart);
785 int cx2 = pixelPositionOnWindow(r.unclippedEnd);
786
787 int gx1 = pixelPositionOnWindow(r.getStart());
788 int gx2 = pixelPositionOnWindow(r.getEnd());
789
790 int width = gx2 - gx1;
791
792 if ((cx2 - cx1) <= 5) {
793
794 draw(r, y);
795 }
796 else {
797
798 boolean drawBase = trackWindow.getSequenceLength() <= (trackWindow.getPixelWidth() / FONT_WIDTH);
799
800 CIGAR cigar = new CIGAR(r.cigar);
801 int readStart = r.getStart();
802 int seqIndex = 0;
803
804
805 List<PostponedInsertion> postponed = new ArrayList<PostponedInsertion>();
806
807 for (int cigarIndex = 0; cigarIndex < cigar.size(); cigarIndex++) {
808 CIGAR.Element e = cigar.get(cigarIndex);
809
810 int readEnd = readStart + e.length;
811 int x1 = pixelPositionOnWindow(readStart);
812 switch (e.type) {
813 case Deletions:
814
815
816
817 drawPadding(x1, pixelPositionOnWindow(readEnd), y, style.getSAMReadColor(r), true);
818 break;
819 case Insertions:
820
821
822
823 if (r.getSequence() != null)
824 postponed.add(new PostponedInsertion(x1, r.getSequence().substring(seqIndex, seqIndex + e.length)));
825 readEnd = readStart;
826 seqIndex += e.length;
827 break;
828 case Padding:
829
830
831
832 readEnd = readStart;
833 drawPadding(x1, pixelPositionOnWindow(readStart) + 1, y, style.getPaddingColor(), false);
834 break;
835 case Matches: {
836 int x2 = pixelPositionOnWindow(readEnd);
837
838 if (drawBase && r.getSequence() != null) {
839
840 drawBases(readStart, y, r.getSequence().substring(seqIndex, seqIndex + e.length),
841 r.getQV() != null ? r.getQV().substring(seqIndex, seqIndex + e.length) : null);
842 }
843 else {
844 drawGeneRect(x1, x2, y, style.getSAMReadColor(r), style.drawShadow);
845 }
846
847 seqIndex += e.length;
848 }
849 break;
850 case SkippedRegion:
851 drawPadding(x1, pixelPositionOnWindow(readEnd), y, style.getReadColor(r), true);
852 break;
853 case SoftClip: {
854 int softclipStart = cigarIndex == 0 ? readStart - e.length : readStart;
855 int softclipEnd = cigarIndex == 0 ? readStart : readStart + e.length;
856 readEnd = softclipEnd;
857
858 int x0 = pixelPositionOnWindow(softclipStart);
859 x1 = pixelPositionOnWindow(softclipEnd);
860
861 if (drawBase && r.getSequence() != null) {
862 drawBases(softclipStart, y, r.getSequence().substring(seqIndex, seqIndex + e.length).toLowerCase(), r.getQV() != null ? r
863 .getQV().substring(seqIndex, seqIndex + e.length) : null);
864 }
865 else {
866 drawGeneRect(x0, x1, y, style.getClippedReadColor(r), style.drawShadow);
867 }
868
869 seqIndex += e.length;
870 }
871 break;
872 case HardClip:
873 break;
874 }
875 readStart = readEnd;
876 }
877
878 for (PostponedInsertion each : postponed) {
879 drawGeneRect(each.pixelX, each.pixelX + 1, y, getColor("#111111", 1.0f), true);
880
881 }
882 }
883
884 }
885 catch (UTGBClientException e) {
886
887 draw(r, y);
888 }
889
890 if (drawLabel)
891 drawLabel(r, y);
892 }
893
894 @Override
895 public void visitSequence(ReferenceSequence referenceSequence) {
896
897
898 }
899
900 @Override
901 public void visitReadCoverage(ReadCoverage readCoverage) {
902 drawBlock(readCoverage);
903 }
904
905 @Override
906 public void visitGraph(GraphData graph) {
907
908
909 }
910
911 @Override
912 public void visitReadList(ReadList readList) {
913
914
915 }
916
917 };
918
919 private class FindMaximumHeight extends OnGenomeDataVisitorBase {
920 int maxHeight = 1;
921
922 @Override
923 public void visitReadCoverage(ReadCoverage readCoverage) {
924 if (readCoverage.coverage == null)
925 return;
926
927 int startPosOfCoverageOnGenome = readCoverage.getStart();
928 int viewStartOnGenome = trackWindow.getStartOnGenome();
929 int viewEndOnGenome = trackWindow.getEndOnGenome();
930
931 TrackWindow w = new TrackWindow(readCoverage.pixelWidth, readCoverage.getStart(), readCoverage.getEnd());
932 int startPosInCoveragePixel = w.convertToPixelX(viewStartOnGenome);
933 int endPosInCoveragePixel = w.convertToPixelX(viewEndOnGenome);
934 if (endPosInCoveragePixel > readCoverage.pixelWidth)
935 endPosInCoveragePixel = readCoverage.pixelWidth;
936
937
938 for (int i = startPosInCoveragePixel; i < endPosInCoveragePixel; ++i) {
939 int height = readCoverage.coverage[i];
940 if (height > maxHeight)
941 maxHeight = height;
942 }
943 }
944 }
945
946 private class CoveragePainter extends OnGenomeDataVisitorBase {
947
948 private int heigtOfRead = 1;
949 private float scalingFactor = 1.0f;
950
951 public CoveragePainter(int heightOfRead, float scalingFactor) {
952 this.heigtOfRead = heightOfRead;
953 this.scalingFactor = scalingFactor;
954 }
955
956 @Override
957 public void visitReadCoverage(ReadCoverage readCoverage) {
958 canvas.saveContext();
959 canvas.setStrokeStyle(getColor("#FFFFFF", 0.0f));
960 canvas.setFillStyle(getColor("#6699CC", 0.6f));
961 canvas.setLineWidth(0.1f);
962 canvas.setLineCap("round");
963
964 int x1 = pixelPositionOnWindow(trackWindow.getStartOnGenome());
965 int x2 = pixelPositionOnWindow(trackWindow.getEndOnGenome());
966
967 if (x1 < 0)
968 x1 = 0;
969 if (x2 > readCoverage.pixelWidth)
970 x2 = readCoverage.pixelWidth;
971
972 int w = x2 - x1;
973 canvas.scale(trackWindow.getPixelWidth() / (double) w, 1.0f);
974
975 canvas.saveContext();
976 canvas.beginPath();
977 canvas.moveTo(-0.5f, -1.0f);
978 for (int x = x1; x < x2; ++x) {
979 int h = readCoverage.coverage[x];
980 if (h <= 0) {
981 canvas.lineTo(x - x1 + 0.5f, -1.0f);
982 continue;
983 }
984
985 int y = (int) ((h * heigtOfRead) * scalingFactor);
986 canvas.lineTo(x - x1 + 0.5f, y + 0.5f);
987 canvas.stroke();
988 }
989 canvas.moveTo(readCoverage.pixelWidth + 0.5f, -1.0f);
990 canvas.fill();
991 canvas.restoreContext();
992
993 canvas.restoreContext();
994 }
995 }
996
997 private class RoughCoveragePainter extends OnGenomeDataVisitorBase {
998
999 private int heigtOfRead = 1;
1000 private float scalingFactor = 1.0f;
1001
1002 public RoughCoveragePainter(int heightOfRead, float scalingFactor) {
1003 this.heigtOfRead = heightOfRead;
1004 this.scalingFactor = scalingFactor;
1005 }
1006
1007 @Override
1008 public void visitReadCoverage(ReadCoverage readCoverage) {
1009 canvas.saveContext();
1010 canvas.setFillStyle(getColor("#6699CC", 0.8f));
1011 canvas.setLineWidth(1.0f);
1012 canvas.setLineCap("round");
1013
1014 if (prefetchWindow == null)
1015 return;
1016
1017 double x1 = prefetchWindow.convertToPixelXDouble(trackWindow.getStartOnGenome());
1018 double x2 = prefetchWindow.convertToPixelXDouble(trackWindow.getEndOnGenome() + 1);
1019
1020 double w = x2 - x1;
1021 canvas.scale(trackWindow.getPixelWidth() / w, 1.0f);
1022
1023 if (x1 < 0)
1024 x1 = 0;
1025 if (x2 > readCoverage.pixelWidth)
1026 x2 = readCoverage.pixelWidth;
1027
1028 for (int x = (int) x1; x < (int) x2; ++x) {
1029 int h = readCoverage.coverage[x];
1030 if (h <= 0) {
1031 continue;
1032 }
1033 int y = (int) ((h * heigtOfRead) * scalingFactor);
1034 if (y <= 0)
1035 y = 1;
1036 canvas.saveContext();
1037 canvas.translate(x - x1 + 0.5f, 0);
1038 canvas.fillRect(0, 0, 1, y - 0.5f);
1039 canvas.restoreContext();
1040 }
1041
1042 canvas.restoreContext();
1043 }
1044 }
1045
1046 private final int TRACK_COLLAPSE_COVERAGE_THRESHOLD = 40;
1047
1048 private GraphScale scale = new GraphScale();
1049
1050 public <T extends OnGenome> void drawBlock(ReadCoverage block) {
1051
1052
1053 FindMaximumHeight hFinder = new FindMaximumHeight();
1054 block.accept(hFinder);
1055
1056
1057 int heightOfRead = style.minReadHeight;
1058
1059 int canvasHeight = hFinder.maxHeight * heightOfRead;
1060 float scalingFactor = 1.0f;
1061
1062 final int MAX_CANVAS_HEIGHT = 50;
1063 if (canvasHeight > MAX_CANVAS_HEIGHT) {
1064 scalingFactor = (float) (MAX_CANVAS_HEIGHT) / canvasHeight;
1065 canvasHeight = MAX_CANVAS_HEIGHT;
1066 }
1067
1068 setPixelSize(trackWindow.getPixelWidth(), canvasHeight);
1069
1070
1071 OnGenomeDataVisitor cPainter;
1072 switch (coverageStyle) {
1073 case SMOOTH:
1074 cPainter = new CoveragePainter(heightOfRead, scalingFactor);
1075 break;
1076 case DEFAULT:
1077 default:
1078 cPainter = new RoughCoveragePainter(heightOfRead, scalingFactor);
1079 break;
1080 }
1081
1082 block.accept(cPainter);
1083
1084 GraphStyle scaleStyle = new GraphStyle();
1085 scaleStyle.autoScale = false;
1086 scaleStyle.minValue = hFinder.maxHeight;
1087 scaleStyle.maxValue = 0;
1088 scaleStyle.windowHeight = canvasHeight;
1089 scaleStyle.drawScale = false;
1090 scale.draw(scaleStyle, this.trackWindow);
1091 panel.add(scale, 0, 0);
1092 }
1093
1094 public void resetData(List<OnGenome> readSet) {
1095 intervalLayout.reset(readSet, geneHeight);
1096 hasCache = true;
1097 }
1098
1099 private ImageElement imageACGT = null;
1100
1101 public void draw() {
1102
1103 boolean drawBase = trackWindow.getSequenceLength() <= (trackWindow.getPixelWidth() / FONT_WIDTH);
1104 if (drawBase && imageACGT == null) {
1105 int pixelWidthOfBase = (int) (trackWindow.getPixelLengthPerBase() + 0.1d);
1106 ImageLoader.loadImages(new String[] { "utgb-core/ACGT.png?fontWidth=" + pixelWidthOfBase + "&height=" + style.readHeight }, new CallBack() {
1107 public void onImagesLoaded(ImageElement[] imageElements) {
1108 imageACGT = imageElements[0];
1109 layout();
1110 }
1111 });
1112 }
1113 else {
1114 layout();
1115 }
1116 }
1117
1118 private void layout() {
1119
1120 int maxOffset = intervalLayout.createLocalLayout(geneHeight);
1121
1122 if (maxOffset > TRACK_COLLAPSE_COVERAGE_THRESHOLD)
1123 geneHeight = style.minReadHeight;
1124 else
1125 geneHeight = style.readHeight;
1126
1127 int h = geneHeight + geneMargin;
1128 int height = (maxOffset + 1) * h;
1129
1130 clearWidgets();
1131 setPixelSize(trackWindow.getPixelWidth(), height);
1132
1133 final ReadPainter painter = new ReadPainter();
1134
1135 intervalLayout.depthFirstSearch(new PrioritySearchTree.Visitor<LocusLayout>() {
1136 public void visit(LocusLayout gl) {
1137 painter.setLayoutInfo(gl);
1138 gl.getLocus().accept(painter);
1139 }
1140 });
1141 }
1142
1143 @Override
1144 public void setPixelSize(int width, int height) {
1145 canvas.setCoordSize(width, height);
1146 canvas.setPixelWidth(width);
1147 canvas.setPixelHeight(height);
1148 Style.scaleX(canvas, 1);
1149 panel.setPixelSize(width, height);
1150 basePanel.setPixelSize(width, height);
1151 }
1152
1153 public static Color getColor(String hex, float alpha) {
1154 return ColorUtil.toColor(hex, alpha);
1155 }
1156
1157 public Color getGeneColor(Interval l) {
1158 return getGeneColor(l, 1f);
1159 }
1160
1161 public Color getGeneColor(Interval l, float alpha) {
1162 return style.getReadColor(l, alpha);
1163 }
1164
1165 private static String getExonColorText(OnGenome g) {
1166 final String senseColor = "#d80067";
1167 final String antiSenseColor = "#0067d8";
1168
1169 if (g instanceof Interval) {
1170 Interval r = (Interval) g;
1171 if (r.getColor() == null) {
1172 return r.isSense() ? senseColor : antiSenseColor;
1173 }
1174 else {
1175 return r.getColor();
1176 }
1177 }
1178 else
1179 return senseColor;
1180 }
1181
1182 public Color getExonColor(Gene g) {
1183 return getGeneColor(g, 0.5f);
1184 }
1185
1186 public Color getCDSColor(Interval g, float alpha) {
1187 return getGeneColor(g, alpha);
1188 }
1189
1190 public Color getCDSColor(Interval g) {
1191 return getGeneColor(g);
1192 }
1193
1194 public void draw(Gene gene, List<Exon> exonList, CDS cds, int yPosition) {
1195
1196
1197 if (exonList.isEmpty()) {
1198 Color c = getGeneColor(gene);
1199 drawGeneRect(pixelPositionOnWindow(gene.getStart()), pixelPositionOnWindow(gene.getEnd()), yPosition, c, true);
1200 }
1201
1202 for (Exon e : exonList) {
1203 drawExon(gene, e, cds, yPosition);
1204 }
1205
1206 canvas.saveContext();
1207 canvas.setFillStyle(getGeneColor(gene, 0.7f));
1208 canvas.setStrokeStyle(getGeneColor(gene, 0.7f));
1209 canvas.setLineWidth(0.5f);
1210
1211
1212 boolean isSense = gene.isSense() ? !reverse : reverse;
1213 double arrowHeight = geneHeight / 2.0 + 0.5;
1214
1215 for (int i = 0; i < exonList.size() - 1; i++) {
1216 Exon prev = exonList.get(i);
1217 Exon next = exonList.get(i + 1);
1218
1219 int x1 = pixelPositionOnWindow(prev.getEnd());
1220 int x2 = pixelPositionOnWindow(next.getStart());
1221 float yAxis = yPosition + (geneHeight / 2) + 0.5f;
1222
1223 canvas.saveContext();
1224 canvas.beginPath();
1225 canvas.moveTo(drawPosition(x1) + 0.5f, yAxis);
1226 canvas.lineTo(drawPosition(x2) - 0.5f, yAxis);
1227 canvas.stroke();
1228 canvas.restoreContext();
1229
1230 for (int x = x1; x + 4 <= x2; x += 5) {
1231 canvas.saveContext();
1232 canvas.translate(drawPosition(x) + 2.0f, yPosition + arrowHeight);
1233 if (!isSense)
1234 canvas.rotate(Math.PI);
1235 canvas.beginPath();
1236 canvas.moveTo(-2.0f, -arrowHeight + 1.5f);
1237 canvas.lineTo(1.5f, 0);
1238 canvas.lineTo(-2.0f, arrowHeight - 1.5f);
1239 canvas.stroke();
1240 canvas.restoreContext();
1241 }
1242
1243 }
1244 canvas.restoreContext();
1245
1246 }
1247
1248 public void drawPadding(int x1, int x2, int y, Color c, boolean drawShadow) {
1249
1250 canvas.saveContext();
1251 canvas.setFillStyle(c);
1252 canvas.setStrokeStyle(c);
1253 canvas.setLineWidth(0.5f);
1254 float yPos = y + (geneHeight / 2) + 0.5f;
1255
1256 canvas.beginPath();
1257 canvas.moveTo(drawPosition(x1) + 0.5f, yPos);
1258 canvas.lineTo(drawPosition(x2) - 0.5f, yPos);
1259 canvas.stroke();
1260
1261 canvas.restoreContext();
1262
1263 }
1264
1265 private int drawPosition(int x) {
1266 if (reverse)
1267 return (trackWindow.getPixelWidth() - x);
1268 else
1269 return x;
1270 }
1271
1272 public void draw(Interval gene, int yPosition) {
1273 int gx = pixelPositionOnWindow(gene.getStart());
1274 int gx2 = pixelPositionOnWindow(gene.getEnd());
1275
1276 drawGeneRect(gx, gx2, yPosition, getCDSColor(gene), style.drawShadow);
1277 }
1278
1279 public void draw(Gene gene, int yPosition) {
1280 int gx = pixelPositionOnWindow(gene.getStart());
1281 int gx2 = pixelPositionOnWindow(gene.getEnd());
1282
1283 drawGeneRect(gx, gx2, yPosition, getGeneColor(gene), style.drawShadow);
1284 }
1285
1286 public void drawExon(Gene gene, Exon exon, CDS cds, int yPosition) {
1287 int ex = pixelPositionOnWindow(exon.getStart());
1288 int ex2 = pixelPositionOnWindow(exon.getEnd());
1289
1290 drawGeneRect(ex, ex2, yPosition, getExonColor(gene), style.drawShadow);
1291
1292
1293 if (cds != null) {
1294 int cx = pixelPositionOnWindow(cds.getStart());
1295 int cx2 = pixelPositionOnWindow(cds.getEnd());
1296
1297 if (cx <= cx2) {
1298 if (ex <= cx2 && ex2 >= cx) {
1299 int cdsStart = (ex <= cx) ? cx : ex;
1300 int cdsEnd = (ex2 <= cx2) ? ex2 : cx2;
1301
1302 drawGeneRect(cdsStart, cdsEnd, yPosition, getCDSColor(gene), false);
1303 }
1304 }
1305
1306 }
1307
1308 }
1309
1310 public void drawGeneRect(int x1, int x2, int y, Color c, boolean drawShadow) {
1311
1312 float boxWidth = x2 - x1 - 0.5f;
1313 if (boxWidth <= 0)
1314 boxWidth = 1f;
1315
1316 canvas.saveContext();
1317 double drawX = drawPosition(reverse ? x2 : x1);
1318 canvas.translate(drawX, y);
1319 canvas.setFillStyle(Color.WHITE);
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330 canvas.setFillStyle(c);
1331 canvas.fillRect(0, 0, boxWidth, geneHeight);
1332 canvas.restoreContext();
1333
1334 if (drawShadow) {
1335 canvas.saveContext();
1336 canvas.setStrokeStyle(new Color(30, 30, 30, 0.6f));
1337 double shadowStart = drawPosition(reverse ? x2 : x1);
1338 canvas.translate(shadowStart, y);
1339 canvas.beginPath();
1340 canvas.moveTo(1.5f, geneHeight + 0.5f);
1341 canvas.lineTo(boxWidth + 0.5f, geneHeight + 0.5f);
1342 canvas.lineTo(boxWidth + 0.5f, 0.5f);
1343 canvas.stroke();
1344 canvas.restoreContext();
1345 }
1346
1347 }
1348
1349 public void setReadStyle(ReadDisplayStyle style) {
1350 this.style = style;
1351 intervalLayout.setKeepSpaceForLabels(style.showLabels);
1352 imageACGT = null;
1353
1354 if (style.coverageStyle != null) {
1355 CoverageStyle s = CoverageStyle.valueOf(CoverageStyle.class, style.coverageStyle.toUpperCase());
1356 if (s != null) {
1357 coverageStyle = s;
1358 }
1359 }
1360
1361 intervalLayout.setAllowOverlapPairedReads(style.overlapPairedReads);
1362 }
1363
1364 }