/*******************************************************************************
* GenPlay, Einstein Genome Analyzer
* Copyright (C) 2009, 2014 Albert Einstein College of Medicine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* Authors: Julien Lajugie <julien.lajugie@einstein.yu.edu>
* Nicolas Fourel <nicolas.fourel@einstein.yu.edu>
* Eric Bouhassira <eric.bouhassira@einstein.yu.edu>
*
* Website: <http://genplay.einstein.yu.edu>
******************************************************************************/
package edu.yu.einstein.genplay.gui.track.layer.variantLayer;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.List;
import edu.yu.einstein.genplay.core.manager.project.ProjectManager;
import edu.yu.einstein.genplay.core.manager.project.ProjectWindow;
import edu.yu.einstein.genplay.core.multiGenome.data.display.VariantDisplayList;
import edu.yu.einstein.genplay.core.multiGenome.data.display.variant.MixVariant;
import edu.yu.einstein.genplay.core.multiGenome.data.display.variant.ReferenceVariant;
import edu.yu.einstein.genplay.core.multiGenome.data.display.variant.Variant;
import edu.yu.einstein.genplay.core.multiGenome.data.display.variant.VariantDisplay;
import edu.yu.einstein.genplay.dataStructure.enums.AlleleType;
import edu.yu.einstein.genplay.dataStructure.enums.VariantType;
import edu.yu.einstein.genplay.dataStructure.genomeWindow.GenomeWindow;
import edu.yu.einstein.genplay.gui.MGDisplaySettings.MGDisplaySettings;
import edu.yu.einstein.genplay.gui.MGDisplaySettings.VariantLayerDisplaySettings;
import edu.yu.einstein.genplay.util.colors.Colors;
/**
* @author Nicolas Fourel
* @version 0.1
*/
class MultiGenomeVariantDrawer implements Serializable {
/** Generated serial version ID */
private static final long serialVersionUID = -6140085563221274861L;
private static final int SAVED_FORMAT_VERSION_NUMBER = 0; // saved format version
private ProjectWindow projectWindow; // instance of the genome window manager
private MultiGenomeDrawer drawer;
private AlleleType currentDrawingAllele;
private int variantOpacity; // Transparency of the stripes
/**
* Constructor of {@link MultiGenomeVariantDrawer}
* @param drawer
*/
protected MultiGenomeVariantDrawer (MultiGenomeDrawer drawer) {
projectWindow = ProjectManager.getInstance().getProjectWindow();
setDrawer(drawer);
variantOpacity = 0;
}
/**
* Draws the edge of a deletion stripe.
* @param g graphics object
* @param x x coordinate
* @param y y coordinate
* @param width width of the stripe
* @param height height of the stripe
*/
private void drawDeletion (Graphics g, int x, int y, int width, int height) {
if (MGDisplaySettings.DRAW_DELETION_EDGE == MGDisplaySettings.YES_MG_OPTION) { // checks if the option is activated
float dash1[] = {5.0f}; // length of the lines
BasicStroke line = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, dash1, 0.0f); // creates a stroke line
Graphics2D g2d = (Graphics2D) g.create(); // create a temporary graphic
g2d.setStroke(line); // give the stroke line to the graphic
g2d.setColor(Colors.BLACK); // color of the edge (black)
g2d.drawRect(x, y, width - 1, height); // draw the edge all around the stripe
}
}
/**
* Draws the list of variation for a genome
* @param g Graphics object
* @param width width of the graphics
* @param height height of the graphics
* @param genomeWindow genome window currently displayed
* @param variantList list of variants
*/
protected void drawGenome (Graphics g, int width, int height, GenomeWindow genomeWindow, List<VariantDisplay> variants) {
if ((variants != null) && (variants.size() > 0)) {
Color mixColor = new Color(Colors.BLUE.getRed(), Colors.BLUE.getGreen(), Colors.BLUE.getBlue()); // color for mixed variant
for (int i = 0; i < variants.size(); i++) {
VariantDisplay variant = variants.get(i);
VariantType type = variant.getVariant().getType(); // gets its type
Color color;
if (type == VariantType.REFERENCE_INSERTION) {
color = MGDisplaySettings.REFERENCE_INSERTION_COLOR;
} else if (type == VariantType.REFERENCE_DELETION) {
color = MGDisplaySettings.REFERENCE_DELETION_COLOR;
} else if (type == VariantType.REFERENCE_SNP) {
color = MGDisplaySettings.REFERENCE_SNP_COLOR;
} else if (type == VariantType.NO_CALL) {
color = MGDisplaySettings.NO_CALL_COLOR;
} else if (type == VariantType.MIX) {
color = mixColor;
} else {
color = getVariantColor(variant.getGenomeName(), type); // in order to get which color has been defined
}
drawVariant(g, height, variant, color, genomeWindow); // draw the variant
}
}
}
/**
* Draws the edge of an insertion stripe.
* @param g graphics object
* @param x x coordinate
* @param y y coordinate
* @param width width of the stripe
* @param height height of the stripe
*/
private void drawInsertion (Graphics g, int x, int y, int width, int height) {
if (MGDisplaySettings.DRAW_INSERTION_EDGE == MGDisplaySettings.YES_MG_OPTION) { // checks if the option is activated
Graphics gTmp = g.create(); // creates a temporary graphics
gTmp.setColor(Colors.BLACK); // color of the edge (black)
gTmp.drawRect(x, y, width, height); // the edge here is a simple line all around the stripe
}
}
/**
* Draws the letters (nucleotides) over the stripe.
* @param g graphics object
* @param graphicsHeigth height of the graphics object
* @param x x coordinate
* @param width width of the stripe
* @param height height of the stripe
* @param variantDisplay variant
* @param nucleotideNumber number of nucleotide to display
*/
private void drawLetters (Graphics g, int graphicsHeigth, int x, int width, int height, VariantDisplay variantDisplay, int nucleotideNumber) {
Variant variant = variantDisplay.getVariant();
VariantType variantType = variant.getType(); // gets the variant type
if ( ((variantType == VariantType.INSERTION) && (MGDisplaySettings.DRAW_INSERTION_LETTERS == MGDisplaySettings.YES_MG_OPTION)) || // checks all options in order to determine if the letters must be drawn
((variantType == VariantType.DELETION) && (MGDisplaySettings.DRAW_DELETION_LETTERS == MGDisplaySettings.YES_MG_OPTION)) ||
((variantType == VariantType.SNPS) && (MGDisplaySettings.DRAW_SNP_LETTERS == MGDisplaySettings.YES_MG_OPTION)) ||
((variant instanceof ReferenceVariant) && (MGDisplaySettings.DRAW_REFERENCE_LETTERS == MGDisplaySettings.YES_MG_OPTION))) {
// if the letters must be drawn
double windowWidth = width / nucleotideNumber; // calculate the size of window (here, the window is the width of a nucleotide on the screen)
FontMetrics fm = g.getFontMetrics(); // get the font metrics
if ((fm.getHeight() < height) && (fm.stringWidth("A") < windowWidth)) { // verifies if the height of the font is smaller than the height of the stripe AND if the width of a reference letter (A) is smaller than a window size
String letters = variantDisplay.getVariantSequence();
g.setColor(Colors.BLACK); // set the color of the letters
int letterY = (int) (graphicsHeigth - (height / 2d)); // define where the draw will start on the Y axis
Graphics2D g2d = (Graphics2D) g.create(); // we reverse all coordinates to display the letter on the right way
if (currentDrawingAllele == AlleleType.ALLELE02) {
g2d.scale(1, -1);
letterY *= -1;
//letterY -= fm.getHeight() / 2d; // commented because it was buggy on windows
} else {
//letterY += fm.getHeight() / 2d; // commented because it was buggy on windows
}
int firstNucleotide = projectWindow.screenToGenomePosition(x) - variant.getStart(); // retrieve the position of the first displayed nucleotide in the variant
firstNucleotide = Math.max(0, firstNucleotide);
for (int i = 0; i < nucleotideNumber; i++) { // for all the nucleotide that are supposed to be displayed
String letter = "-"; // the default letter is the question mark
if ((letters != "-") && ((i + firstNucleotide) < letters.length())) { // if the letters are different to the question mark and if the current index is smaller than the string length
letter = letters.charAt(i + firstNucleotide) + ""; // we get the current character
}
//int xC = (int) Math.round(x + (i * windowWidth) + ((windowWidth - fm.stringWidth(letter)) * 0.5)); // the horizontal position from where the draw starts: x (of the stripe) + size of a window * current window number + (windows width - letter width) / 2 (for the middle)
int xC = projectWindow.genomeToScreenPosition(variant.getStart() + i + firstNucleotide);
g2d.drawString(letter, xC, letterY); // we draw the letter
}
}
}
}
/**
* Draws the line on the middle of a multi genome track
* @param g graphics object
*/
protected void drawMultiGenomeLine (Graphics g, int width, int height) {
Color color = new Color(Colors.GREY.getRed(), Colors.GREY.getGreen(), Colors.GREY.getBlue(), variantOpacity);
g.setColor(color);
int y = height / 2;
g.drawLine(0, y, width, y);
}
/**
* Draw a light gray mask over the track with a text
* @param g graphics object
* @param height height of the graphics
* @param text a text to display
*/
protected void drawMultiGenomeMask (Graphics g, int height, String text) {
Font oldFont = g.getFont();
Font newFont = g.getFont().deriveFont(Font.BOLD, 14);
g. setFont(newFont);
g.setColor(Colors.RED);
g.drawString(text, 10, height -5);
g. setFont(oldFont);
}
/**
* Draw the filter pattern
* @param g graphics object
* @param x x coordinate
* @param width width of the stripe
* @param height height of the stripe
*/
private void drawFilterPattern (Graphics g, int x, int y, int width, int height) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Colors.GREY);
g.drawLine(x, y, x + width, y + height);
g.drawLine(x, y + height, x + width, y);
}
/**
* Draw a blank of synchronization.
* A blank of synchronization is drawn on the full height of the clip.
* @param g graphics object
* @param x x coordinate
* @param width width of the stripe
* @param height height of the graphics
*/
private void drawReference (Graphics g, int x, int width, int height) {
g.fillRect(x, 0, width, height);
}
/**
* Draws a rectangle symbolizing a variant
* @param g graphics object
* @param height height of the graphics
* @param variantDisplay the variant
* @param color the color of the stripe
* @param genomeWindow the displayed genome window
*/
private void drawVariant (Graphics g, int height, VariantDisplay variantDisplay, Color color, GenomeWindow genomeWindow) {
Variant variant = variantDisplay.getVariant();
// Get start and stop position
int start = variant.getStart();
int stop = variant.getStop();
// Fits the start and stop position to the screen
if ((start < genomeWindow.getStart()) && (stop > genomeWindow.getStart())) { // if the variant starts before the left edge of the track but stop after
start = genomeWindow.getStart(); // the drawing must start from the left edge of the track
}
if ((start < genomeWindow.getStop()) && (stop > genomeWindow.getStop())) { // if the variant start before the right edge of the track and ends up further,
stop = genomeWindow.getStop(); // the drawing must stop at the right edge of the track
}
// Transform the start and stop position to screen coordinates
int x = projectWindow.genomeToScreenPosition(start); // get the position where the variant starts
int width = projectWindow.genomeToScreenWidth(stop - start); // get the width of the variant on the screen
if (width == 0) { // if the width is 0 pixel,
width = 1; // we set it to 1 in order to be seen
}
// Get the height of the clip and of the stripe
int variantHeight = getVariantHeight(variant, height); // Instantiate the int for the height of the variant
// Sets the stripe color
Color newColor;
if ((drawer.getVariantUnderMouse() != null) && drawer.getVariantUnderMouse().equals(variant)) { // if there is a variant under the mouse
newColor = Colors.stripeFilter(color); // we change the color of the variant
} else { // if not
newColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), variantOpacity); // we use the defined color taking into account the opacity
}
g.setColor(newColor); // we set the graphic object color
// if it is not a blank of synchronization
int y = height - variantHeight; // y represents the top left corner of the stripes, the axis goes down to the bottom
// Draws the variant
int nucleotideNumber;
if (variant instanceof ReferenceVariant) { // drawing a reference stripe requires a different method (shorter and more simple)
if (color != null) { // if color is null, it means we don't want to draw the reference
drawReference(g, x, width, height);
}
} else {
g.fillRect(x, y, width, variantHeight); // draw the stripe
// Draws the edge line of stripes
if (variant.getType() == VariantType.INSERTION) { // the edge of an insertion and a deletion are different
drawInsertion(g, x, y, width, variantHeight);
} else if (variant.getType() == VariantType.DELETION) {
drawDeletion(g, x, y, width, variantHeight);
}
}
if (variantDisplay.getDisplay() == VariantDisplayList.SHOW_FILTER) {
drawFilterPattern(g, x, y, width, variantHeight);
}
nucleotideNumber = stop - start;
if (nucleotideNumber == 0) {
nucleotideNumber = 1;
}
// Draw the variant letters
if (variant.getType() != VariantType.MIX) {
drawLetters(g, height, x, width, variantHeight, variantDisplay, nucleotideNumber); // draw the letters (nucleotides) over the stripe
}
}
/**
* Gets the color defined for a variant according to its type and a genome
* @param genome the genome name
* @param type the variant type
* @return the associated color
*/
private Color getVariantColor (String genome, VariantType type) {
Color color = null;
for (VariantLayerDisplaySettings data: drawer.getVariantDataList()) {
if (data.getGenome().equals(genome)) {
int variantIndex = data.getVariationTypeList().indexOf(type);
if (variantIndex != -1) {
color = data.getColorList().get(variantIndex);
break;
}
}
}
return color;
}
protected int getVariantHeight (Variant variant, int clipHeight) {
int height; // Instantiate the int for the height of the variant
if (variant instanceof ReferenceVariant) { // drawing a reference stripe requires a different method (shorter and more simple)
height = clipHeight;
} else {
int score; // get the score of the variant
if (variant instanceof MixVariant) {
score = 101;
} else {
score = (int) variant.getScore(); // get the score of the variant
}
if (score > 100) { // if the score is higher than 100,
height = clipHeight; // the variant height is the height of the clip
} else { // if it is less than 100
height = (score * clipHeight) / 100; // the variant height is the percentage between its score and the height clip
}
}
return height;
}
/**
* Initializes the variant opacity
*/
protected void initializeStripesOpacity () {
variantOpacity = MGDisplaySettings.getInstance().getVariousSettings().getColorOpacity(); // gets the opacity for the stripes
}
/**
* Method used for unserialization
* @param in
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.readInt();
currentDrawingAllele = (AlleleType) in.readObject();
variantOpacity = in.readInt();
projectWindow = ProjectManager.getInstance().getProjectWindow();
}
/**
* @param allele set the current allele
*/
protected void setCurrentAllele (AlleleType allele) {
currentDrawingAllele = allele;
}
/**
* @param drawer the drawer to set
*/
protected void setDrawer(MultiGenomeDrawer drawer) {
this.drawer = drawer;
}
/**
* Method used for serialization
* @param out
* @throws IOException
*/
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.writeInt(SAVED_FORMAT_VERSION_NUMBER);
out.writeObject(currentDrawingAllele);
out.writeInt(variantOpacity);
}
}