View Javadoc

1   /*--------------------------------------------------------------------------
2    *  Copyright 2009 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  // ReadTrack.java
20  // Since: May 16, 2010
21  //
22  // $URL$ 
23  // $Author$
24  //--------------------------------------
25  package org.utgenome.gwt.utgb.client.track.lib;
26  
27  import java.util.List;
28  
29  import org.utgenome.gwt.utgb.client.UTGBEntryPointBase;
30  import org.utgenome.gwt.utgb.client.bio.ChrLoc;
31  import org.utgenome.gwt.utgb.client.bio.GenomeDB;
32  import org.utgenome.gwt.utgb.client.bio.GenomeDB.DBType;
33  import org.utgenome.gwt.utgb.client.bio.GraphWindow;
34  import org.utgenome.gwt.utgb.client.bio.OnGenome;
35  import org.utgenome.gwt.utgb.client.bio.OnGenomeDataVisitorBase;
36  import org.utgenome.gwt.utgb.client.bio.ReadCoverage;
37  import org.utgenome.gwt.utgb.client.bio.ReadQueryConfig;
38  import org.utgenome.gwt.utgb.client.bio.ReadQueryConfig.Layout;
39  import org.utgenome.gwt.utgb.client.canvas.GWTGenomeCanvas;
40  import org.utgenome.gwt.utgb.client.canvas.LocusClickHandler;
41  import org.utgenome.gwt.utgb.client.canvas.ReadDisplayStyle;
42  import org.utgenome.gwt.utgb.client.db.ValueDomain;
43  import org.utgenome.gwt.utgb.client.db.datatype.StringType;
44  import org.utgenome.gwt.utgb.client.track.Track;
45  import org.utgenome.gwt.utgb.client.track.TrackBase;
46  import org.utgenome.gwt.utgb.client.track.TrackConfig;
47  import org.utgenome.gwt.utgb.client.track.TrackConfigChange;
48  import org.utgenome.gwt.utgb.client.track.TrackFrame;
49  import org.utgenome.gwt.utgb.client.track.TrackGroup;
50  import org.utgenome.gwt.utgb.client.track.TrackGroupPropertyChange;
51  import org.utgenome.gwt.utgb.client.track.TrackWindow;
52  import org.utgenome.gwt.utgb.client.track.UTGBProperty;
53  import org.utgenome.gwt.utgb.client.util.BrowserInfo;
54  import org.utgenome.gwt.utgb.client.util.CanonicalProperties;
55  import org.utgenome.gwt.utgb.client.util.Properties;
56  
57  import com.google.gwt.core.client.GWT;
58  import com.google.gwt.user.client.Window;
59  import com.google.gwt.user.client.rpc.AsyncCallback;
60  import com.google.gwt.user.client.ui.FlexTable;
61  import com.google.gwt.user.client.ui.Widget;
62  
63  /**
64   * Track for displaying read data in BED,SAM,BAM formats.
65   * 
66   * <h3>View Example</h3>
67   * 
68   * <pre>
69   * -track
70   *  -class: ReadTrack
71   *  -name: (track name)
72   *  -properties
73   *    -path: (database path. e.g, db/imported/hg19/myread.bed)
74   *    -onclick.url: http://www.google.com/search?q=%q
75   *    -onclick.action: (none|link|info|set)
76   *    -showLabels: (true|false)
77   *    -onclick.p.key: current.read
78   *    -onclick.p.value: %q
79   * </pre>
80   * 
81   * <p>
82   * You can customize the behavior of the browser when clicking a read using <i>onclick.action</i> parameter:
83   * </p>
84   * 
85   * <h3>On-Click Action Types</h3>
86   * <table>
87   * <tr>
88   * <th>Type</th>
89   * <th>Action</th>
90   * </tr>
91   * <tr>
92   * <td>none</td>
93   * <td>Disables click action</td>
94   * </tr>
95   * <tr>
96   * <td>link</td>
97   * <td>Opens an URL specified in <i>onclick.url</i> parameter.</td>
98   * </tr>
99   * <tr>
100  * <td>info</td>
101  * <td>Displays the detailed read information.</td>
102  * </tr>
103  * <tr>
104  * <td>set</td>
105  * <td>Sets a track group property (specified by <i>onclick.p.key</i>) using the value <i>onclick.p.value</i>.</td>
106  * </tr>
107  * </table>
108  * 
109  * <p>
110  * You can embed track or track group parameters in <i>onclick.url</i> and <i>target.value</i>:
111  * </p>
112  * 
113  * <h3>Parameters for On-Click event</h3>
114  * <table>
115  * <tr>
116  * <th>pattern</th>
117  * <th>to be replaced with</th>
118  * </tr>
119  * <tr>
120  * <td>%q</td>
121  * <td>Clicked read name</td>
122  * </tr>
123  * <tr>
124  * <td>%qstart</td>
125  * <td>Start position of the clicked read</td>
126  * </tr>
127  * <tr>
128  * <td>%qend</td>
129  * <td>End position of the clicked read</td>
130  * </tr>
131  * <tr>
132  * <td>%qlen</td>
133  * <td>Length of the clicked read</td>
134  * </tr>
135  * <tr>
136  * <td>%species</td>
137  * <td>Species name</td>
138  * </tr>
139  * <tr>
140  * <td>%ref</td>
141  * <td>Reference sequence name</td>
142  * </tr>
143  * <tr>
144  * <td>%chr</td>
145  * <td>Chromosome/contig/scaffold name</td>
146  * </tr>
147  * <tr>
148  * <td>%start</td>
149  * <td>Start position on the genome (inclusive)</td>
150  * </tr>
151  * <tr>
152  * <td>%end</td>
153  * <td>End position on the genome (exclusive)</td>
154  * </tr>
155  * <tr>
156  * <td>%len</td>
157  * <td>Sequence length currently displayed</td>
158  * </tr>
159  * <tr>
160  * <td>%pixelwidth</td>
161  * <td>Pixel width of the tracks</td>
162  * </tr>
163  * 
164  * 
165  * </table>
166  * 
167  * @author leo
168  * 
169  */
170 public class ReadTrack extends TrackBase {
171 
172 	// track configuration parameters
173 	private final String CONFIG_DB_TYPE = "dbType";
174 	private final String CONFIG_PATH = "path";
175 	private final String CONFIG_WIG_PATH = "wig path";
176 	private final String CONFIG_GRAPH_WINDOW = "window function";
177 	private final String CONFIG_LAYOUT = "layout";
178 	private final String CONFIG_ONCLICK_ACTION = "onclick.action";
179 	private final String CONFIG_ONCLICK_URL = "onclick.url";
180 	private final String CONFIG_ONCLICK_P_KEY = "onclick.set";
181 
182 	private ReadDisplayStyle style = new ReadDisplayStyle();
183 
184 	// widgets
185 	private FlexTable layoutTable = new FlexTable();
186 	private GWTGenomeCanvas geneCanvas = new GWTGenomeCanvas();
187 
188 	public static TrackFactory factory() {
189 		return new TrackFactory() {
190 			@Override
191 			public Track newInstance() {
192 				return new ReadTrack();
193 			}
194 		};
195 	}
196 
197 	public ReadTrack() {
198 		this("Read Track", "AUTO");
199 
200 	}
201 
202 	public ReadTrack(String trackName, String dbType) {
203 		super("Read Track");
204 
205 		getConfig().setParameter(CONFIG_DB_TYPE, dbType);
206 
207 		layoutTable.setBorderWidth(0);
208 		layoutTable.setCellPadding(0);
209 		layoutTable.setCellSpacing(0);
210 		layoutTable.setWidget(0, 1, geneCanvas);
211 
212 		updateClickAction();
213 	}
214 
215 	public String resolveURL(String urlTemplate, OnGenome locus) {
216 		String url = urlTemplate;
217 		if (url == null)
218 			return url;
219 
220 		if (locus != null) {
221 			if (locus.getName() != null) {
222 				if (url.contains("%q"))
223 					url = url.replaceAll("%q", locus.getName());
224 				if (url.contains("%qname"))
225 					url = url.replaceAll("%qname", locus.getName());
226 			}
227 
228 			if (url.contains("%qstart"))
229 				url = url.replaceAll("%qstart", Integer.toString(locus.getStart()));
230 			if (url.contains("%qend"))
231 				url = url.replaceAll("%qend", Integer.toString(locus.getEnd()));
232 			if (url.contains("%qlen"))
233 				url = url.replaceAll("%qlen", Integer.toString(locus.length()));
234 
235 		}
236 
237 		// replace track group properties
238 		return resolvePropertyValues(url);
239 	}
240 
241 	private void updateClickAction() {
242 
243 		String clickAction = getConfig().getParameter(CONFIG_ONCLICK_ACTION);
244 		if (clickAction == null)
245 			return;
246 		if ("none".equals(clickAction)) {
247 			geneCanvas.setLocusClickHandler(null);
248 		}
249 		else if ("link".equals(clickAction)) {
250 			geneCanvas.setLocusClickHandler(new LocusClickHandler() {
251 				public void onClick(int x, int y, OnGenome locus) {
252 					String url = getConfig().getParameter(CONFIG_ONCLICK_URL);
253 					url = resolveURL(url, locus);
254 					Window.open(url, "locus", "");
255 				}
256 			});
257 		}
258 		else if ("info".equals(clickAction)) {
259 			geneCanvas.setLocusClickHandler(new LocusClickHandler() {
260 				public void onClick(int x, int y, OnGenome locus) {
261 					geneCanvas.displayInfo(x, y, locus);
262 				}
263 			});
264 		}
265 		else if ("set".equals(clickAction)) {
266 			geneCanvas.setLocusClickHandler(new LocusClickHandler() {
267 				public void onClick(int clientX, int clientY, OnGenome locus) {
268 					String key = getConfig().getParameter(CONFIG_ONCLICK_P_KEY);
269 					if (key == null)
270 						return;
271 
272 					// parse name:%q,chr:%chr
273 					String[] actions = key.split(",");
274 					if (actions == null)
275 						return;
276 
277 					Properties prop = new Properties();
278 					for (String each : actions) {
279 						String[] keyAndValue = each.split(":");
280 						if (keyAndValue == null || keyAndValue.length != 2)
281 							continue;
282 
283 						String value = resolveURL(keyAndValue[1].trim(), locus);
284 						if (value == null)
285 							return;
286 						prop.put(keyAndValue[0].trim(), value);
287 					}
288 
289 					getTrackGroup().getPropertyWriter().setProperty(prop);
290 				}
291 			});
292 		}
293 
294 	}
295 
296 	public Widget getWidget() {
297 		return layoutTable;
298 	}
299 
300 	@Override
301 	public void setUp(TrackFrame trackFrame, TrackGroup group) {
302 
303 		geneCanvas.setTrackGroup(group);
304 
305 		update(group.getTrackWindow(), true);
306 		TrackConfig config = getConfig();
307 		config.addConfig("DB Path", new StringType(CONFIG_PATH), "");
308 		config.addConfig("WIG DB Path", new StringType(CONFIG_WIG_PATH), "");
309 		ValueDomain windowFuncitionTypes = ValueDomain.createNewValueDomain(new String[] { "MAX", "MIN", "MEDIAN", "AVG" });
310 		config.addConfig("Window Function", new StringType(CONFIG_GRAPH_WINDOW, windowFuncitionTypes), "MEDIAN");
311 		config.addHiddenConfig(CONFIG_DB_TYPE, "AUTO");
312 
313 		style.setup(config);
314 
315 		ValueDomain actionTypes = ValueDomain.createNewValueDomain(new String[] { "none", "link", "info", "set" });
316 		config.addConfig("On Click Action", new StringType(CONFIG_ONCLICK_ACTION, actionTypes), "none");
317 		config.addConfig("On Click URL", new StringType(CONFIG_ONCLICK_URL), "http://www.google.com/search?q=%q");
318 		config.addConfig("On Click Set (key:value, ...)", new StringType(CONFIG_ONCLICK_P_KEY), "read:%q");
319 
320 		updateClickAction();
321 	}
322 
323 	private boolean needUpdateForGraphicRefinement = false;
324 
325 	@Override
326 	public void beforeChangeTrackWindow(TrackWindow newWindow) {
327 
328 		if ("coverage".equals(style.layout) && current != null && !current.hasSameScaleWith(newWindow)) {
329 			needUpdateForGraphicRefinement = true;
330 		}
331 
332 	}
333 
334 	@Override
335 	public void draw() {
336 
337 		// set up drawing options
338 
339 		geneCanvas.setReadStyle(style);
340 
341 		geneCanvas.draw();
342 		getFrame().loadingDone();
343 
344 	}
345 
346 	public static int calcXPositionOnWindow(long indexOnGenome, long startIndexOnGenome, long endIndexOnGenome, int windowWidth) {
347 		double v = (indexOnGenome - startIndexOnGenome) * (double) windowWidth;
348 		double v2 = v / (endIndexOnGenome - startIndexOnGenome);
349 		return (int) v2;
350 	}
351 
352 	@Override
353 	public void onChangeTrackWindow(TrackWindow newWindow) {
354 
355 		update(newWindow, false);
356 	}
357 
358 	@Override
359 	public void onChangeTrackGroupProperty(TrackGroupPropertyChange change) {
360 
361 		if (change.containsOneOf(new String[] { UTGBProperty.SPECIES, UTGBProperty.REVISION, UTGBProperty.TARGET })) {
362 			geneCanvas.clear();
363 			update(change.getTrackWindow(), false);
364 		}
365 	}
366 
367 	private TrackWindow current;
368 
369 	protected void update(TrackWindow newWindow, boolean forceReload) {
370 
371 		current = newWindow;
372 
373 		if (!forceReload && geneCanvas.hasCacheCovering(newWindow)) {
374 			if (!needUpdateForGraphicRefinement) {
375 				geneCanvas.setTrackWindow(newWindow, false);
376 				refresh();
377 				return;
378 			}
379 		}
380 
381 		geneCanvas.setTrackWindow(newWindow, true);
382 
383 		// retrieve gene data from the API
384 		TrackWindow prefetchWindow = geneCanvas.getPrefetchWindow();
385 		String chr = getTrackGroupProperty(UTGBProperty.TARGET);
386 
387 		ReadQueryConfig queryConfig = new ReadQueryConfig(prefetchWindow.getPixelWidth(), BrowserInfo.isCanvasSupported(), Layout.valueOf(Layout.class,
388 				style.layout.toUpperCase()), style.numReadsMax, getWIGPath());
389 		queryConfig.window = GraphWindow.valueOf(GraphWindow.class, getConfig().getString(CONFIG_GRAPH_WINDOW, "MEDIAN"));
390 
391 		getFrame().setNowLoading();
392 		getBrowserService().getOnGenomeData(getGenomeDB(), new ChrLoc(chr, prefetchWindow.getStartOnGenome(), prefetchWindow.getEndOnGenome()), queryConfig,
393 				new AsyncCallback<List<OnGenome>>() {
394 
395 					public void onFailure(Throwable e) {
396 						GWT.log("failed to retrieve gene data", e);
397 						UTGBEntryPointBase.showErrorMessage("read data retrieval failed: " + e.getMessage());
398 						needUpdateForGraphicRefinement = true;
399 						getFrame().loadingDone();
400 					}
401 
402 					public void onSuccess(List<OnGenome> dataSet) {
403 
404 						if ("pileup".equals(style.layout) && dataSet.size() > 0 && DataChecker.isReadCoverage(dataSet.get(0))) {
405 							needUpdateForGraphicRefinement = true;
406 							// narrow down the prefetch range
407 							float prefetchFactor = geneCanvas.getPrefetchFactor();
408 							prefetchFactor /= 2.0;
409 							geneCanvas.setPrefetchFactor(prefetchFactor);
410 						}
411 						else {
412 							float newPrefetchFactor = geneCanvas.getPrefetchFactor() * 2.0f;
413 							if (newPrefetchFactor > 1.0f)
414 								newPrefetchFactor = 1.0f;
415 
416 							// broaden the prefetch range upon successful read data retrieval
417 							geneCanvas.setPrefetchFactor(newPrefetchFactor);
418 							needUpdateForGraphicRefinement = false;
419 						}
420 						geneCanvas.resetData(dataSet);
421 						refresh();
422 					}
423 
424 				});
425 
426 	}
427 
428 	private static class DataChecker extends OnGenomeDataVisitorBase {
429 		public boolean flag = false;
430 
431 		public static boolean isReadCoverage(OnGenome data) {
432 			DataChecker dataChecker = new DataChecker();
433 			data.accept(dataChecker);
434 			return dataChecker.flag;
435 		}
436 
437 		@Override
438 		public void visitReadCoverage(ReadCoverage readCoverage) {
439 			flag = true;
440 		}
441 	}
442 
443 	protected String getPath() {
444 		String path = getConfig().getParameter(CONFIG_PATH);
445 		return resolvePropertyValues(path);
446 	}
447 
448 	protected String getWIGPath() {
449 		String path = getConfig().getParameter(CONFIG_WIG_PATH);
450 		return resolvePropertyValues(path);
451 	}
452 
453 	/**
454 	 * Override this method to extend db info parameters
455 	 * 
456 	 * @return
457 	 */
458 	public GenomeDB getGenomeDB() {
459 		String ref = getTrackGroupProperty(UTGBProperty.REVISION);
460 		String dbType = getConfig().getString("dbType", "AUTO");
461 		return new GenomeDB(DBType.valueOf(DBType.class, dbType), getPath(), ref);
462 	}
463 
464 	@Override
465 	public void onChangeTrackConfig(TrackConfigChange change) {
466 
467 		style.loadConfig(getConfig());
468 
469 		if (change.containsOneOf(new String[] { CONFIG_ONCLICK_ACTION, CONFIG_ONCLICK_URL })) {
470 			updateClickAction();
471 		}
472 
473 		if (change.containsOneOf(new String[] { CONFIG_PATH, CONFIG_WIG_PATH, CONFIG_DB_TYPE })) {
474 			refresh();
475 		}
476 
477 		if (change.containsOneOf(new String[] { ReadDisplayStyle.CONFIG_SHOW_LABELS, ReadDisplayStyle.CONFIG_PE_OVERLAP,
478 				ReadDisplayStyle.CONFIG_SHOW_BASE_QUALITY, ReadDisplayStyle.CONFIG_READ_HEIGHT, ReadDisplayStyle.CONFIG_MIN_READ_HEIGHT,
479 				ReadDisplayStyle.CONFIG_COVERAGE_STYLE, ReadDisplayStyle.CONFIG_DRAW_SHADOW, ReadDisplayStyle.CONFIG_SHOW_STRAND })) {
480 			refresh();
481 		}
482 
483 		if (change.containsOneOf(new String[] { CONFIG_LAYOUT, CONFIG_GRAPH_WINDOW, ReadDisplayStyle.CONFIG_NUM_READ_MAX })) {
484 			update(getTrackWindow(), true);
485 		}
486 	}
487 
488 	@Override
489 	public void restoreProperties(CanonicalProperties properties) {
490 		super.restoreProperties(properties);
491 		updateClickAction();
492 		style.loadConfig(getConfig());
493 	}
494 
495 }