View Javadoc

1   /*--------------------------------------------------------------------------
2    *  Copyright 2008 utgenome.org
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   *--------------------------------------------------------------------------*/
16  //--------------------------------------
17  // utgb-core Project
18  //
19  // GWTGenomeCanvas.java
20  // Since: Jul 8, 2008
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   * Browser-side graphic canvas for drawing gene objects
86   * 
87   * @author leo
88   * 
89   */
90  public class GWTGenomeCanvas extends TouchableComposite {
91  
92  	private ReadDisplayStyle style = new ReadDisplayStyle();
93  	//	private int defaultGeneHeight = 12;
94  	//	private int defaultMinGeneHeight = 2;
95  
96  	private int geneHeight = style.readHeight;
97  	private int geneMargin = 2;
98  
99  	private boolean reverse = false;
100 
101 	// widget
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 			// invoke a click event
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 		// show readLabels 
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 					//final int yMax = Math.max(h - 200 + Window.getScrollTop(), Window.getScrollTop());
328 
329 					if (x > xMax)
330 						x = xMax;
331 					//					if (y > yMax)
332 					//						y = yMax;
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 	 * compute the overlapped intervals for the mouse over event
350 	 * 
351 	 * @param event
352 	 * @param xBorder
353 	 * @return
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 		// add touch handler for iPad
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 				//event.preventDefault();
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 				//event.preventDefault();
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 	 * @param newWindow
457 	 */
458 	public void setTrackWindow(TrackWindow newWindow, boolean resetPrefetchWindow) {
459 
460 		if (resetPrefetchWindow || !hasCacheCovering(newWindow)) { // when need to prefetch the data
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 		//imageACGT = null;
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 	 * A class for drawing OnGenome objects on a canvas
546 	 * 
547 	 * @author leo
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 					// when the pixel range is narrow, draw a rectangle only 
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 					// Drawing insertions should be postponed after all of he read bases are painted.
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 							// ref : AAAAAA
815 							// read: ---AAA
816 							// cigar: 3D3M
817 							drawPadding(x1, pixelPositionOnWindow(readEnd), y, style.getSAMReadColor(r), true);
818 							break;
819 						case Insertions:
820 							// ref : ---AAA
821 							// read: AAAAAA
822 							// cigar: 3I3M
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 							// ref : AAAAAA
830 							// read: ---AAA
831 							// cigar: 3P3M
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 								//drawGeneRect(x1, x2, y, getCDSColor(r, 0.3f), true);
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 				// when parsing CIGAR string fails, simply draw a rectangle
887 				draw(r, y);
888 			}
889 
890 			if (drawLabel)
891 				drawLabel(r, y);
892 		}
893 
894 		@Override
895 		public void visitSequence(ReferenceSequence referenceSequence) {
896 			// TODO Auto-generated method stub
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 			// TODO Auto-generated method stub
908 
909 		}
910 
911 		@Override
912 		public void visitReadList(ReadList readList) {
913 			// TODO Auto-generated method stub
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 			// set canvas size
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 		// compute max height
1053 		FindMaximumHeight hFinder = new FindMaximumHeight();
1054 		block.accept(hFinder);
1055 
1056 		//int heightOfRead = hFinder.maxHeight > TRACK_COLLAPSE_COVERAGE_THRESHOLD ? 2 : defaultGeneHeight;
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 		// draw coverage
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 		// assume that exonList are sorted
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 		// draw the arrow between exons
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 		// draw CDS
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 		//		canvas.fillRect(0, 0, boxWidth, geneHeight);
1321 		//		if (!BrowserInfo.isIE() && (boxWidth > 5 && geneHeight > 4)) {
1322 		//			CanvasGradient grad = canvas.createLinearGradient(0, 0, 0, geneHeight);
1323 		//			grad.addColorStop(0, c);
1324 		//			grad.addColorStop(0.1, Color.WHITE);
1325 		//			grad.addColorStop(0.5, c);
1326 		//			grad.addColorStop(1, c);
1327 		//			canvas.setFillStyle(grad);
1328 		//		}
1329 		//		else
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 }