1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package org.utgenome.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
49
50
51
52
53 public class GeneCanvas {
54
55 private int geneHeight = 10;
56 private int geneMargin = 1;
57
58 private GenomeCanvas canvas;
59
60
61 private int thresholdOfNumGenesToDrawLabel = 50;
62 private int gapWidth = 5;
63
64
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
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
169 int maxOffset = createLayout(geneList);
170
171 int height = (maxOffset + 1) * (geneHeight + geneMargin);
172 if (height <= 0) {
173 height = geneHeight + geneMargin;
174 }
175
176
177 setPixelHeight(height);
178
179
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
250
251 drawGeneRect(gene.getStart(), gene.getEnd(), yPosition, getGeneColor(gene));
252
253
254 for (Exon e : exonList) {
255 draw(gene, e, cds, yPosition);
256 }
257
258
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 }