/******************************************************************************* * 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.Graphics; import java.awt.Graphics2D; import java.awt.event.MouseEvent; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import edu.yu.einstein.genplay.core.comparator.ListComparator; import edu.yu.einstein.genplay.core.manager.project.ProjectManager; import edu.yu.einstein.genplay.core.multiGenome.VCF.VCFStatistics.VCFFileStatistics; import edu.yu.einstein.genplay.core.multiGenome.data.display.VariantDisplayList; import edu.yu.einstein.genplay.core.multiGenome.data.display.VariantDisplayMultiListScanner; import edu.yu.einstein.genplay.core.multiGenome.data.display.variant.Variant; import edu.yu.einstein.genplay.core.multiGenome.filter.MGFilter; import edu.yu.einstein.genplay.dataStructure.chromosome.Chromosome; import edu.yu.einstein.genplay.dataStructure.enums.AlleleType; import edu.yu.einstein.genplay.dataStructure.genomeWindow.GenomeWindow; import edu.yu.einstein.genplay.gui.MGDisplaySettings.FiltersData; import edu.yu.einstein.genplay.gui.MGDisplaySettings.MGDisplaySettings; import edu.yu.einstein.genplay.gui.MGDisplaySettings.VariantLayerDisplaySettings; import edu.yu.einstein.genplay.gui.dialog.multiGenomeDialog.variantInformation.VariantInformationDialog; /** * @author Nicolas Fourel */ public class MultiGenomeDrawer implements Serializable { /** Generated serial version ID */ private static final long serialVersionUID = -6660557171751127841L; private static final int SAVED_FORMAT_VERSION_NUMBER = 0; // saved format version private MultiGenomeVariantDrawer variantDrawer; private MultiGenomeListHandler handler; private List<VariantInformationDialog> variantDialogs; private Chromosome chromosome; private VCFFileStatistics statistics; private List<VariantLayerDisplaySettings> variantDataList; private List<MGFilter> filtersList; private List<VariantDisplayList> variantDisplayList; private boolean showReference; private boolean showFilter; private boolean forceFitToScreen; private boolean locked; private Variant variantUnderMouse; /** * Constructor of {@link MultiGenomeDrawer} */ public MultiGenomeDrawer () { variantDataList = new ArrayList<VariantLayerDisplaySettings>(); variantDisplayList = new ArrayList<VariantDisplayList>(); forceFitToScreen = false; locked = false; statistics = null; variantDrawer = new MultiGenomeVariantDrawer(this); handler = new MultiGenomeListHandler(); variantDialogs = new ArrayList<VariantInformationDialog>(); variantUnderMouse = null; chromosome = getCurrentChromosome(); MGDisplaySettings displaySettings = MGDisplaySettings.getInstance(); showFilter = displaySettings.includeFilteredVariants(); showReference = displaySettings.includeReferences(); } /** * Draws stripes showing information for multi genome. The method checks if the track must show both allele or only one, in order to split the track or not. * @param g graphics object * @param width * @param height */ public void drawMultiGenomeInformation(Graphics g, int width, int height) { if ((variantDataList != null) && (variantDataList.size() > 0)) { variantDrawer.initializeStripesOpacity(); if (!locked) { // if there are stripes GenomeWindow genomeWindow = ProjectManager.getInstance().getProjectWindow().getGenomeWindow(); double xRatio = ProjectManager.getInstance().getProjectWindow().getXRatio(); int halfHeight = height / 2; // calculates the half of the height track Graphics allele01Graphic = g.create(0, 0, width, halfHeight); // create a graphics for the first allele that correspond to the upper half of the track Graphics2D allele02Graphic = (Graphics2D) g.create(0, halfHeight, width, halfHeight); // create a 2D graphics for the second allele that correspond to the lower // half of the track allele02Graphic.scale(1, -1); // all Y axis (vertical) coordinates must be reversed for the second allele allele02Graphic.translate(0, -halfHeight); // translates all coordinates of the graphic for the second allele if (forceFitToScreen) { handler.forceFitToScreen(xRatio); forceFitToScreen = false; } variantDrawer.setCurrentAllele(AlleleType.ALLELE01); variantDrawer.drawGenome(allele01Graphic, width, halfHeight, genomeWindow, handler.getFittedData(genomeWindow, xRatio, 0)); // draw the stripes for the first allele variantDrawer.setCurrentAllele(AlleleType.ALLELE02); variantDrawer.drawGenome(allele02Graphic, width, halfHeight, genomeWindow, handler.getFittedData(genomeWindow, xRatio, 1)); // draw the stripes for the second allele variantDrawer.drawMultiGenomeLine(g, width, height); // draw a line in the middle of the track to distinguish upper and lower half. } else { variantDrawer.drawMultiGenomeMask(g, height, "Multi genome display interrupted while loading information"); } } } /** * @return the current chromosome */ private Chromosome getCurrentChromosome() { return ProjectManager.getInstance().getProjectWindow().getGenomeWindow().getChromosome(); } /** * @return the list of {@link MGFilter} */ public List<MGFilter> getFiltersList() { return filtersList; } /** * @return the {@link VCFFileStatistics} of the track */ public VCFFileStatistics getStatistics() { return statistics; } /** * @return the list of {@link VariantLayerDisplaySettings} */ public List<VariantLayerDisplaySettings> getVariantDataList() { return variantDataList; } /** * This method does not process anything, only return the {@link Variant} that has been found earlier! * @return the {@link Variant} under the mouse. */ public Variant getVariantUnderMouse () { return variantUnderMouse; } /** * Scans lists and processes data in order to retrieve the {@link Variant} that is under the pointer of the mouse. * @param height height of the track * @param x x position of the pointer * @param y y position of the pointer * @return the {@link Variant} under the mouse, null if there is no {@link Variant}. */ private Variant getVariantUnderMouse (int trackHeight, int x, int y) { // Get the allele index int alleleIndex = 0; if (y > (trackHeight / 2)) { alleleIndex = 1; } // Get the meta genome position int pos = ProjectManager.getInstance().getProjectWindow().screenToGenomePosition(x); // we translate the position on the screen into a position on the genome // Get the right list of variants in the area of the actual position List<Variant> results = new ArrayList<Variant>(); for (VariantDisplayList list: variantDisplayList) { List<Variant> result = list.getVariantsInArea(alleleIndex, pos); if ((result != null) && !result.isEmpty()) { results.addAll(result); } } // If at least one variant has been found if (results.size() > 0) { // Sort the list by score Collections.sort(results, new Comparator<Variant>() { @Override public int compare(Variant o1, Variant o2) { if (o1.getScore() < o2.getScore()) { return -1; } else if (o1.getScore() > o2.getScore()) { return 1; } return 0; } }); int clipHeight = trackHeight / 2; int index = 0; int size = results.size(); boolean found = false; while (!found && (index < size)) { Variant variant = results.get(index); int height = variantDrawer.getVariantHeight(variant, clipHeight); int yVariant = clipHeight - height; if (alleleIndex == 0) { if (y >= yVariant) { found = true; } } else { y -= clipHeight; if (y <= height) { found = true; } } if (!found) { index++; } } if (found) { return results.get(index); } else { return results.get(0); } } return null; } /** * Compare the current chromosome and the chromosome used for the stripes/filter * @return true if the chromosome has changed, false otherwise */ private boolean hasChromosomeChanged() { if (chromosome == null) { return true; } return !chromosome.equals(getCurrentChromosome()); } /** * Compare given multi genome information with the current ones. * @param stripesList the new stripes list * @param filtersList the new filters list * @return true if new information are different than the current ones */ public boolean hasMultiGenomeInformationChanged(List<VariantLayerDisplaySettings> stripesList, List<MGFilter> filtersList) { if (haveVariantsChanged(stripesList) || haveFiltersChanged(filtersList)) { return true; } else { return false; } } /** * Checks if the track has to be repaint when the mouse exit from it. Basically, it has to be repaint if a variant was under the mouse in order to not highlight it. * @return true if the track has to be repaint, false otherwise */ public boolean hasToBeRepaintAfterExit() { if (variantUnderMouse != null) { variantUnderMouse = null; return true; } return false; } private boolean hasToReset (List<VariantLayerDisplaySettings> variantDataList) { if ((this.variantDataList.size() > 0) && (variantDataList.size() == 0)) { return true; } return false; } /** * Compare given filters information with the current one. * @param filtersList the new filters list * @return true if new information are different than the current ones */ private boolean haveFiltersChanged(List<MGFilter> filtersList) { ListComparator<MGFilter> filtersComparator = new ListComparator<MGFilter>(); return filtersComparator.areDifferent(this.filtersList, filtersList); } /** * Check if display major options have changed * @return true if display major options have changed, false otherwise */ private boolean haveOptionsChanged() { boolean showReference = MGDisplaySettings.getInstance().includeReferences(); boolean showFiltered = MGDisplaySettings.DRAW_FILTERED_VARIANT == MGDisplaySettings.YES_MG_OPTION; if ((this.showReference != showReference) || (showFilter != showFiltered)) { this.showReference = showReference; showFilter = showFiltered; return true; } return false; } /** * Compare given variant information with the current one. * @param variantDataList the new stripes list * @return true if new information are different than the current ones */ private boolean haveVariantsChanged(List<VariantLayerDisplaySettings> variantDataList) { ListComparator<VariantLayerDisplaySettings> stripesComparator = new ListComparator<VariantLayerDisplaySettings>(); return stripesComparator.areDifferent(this.variantDataList, variantDataList); } /** * Look if the mouse is on top of a {@link Variant}, set the related attribute and return the result. * @param height height of the track * @param e the {@link MouseEvent} * @return true if the mouse is on top of a {@link Variant}, false otherwise */ public boolean isOverVariant(int height, MouseEvent e) { if (ProjectManager.getInstance().isMultiGenomeProject() && !locked) { // if we are in multi genome project Variant variant = getVariantUnderMouse(height, e.getX(), e.getY()); // we get the variant (Y is needed to know if the variant is on the upper or lower half of the track) if (variant != null) { // if a variant has been found variantUnderMouse = variant; // the mouse is on this variant (we save it) return true; // we return true } else if (variantUnderMouse != null) { // no variant has been found but one was already defined (the mouse is just getting out of the stripe) variantUnderMouse = null; // there is no variant under the mouse anymore return true; } } return false; } /** * Disposes all {@link VariantInformationDialog} related to this track */ private void killStripesDialogs() { for (VariantInformationDialog dialog : variantDialogs) { dialog.dispose(); } variantDialogs = new ArrayList<VariantInformationDialog>(); } /** * Locks the painting in all methods (used when information is changing) */ public void lockPainting() { locked = true; } /** * Method used for unserialization * @param in * @throws IOException * @throws ClassNotFoundException */ @SuppressWarnings("unchecked") private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.readInt(); variantDrawer = (MultiGenomeVariantDrawer) in.readObject(); handler = (MultiGenomeListHandler) in.readObject(); variantDialogs = new ArrayList<VariantInformationDialog>(); chromosome = (Chromosome) in.readObject(); statistics = (VCFFileStatistics) in.readObject(); variantDataList = (List<VariantLayerDisplaySettings>) in.readObject(); filtersList = (List<MGFilter>) in.readObject(); variantDisplayList = (List<VariantDisplayList>) in.readObject(); showReference = in.readBoolean(); showFilter = in.readBoolean(); forceFitToScreen = in.readBoolean(); locked = in.readBoolean(); variantUnderMouse = (Variant) in.readObject(); variantDrawer.setDrawer(this); } /** * Sets the {@link MGFilter} list * @param filterList the new {@link MGFilter} list to use */ public void setFiltersList(List<MGFilter> filterList) { filtersList = filterList; } /** * Sets the {@link VCFFileStatistics} to use * @param statistics the {@link VCFFileStatistics} to use */ public void setStatistics(VCFFileStatistics statistics) { this.statistics = statistics; } /** * Sets the {@link VariantLayerDisplaySettings} list * @param variantDataList the new {@link VariantLayerDisplaySettings} list to use */ public void setVariantDataList(List<VariantLayerDisplaySettings> variantDataList) { this.variantDataList = variantDataList; } /** * Shows the {@link VariantInformationDialog} is the mouse is on top of a {@link Variant} * @param height height of the track * @param e the {@link MouseEvent} */ public void showVariantInformationDialog(int height, MouseEvent e) { if (ProjectManager.getInstance().isMultiGenomeProject()) { // we must be in a multi genome project if (variantUnderMouse != null) { VariantInformationDialog toolTip = new VariantInformationDialog(this); // we create the information dialog variantDialogs.add(toolTip); int pos = Math.round(ProjectManager.getInstance().getProjectWindow().screenToGenomePosition(e.getX())) - 1; // we translate the position on the screen into a position on the genome VariantDisplayMultiListScanner iterator = new VariantDisplayMultiListScanner(variantDisplayList); iterator.initializeDiploide(); iterator.setPosition(pos); iterator.setDisplayDependancy(true); toolTip.show(iterator, e.getXOnScreen(), e.getYOnScreen()); // we show it } } } /** * Unlocks the painting in all methods (used once information changed) */ public void unlockPainting() { locked = false; } private boolean updateFilter (List<MGFilter> filtersList) { boolean filtersHaveChanged = false; if (haveFiltersChanged(filtersList)) { filtersHaveChanged = true; this.filtersList = filtersList; } return filtersHaveChanged; } /** * Update the variant lists * @param variantDataList the {@link VariantLayerDisplaySettings} settings * @param filtersList the {@link FiltersData} settings */ public void updateMultiGenomeInformation(List<VariantLayerDisplaySettings> variantDataList, List<MGFilter> filtersList) { boolean generateLists = false; boolean filtersHaveChanged = false; if (hasToReset(variantDataList)) { this.variantDataList = new ArrayList<VariantLayerDisplaySettings>(); variantDisplayList = new ArrayList<VariantDisplayList>(); statistics = null; variantUnderMouse = null; handler.initialize(variantDisplayList); } else if (hasChromosomeChanged()) { chromosome = getCurrentChromosome(); generateLists = true; if (variantDisplayList.size() > 0) { for (VariantDisplayList variantDisplay: variantDisplayList) { variantDisplay.generateLists(); } } } else { // Update variants generateLists = updateVariant(variantDataList); // Update filters filtersHaveChanged = updateFilter(filtersList); } if (generateLists || filtersHaveChanged) { statistics = null; for (VariantDisplayList currentList: variantDisplayList) { currentList.updateDisplay(filtersList, showFilter); } handler.initialize(variantDisplayList); forceFitToScreen = true; } boolean haveOptionsChanged = haveOptionsChanged(); if (haveOptionsChanged) { forceFitToScreen = true; for (VariantDisplayList currentList: variantDisplayList) { currentList.updateDisplayForOption(showReference, showFilter); } } if (generateLists || filtersHaveChanged || haveOptionsChanged) { killStripesDialogs(); } } private boolean updateVariant (List<VariantLayerDisplaySettings> variantDataList) { boolean generateLists = false; if ((variantDataList != null) && (variantDataList.size() > 0)) { List<VariantLayerDisplaySettings> newVariantDataList = new ArrayList<VariantLayerDisplaySettings>(); List<VariantDisplayList> newVariantDisplayList = new ArrayList<VariantDisplayList>(); for (int i = 0; i < variantDataList.size(); i++) { VariantLayerDisplaySettings newData = variantDataList.get(i); if (newData != null) { newVariantDataList.add(newData); int dataIndex = this.variantDataList.indexOf(newData); if ((dataIndex > -1) && !newData.hasChanged()) { newVariantDisplayList.add(variantDisplayList.get(dataIndex)); } else { generateLists = true; newData.setHasChanged(false); VariantDisplayList newList = new VariantDisplayList(); newList.initialize(newData.getGenome(), newData.getVariationTypeList()); newList.generateLists(); newVariantDisplayList.add(newList); } } } this.variantDataList = newVariantDataList; variantDisplayList = newVariantDisplayList; } return generateLists; } /** * 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(variantDrawer); out.writeObject(handler); out.writeObject(chromosome); out.writeObject(statistics); out.writeObject(variantDataList); out.writeObject(filtersList); out.writeObject(variantDisplayList); out.writeBoolean(showReference); out.writeBoolean(showFilter); out.writeBoolean(forceFitToScreen); out.writeBoolean(locked); out.writeObject(variantUnderMouse); } }