package aliview.gui.pane; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.event.MouseEvent; import java.awt.font.TextAttribute; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.MemoryImageSource; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.ToolTipManager; import org.apache.log4j.Logger; import utils.OSNativeUtils; import utils.nexus.CharSet; import utils.nexus.CharSets; import aliview.AliView; import aliview.AminoAcid; import aliview.Base; import aliview.NucleotideUtilities; import aliview.alignment.AAHistogram; import aliview.alignment.AliHistogram; import aliview.alignment.Alignment; import aliview.alignment.NucleotideHistogram; import aliview.color.ColorScheme; import aliview.color.ColorSchemeFactory; import aliview.color.ColorUtils; import aliview.messenges.Messenger; import aliview.sequences.AminoAcidAndPosition; import aliview.sequences.Sequence; import aliview.settings.Settings; import aliview.utils.ArrayUtilities; // HAS to be JPanel - JComponent is not enough for only partial cliprect when in jscrollpane when painting // When JComponent only then I had to paint it all (maybe because of layoutmanager?) public class AlignmentPane extends JPanel{ private static final long serialVersionUID = 601195400946835871L; private static final Logger logger = Logger.getLogger(AlignmentPane.class); private static final double MIN_CHAR_SIZE = 0; private static final int MAX_CHAR_SIZE = 100; private static final double CHAR_HEIGHT_RATIO = 1.4; public static final int MAX_CHARSIZE_TO_DRAW = 6; //private static final Color ALPHACOLOR = new Color(255, 255,255, 128 ); double charWidth = 10; double charHeight = 12; private Font baseFont = new Font(Font.MONOSPACED, Font.PLAIN, (int)charWidth); private Font highDPIFont = new Font(Font.MONOSPACED, Font.PLAIN, (int)charWidth); private int highDPIScaleFactor = 1; private Alignment alignment; private ColorScheme colorSchemeAminoAcid = Settings.getColorSchemeAminoAcid(); private ColorScheme colorSchemeNucleotide = Settings.getColorSchemeNucleotide(); // private Rectangle tempSelectionRect; // TODO This should instead be tracing a sequence instead of a position? int differenceTraceSequencePosition = 0; private boolean showTranslation = false; private boolean showTranslationAndNuc = false; // private boolean showTranslationOnePos = false; private AlignmentRuler alignmentRuler; private CharsetRuler charsetRuler; private boolean drawAminoAcidCode; private boolean drawCodonPosOnRuler; private Rectangle lastClip = new Rectangle(); private boolean rulerIsDirty; boolean highlightDiffTrace = false; boolean highlightNonCons; boolean highlightCons; private boolean ignoreGapInTranslation; private byte byteToDraw; private long endTime; // performance measure private int drawCounter = 0; // performance measure private int DRAWCOUNT_LOF_INTERVAL = 1; // performance measure private int fontCase = Settings.getFontCase().getIntValue(); CharPixelsContainer charPixDefaultNuc; CharPixelsContainer charPixSelectedNuc; CharPixelsContainer charPixConsensusNuc; AACharPixelsContainer charPixDefaultAA; AACharPixelsContainer charPixSelectedAA; AACharPixelsContainer charPixConsensusAA; TranslationCharPixelsContainer charPixTranslationDefault; TranslationCharPixelsContainer charPixTranslationSelected; TranslationCharPixelsContainer charPixTranslationLetter; TranslationCharPixelsContainer charPixTranslationSelectedLetter; TranslationCharPixelsContainer charPixTranslationAndNucDefault; TranslationCharPixelsContainer charPixTranslationAndNucSelected; TranslationCharPixelsContainer charPixTranslationAndNucDefaultNoAALetter; TranslationCharPixelsContainer charPixTranslationAndNucSelectedNoAALetter; TranslationCharPixelsContainer charPixTranslationAndNucDominantNuc; TranslationCharPixelsContainer charPixTranslationAndNucDominantNucNoAALetter; TranslationCharPixelsContainer charPixTranslationAndNucDominantNucSelected; TranslationCharPixelsContainer charPixTranslationAndNucDominantNucNoAALetterSelected; private double smallCharsSizeNumber = 0; private int CHARSET_LINE_HEIGHT = 5; public AlignmentPane() { highDPIScaleFactor = (int)OSNativeUtils.getHighDPIScaleFactor(); createAdjustedDerivedBaseFont(); createAdjustedDerivedHighDPIFont(); createCharPixelsContainers(); // highDPIScaleFactor = 1; logger.info("highDPIScaleFactor" + highDPIScaleFactor); this.setOpaque(true); //this.setDoubleBuffered(false); //this.setBackground(Color.white); //this.infoLabel = infoLabel; alignmentRuler = new AlignmentRuler(this); charsetRuler = new CharsetRuler(this); } public long getEndTime(){ return endTime; } public boolean isOnlyDrawDiff() { return highlightDiffTrace; } public void setHighlightDiffTrace(boolean highlightDiff) { this.highlightDiffTrace = highlightDiff; } public void setHighlightNonCons(boolean b) { this.highlightNonCons = b; } public boolean isHighlightNonCons() { return highlightNonCons; } public void setHighlightCons(boolean b) { this.highlightCons = b; } public boolean isHighlightCons() { return highlightCons; } public void setDrawCodonPosOnRuler(boolean drawCodonPosOnRuler) { this.drawCodonPosOnRuler = drawCodonPosOnRuler; } public boolean getDrawCodonPosOnRuler() { return this.drawCodonPosOnRuler; } public void setShowCharsetRuler(boolean selected) { charsetRuler.setVisible(selected); } public boolean decCharSize(){ // stop when everything is in view (or char is 1 for smaller alignments) // Dimension prefSize = getPreferredSize(); // go on decreasing while everything is not in view or while font size >=1 boolean didDecrease = false; if(this.getSize().width > this.getVisibleRect().width || this.getSize().height > this.getVisibleRect().height || charWidth >=1){ double preferredWidth = charWidth; double preferredHeight = charHeight; if(charWidth > 1){ // a little bit faster above char 18 if(charWidth >= 18){ preferredWidth = (int) (charWidth - 0.12*charWidth); // +1 }else{ preferredWidth = charWidth - 1; } preferredHeight = (int)(preferredWidth*CHAR_HEIGHT_RATIO);// 1.2 * charWidth; } else{ if(charWidth == 1){ smallCharsSizeNumber = 1; }else{ smallCharsSizeNumber ++; } preferredWidth = Math.pow(0.85, smallCharsSizeNumber); preferredHeight = preferredWidth; } if(preferredWidth >= MIN_CHAR_SIZE){ charWidth = preferredWidth; charHeight = preferredHeight; } //baseFont = new Font(baseFont.getName(), baseFont.getStyle(), (int)charWidth); createAdjustedDerivedBaseFont(); createAdjustedDerivedHighDPIFont(); createCharPixelsContainers(); // logFontMetrics(); this.validateSize(); didDecrease = true; } return didDecrease; } public void incCharSize(){ if(charWidth >= 1){ // a little bit faster above char 16 if(charWidth >= 16){ charWidth = (int) (charWidth + 0.12*charWidth); // +1 } else{ charWidth = (int) charWidth + 1; // +1 } charHeight = (int)(charWidth*CHAR_HEIGHT_RATIO); }else{ smallCharsSizeNumber --; if(smallCharsSizeNumber <= 0){ charWidth = 1; } else{ charWidth = Math.pow(0.85, smallCharsSizeNumber); } charHeight = charWidth; // +1 } if(charWidth > MAX_CHAR_SIZE){ charWidth = MAX_CHAR_SIZE; charHeight = (int)(charWidth*CHAR_HEIGHT_RATIO); } //baseFont = new Font(baseFont.getName(), baseFont.getStyle(), (int)charWidth); createAdjustedDerivedBaseFont(); createAdjustedDerivedHighDPIFont(); createCharPixelsContainers(); // logFontMetrics(); this.validateSize(); } private void createCharPixelsContainers(){ long startTime = System.currentTimeMillis(); Font charFont = highDPIFont; // no less than 1 int charPixWidth = Math.max(1, (int)(getCharWidth())); charPixWidth = charPixWidth * highDPIScaleFactor; // no less than 1 int charPixHeight = Math.max(1, (int)(getCharHeight())); charPixHeight = charPixHeight * highDPIScaleFactor; int charMaxSizeToDraw = (int)MAX_CHARSIZE_TO_DRAW * highDPIScaleFactor; logger.info("charFont" + charFont.getSize()); logger.info("charPixWidth" + charPixWidth); // Nucleotides charPixDefaultNuc = CharPixelsContainer.createDefaultNucleotideContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixSelectedNuc = CharPixelsContainer.createSelectedNucleotideContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixConsensusNuc = CharPixelsContainer.createConsensusNucleotideContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); // Translated charPixTranslationDefault = TranslationCharPixelsContainer.createDefaultTranslationPixelsContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationSelected = TranslationCharPixelsContainer.createSelectedTranslationPixelsContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationLetter = TranslationCharPixelsContainer.createLetterTranslationPixelsContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationSelectedLetter = TranslationCharPixelsContainer.createSelectedLetterTranslationPixelsContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); // Translated and nuc at same time charPixTranslationAndNucDefault = TranslationCharPixelsContainer.createDefaultTranslationAndNucPixelsContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationAndNucDefaultNoAALetter = TranslationCharPixelsContainer.createDefaultTranslationAndNucPixelsContainerNoAALetter(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationAndNucSelected = TranslationCharPixelsContainer.createSelectedTranslationAndNucPixelsContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationAndNucSelectedNoAALetter = TranslationCharPixelsContainer.createSelectedTranslationAndNucPixelsContainerNoAALetter(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationAndNucDominantNuc = TranslationCharPixelsContainer.createDominantNucTranslationAndNucPixelsContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationAndNucDominantNucNoAALetter = TranslationCharPixelsContainer.createDominantNucTranslationAndNucPixelsContainerNoAALetter(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationAndNucDominantNucSelected = TranslationCharPixelsContainer.createSelectedDominantNucTranslationAndNucPixelsContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); charPixTranslationAndNucDominantNucNoAALetterSelected = TranslationCharPixelsContainer.createSelectedDominantNucTranslationAndNucPixelsContainerNoAALetter(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeNucleotide, getFontCase()); // AminoAcid charPixDefaultAA = new AACharPixelsContainer(); if(colorSchemeAminoAcid.getALLCompundColors() != null){ CompoundCharPixelsContainer compContainer = CompoundCharPixelsContainer.createDefaultCompoundColorContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeAminoAcid, getFontCase()); charPixDefaultAA.setCompoundContainer(compContainer); }else{ CharPixelsContainer container = CharPixelsContainer.createDefaultAAContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeAminoAcid, getFontCase()); charPixDefaultAA.setContainer(container); } charPixSelectedAA = new AACharPixelsContainer(); if(colorSchemeAminoAcid.getALLCompundColors() != null){ CompoundCharPixelsContainer compContainer = CompoundCharPixelsContainer.createSelectedCompoundColorContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeAminoAcid, getFontCase()); charPixSelectedAA.setCompoundContainer(compContainer); }else{ CharPixelsContainer container = CharPixelsContainer.createSelectedAAContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeAminoAcid, getFontCase()); charPixSelectedAA.setContainer(container); } charPixConsensusAA = new AACharPixelsContainer(); if(colorSchemeAminoAcid.getALLCompundColors() != null){ CompoundCharPixelsContainer compContainer = CompoundCharPixelsContainer.createDefaultCompoundColorContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeAminoAcid, getFontCase()); charPixConsensusAA.setCompoundContainer(compContainer); }else{ CharPixelsContainer container = CharPixelsContainer.createConsensusAAContainer(charFont, charMaxSizeToDraw, charPixWidth, charPixHeight, colorSchemeAminoAcid, getFontCase()); charPixConsensusAA.setContainer(container); } endTime = System.currentTimeMillis(); logger.info("Creating charPixContainers took " + (endTime - startTime) + " milliseconds"); } private int getFontCase() { return fontCase; } @Override public Font getFont() { return baseFont; } private void createAdjustedDerivedBaseFont() { Map<TextAttribute, Object> attributes = new HashMap<TextAttribute, Object>(); // create a font without Tracking to see the diff in font actual size and specified font size attributes.put(TextAttribute.TRACKING, 0); attributes.put(TextAttribute.SIZE, (int)charWidth); Font calcFont = baseFont.deriveFont(attributes); FontMetrics metrics = getFontMetrics(calcFont); int fontActualWidth = metrics.stringWidth("X"); double sizeDiff = charWidth - fontActualWidth; // Calculate tracking for font size double tracking = (double)sizeDiff/charWidth; logger.info("tracking" + tracking); // Create a font with correct tracking so characters are exactly spaced as pixels on pane attributes.put(TextAttribute.TRACKING, tracking); // 8 attributes.put(TextAttribute.SIZE, (int)charWidth); Font spacedFont = baseFont.deriveFont(attributes); baseFont = spacedFont; } private void createAdjustedDerivedHighDPIFont() { Map<TextAttribute, Object> attributes = new HashMap<TextAttribute, Object>(); // create a font without Tracking to see the diff in font actual size and specified font size attributes.put(TextAttribute.TRACKING, 0); attributes.put(TextAttribute.SIZE, (int)charWidth*highDPIScaleFactor); Font calcFont = baseFont.deriveFont(attributes); FontMetrics metrics = getFontMetrics(calcFont); int fontActualWidth = metrics.stringWidth("X"); double sizeDiff = charWidth*highDPIScaleFactor - fontActualWidth; // Calculate tracking for font size double tracking = (double)sizeDiff/charWidth*highDPIScaleFactor; logger.info("tracking" + tracking); // Create a font with correct tracking so characters are exactly spaced as pixels on pane attributes.put(TextAttribute.TRACKING, tracking); // 8 attributes.put(TextAttribute.SIZE, (int)charWidth*highDPIScaleFactor); Font spacedFont = baseFont.deriveFont(attributes); highDPIFont = spacedFont; } private void logFontMetrics(Font font){ FontMetrics metrics = this.getGraphics().getFontMetrics(font); logger.info("font.getSize()" + font.getSize()); logger.info("font.getSize2D()" + font.getSize2D()); // get the height of a line of text in this // font and render context logger.info("baseFont.getSize()" + baseFont.getSize()); logger.info("font.getSize2D()" + font.getSize2D()); int hgt = metrics.getHeight(); logger.info("metrics.getHeight()" + metrics.getHeight()); logger.info("metrics.getMaxAdvance()" + metrics.getMaxAdvance()); // get the advance of my text in this font logger.info("metrics.getLeading()" + metrics.getLeading()); int adv = metrics.stringWidth("A"); logger.info("metrics.stringWidth(\"A\")" + metrics.stringWidth("AAAAAAAAAA")); logger.info("metrics.stringWidth(\"T\")" + metrics.stringWidth("T")); logger.info("metrics.stringWidth(\"c\")" + metrics.stringWidth("c")); logger.info("font.getAttributes().get(WIDTH_REGULAR)" + font.getAttributes().get(TextAttribute.WIDTH_REGULAR)); } // should throw no valid base error public Point getBasePosition(Base base){ if(base == null){ return null; } int x = (int) (base.getPosition() * charWidth); int y = (int) (alignment.getSequenceIndex(base.getSequence()) * charHeight); Point pos = new Point(x,y); return pos; } public Base selectBaseAt(Point pos) throws InvalidAlignmentPositionException{ Base base = null; base = getBaseAt(pos); if(base != null){ base.getPosition(); base.getSequence(); alignment.getSequenceIndex(base.getSequence()); alignment.setSelectionAt(base.getPosition(), alignment.getSequenceIndex(base.getSequence()),true); } return base; } public int getUngapedPositionInSequenceAt(Point pos) throws InvalidAlignmentPositionException{ int ungapedPos = 0; Base base = getBaseAt(pos); if(base != null){ ungapedPos = base.getUngapedPosition(); } else{ } return ungapedPos; } public int getPositionInSequenceAt(Point pos) throws InvalidAlignmentPositionException{ int xPos = 0; Base base = getBaseAt(pos); if(base != null){ xPos = base.getPosition(); } return xPos; } public void selectColumnAt(Point pos) { int columnIndex = getColumnAt(pos); getAlignment().selectColumn(columnIndex); } public Base getBaseAt(Point pos) throws InvalidAlignmentPositionException{ Point matrixPoint = paneCoordToMatrixCoord(pos); Base base = null; if(alignment.isPositionValid(matrixPoint.x,matrixPoint.y)){ Sequence seq = (Sequence) alignment.getSequences().get(matrixPoint.y); base = new Base(seq, matrixPoint.x); } else{ base = null; } return base; } public Base getClosestBaseAt(Point pos){ Point matrixPoint = paneCoordToMatrixCoord(pos); Base base = null; if(alignment.isPositionValid(matrixPoint.x,matrixPoint.y)){ Sequence seq = (Sequence) alignment.getSequences().get(matrixPoint.y); base = new Base(seq, matrixPoint.x); } else{ // get last sequence Sequence seq = (Sequence) alignment.getSequences().get(alignment.getSequences().getSize()-1); base = new Base(seq, matrixPoint.x); } return base; } public int getColumnAt(Point pos){ Point matrixPoint = paneCoordToMatrixCoord(pos); return matrixPoint.x; } public void setAlignment(Alignment alignment){ this.alignment = alignment; // this.infoLabel.setAlignment(alignment); this.validateSize(); } public void repaintAndForceRuler(){ rulerIsDirty = true; repaint(); } public void paintComponent(Graphics g){ super.paintComponent(g); paintAlignment(g); } public void paintAlignment(Graphics g){ drawCounter ++; long startTime = System.currentTimeMillis(); if(AliView.isDebugMode() && drawCounter % DRAWCOUNT_LOF_INTERVAL == 0){ logger.info("Inside paintAlignment: Time from last endTim " + (startTime - endTime) + " milliseconds"); System.out.println("Inside paintAlignment: Time from last endTim " + (startTime - endTime) + " milliseconds"); } Graphics2D g2d = (Graphics2D) g; // What part of alignment matrix is in view (what part of matrix is in graphical view) Rectangle clip = g2d.getClipBounds(); Rectangle matrixClip = paneCoordToMatrixCoord(clip); // logger.info(matrixClip); int xMin = matrixClip.x - 1; int yMin = matrixClip.y - 1; int xMax = (int) matrixClip.getMaxX() + 1; int yMax = (int) matrixClip.getMaxY() + 1; // // logger.info("yMin" + yMin); // logger.info("yMin" + yMax); // logger.info("xMin" + xMin); // logger.info("xMax" + xMax); // // add one extra position when drawing translated // otherwise there could be some white borders when scrolling if(showTranslation){ xMin --; xMax ++; } // adjust for part of matrix that exists xMin = Math.min(alignment.getMaxX(), xMin); xMin = Math.max(0, xMin); yMin = Math.min(alignment.getMaxY(), yMin); yMin = Math.max(0, yMin); xMax = Math.min(alignment.getMaxX(), xMax); yMax = Math.min(alignment.getMaxY(), yMax); // logger.info("yMin" + yMin); // logger.info("yMax" + yMax); // logger.info("xMin" + xMin); // logger.info("xMax" + xMax); // Extra because pixelCopyDraw int height = (yMax - yMin) * (int)charHeight; int width = (xMax - xMin) * (int)charWidth; // // logger.info("width" + width); // logger.info("height" + height); // // Small chars if(charWidth < 1){ height = clip.height; width = clip.width; } // // logger.info("yMax" + yMax); // logger.info("yMin" + yMin); // logger.info("width" + width); // logger.info("clipHeight" + clip.height); // logger.info("width" + width); // logger.info("height" + height); // TODO adjust for retina int[] pixArray = new int[width* highDPIScaleFactor * height * highDPIScaleFactor]; // logger.info(pixArray.length); RGBArray clipRGB = new RGBArray(pixArray, width*highDPIScaleFactor, height*highDPIScaleFactor); // HERE FILL RGB-ARRAY DRAW... // fillRGBArrayAndPaint(xMin, xMax, yMin, yMax, clipRGB, clip, g2d); fillRGBArrayAndPaintMultithreaded(xMin, xMax, yMin, yMax, clipRGB, clip, g2d); if(drawCounter % DRAWCOUNT_LOF_INTERVAL == 0){ endTime = System.currentTimeMillis(); logger.info("Alignment pane PaintComponent took " + (endTime - startTime) + " milliseconds"); } // repaint ruler also if needed if(clip.x != lastClip.x || clip.width != lastClip.width || rulerIsDirty){ alignmentRuler.repaint(); charsetRuler.repaint(); rulerIsDirty = false; } lastClip = clip; } private void fillRGBArrayAndPaintMultithreaded(int xMin, int xMax, int yMin, int yMax, RGBArray clipRGB, Rectangle clip, Graphics2D g2d){ // these vals are not going to change so get it only once boolean isNucleotideAlignment = alignment.isNucleotideAlignment(); double seqPerPixX = 1/(double)charWidth; double seqPerPixY = 1/(double)charWidth; logger.info("Runtime.getRuntime().availableProcessors()" + Runtime.getRuntime().availableProcessors()); int nThreads = 1; // Only one thread if filesequences - more threads make reading file slower if(alignment.isFileSequences()){ nThreads = 1; }else{ if(Runtime.getRuntime().availableProcessors() > 2){ nThreads = 2; } if(Runtime.getRuntime().availableProcessors() > 4){ nThreads = 3; } } // small chars have their own loop here if(charWidth < 1){ ExecutorService executor = Executors.newFixedThreadPool(nThreads); // No longer: Always start at closest even 10 // double startY = clip.y; // startY = Math.floor(startY/100) * 100; int clipYPos = 0; for(int y = clip.y; y < clip.getMaxY(); y ++){ int ySeq = (int)((double)(y) * seqPerPixY); if(ySeq <= yMax && ySeq >= 0){ int seqYPos = ySeq; Sequence seq = alignment.getSequences().get(seqYPos); int xPosStart = clip.x; int xPosEnd = (int) clip.getMaxX(); if(isNucleotideAlignment){ if(isShowTranslationOnePos()){ SequencePainter seqPainter = new SequencePainterAminoAcid(seq, seqYPos, clipYPos, xPosStart, xPosEnd, seqPerPixX, 1, 1, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); }else if(showTranslation && !isShowTranslationOnePos() && ignoreGapInTranslation){ SequencePainter seqPainter = new SequencePainterAminoAcidTranslatedIgnoreGap(seq, seqYPos, clipYPos, xPosStart, xPosEnd, seqPerPixX, 1, 1, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); }else if(showTranslation){ if(showTranslationAndNuc){ SequencePainter seqPainter = new SequencePainterNucleotideTranslatedShowNucAndAcid(seq, seqYPos, clipYPos, xPosStart, xPosEnd, seqPerPixX, 1, 1, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); }else{ SequencePainter seqPainter = new SequencePainterAminoAcidTranslated(seq, seqYPos, clipYPos, xPosStart, xPosEnd, seqPerPixX, 1, 1, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); } }else{ SequencePainter seqPainter = new SequencePainterNucleotide(seq, seqYPos, clipYPos, xPosStart, xPosEnd, seqPerPixX, 1, 1, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); } } // Draw as AminoAcids else{ SequencePainter seqPainter = new SequencePainterAminoAcid(seq, seqYPos, clipYPos, xPosStart, xPosEnd, seqPerPixX, 1, 1, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); } } else{ logger.info("outside"); } clipYPos ++; } executor.shutdown(); try { executor.awaitTermination(1000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } ///////////////////////// // // Normal char width // ///////////////////////// else{ ExecutorService executor = Executors.newFixedThreadPool(nThreads); int clipYPos = 0; // Loop rows (The sequence painter is painting a row in its own thread) for(int y = yMin; y < yMax; y = y + 1){ int seqYPos = y; Sequence seq = alignment.getSequences().get(seqYPos); int normalCharSeqPerPix = 1; int xPosStart = xMin; int xPosEnd = xMax; if(isNucleotideAlignment){ if(isShowTranslationOnePos()){ SequencePainter seqPainter = new SequencePainterAminoAcid(seq, seqYPos, clipYPos, xPosStart, xPosEnd, normalCharSeqPerPix, charWidth, charHeight, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); }else if(showTranslation && !isShowTranslationOnePos() && ignoreGapInTranslation){ SequencePainter seqPainter = new SequencePainterAminoAcidTranslatedIgnoreGap(seq, seqYPos, clipYPos, xPosStart, xPosEnd, normalCharSeqPerPix, charWidth, charHeight, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); }else if(showTranslation){ if(showTranslationAndNuc){ SequencePainter seqPainter = new SequencePainterNucleotideTranslatedShowNucAndAcid(seq, seqYPos, clipYPos, xPosStart, xPosEnd, normalCharSeqPerPix, charWidth, charHeight, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); }else{ SequencePainter seqPainter = new SequencePainterAminoAcidTranslated(seq, seqYPos, clipYPos, xPosStart, xPosEnd, normalCharSeqPerPix, charWidth, charHeight, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); } // Normal nucleotide }else{ SequencePainter seqPainter = new SequencePainterNucleotide(seq, seqYPos, clipYPos, xPosStart, xPosEnd, normalCharSeqPerPix, charWidth, charHeight, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); } } // Draw as AminoAcids else{ SequencePainter seqPainter = new SequencePainterAminoAcid(seq, seqYPos, clipYPos, xPosStart, xPosEnd, normalCharSeqPerPix, charWidth, charHeight, highDPIScaleFactor, clipRGB, this, alignment); executor.execute(seqPainter); } clipYPos ++; } executor.shutdown(); try { executor.awaitTermination(1000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // // // Draw Excludes by manipulating pixelColor // if(! isShowTranslationOnePos()){ // // Two versions depending on if it is small chars or not // if(charWidth < 1){ // for(int x = clip.x; x < clip.getMaxX() ; x++){ // int xPos =(int)((double)x * (1/(double)charWidth)); // if(alignment.isExcluded(xPos) == true){ // logger.info("is excl"); // ImageUtils.darkerRGBArrayColumn(clipRGB, x); // } // } // }else{ // for(int x = xMin; x < xMax ; x++){ // if(alignment.isExcluded(x) == true){ // // for(int col = x; col < charWidth; col++){ // logger.info("is excl"); // ImageUtils.darkerRGBArrayColumn(clipRGB, col); // } // // } // } // } // } // Now draw the pixels onto the image Image img = createImage(new MemoryImageSource(clipRGB.getScanWidth(), clipRGB.getHeight(), clipRGB.getBackend(), 0, clipRGB.getScanWidth())); // First fill background g2d.setColor(this.getBackground()); g2d.fill(clip); int clipRGBXPos = clip.x; int clipRGBYPos = clip.y; // Adjust because we start always on exact char upp to one pos before if(charWidth > 1){ clipRGBXPos = (int)(xMin * charWidth); clipRGBYPos = (int)(yMin * charHeight); } if (img != null){ // Mac retina screen if(highDPIScaleFactor > 1){ int dx1 = clipRGBXPos; int dx2 = dx1 + clipRGB.getScanWidth() / highDPIScaleFactor; int dy1 = clipRGBYPos; int dy2 = dy1 + clipRGB.getHeight() / highDPIScaleFactor; int sx1 = 0; int sx2 = sx1 + clipRGB.getScanWidth(); int sy1 = 0; int sy2 = sy1 + clipRGB.getHeight(); g2d.drawImage(img,dx1,dy1,dx2,dy2,sx1,sy1,sx2,sy2, null); }else{ g2d.drawImage(img, clipRGBXPos, clipRGBYPos, null); } } // Draw excludes if(isShowTranslationOnePos()){ // calculate height for excludes (this is to avoid drawing below alignment if alignment is not filling panel) int drawExcludesHeight = (int) Math.min(this.getVisibleRect().getHeight(), alignment.getSize() * charHeight); // Two versions depending on if it is small chars or not if(charWidth < 1){ for(int x = clip.x; x < clip.getMaxX() ; x++){ int xPos =(int)((double)x * (1/(double)charWidth)); if(alignment.isExcluded(xPos) == true){ g2d.setColor(ColorScheme.GREY_TRANSPARENT); g2d.fillRect(x, this.getVisibleRect().y, 1, drawExcludesHeight); // logger.info("drawExclude"); } } }else{ for(int x = xMin; x < xMax ; x++){ if(alignment.isExcluded(x) == true){ g2d.setColor(ColorScheme.GREY_TRANSPARENT); g2d.fillRect((int)(x * charWidth), this.getVisibleRect().y, (int)charWidth, drawExcludesHeight); } } } } else{ // calculate height for excludes (this is to avoid drawing below alignment if alignment is not filling panel) int drawExcludesHeight = (int) Math.min(this.getVisibleRect().getHeight(), alignment.getSize() * charHeight); // Two versions depending on if it is small chars or not if(charWidth < 1){ for(int x = clip.x; x < clip.getMaxX() ; x++){ int xPos =(int)((double)x * (1/(double)charWidth)); if(alignment.isExcluded(xPos) == true){ g2d.setColor(ColorScheme.GREY_TRANSPARENT); g2d.fillRect(x, this.getVisibleRect().y, 1, drawExcludesHeight); // logger.info("drawExclude"); } } }else{ for(int x = xMin; x < xMax ; x++){ if(alignment.isExcluded(x) == true){ g2d.setColor(ColorScheme.GREY_TRANSPARENT); g2d.fillRect((int)(x * charWidth), this.getVisibleRect().y, (int)charWidth, drawExcludesHeight); } } } } logger.info("done"); } public Alignment getAlignment() { return alignment; } public void validateSequenceOrder(){ // verify that tracing sequence not is out of index if(differenceTraceSequencePosition >= alignment.getSize()){ differenceTraceSequencePosition = 0; } } public int selectWithin(Rectangle rect){ // First clear int selectionSize = addSelectionWithin(rect); return selectionSize; } public int selectColumnsWithin(Rectangle rect) { // First clear int selectionSize = addColumnSelectionWithin(rect); return selectionSize; } public int addColumnSelectionWithin(Rectangle rect){ int nSelection = 0; // grow so all sequences are included Rectangle columns = new Rectangle(rect.x, 0, rect.width, this.getHeight()); return addSelectionWithin(columns); } public int addSelectionWithin(Rectangle rect){ int nSelection = 0; // calculate what part of alignment matrix is in view (what part of matrix is in graphical view) Rectangle bounds = paneCoordToMatrixCoord(rect); alignment.setSelectionWithin(bounds); return nSelection; } /* private Rectangle getTempSelection() { return alignment.getTempSelection(); } */ /* public void clearTempSelection() { this.tempSelectionRect = null; } */ public Rectangle paneCoordToMatrixCoord(Rectangle rect){ // TODO maybe problem when calculating a 0-width rect - then it will give eg. xmin=34 xmax=35 // logger.info("rect.getMinX()" + rect.getMinX()); // logger.info("rect.getMaxX()" + rect.getMaxX()); // logger.info("rect.getMinX()/charWidth" + rect.getMinX()/charWidth); int matrixMinX = (int) Math.floor(rect.getMinX()/charWidth); // always round down int matrixMaxX = (int) Math.floor(rect.getMaxX()/charWidth); // always round up int matrixMinY = (int) Math.floor(rect.getMinY()/charHeight); // always round down int matrixMaxY = (int) Math.floor(rect.getMaxY()/charHeight); // always round down // also set min to 0 matrixMinX = Math.max(0, matrixMinX); matrixMaxX = Math.max(0, matrixMaxX); matrixMinY = Math.max(0, matrixMinY); matrixMaxY = Math.max(0, matrixMaxY); // // logger.info("matrixMinX" + matrixMinX); // logger.info("matrixMaxX" + matrixMaxX); // logger.info(getMatrixTopOffset()); Rectangle converted = new Rectangle(matrixMinX, matrixMinY, matrixMaxX - matrixMinX, matrixMaxY - matrixMinY); // logger.info("converted" + converted); return converted; } // todo this should be listening to changes in alignmnet instead public void updateStatisticsLabel(){ // logger.info("unimplemented should be done by changelistener"); } public void validateSize() { // Set component preferred size Dimension current = getSize(); Dimension prefSize = getCalculatedPreferredSize(); // Rectangle prefRect = this.getVisibleRect(); if(current.width != prefSize.width || current.height != prefSize.height){ this.setPreferredSize(prefSize); //this.updateStatisticsLabel(); this.rulerIsDirty = true; this.revalidate(); } // this.scrollRectToVisible(prefRect); } @Override public void setSize(Dimension d) { super.setSize(d); } @Override public Dimension getPreferredSize() { return getCalculatedPreferredSize(); } private Dimension getCalculatedPreferredSize(){ Dimension newDim; // logger.info("charWidth" + charWidth); // logger.info("charHeight" + charHeight); // if(showTranslationOnePos){ // newDim = new Dimension((int) (charWidth * alignment.getAlignentMeta().getCodonPositions().getLengthOfTranslatedPos()), (int)(charHeight * alignment.getSize())); // }else{ newDim = new Dimension((int) (charWidth * alignment.getMaximumSequenceLength()), (int)(charHeight * alignment.getSize())); // } // logger.info("newDim" + newDim); if(newDim.width == Integer.MAX_VALUE || newDim.height == Integer.MAX_VALUE){ Messenger.showMaxJPanelSizeMessageOnceThisSession(); // logger.info("Hit max jpanel length"); } return newDim; } public Point paneCoordToMatrixCoord(Point pos){ int matrixX = (int) Math.floor(pos.getX() / charWidth); int matrixY = (int) Math.floor(pos.getY() / charHeight); Point converted = new Point(matrixX, matrixY); return converted; } public Point matrixCoordToPaneCoord(Point pos){ int paneX = (int) (pos.getX() * charWidth); int paneY = (int) (pos.getY() * charHeight); Point converted = new Point(paneX, paneY); return converted; } public Rectangle matrixCoordToPaneCoord(Rectangle rect){ Point min = new Point((int)rect.getMinX(), (int)rect.getMinY()); // logger.info("min" + min); Point max = new Point((int)rect.getMaxX(), (int)rect.getMaxY()); // logger.info("max" + max); Rectangle converted = new Rectangle(matrixCoordToPaneCoord(min)); converted.add(matrixCoordToPaneCoord(max)); // logger.info("converted" + converted); return converted; } public boolean isPointWithinMatrix(Point pos) { Point matrixPoint = paneCoordToMatrixCoord(pos); return alignment.isPositionValid(matrixPoint.x, matrixPoint.y); } public double getCharHeight() { return this.charHeight; } public double getCharWidth() { return this.charWidth; } public void setDifferenceTraceSequence(Point pos) throws InvalidAlignmentPositionException { Point matrixPoint = paneCoordToMatrixCoord(pos); Sequence seq = null; if(alignment.isPositionValid(matrixPoint.x,matrixPoint.y)){ // todo this should be changed because problem when removed or moved this.differenceTraceSequencePosition = matrixPoint.y; } else{ throw new InvalidAlignmentPositionException("Position is out of range" + pos); } } public void setDifferenceTraceSequence(int nIndex){ this.differenceTraceSequencePosition = nIndex; } public Sequence getSequenceAt(Point pos) throws InvalidAlignmentPositionException { Point matrixPoint = paneCoordToMatrixCoord(pos); Sequence seq = null; if(alignment.isPositionValid(matrixPoint.x,matrixPoint.y)){ seq = (Sequence) alignment.getSequences().get(matrixPoint.y); } else{ throw new InvalidAlignmentPositionException("Position is out of range" + pos); } return seq; } public boolean isWithinExistingSelection(Point point) { boolean isSelected = false; try { Base base = getBaseAt(point); if(base != null){ isSelected = base.isSelected(); } } catch (InvalidAlignmentPositionException e) { // TODO Auto-generated catch block e.printStackTrace(); } return isSelected; } public void setShowTranslation(boolean showTranslation){ this.showTranslation = showTranslation; } public boolean isShowTranslation() { return showTranslation; } public boolean isShowTranslationOnePos() { return alignment.isTranslatedOnePos(); } public JComponent getRulerComponent(){ return this.alignmentRuler; } public JComponent getCharsetRulerComponent(){ return this.charsetRuler; } public void setDrawAminoAcidCode(boolean drawCode){ this.drawAminoAcidCode = drawCode; } public boolean isDrawAminoAcidCode(){ return this.drawAminoAcidCode; } public void setColorSchemeAminoAcid(ColorScheme aScheme){ this.colorSchemeAminoAcid = aScheme; createCharPixelsContainers(); } public void setColorSchemeNucleotide(ColorScheme aScheme) { this.colorSchemeNucleotide = aScheme; createCharPixelsContainers(); } public Point getVisibleUpperLeftMatrixPos() { Rectangle rect = this.getVisibleRect(); Point ulPanePos = rect.getLocation(); Point ulMatrixPos = paneCoordToMatrixCoord(ulPanePos); return ulMatrixPos; } public void scrollToVisibleUpperLeftMatrixPos(Point ulPos) { Point ulPanePos = matrixCoordToPaneCoord(ulPos); Rectangle rect = new Rectangle(ulPanePos, this.getVisibleRect().getSize()); rect.grow(-10, -10); logger.info("ulPanePos" + ulPanePos); logger.info("currentVisibleRect " + this.getVisibleRect()); logger.info("Scroll to rect" + rect); // TODO Maye make this better working // As a workaround first setLocation(0,0) // then scrollRectToVisible is working OK this.setLocation(0,0); this.scrollRectToVisible(rect); logger.info("after this.getVisibleRect()" + this.getVisibleRect()); } public void scrollMatrixX(int offset) { int offsetPane = (int)(offset * charWidth); this.setLocation( getLocation().x + offsetPane, getLocation().y ); } public boolean getIgnoreGapInTranslation(){ return ignoreGapInTranslation; } public void setIgnoreGapInTranslation(boolean ignoreGapInTranslation) { this.ignoreGapInTranslation = ignoreGapInTranslation; } public void setFontCase(int fontCase){ this.fontCase = fontCase; createCharPixelsContainers(); } public void scrollRectToSelection() { Rectangle selectRect = alignment.getSelectionAsMinRect(); if(selectRect != null){ Rectangle grown1xtra = new Rectangle(selectRect.x - 1, selectRect.y - 1, selectRect.width + 3, selectRect.height + 3); Rectangle paneCoord = matrixCoordToPaneCoord(grown1xtra); if(! getVisibleRect().contains(selectRect)){ logger.info("not visible"); scrollRectToVisible(paneCoord); } } } public void scrollRectToSelectionCenter() { Rectangle selectRect = alignment.getSelectionAsMinRect(); if(selectRect != null){ Rectangle paneCoord = matrixCoordToPaneCoord(selectRect); if(! getVisibleRect().contains(selectRect)){ logger.info("not visible"); Rectangle newVisible = new Rectangle(paneCoord); //logger.info("new visible" + newVisible); newVisible.grow(getVisibleRect().width/2,getVisibleRect().height/2); //logger.info("newVisible" + newVisible); scrollRectToVisible(newVisible); } } } public boolean getShowTranslationAndNuc() { return showTranslationAndNuc; } public void setShowTranslationAndNuc(boolean b) { showTranslationAndNuc = b; } public int getDifferenceTraceSequencePosition() { return differenceTraceSequencePosition; } public boolean isHighlightDiffTrace() { return highlightDiffTrace; } private class AlignmentRuler extends JPanel{ private AlignmentPane alignmentPane; public AlignmentRuler(AlignmentPane alignmentPane) { this.alignmentPane = alignmentPane; } public void paintComponent(Graphics g){ super.paintComponent(g); paintRuler(g); } public void paintRuler(Graphics g){ long startTime = System.currentTimeMillis(); //super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); // g2d.setRenderingHint(RenderingHints.KEY_RENDERING, // RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); // g2d.setRenderingHint(RenderingHints.KEY_RENDERING, // RenderingHints.VALUE_RENDER_QUALITY); // g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // RenderingHints.VALUE_ANTIALIAS_ON); // g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, // RenderingHints.VALUE_TEXT_ANTIALIAS_ON); // //g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, // RenderingHints.VALUE_FRACTIONALMETRICS_OFF); //g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, // RenderingHints.VALUE_COLOR_RENDER_SPEED); //g2d.setRenderingHint(RenderingHints.KEY_DITHERING, // RenderingHints.VALUE_DITHER_DISABLE); // g2d.setFont(baseFont); // What part of alignment matrix is in view (what part of matrix is in graphical view) Rectangle paneClip = alignmentPane.getVisibleRect(); Rectangle matrixClip = paneCoordToMatrixCoord(paneClip); // todo calculate from font metrics double charCenterXOffset = 0.9997; // NUMBERS int rulerCharWidth = 11; //int rulerCharHeight = 11; Font rulerFont = new Font(alignmentPane.getFont().getName(), alignmentPane.getFont().getStyle(), (int)rulerCharWidth); g2d.setFont(rulerFont); // // Draw ruler background // Rectangle rulerRect = new Rectangle(this.getVisibleRect()); g2d.setColor(getBackground()); g2d.fill(rulerRect); int offsetDueToScrollPanePosition = 0; // Normal char-with smaller if(charWidth >= 1){ offsetDueToScrollPanePosition = paneClip.x % (int)charWidth; offsetDueToScrollPanePosition = offsetDueToScrollPanePosition -1; // Tickmarks int posTick = 0; int count = 0; int maxY = alignment.getMaxY(); int maxX = alignment.getMaxX(); // if(showTranslationOnePos){ // maxX = alignment.getAlignentMeta().getCodonPositions().getTranslatedAminAcidLength(); // } for(int x = matrixClip.x ; x < matrixClip.getMaxX() + 1; x++){ // Only draw part of matrix that exists if(maxY > 0 && x >= 0 && x < maxX){ // draw codon-pos background on ruler depending on codonpos if(drawCodonPosOnRuler && ! isShowTranslationOnePos()){ int codonPos = alignment.getCodonPosAt(x); //logger.info(codonPos); Color codonPosColor = Color.GREEN; if(codonPos == 0){ codonPosColor = Color.LIGHT_GRAY; }else if(codonPos == 1){ codonPosColor = Color.GREEN; }else if(codonPos == 2){ codonPosColor = Color.orange; }else if(codonPos == 3){ codonPosColor = Color.red; } g2d.setColor(codonPosColor); int boxHeight = 5; // we are drawing not on a large scrollable ruler, but a window sized fixed pane we have to adjust with offsetDueToScrollPanePosition g2d.fillRect((int)(posTick * charCenterXOffset * charWidth - offsetDueToScrollPanePosition), (int) (rulerRect.getMaxY() - boxHeight), (int)charWidth, boxHeight); } // draw tickmarks g2d.setColor(Color.DARK_GRAY); // make every 5 tickmarks a bit bigger if(x % 5 == 4 && charWidth > 0.6){ // it has to be 4 and not 0 due to the fact that 1:st base har position 0 in matrix // we are drawing not on a large scrollable ruler, but a window sized fixed pane we have to adjust with offsetDueToScrollPanePosition // since it is hidden in scrollpane g2d.drawLine((int)(posTick * charCenterXOffset * charWidth + charWidth/2 - offsetDueToScrollPanePosition), (int) (rulerRect.getMaxY() - 2), (int)(posTick * charCenterXOffset * charWidth + charWidth/2 - offsetDueToScrollPanePosition), (int)rulerRect.getMaxY() - 5); } // dont draw smallest tick if to small else if(charWidth > 4){ // we are drawing not on a large scrollable ruler, but a window sized fixed pane we have to adjust with offsetDueToScrollPanePosition // since it is hidden in scrollpane g2d.drawLine((int)(posTick * charCenterXOffset * charWidth + charWidth/2 - offsetDueToScrollPanePosition), (int) (rulerRect.getMaxY() - 2), (int)(posTick * charCenterXOffset * charWidth + charWidth/2 - offsetDueToScrollPanePosition), (int)rulerRect.getMaxY() - 3); } // and numbers posTick ++; } count ++; } // NUMBERS // Only draw every xx pos int drawEveryNpos = 10; if(charWidth < 4){ drawEveryNpos = 50; }else if(charWidth < 5){ drawEveryNpos = 20; } // position numbers int lastTextEndPos = 0; int pos = 0; for(int x = matrixClip.x ; x < matrixClip.getMaxX() + 1; x++){ if(x % drawEveryNpos == 0){ String posText = Integer.toString(x); int stringSizeOffset = g2d.getFontMetrics().stringWidth(posText) / 2; //int stringSizeOffset = (int)((posText.length()*0.8 * rulerCharWidth) / 2) + 5; //int stringSizeOffset = ( posText.length()*(rulerFont.getSize()) ) / 2; // int stringSizeOffset = (int)((posText.length()*0.8 * rulerCharWidth) / 2) + 5; int textPosX = (int)((pos -1) * charCenterXOffset * charWidth + charWidth/2 - offsetDueToScrollPanePosition) - stringSizeOffset; // dont draw on top of last (if number is very long) if(lastTextEndPos < textPosX){ g2d.drawString(posText, textPosX, 10); lastTextEndPos = textPosX + stringSizeOffset + 40; // add 40 extra space between numbers } } pos ++; } } // Less than one pix char size else{ double seqOffsetVisiblePanePos = matrixClip.getMinX() -1; //(double)paneClip.x / charWidth; // pos per pixel // double posPerPix = 1/charWidth; double posPerPix = matrixClip.getWidth() / paneClip.getWidth(); int xStep = 10; if(posPerPix < 2.5){ xStep = 10; } else{ // This loop is the same as all the commented (else if) below // first set something if something in loop goes wrong... xStep = 100000000; for(int posPixRange = 5; posPixRange < Integer.MAX_VALUE; posPixRange = (int)(posPixRange * 2)){ if(posPerPix < posPixRange){ xStep = posPixRange * 5; break; } } } /* else if(posPerPix < 5){ xStep = 25; } else if(posPerPix < 10){ xStep = 50; } else if(posPerPix < 20){ xStep = 100; } else if(posPerPix < 40){ xStep = 200; } else if(posPerPix < 80){ xStep = 400; } else if(posPerPix < 160){ xStep = 800; } else if(posPerPix < 320){ xStep = 1600; } else if(posPerPix < 640){ xStep = 3200; } else if(posPerPix < 1000){ xStep = 5000; } else if(posPerPix < 2000){ xStep = 10000; } else{ xStep = 80000; } */ double startPosSeq = roundToClosestUpper((int)seqOffsetVisiblePanePos,xStep); int startPosPane = (int) (charWidth * startPosSeq); // logger.info("ruler startPosSeq" + startPosSeq); // logger.info("ruler startPosPane" + startPosPane); // logger.info("posPerPix" + posPerPix); int maxY = alignment.getMaxY(); int maxX = alignment.getMaxX(); // if(showTranslationOnePos){ // maxX = alignment.getAlignentMeta().getCodonPositions().getTranslatedAminAcidLength(); // } int maxVisibleSeq = (int)matrixClip.getMaxX(); logger.info("maxVisibleSeq" + maxVisibleSeq + 200); int lastTextEndPos = 0; // Tickmarks int countTicks = 0; // Same color for everything g2d.setColor(Color.DARK_GRAY); // X Loop Start for(int xSeq = (int)startPosSeq; xSeq < maxVisibleSeq; xSeq = xSeq + xStep){ // get closest pane pos int xPane = (int) ( (double) xSeq / posPerPix ); // // logger.info("maxX" + maxX); // logger.info("xPane" + xPane); // Only draw part of matrix that exists if(maxY > 0 && xSeq >= 0 && xSeq < maxX){ // no no codon-pos-ruler // we are drawing not on a large scrollable ruler, but a window sized fixed pane we have to adjust with pane.x // since it is hidden in scrollpane int tickPosX = (xPane - paneClip.x); // larger and text every 10-interval int tickSize; int largerInterval = xStep * 10; if(xSeq % largerInterval == 0){ String posText = Integer.toString(xSeq); int stringSizeOffset = g2d.getFontMetrics().stringWidth(posText) / 2; // int stringSizeOffset = ( posText.length()*(rulerFont.getSize() -1) ) / 2; //int stringSizeOffset = (int)((posText.length() * (rulerCharWidth)) / 2) ; int textPosX = (int)(tickPosX - stringSizeOffset); // dont draw text outside if(textPosX >=0){ // dont draw on top of last (if number is very long) if(lastTextEndPos < textPosX){ g2d.drawString(posText, textPosX, 10); lastTextEndPos = textPosX + stringSizeOffset + 40; // add 40 extra space between numbers } } // larger tick size tickSize = 3; }else{ // smaller tick size tickSize = 1; } // draw tick g2d.drawLine(tickPosX, (int) (rulerRect.getMaxY() - 2),tickPosX, (int)rulerRect.getMaxY() - 2 - tickSize); countTicks ++; } } } // end draw small char long endTime = System.currentTimeMillis(); logger.info("Ruler PaintComponent took " + (endTime - startTime) + " milliseconds"); } private int roundToClosestUpper(int inval, int roundTo) { // int rounded = ((num + 99) / 100 ) * 100; int rounded = ((inval + roundTo -1) / roundTo ) * roundTo; return rounded; } } // end Ruler class private class CharsetRuler extends JPanel{ private AlignmentPane alignmentPane; private Color[] charsetColors = new Color[]{new Color(107,215,204), new Color(239,189,93), new Color(215,127,163),new Color(210,213,102), new Color(127,107,215),new Color(203,241,136)}; public CharsetRuler(AlignmentPane alignmentPane) { this.alignmentPane = alignmentPane; // Add this component to ToolTipManager ToolTipManager.sharedInstance().registerComponent(this); } public void paintComponent(Graphics g){ super.paintComponent(g); if(isVisible() && !isShowTranslationOnePos()){ paintCharsetRuler(g); } } @Override public Dimension getPreferredSize(){ logger.info("get pref size"); if(!isVisible()){ return new Dimension(0,0); }else{ Dimension superSize = super.getPreferredSize(); int preferredHeight = calculatePreferredHeight(); return new Dimension(superSize.width, preferredHeight); } } private int calculatePreferredHeight(){ // loop through all charsets and see how many overlaps int maxCharsetOverlapCount = alignment.getAlignmentMeta().getCharsets().getMaxOverlapCount(); int preferredHeight = CHARSET_LINE_HEIGHT * (maxCharsetOverlapCount + 1); // 1 overlap means t return preferredHeight; } public void paintCharsetRuler(Graphics g){ long startTime = System.currentTimeMillis(); Graphics2D g2d = (Graphics2D) g; // What part of alignment matrix is in view (what part of matrix is in graphical view) Rectangle paneClip = alignmentPane.getVisibleRect(); Rectangle matrixClip = paneCoordToMatrixCoord(paneClip); // Draw ruler background Rectangle rulerRect = new Rectangle(this.getVisibleRect()); g2d.setColor(colorSchemeNucleotide.getBaseBackgroundColor(NucleotideUtilities.GAP)); g2d.fill(rulerRect); int offsetDueToScrollPanePosition = paneClip.x; CharSets charsets = alignment.getAlignmentMeta().getCharsets(); int maxCharsetOverlapCount = charsets.getMaxOverlapCount(); logger.info("maxCharsetOverlapCount" + maxCharsetOverlapCount); int maxX = Math.min(alignment.getMaxX(), (int) matrixClip.getMaxX()); int minX = (int) matrixClip.getMinX(); int colorIndex = 0; int charsetIndex = 0; for(CharSet charSet: charsets){ if(charSet.intersects(minX,maxX)){ logger.info("intersects" + charSet.getName()); int lineHeight = CHARSET_LINE_HEIGHT; int charsetLineYPos = (charsetIndex % (maxCharsetOverlapCount + 1)) * lineHeight; logger.info("charsetLineYPos" + charsetLineYPos); int charSetMinX = charSet.getMinimumStartPos(); int charSetMaxX = charSet.getMaximumEndPos(); Point charSetMinXPanePos = alignmentPane.matrixCoordToPaneCoord(new Point(charSetMinX, 0)); Point charSetMaxXPanePos = alignmentPane.matrixCoordToPaneCoord(new Point(charSetMaxX, 0)); int width = charSetMaxXPanePos.x - charSetMinXPanePos.x + (int)(1*charWidth); // + 1 because if start and end is same should be one pixel // we are drawing not on a large scrollable ruler, but a window sized fixed pane we have to adjust with offsetDueToScrollPanePosition Rectangle charsetRect = new Rectangle(charSetMinXPanePos.x - offsetDueToScrollPanePosition, charsetLineYPos, width, lineHeight); Color charsetColor = charsetColors[colorIndex % charsetColors.length]; g2d.setColor(charsetColor); g2d.fill(charsetRect); } charsetIndex ++; colorIndex ++; } long endTime = System.currentTimeMillis(); logger.info("CharsetRuler PaintComponent took " + (endTime - startTime) + " milliseconds"); } private int roundToClosestUpper(int inval, int roundTo) { // int rounded = ((num + 99) / 100 ) * 100; int rounded = ((inval + roundTo -1) / roundTo ) * roundTo; return rounded; } @Override public String getToolTipText(MouseEvent event) { logger.info("ToolTipLoc:" + event.getPoint()); Rectangle paneClip = alignmentPane.getVisibleRect(); int offsetDueToScrollPanePosition = paneClip.x; int xPosPane = offsetDueToScrollPanePosition + event.getPoint().x; Point posMatrix = paneCoordToMatrixCoord(new Point(xPosPane,0)); String toolTip = "<html>"; CharSets charsets = alignment.getAlignmentMeta().getCharsets(); for(CharSet charSet: charsets){ if(charSet.contains(posMatrix.x)){ toolTip += charSet.getName() + "<br>"; } } toolTip += "</html>"; return toolTip; } } // end CodonPosRuler class }