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  // GeneCanvas.java
20  // Since: Jul 8, 2008
21  //
22  // $URL: http://svn.utgenome.org/utgb/trunk/utgb/utgb-core/src/main/java/org/utgenome/gwt/utgb/client/canvas/GeneCanvas.java $ 
23  // $Author: leo $
24  //--------------------------------------
25  package org.utgenome.graphics;
26  
27  import java.awt.Color;
28  import java.awt.Font;
29  import java.awt.FontMetrics;
30  import java.io.IOException;
31  import java.io.OutputStream;
32  import java.util.HashSet;
33  import java.util.List;
34  
35  import javax.imageio.ImageIO;
36  
37  import org.utgenome.gwt.utgb.client.bio.CDS;
38  import org.utgenome.gwt.utgb.client.bio.Exon;
39  import org.utgenome.gwt.utgb.client.bio.Gene;
40  import org.utgenome.gwt.utgb.client.bio.Interval;
41  import org.utgenome.gwt.utgb.client.bio.OnGenome;
42  import org.utgenome.gwt.utgb.client.bio.Read;
43  import org.utgenome.gwt.utgb.client.canvas.PrioritySearchTree;
44  import org.utgenome.gwt.utgb.server.app.MethylViewer;
45  import org.xerial.util.Pair;
46  
47  /**
48   * Browser-side graphic canvas for drawing gene objects
49   * 
50   * @author leo
51   * 
52   */
53  public class GeneCanvas {
54  
55  	private int geneHeight = 10;
56  	private int geneMargin = 1;
57  
58  	private GenomeCanvas canvas;
59  
60  	// ad hoc parameters
61  	private int thresholdOfNumGenesToDrawLabel = 50;
62  	private int gapWidth = 5;
63  
64  	// widget
65  	private PrioritySearchTree<LocusLayout> locusLayout = new PrioritySearchTree<LocusLayout>();
66  
67  	public GeneCanvas() {
68  	}
69  
70  	public GeneCanvas(int pixelWidth, int pixelHeight, GenomeWindow genomeWindow) {
71  		canvas = new GenomeCanvas(pixelWidth, pixelHeight, genomeWindow);
72  	}
73  
74  	public void setGeneHeight(int height) {
75  		this.geneHeight = height;
76  		canvas.setGeneHeight(height);
77  	}
78  
79  	public void setPixelHeight(int height) {
80  		canvas.setPixelHeight(height);
81  	}
82  
83  	public static int width(int x1, int x2) {
84  		return (x1 < x2) ? x2 - x1 : x1 - x2;
85  	}
86  
87  	public class LocusLayout {
88  		private OnGenome gene;
89  		private int yOffset;
90  
91  		public LocusLayout(OnGenome gene, int yOffset) {
92  			this.gene = gene;
93  			this.yOffset = yOffset;
94  		}
95  
96  		public OnGenome getLocus() {
97  			return gene;
98  		}
99  
100 		public int getYOffset() {
101 			return yOffset;
102 		}
103 
104 		@Override
105 		public String toString() {
106 			return "yOffset=" + yOffset;
107 		}
108 	}
109 
110 	public void setThresholdGenes(int thresholdGeneNames) {
111 		this.thresholdOfNumGenesToDrawLabel = thresholdGeneNames;
112 	}
113 
114 	public void setGapWidth(int gapWidth) {
115 		this.gapWidth = gapWidth;
116 	}
117 
118 	<T extends OnGenome> int createLayout(List<T> locusList) {
119 		int maxYOffset = 0;
120 		locusLayout.clear();
121 
122 		boolean drawLabel = locusList.size() < thresholdOfNumGenesToDrawLabel;
123 
124 		Font f = new Font("SansSerif", Font.PLAIN, geneHeight);
125 
126 		FontMetrics fontMetrics = canvas.getGraphics().getFontMetrics(f);
127 		long leftOnGenome = canvas.getGenomeWindow().startIndexOnGenome;
128 
129 		for (OnGenome l : locusList) {
130 
131 			int x1 = l.getStart();
132 			int x2 = l.getStart() + l.length();
133 
134 			if (drawLabel) {
135 				int width = fontMetrics.stringWidth(l.getName()) + gapWidth;
136 				int fontWidthOnGenome = canvas.getGenomeWindow().toGenomeLength(width, canvas.getWidth());
137 				if ((x1 - fontWidthOnGenome) < leftOnGenome)
138 					x2 += fontWidthOnGenome;
139 				else
140 					x1 -= fontWidthOnGenome;
141 			}
142 
143 			List<LocusLayout> activeLocus = locusLayout.rangeQuery(x1, Integer.MAX_VALUE, x2);
144 
145 			HashSet<Integer> filledY = new HashSet<Integer>();
146 			// overlap test
147 			for (LocusLayout al : activeLocus) {
148 				filledY.add(al.yOffset);
149 			}
150 
151 			int blankY = 0;
152 			for (; filledY.contains(blankY); blankY++) {
153 			}
154 
155 			locusLayout.insert(new LocusLayout(l, blankY), x2, x1);
156 
157 			if (blankY > maxYOffset)
158 				maxYOffset = blankY;
159 		}
160 
161 		if (maxYOffset <= 0)
162 			maxYOffset = 1;
163 		return maxYOffset;
164 	}
165 
166 	public <E extends OnGenome> void draw(List<E> geneList) {
167 
168 		// create a gene layout
169 		int maxOffset = createLayout(geneList);
170 
171 		int height = (maxOffset + 1) * (geneHeight + geneMargin);
172 		if (height <= 0) {
173 			height = geneHeight + geneMargin;
174 		}
175 
176 		// set the canvas size appropriately to adjust the track frame height when refresh() is called
177 		setPixelHeight(height);
178 
179 		// draw genes according the locus layout
180 		locusLayout.depthFirstSearch(new PrioritySearchTree.Visitor<LocusLayout>() {
181 			final int h = geneHeight + geneMargin;
182 
183 			public void visit(LocusLayout layout) {
184 				layout.yOffset = layout.yOffset * h;
185 				OnGenome l = layout.getLocus();
186 				long lx = l.getStart();
187 				long lx2 = l.getStart() + l.length();
188 
189 				long geneWidth = lx2 - lx;
190 				if (geneWidth <= 10) {
191 					draw(l, layout.getYOffset());
192 				}
193 				else {
194 					if (Gene.class.isInstance(l)) {
195 						Gene gene = (Gene) l;
196 						CDS cds = gene.getCDS().size() > 0 ? gene.getCDS().get(0) : null;
197 						draw(gene, gene.getExon(), cds, layout.getYOffset());
198 					}
199 					else if (MethylViewer.MethlEntry.class.isInstance(l)) {
200 						MethylViewer.MethlEntry m = MethylViewer.MethlEntry.class.cast(l);
201 
202 						drawGeneRect(l.getStart(), l.getStart() + l.length(), layout.getYOffset(), getGeneColor(l, 0.7f));
203 
204 						int s = canvas.getXPosOnWindow(l.getStart());
205 						int e = canvas.getXPosOnWindow(l.getStart() + l.length());
206 						int w = e - s;
207 						if (w < 0)
208 							w = -w;
209 
210 						Pair<List<Integer>, List<Integer>> mPosAndCPos = m.getMPos();
211 
212 						if (w > 3) {
213 							for (int cOffset : mPosAndCPos.getSecond()) {
214 								long cPos = l.getStart() + cOffset;
215 								drawGeneRect(cPos, cPos + 1, layout.getYOffset(), hexValue2Color("#6666FF", 0.3f));
216 							}
217 						}
218 						for (int mOffset : mPosAndCPos.getFirst()) {
219 							long mPos = l.getStart() + mOffset;
220 							drawGeneRect(mPos, mPos + 1, layout.getYOffset(), hexValue2Color("#F80033", 0.1f));
221 						}
222 
223 						if (locusLayout.size() <= thresholdOfNumGenesToDrawLabel) {
224 							canvas.drawLocusLabel(Integer.toString(m.frequency), lx, lx2, layout.getYOffset() + geneHeight, 9.0f, hexValue2Color("#6030F0",
225 									0.0f));
226 						}
227 
228 					}
229 					else if (Interval.class.isInstance(l)) {
230 						draw(l, layout.getYOffset());
231 					}
232 				}
233 				if (l.getName() != null && locusLayout.size() <= thresholdOfNumGenesToDrawLabel) {
234 					canvas.drawText(l.getName(), lx, lx2, layout.getYOffset(), geneHeight - 1, getExonColor(l));
235 				}
236 			}
237 		});
238 
239 	}
240 
241 	public void draw(OnGenome locus, int yOffset) {
242 		if (Gene.class.isInstance(locus))
243 			draw((Gene) locus, yOffset);
244 		else
245 			drawGeneRect(locus.getStart(), locus.getStart() + locus.length(), yOffset, getGeneColor(locus));
246 	}
247 
248 	public void draw(Gene gene, List<Exon> exonList, CDS cds, int yPosition) {
249 		// assumption: exonList are sorted
250 
251 		drawGeneRect(gene.getStart(), gene.getEnd(), yPosition, getGeneColor(gene));
252 
253 		//GWT.log("exon: ", null);
254 		for (Exon e : exonList) {
255 			draw(gene, e, cds, yPosition);
256 		}
257 
258 		// draw arrow between exons
259 		for (int i = 0; i < exonList.size() - 1; i++) {
260 			Exon prev = exonList.get(i);
261 			Exon next = exonList.get(i + 1);
262 
263 			long x1 = prev.getEnd();
264 			long x2 = next.getStart();
265 			long yAxis = yPosition + (geneHeight / 2);
266 			long xMiddle = (x1 + x2) / 2;
267 
268 			long range = x2 - x1;
269 
270 			Color exonColor = getExonColor(gene);
271 			if (range > 10) {
272 				canvas.drawLine(x1, yAxis, xMiddle, yPosition, exonColor);
273 				canvas.drawLine(xMiddle, yPosition, x2, yAxis, exonColor);
274 			}
275 			else
276 				canvas.drawLine(x1, yAxis, x2, yAxis, exonColor);
277 
278 		}
279 
280 	}
281 
282 	public void draw(Gene gene, int yPosition) {
283 		drawGeneRect(gene.getStart(), gene.getEnd(), yPosition, getCDSColor(gene));
284 	}
285 
286 	public void draw(Gene gene, Exon exon, CDS cds, int yPosition) {
287 		long ex = exon.getStart();
288 		long ex2 = exon.getEnd();
289 
290 		drawGeneRect(ex, ex2, yPosition, getExonColor(gene));
291 
292 		if (cds != null) {
293 			long cx = cds.getStart();
294 			long cx2 = cds.getEnd();
295 
296 			if (cx <= cx2) {
297 				if (ex <= cx2 && ex2 >= cx) {
298 					long cdsStart = (ex <= cx) ? cx : ex;
299 					long cdsEnd = (ex2 <= cx2) ? ex2 : cx2;
300 					drawGeneRect(cdsStart, cdsEnd, yPosition, getCDSColor(gene));
301 				}
302 			}
303 
304 		}
305 	}
306 
307 	public void drawGeneRect(long x1, long x2, int y, Color c) {
308 		canvas.drawGeneRect(x1, x2, y, geneHeight, c);
309 	}
310 
311 	public void toPNG(OutputStream out) throws IOException {
312 		ImageIO.write(canvas.getBufferedImage(), "png", out);
313 	}
314 
315 	public Color getExonColor(OnGenome g) {
316 		return hexValue2Color(getExonColorText(g), 0.3f);
317 	}
318 
319 	public Color getGeneColor(OnGenome g) {
320 		return hexValue2Color(getExonColorText(g), 0.7f);
321 	}
322 
323 	public Color getGeneColor(OnGenome g, float offset) {
324 		return hexValue2Color(getExonColorText(g), offset);
325 	}
326 
327 	public Color getCDSColor(OnGenome g) {
328 		return hexValue2Color(getExonColorText(g), 0.5f);
329 	}
330 
331 	public Color getIntronColor(OnGenome g) {
332 		return hexValue2Color(getExonColorText(g), 0.5f);
333 	}
334 
335 	public String getExonColorText(OnGenome g) {
336 		if (g instanceof Read) {
337 			Read r = (Read) g;
338 			if (r.getColor() == null) {
339 				if (r.isSense()) {
340 					return "#d80067";
341 				}
342 				else {
343 					return "#0067d8";
344 				}
345 			}
346 			else
347 				return r.getColor();
348 
349 		}
350 		else
351 			return "#d80067";
352 	}
353 
354 	public Color hexValue2Color(String hex, float offset) {
355 		int r_value = Integer.parseInt(hex.substring(1, 3), 16);
356 		int g_value = Integer.parseInt(hex.substring(3, 5), 16);
357 		int b_value = Integer.parseInt(hex.substring(5, 7), 16);
358 		return new Color(colorOffset(r_value, offset), colorOffset(g_value, offset), colorOffset(b_value, offset));
359 	}
360 
361 	public int colorOffset(int color, float offset) {
362 		return (int) (color + ((255 - color) * offset));
363 	}
364 
365 }