Java tutorial
/** * Copyright (c) 2010-2011 by Fred Hutchinson Cancer Research Center. All Rights Reserved. * Portions of code, copyright (c) 2013 by F. Hoffmann-La Roche Ltd and Tessella Inc. * This software is licensed under the terms of the GNU Lesser General * Public License (LGPL), Version 2.1 which is available at http://www.opensource.org/licenses/lgpl-2.1.php. * THE SOFTWARE IS PROVIDED "AS IS." FRED HUTCHINSON CANCER RESEARCH CENTER MAKES NO * REPRESENTATIONS OR WARRANTES OF ANY KIND CONCERNING THE SOFTWARE, EXPRESS OR IMPLIED, * INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, * WHETHER OR NOT DISCOVERABLE. IN NO EVENT SHALL FRED HUTCHINSON CANCER RESEARCH * CENTER OR ITS TRUSTEES, DIRECTORS, OFFICERS, EMPLOYEES, AND AFFILIATES BE LIABLE FOR * ANY DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, INCIDENTAL OR * CONSEQUENTIAL DAMAGES, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, * REGARDLESS OF WHETHER FRED HUTCHINSON CANCER RESEARCH CENTER SHALL BE ADVISED, * SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE * FOREGOING. * * THE SOFTWARE IS PROVIDED "AS IS." F.HOFFMANN-LA ROCHE AND TESSELLA INC MAKE NO * REPRESENTATIONS OR WARRANTES OF ANY KIND CONCERNING THE SOFTWARE, EXPRESS OR IMPLIED, * INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, * WHETHER OR NOT DISCOVERABLE. IN NO EVENT SHALL F.HOFFMANN-LA ROCHE AND TESSELLA INC * OR ITS TRUSTEES, DIRECTORS, OFFICERS, EMPLOYEES, AND AFFILIATES BE LIABLE FOR * ANY DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, INCIDENTAL OR * CONSEQUENTIAL DAMAGES, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, * REGARDLESS OF WHETHER F.HOFFMANN-LA ROCHE AND TESSELLA INC SHALL BE ADVISED, * SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE * FOREGOING. */ package org.broad.igv.renderer; //~--- non-JDK imports -------------------------------------------------------- import org.apache.commons.math.stat.Frequency; import org.apache.log4j.Logger; import org.broad.igv.PreferenceManager; import org.broad.igv.feature.IGVFeature; import org.broad.igv.feature.SpliceJunctionFeature; import org.broad.igv.feature.Strand; import org.broad.igv.track.FeatureTrack; import org.broad.igv.track.RenderContext; import org.broad.igv.track.Track; import org.broad.igv.ui.FontManager; import java.awt.*; import java.awt.geom.GeneralPath; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Renderer for splice junctions. Draws a filled-in arc for each junction, with the width of the * arc representing the depth of coverage. If coverage information is present for the flanking * regions, draws that, too; otherwise indicates flanking regions with rectangles * * @author dhmay */ public class SpliceJunctionRenderer extends IGVFeatureRenderer { private static Logger log = Logger.getLogger(SpliceJunctionRenderer.class); //color for drawing all arcs Color ARC_COLOR_NEG = new Color(50, 50, 150, 140); //transparent dull blue Color ARC_COLOR_POS = new Color(150, 50, 50, 140); //transparent dull red Color ARC_COLOR_HIGHLIGHT_NEG = new Color(90, 90, 255, 255); //opaque, brighter blue Color ARC_COLOR_HIGHLIGHT_POS = new Color(255, 90, 90, 255); //opaque, brighter red //central horizontal line color Color COLOR_CENTERLINE = new Color(0, 0, 0, 100); //maximum depth that can be displayed, due to track height limitations. Junctions with //this depth and deeper will all look the same protected int maxDepth = 50; /** * Note: assumption is that featureList is sorted by pStart position. * * @param featureList * @param context * @param trackRectangle * @param track */ @Override public void render(List<IGVFeature> featureList, RenderContext context, Rectangle trackRectangle, Track track) { double origin = context.getOrigin(); double locScale = context.getScale(); // TODO -- use enum instead of string "Color" if ((featureList != null) && !featureList.isEmpty()) { // Create a graphics object to draw font names. Graphics are not cached // by font, only by color, so its neccessary to create a new one to prevent // affecting other tracks. Font font = FontManager.getFont(track.getFontSize()); Graphics2D fontGraphics = (Graphics2D) context.getGraphic2DForColor(Color.BLACK).create(); if (PreferenceManager.getInstance().getAsBoolean(PreferenceManager.ENABLE_ANTIALISING)) { fontGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } fontGraphics.setFont(font); //determine whether to show flanking regions PreferenceManager prefs = PreferenceManager.getInstance(); boolean shouldShowFlankingRegions = prefs .getAsBoolean(PreferenceManager.SAM_SHOW_JUNCTION_FLANKINGREGIONS); // Track coordinates double trackRectangleX = trackRectangle.getX(); double trackRectangleMaxX = trackRectangle.getMaxX(); SpliceJunctionFeature selectedFeature = (SpliceJunctionFeature) ((FeatureTrack) track) .getSelectedFeature(); // Start of Roche-Tessella modification if (track.getAutoScale()) { Frequency f = new Frequency(); List<Integer> scores = new ArrayList<Integer>(); for (IGVFeature feature : featureList) { SpliceJunctionFeature junctionFeature = (SpliceJunctionFeature) feature; f.addValue(junctionFeature.getScore()); scores.add((int) junctionFeature.getScore()); } Collections.sort(scores); Collections.reverse(scores); for (int s : scores) { if (f.getCumPct(s) < 0.99) { maxDepth = s; break; } } } // End of Roche-Tessella modification for (IGVFeature feature : featureList) { SpliceJunctionFeature junctionFeature = (SpliceJunctionFeature) feature; //if same junction as selected feature, highlight boolean shouldHighlight = false; if (selectedFeature != null && selectedFeature.isSameJunction(junctionFeature)) { setHighlightFeature(junctionFeature); shouldHighlight = true; } // Get the pStart and pEnd of the entire feature. at extreme zoom levels the // virtual pixel value can be too large for an int, so the computation is // done in double precision and cast to an int only when its confirmed its // within the field of view. int flankingStart = junctionFeature.getStart(); int flankingEnd = junctionFeature.getEnd(); int junctionStart = junctionFeature.getJunctionStart(); int junctionEnd = junctionFeature.getJunctionEnd(); double virtualPixelStart = Math.round((flankingStart - origin) / locScale); double virtualPixelEnd = Math.round((flankingEnd - origin) / locScale); double virtualPixelJunctionStart = Math.round((junctionStart - origin) / locScale); double virtualPixelJunctionEnd = Math.round((junctionEnd - origin) / locScale); // If the any part of the feature fits in the // Track rectangle draw it if ((virtualPixelEnd >= trackRectangleX) && (virtualPixelStart <= trackRectangleMaxX)) { // int displayPixelEnd = (int) Math.min(trackRectangleMaxX, virtualPixelEnd); int displayPixelStart = (int) Math.max(trackRectangleX, virtualPixelStart); float depth = junctionFeature.getJunctionDepth(); Color color = feature.getColor(); drawFeature((int) virtualPixelStart, (int) virtualPixelEnd, (int) virtualPixelJunctionStart, (int) virtualPixelJunctionEnd, depth, trackRectangle, context, feature.getStrand(), junctionFeature, shouldHighlight, color, shouldShowFlankingRegions); } } //draw a central horizontal line Graphics2D g2D = context.getGraphic2DForColor(COLOR_CENTERLINE); g2D.drawLine((int) trackRectangleX, (int) trackRectangle.getCenterY(), (int) trackRectangleMaxX, (int) trackRectangle.getCenterY()); } } /** * Draw depth of coverage for the starting or ending flanking region * * @param g2D * @param pixelStart * @param pixelLength * @param regionDepthArray * @param maxPossibleArcHeight * @param trackRectangle * @param isPositiveStrand */ protected void drawFlankingRegion(Graphics g2D, int pixelStart, int pixelLength, int[] regionDepthArray, int maxPossibleArcHeight, Rectangle trackRectangle, boolean isPositiveStrand) { for (int i = 0; i < pixelLength; i++) { float arrayIndicesPerPixel = (float) regionDepthArray.length / (float) pixelLength; int flankingRegionArrayPixelMinIndex = (int) (i * arrayIndicesPerPixel); int flankingRegionArrayPixelMaxIndex = (int) ((i + 1) * arrayIndicesPerPixel); flankingRegionArrayPixelMinIndex = Math.max(0, Math.min(flankingRegionArrayPixelMinIndex, regionDepthArray.length - 1)); flankingRegionArrayPixelMaxIndex = Math.max(0, Math.min(flankingRegionArrayPixelMaxIndex, regionDepthArray.length - 1)); int meanDepthThisPixel = 0; for (int j = flankingRegionArrayPixelMinIndex; j <= flankingRegionArrayPixelMaxIndex; j++) meanDepthThisPixel += regionDepthArray[j]; meanDepthThisPixel /= (flankingRegionArrayPixelMaxIndex - flankingRegionArrayPixelMinIndex + 1); meanDepthThisPixel = Math.min(maxDepth, meanDepthThisPixel); int pixelHeight = Math.max(maxPossibleArcHeight * meanDepthThisPixel / maxDepth, 2); g2D.fillRect(pixelStart + i, (int) trackRectangle.getCenterY() + (isPositiveStrand ? -pixelHeight : 0), 1, pixelHeight); } } /** * Draw a filled arc representing a single feature. The thickness and height of the arc are proportional to the * depth of coverage. Some of this gets a bit arcane -- the result of lots of visual tweaking. * * @param pixelFeatureStart the starting position of the feature, whether on-screen or not * @param pixelFeatureEnd the ending position of the feature, whether on-screen or not * @param pixelJunctionStart the starting position of the junction, whether on-screen or not * @param pixelJunctionEnd the ending position of the junction, whether on-screen or not * @param depth coverage depth * @param trackRectangle * @param context * @param strand * @param junctionFeature * @param shouldHighlight * @param featureColor the color specified for this feature. May be null. */ protected void drawFeature(int pixelFeatureStart, int pixelFeatureEnd, int pixelJunctionStart, int pixelJunctionEnd, float depth, Rectangle trackRectangle, RenderContext context, Strand strand, SpliceJunctionFeature junctionFeature, boolean shouldHighlight, Color featureColor, boolean shouldShowFlankingRegions) { boolean isPositiveStrand = true; // Get the feature's direction, color appropriately if (strand != null && strand.equals(Strand.NEGATIVE)) isPositiveStrand = false; //If the feature color is specified, use it, except that we set our own alpha depending on whether //the feature is highlighted. Otherwise default based on strand and highlight. Color color; if (featureColor != null) { int r = featureColor.getRed(); int g = featureColor.getGreen(); int b = featureColor.getBlue(); int alpha = shouldHighlight ? 255 : 140; color = new Color(r, g, b, alpha); } else { if (isPositiveStrand) color = shouldHighlight ? ARC_COLOR_HIGHLIGHT_POS : ARC_COLOR_POS; else color = shouldHighlight ? ARC_COLOR_HIGHLIGHT_NEG : ARC_COLOR_NEG; } Graphics2D g2D = context.getGraphic2DForColor(color); if (PreferenceManager.getInstance().getAsBoolean(PreferenceManager.ENABLE_ANTIALISING)) { g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } //Height of top of an arc of maximum depth int maxPossibleArcHeight = (trackRectangle.height - 1) / 2; if (shouldShowFlankingRegions) { if (junctionFeature.hasFlankingRegionDepthArrays()) { //draw a wigglegram of the splice junction flanking region depth of coverage int startFlankingRegionPixelLength = pixelJunctionStart - pixelFeatureStart; int endFlankingRegionPixelLength = pixelFeatureEnd - pixelJunctionEnd; drawFlankingRegion(g2D, pixelFeatureStart, startFlankingRegionPixelLength, junctionFeature.getStartFlankingRegionDepthArray(), maxPossibleArcHeight, trackRectangle, isPositiveStrand); drawFlankingRegion(g2D, pixelJunctionEnd + 1, endFlankingRegionPixelLength, junctionFeature.getEndFlankingRegionDepthArray(), maxPossibleArcHeight, trackRectangle, isPositiveStrand); } else { //Draw rectangles indicating the overlap on each side of the junction int overlapRectHeight = 3; int overlapRectTopX = (int) trackRectangle.getCenterY() + (isPositiveStrand ? -2 : 0); if (pixelFeatureStart < pixelJunctionStart) { g2D.fillRect(pixelFeatureStart, overlapRectTopX, pixelJunctionStart - pixelFeatureStart, overlapRectHeight); } if (pixelJunctionEnd < pixelFeatureEnd) { g2D.fillRect(pixelJunctionEnd, overlapRectTopX, pixelFeatureEnd - pixelJunctionEnd, overlapRectHeight); } } } //Create a path describing the arc, using Bezier curves. The Bezier control points for the top and //bottom arcs are based on the boundary points of the rectangles containing the arcs //proportion of the maximum arc height used by a minimum-height arc double minArcHeightProportion = 0.33; int innerArcHeight = (int) (maxPossibleArcHeight * minArcHeightProportion); float depthProportionOfMax = Math.min(1, depth / maxDepth); int arcWidth = Math.max(1, (int) ((1 - minArcHeightProportion) * maxPossibleArcHeight * depthProportionOfMax)); int outerArcHeight = innerArcHeight + arcWidth; //Height of bottom of the arc int arcBeginY = (int) trackRectangle.getCenterY() + (isPositiveStrand ? -1 : 1); int outerArcPeakY = isPositiveStrand ? arcBeginY - outerArcHeight : arcBeginY + outerArcHeight; int innerArcPeakY = isPositiveStrand ? arcBeginY - innerArcHeight : arcBeginY + innerArcHeight; //dhmay: I don't really understand Bezier curves. For some reason I have to put the Bezier control //points farther up or down than I want the arcs to extend. This multiplier seems about right int outerBezierY = arcBeginY + (int) (1.3 * (outerArcPeakY - arcBeginY)); int innerBezierY = arcBeginY + (int) (1.3 * (innerArcPeakY - arcBeginY)); //Putting the Bezier control points slightly off to the sides of the arc int bezierXPad = Math.max(1, (pixelJunctionEnd - pixelJunctionStart) / 30); GeneralPath arcPath = new GeneralPath(); arcPath.moveTo(pixelJunctionStart, arcBeginY); arcPath.curveTo(pixelJunctionStart - bezierXPad, outerBezierY, //Bezier 1 pixelJunctionEnd + bezierXPad, outerBezierY, //Bezier 2 pixelJunctionEnd, arcBeginY); //Arc end arcPath.curveTo(pixelJunctionEnd + bezierXPad, innerBezierY, //Bezier 1 pixelJunctionStart - bezierXPad, innerBezierY, //Bezier 2 pixelJunctionStart, arcBeginY); //Arc end //Draw the arc, to ensure outline is drawn completely (fill won't do it, necessarily). This will also //give the arc a darker outline g2D.draw(arcPath); //Fill the arc g2D.fill(arcPath); g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT); g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); } }