2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.renderer;
23 import java.awt.BasicStroke;
24 import java.awt.Color;
26 import java.awt.FontMetrics;
27 import java.awt.Graphics;
28 import java.awt.Graphics2D;
29 import java.awt.Image;
30 import java.awt.RenderingHints;
31 import java.awt.Stroke;
32 import java.awt.geom.AffineTransform;
33 import java.awt.image.ImageObserver;
34 import java.util.BitSet;
35 import java.util.HashMap;
36 import java.util.Hashtable;
39 import org.jfree.graphics2d.svg.SVGGraphics2D;
40 import org.jibble.epsgraphics.EpsGraphics2D;
42 import jalview.analysis.AAFrequency;
43 import jalview.analysis.CodingUtils;
44 import jalview.analysis.Rna;
45 import jalview.analysis.StructureFrequency;
46 import jalview.api.AlignViewportI;
47 import jalview.bin.Cache;
48 import jalview.bin.Console;
49 import jalview.datamodel.AlignmentAnnotation;
50 import jalview.datamodel.Annotation;
51 import jalview.datamodel.ColumnSelection;
52 import jalview.datamodel.HiddenColumns;
53 import jalview.datamodel.ProfilesI;
54 import jalview.renderer.api.AnnotationRendererFactoryI;
55 import jalview.renderer.api.AnnotationRowRendererI;
56 import jalview.schemes.ColourSchemeI;
57 import jalview.schemes.NucleotideColourScheme;
58 import jalview.schemes.ResidueProperties;
59 import jalview.schemes.ZappoColourScheme;
60 import jalview.util.Platform;
62 public class AnnotationRenderer
64 private static final int UPPER_TO_LOWER = 'a' - 'A'; // 32
66 private static final int CHAR_A = 'A'; // 65
68 private static final int CHAR_Z = 'Z'; // 90
71 * flag indicating if timing and redraw parameter info should be output
73 private final boolean debugRedraw;
75 private int charWidth, endRes, charHeight;
77 private boolean validCharWidth, hasHiddenColumns;
79 private FontMetrics fm;
81 private final boolean USE_FILL_ROUND_RECT = Platform.isAMacAndNotJS();
83 boolean av_renderHistogram = true, av_renderProfile = true,
84 av_normaliseProfile = false;
86 ResidueShaderI profcolour = null;
88 private ColumnSelection columnSelection;
90 private HiddenColumns hiddenColumns;
92 private ProfilesI hconsensus;
94 private Hashtable<String, Object>[] complementConsensus;
96 private Hashtable<String, Object>[] hStrucConsensus;
98 private boolean av_ignoreGapsConsensus;
100 private boolean renderingVectors = false;
102 private boolean glyphLineDrawn = false;
105 * attributes set from AwtRenderPanelI
108 * old image used when data is currently being calculated and cannot be
111 private Image fadedImage;
114 * panel being rendered into
116 private ImageObserver annotationPanel;
119 * width of image to render in panel
121 private int imgWidth;
124 * offset to beginning of visible area
129 * offset to end of visible area
131 private int visHeight;
134 * indicate if the renderer should only render the visible portion of the
135 * annotation given the current view settings
137 private boolean useClip = true;
140 * master flag indicating if renderer should ever try to clip. not enabled for
143 private boolean canClip = false;
146 * Property to set text antialiasing method
148 private static final String TEXT_ANTIALIAS_METHOD = "TEXT_ANTIALIAS_METHOD";
150 private static final Object textAntialiasMethod;
154 final String textAntialiasMethodPref = Cache
155 .getDefault(TEXT_ANTIALIAS_METHOD, "GASP");
156 switch (textAntialiasMethodPref)
159 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
162 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_GASP;
165 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HBGR;
168 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB;
171 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VBGR;
174 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB;
177 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_OFF;
180 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT;
183 jalview.bin.Console.warn(TEXT_ANTIALIAS_METHOD + " value '"
184 + textAntialiasMethodPref
185 + "' not recognised, defaulting to 'GASP'. See https://docs.oracle.com/javase/8/docs/api/java/awt/RenderingHints.html#KEY_TEXT_ANTIALIASING");
186 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_GASP;
190 public AnnotationRenderer()
196 * Create a new annotation Renderer
199 * flag indicating if timing and redraw parameter info should be
202 public AnnotationRenderer(boolean debugRedraw)
204 this.debugRedraw = debugRedraw;
208 * Remove any references and resources when this object is no longer required
210 public void dispose()
212 hiddenColumns = null;
214 complementConsensus = null;
215 hStrucConsensus = null;
217 annotationPanel = null;
218 rendererFactoryI = null;
221 void drawStemAnnot(Graphics g, Annotation[] row_annotations, int lastSSX,
222 int x, int y, int iconOffset, int startRes, int column,
223 boolean validRes, boolean validEnd)
225 int sCol = (lastSSX / charWidth)
226 + hiddenColumns.visibleToAbsoluteColumn(startRes);
228 int x2 = (x * charWidth);
230 char dc = (column == 0 || row_annotations[column - 1] == null) ? ' '
231 : row_annotations[column - 1].secondaryStructure;
233 boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
234 || dc != row_annotations[sCol - 1].secondaryStructure
236 boolean diffdownstream = !validRes || !validEnd
237 || row_annotations[column] == null
238 || dc != row_annotations[column].secondaryStructure;
240 if (diffupstream || diffdownstream)
242 // draw glyphline under arrow
243 drawGlyphLine(g, lastSSX, x, y, iconOffset);
245 g.setColor(STEM_COLOUR);
247 if (column > 0 && Rna.isClosingParenthesis(dc))
250 // if (validRes && column>1 && row_annotations[column-2]!=null &&
251 // dc.equals(row_annotations[column-2].displayCharacter))
254 * if new annotation with a closing base pair half of the stem,
255 * display a backward arrow
257 fillPolygon(g, new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
259 { y + iconOffset + 1, y + 13 + iconOffset,
260 y + 7 + iconOffset },
271 // display a forward arrow
275 * if annotation ending with an opeing base pair half of the stem,
276 * display a forward arrow
278 fillPolygon(g, new int[] { x2 - 6, x2 - 6, x2 - 1 },
280 { y + iconOffset + 1, y + 13 + iconOffset,
281 y + 7 + iconOffset },
292 fillRect(g, x1, y + 4 + iconOffset, x2 - x1, 6);
295 void drawNotCanonicalAnnot(Graphics g, Color nonCanColor,
296 Annotation[] row_annotations, int lastSSX, int x, int y,
297 int iconOffset, int startRes, int column, boolean validRes,
300 // Console.info(nonCanColor);
302 int sCol = (lastSSX / charWidth)
303 + hiddenColumns.visibleToAbsoluteColumn(startRes);
305 int x2 = (x * charWidth);
307 String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
308 : row_annotations[column - 1].displayCharacter;
310 boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
311 || !dc.equals(row_annotations[sCol - 1].displayCharacter)
313 boolean diffdownstream = !validRes || !validEnd
314 || row_annotations[column] == null
315 || !dc.equals(row_annotations[column].displayCharacter);
316 // Console.info("Column "+column+" diff up:
318 // down:"+diffdownstream);
319 // If a closing base pair half of the stem, display a backward arrow
320 if (diffupstream || diffdownstream)
322 // draw glyphline under arrow
323 drawGlyphLine(g, lastSSX, x, y, iconOffset);
325 g.setColor(nonCanColor);
326 if (column > 0 && Rna.isClosingParenthesis(dc))
330 // if (validRes && column>1 && row_annotations[column-2]!=null &&
331 // dc.equals(row_annotations[column-2].displayCharacter))
333 fillPolygon(g, new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
335 { y + iconOffset + 1, y + 13 + iconOffset,
336 y + 7 + iconOffset },
348 // display a forward arrow
351 fillPolygon(g, new int[] { x2 - 6, x2 - 6, x2 - 1 },
353 { y + iconOffset + 1, y + 13 + iconOffset,
354 y + 7 + iconOffset },
365 fillRect(g, x1, y + 4 + iconOffset, x2 - x1, 6);
368 // public void updateFromAnnotationPanel(FontMetrics annotFM, AlignViewportI
370 public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
373 fm = annotPanel.getFontMetrics();
374 annotationPanel = annotPanel;
375 fadedImage = annotPanel.getFadedImage();
376 imgWidth = annotPanel.getFadedImageWidth();
377 // visible area for rendering
378 int[] bounds = annotPanel.getVisibleVRange();
382 visHeight = bounds[1];
397 rendererFactoryI = AnnotationRendererFactory.getRendererFactory();
398 updateFromAlignViewport(av);
401 public void updateFromAlignViewport(AlignViewportI av)
403 charWidth = av.getCharWidth();
404 endRes = av.getRanges().getEndRes();
405 charHeight = av.getCharHeight();
406 hasHiddenColumns = av.hasHiddenColumns();
407 validCharWidth = av.isValidCharWidth();
408 av_renderHistogram = av.isShowConsensusHistogram();
409 av_renderProfile = av.isShowSequenceLogo();
410 av_normaliseProfile = av.isNormaliseSequenceLogo();
411 profcolour = av.getResidueShading();
412 if (profcolour == null || profcolour.getColourScheme() == null)
415 * Use default colour for sequence logo if
416 * the alignment has no colourscheme set
417 * (would like to use user preference but n/a for applet)
419 ColourSchemeI col = av.getAlignment().isNucleotide()
420 ? new NucleotideColourScheme()
421 : new ZappoColourScheme();
422 profcolour = new ResidueShader(col);
424 columnSelection = av.getColumnSelection();
425 hiddenColumns = av.getAlignment().getHiddenColumns();
426 hconsensus = av.getSequenceConsensusHash();
427 complementConsensus = av.getComplementConsensusHash();
428 hStrucConsensus = av.getRnaStructureConsensusHash();
429 av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
433 * Returns profile data; the first element is the profile type, the second is
434 * the number of distinct values, the third the total count, and the remainder
435 * depend on the profile type.
441 int[] getProfileFor(AlignmentAnnotation aa, int column)
443 // TODO : consider refactoring the global alignment calculation
444 // properties/rendering attributes as a global 'alignment group' which holds
445 // all vis settings for the alignment as a whole rather than a subset
447 if (aa.autoCalculated && (aa.label.startsWith("Consensus")
448 || aa.label.startsWith("cDNA Consensus")))
450 boolean forComplement = aa.label.startsWith("cDNA Consensus");
451 if (aa.groupRef != null && aa.groupRef.consensusData != null
452 && aa.groupRef.isShowSequenceLogo())
454 // TODO? group consensus for cDNA complement
455 return AAFrequency.extractProfile(
456 aa.groupRef.consensusData.get(column),
457 aa.groupRef.getIgnoreGapsConsensus());
459 // TODO extend annotation row to enable dynamic and static profile data to
461 if (aa.groupRef == null && aa.sequenceRef == null)
465 return AAFrequency.extractCdnaProfile(complementConsensus[column],
466 av_ignoreGapsConsensus);
470 return AAFrequency.extractProfile(hconsensus.get(column),
471 av_ignoreGapsConsensus);
477 if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
479 // TODO implement group structure consensus
481 * if (aa.groupRef != null && aa.groupRef.consensusData != null &&
482 * aa.groupRef.isShowSequenceLogo()) { //TODO check what happens for
483 * group selections return StructureFrequency.extractProfile(
484 * aa.groupRef.consensusData[column], aa.groupRef
485 * .getIgnoreGapsConsensus()); }
487 // TODO extend annotation row to enable dynamic and static profile data
490 if (aa.groupRef == null && aa.sequenceRef == null
491 && hStrucConsensus != null
492 && hStrucConsensus.length > column)
494 return StructureFrequency.extractProfile(hStrucConsensus[column],
495 av_ignoreGapsConsensus);
504 private AnnotationRendererFactoryI rendererFactoryI;
507 * Render the annotation rows associated with an alignment.
512 * data and view settings to render
514 * destination for graphics
516 * row where a mouse event occured (or -1)
518 * first column that will be drawn
520 * last column that will be drawn
521 * @return true if the fadedImage was used for any alignment annotation rows
522 * currently being calculated
524 public boolean drawComponent(AwtRenderPanelI annotPanel,
525 AlignViewportI av, Graphics g, int activeRow, int startRes,
528 return drawComponent(annotPanel, av, g, activeRow, startRes, endRes,
532 public boolean drawComponent(AwtRenderPanelI annotPanel,
533 AlignViewportI av, Graphics g, int activeRow, int startRes,
534 int endRes, boolean forExport)
536 if (g instanceof EpsGraphics2D || g instanceof SVGGraphics2D)
538 this.setVectorRendering(true);
540 Graphics2D g2d = (Graphics2D) g;
542 long stime = System.currentTimeMillis();
543 boolean usedFaded = false;
545 // AnnotationPanel needs to implement: ImageObserver, access to
547 updateFromAwtRenderPanel(annotPanel, av);
548 fm = g.getFontMetrics();
549 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
560 boolean validRes = false;
561 boolean validEnd = false;
562 boolean labelAllCols = false;
563 // boolean centreColLabels;
564 // boolean centreColLabelsDef = av.isCentreColumnLabels();
565 boolean scaleColLabel = false;
566 final AlignmentAnnotation consensusAnnot = av
567 .getAlignmentConsensusAnnotation();
568 final AlignmentAnnotation structConsensusAnnot = av
569 .getAlignmentStrucConsensusAnnotation();
570 final AlignmentAnnotation complementConsensusAnnot = av
571 .getComplementConsensusAnnotation();
573 BitSet graphGroupDrawn = new BitSet();
576 int yfrom = 0, f_i = 0, yto = 0, f_to = 0;
577 boolean clipst = false, clipend = false;
578 for (int i = 0; i < aa.length; i++)
580 AlignmentAnnotation row = aa[i];
581 boolean renderHistogram = true;
582 boolean renderProfile = false;
583 boolean normaliseProfile = false;
584 boolean isRNA = row.isRNA();
586 // check if this is a consensus annotation row and set the display
587 // settings appropriately
588 // TODO: generalise this to have render styles for consensus/profile
590 if (row.groupRef != null && row == row.groupRef.getConsensus())
592 renderHistogram = row.groupRef.isShowConsensusHistogram();
593 renderProfile = row.groupRef.isShowSequenceLogo();
594 normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
596 else if (row == consensusAnnot || row == structConsensusAnnot
597 || row == complementConsensusAnnot)
599 renderHistogram = av_renderHistogram;
600 renderProfile = av_renderProfile;
601 normaliseProfile = av_normaliseProfile;
604 Annotation[] row_annotations = row.annotations;
609 // centreColLabels = row.centreColLabels || centreColLabelsDef;
610 labelAllCols = row.showAllColLabels;
611 scaleColLabel = row.scaleColLabel;
615 if (!useClip || ((y - charHeight) < visHeight
616 && (y + row.height + charHeight * 2) >= sOffset))
617 {// if_in_visible_region
628 if (row.graphGroup > -1 && graphGroupDrawn.get(row.graphGroup))
633 // this is so that we draw the characters below the graph
638 iconOffset = charHeight - fm.getDescent();
642 else if (row.hasText)
644 iconOffset = charHeight - fm.getDescent();
652 if (row.autoCalculated && av.isCalculationInProgress(row))
656 g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0,
657 y - row.height, imgWidth, y, annotationPanel);
658 g.setColor(Color.black);
659 // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
665 * else if (annotationPanel.av.updatingConservation &&
666 * aa[i].label.equals("Conservation")) {
668 * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
669 * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
670 * annotationPanel.imgWidth, y, annotationPanel);
672 * g.setColor(Color.black); //
673 * g.drawString("Calculating Conservation.....",20, y-row.height/2);
675 * continue; } else if (annotationPanel.av.updatingConservation &&
676 * aa[i].label.equals("Quality")) {
678 * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
679 * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
680 * annotationPanel.imgWidth, y, annotationPanel);
681 * g.setColor(Color.black); // /
682 * g.drawString("Calculating Quality....",20, y-row.height/2);
687 // first pass sets up state for drawing continuation from left-hand
691 // flag used for vector rendition
692 this.glyphLineDrawn = false;
693 x = (startRes == 0) ? 0 : -1;
695 while (x < endRes - startRes)
697 if (hasHiddenColumns)
699 column = hiddenColumns.visibleToAbsoluteColumn(startRes + x);
700 if (column > row_annotations.length - 1)
707 column = startRes + x;
710 if ((row_annotations == null)
711 || (row_annotations.length <= column)
712 || (row_annotations[column] == null))
720 final String displayChar = validRes
721 ? row_annotations[column].displayCharacter
728 g.setColor(Color.red);
730 if (columnSelection != null)
732 if (columnSelection.contains(column))
734 fillRect(g, x * charWidth, y, charWidth, charHeight);
738 if (row.getInvalidStrucPos() > x)
740 g.setColor(Color.orange);
741 fillRect(g, x * charWidth, y, charWidth, charHeight);
743 else if (row.getInvalidStrucPos() == x)
745 g.setColor(Color.orange.darker());
746 fillRect(g, x * charWidth, y, charWidth, charHeight);
748 if (validCharWidth && validRes && displayChar != null
749 && (displayChar.length() > 0))
751 float fmWidth = fm.stringWidth(displayChar);
754 * shrink label width to fit in column, if that is
755 * both configured and necessary
757 boolean scaledToFit = false;
758 float fmScaling = 1f;
759 if (scaleColLabel && fmWidth > charWidth)
762 fmScaling = (float) charWidth / fmWidth;
763 // and update the label's width to reflect the scaling.
767 float charOffset = (charWidth - fmWidth) / 2f;
769 if (row_annotations[column].colour == null)
771 g2d.setColor(Color.black);
775 g2d.setColor(row_annotations[column].colour);
779 * draw the label, unless it is the same secondary structure
780 * symbol (excluding RNA Helix) as the previous column
782 final float xPos = (x * charWidth) + charOffset;
783 final float yPos = y + iconOffset;
785 // Act on a copy of the Graphics2d object
786 Graphics2D g2dCopy = (Graphics2D) g2d.create();
787 // Clip to this annotation line (particularly width).
788 // This removes artifacts from text when side scrolling
789 // (particularly in wrap format), but can result in clipped
790 // characters until a full paint is drawn.
791 // If we're in an image export, set the clip width to be the
792 // entire width of the annotation.
794 int clipWidth = forExport
795 ? row_annotations.length * charWidth - 1
797 g2dCopy.setClip(0, (int) yPos - charHeight, clipWidth,
800 * translate to drawing position _before_ applying any scaling
802 g2dCopy.translate(xPos, yPos);
806 * use a scaling transform to make the label narrower
807 * (JalviewJS doesn't have Font.deriveFont(AffineTransform))
810 AffineTransform.getScaleInstance(fmScaling, 1.0));
812 setAntialias(g2dCopy, true);
813 if (column == 0 || row.graph > 0)
815 g2dCopy.drawString(displayChar, 0, 0);
817 else if (row_annotations[column - 1] == null || (labelAllCols
818 || !displayChar.equals(
819 row_annotations[column - 1].displayCharacter)
820 || (displayChar.length() < 2
821 && row_annotations[column].secondaryStructure == ' ')))
823 g2dCopy.drawString(displayChar, 0, 0);
830 char ss = validRes ? row_annotations[column].secondaryStructure
835 // distinguish between forward/backward base-pairing
836 if (displayChar.indexOf(')') > -1)
845 if ((displayChar.indexOf(']') > -1))
853 // distinguish between forward/backward base-pairing
854 if (displayChar.indexOf('}') > -1)
862 // distinguish between forward/backward base-pairing
863 if (displayChar.indexOf('<') > -1)
869 if (isRNA && (ss >= CHAR_A) && (ss <= CHAR_Z))
871 // distinguish between forward/backward base-pairing
872 int ssLowerCase = ss + UPPER_TO_LOWER;
873 // TODO would .equals() be safer here? or charAt(0)?
874 if (displayChar.indexOf(ssLowerCase) > -1)
876 ss = (char) ssLowerCase;
880 if (!validRes || (ss != lastSS))
886 // int nb_annot = x - temp;
887 // Console.info("\t type :"+lastSS+"\t x
889 // annot :"+nb_annot);
892 case '(': // Stem case for RNA secondary structure
893 case ')': // and opposite direction
894 drawStemAnnot(g, row_annotations, lastSSX, x, y,
895 iconOffset, startRes, column, validRes, validEnd);
902 drawHelixAnnot(g, row_annotations, lastSSX, x, y,
903 iconOffset, startRes, column, validRes,
907 // no break if isRNA - falls through to drawNotCanonicalAnnot!
911 drawSheetAnnot(g, row_annotations, lastSSX, x, y,
912 iconOffset, startRes, column, validRes,
916 // no break if isRNA - fall through to drawNotCanonicalAnnot!
975 Color nonCanColor = getNotCanonicalColor(lastSS);
976 drawNotCanonicalAnnot(g, nonCanColor, row_annotations,
977 lastSSX, x, y, iconOffset, startRes, column,
982 if (isVectorRendering())
984 // draw single full width glyphline
985 drawGlyphLine(g, lastSSX, endRes - x, y, iconOffset);
986 // disable more glyph lines
987 this.glyphLineDrawn = true;
991 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1006 lastSSX = (x * charWidth);
1013 if (column >= row_annotations.length)
1015 column = row_annotations.length - 1;
1022 if ((row_annotations == null) || (row_annotations.length <= column)
1023 || (row_annotations[column] == null))
1041 drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1042 startRes, column, validRes, validEnd);
1045 // no break if isRNA - fall through to drawNotCanonicalAnnot!
1050 drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1051 startRes, column, validRes, validEnd);
1054 // no break if isRNA - fall through to drawNotCanonicalAnnot!
1057 case ')': // Stem case for RNA secondary structure
1059 drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1060 startRes, column, validRes, validEnd);
1117 // Console.info(lastSS);
1118 Color nonCanColor = getNotCanonicalColor(lastSS);
1119 drawNotCanonicalAnnot(g, nonCanColor, row_annotations, lastSSX,
1120 x, y, iconOffset, startRes, column, validRes, validEnd);
1123 if (isVectorRendering())
1125 // draw single full width glyphline
1126 drawGlyphLine(g, lastSSX, endRes - x, y, iconOffset);
1127 // disable more glyph lines
1128 this.glyphLineDrawn = true;
1132 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1138 if (row.graph > 0 && row.graphHeight > 0)
1140 if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1142 if (row.graphGroup > -1 && !graphGroupDrawn.get(row.graphGroup))
1144 // TODO: JAL-1291 revise rendering model so the graphGroup map is
1145 // computed efficiently for all visible labels
1146 float groupmax = -999999, groupmin = 9999999;
1147 for (int gg = 0; gg < aa.length; gg++)
1149 if (aa[gg].graphGroup != row.graphGroup)
1156 aa[gg].visible = false;
1158 if (aa[gg].graphMax > groupmax)
1160 groupmax = aa[gg].graphMax;
1162 if (aa[gg].graphMin < groupmin)
1164 groupmin = aa[gg].graphMin;
1168 for (int gg = 0; gg < aa.length; gg++)
1170 if (aa[gg].graphGroup == row.graphGroup)
1172 drawLineGraph(g, aa[gg], aa[gg].annotations, startRes,
1173 endRes, y, groupmin, groupmax, row.graphHeight);
1177 graphGroupDrawn.set(row.graphGroup);
1181 drawLineGraph(g, row, row_annotations, startRes, endRes, y,
1182 row.graphMin, row.graphMax, row.graphHeight);
1185 else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1187 drawBarGraph(g, row, row_annotations, startRes, endRes,
1188 row.graphMin, row.graphMax, y, renderHistogram,
1189 renderProfile, normaliseProfile);
1193 AnnotationRowRendererI renderer = rendererFactoryI
1194 .getRendererFor(row);
1195 if (renderer != null)
1197 renderer.renderRow(g, charWidth, charHeight, hasHiddenColumns,
1198 av, hiddenColumns, columnSelection, row,
1199 row_annotations, startRes, endRes, row.graphMin,
1200 row.graphMax, y, isVectorRendering());
1204 if (renderer == null)
1207 .println("No renderer found for " + row.toString());
1212 "rendered with " + renderer.getClass().toString());
1221 if (clipst && !clipend)
1225 } // end if_in_visible_region
1226 if (row.graph > 0 && row.hasText)
1242 Console.warn("Start clip at : " + yfrom + " (index " + f_i + ")");
1246 Console.warn("End clip at : " + yto + " (index " + f_to + ")");
1250 Console.warn("Annotation Rendering time:"
1251 + (System.currentTimeMillis() - stime));
1258 public static final Color GLYPHLINE_COLOR = Color.gray;
1260 public static final Color SHEET_COLOUR = Color.green;
1262 public static final Color HELIX_COLOUR = Color.red;
1264 public static final Color STEM_COLOUR = Color.blue;
1266 // private Color sdNOTCANONICAL_COLOUR;
1268 void drawGlyphLine(Graphics g, int lastSSX, int x, int y, int iconOffset)
1272 // if we've drawn a single long glyphline for an export, don't draw the
1277 g.setColor(GLYPHLINE_COLOR);
1278 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
1281 void drawSheetAnnot(Graphics g, Annotation[] row,
1283 int lastSSX, int x, int y, int iconOffset, int startRes,
1284 int column, boolean validRes, boolean validEnd)
1286 if (!validEnd || !validRes || row == null || row[column] == null
1287 || row[column].secondaryStructure != 'E')
1289 // draw the glyphline underneath
1290 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1292 g.setColor(SHEET_COLOUR);
1293 fillRect(g, lastSSX, y + 4 + iconOffset,
1294 (x * charWidth) - lastSSX - 4, 6);
1297 { (x * charWidth) - 6, (x * charWidth) - 6,
1298 (x * charWidth - 1) },
1300 { y + iconOffset + 1, y + 13 + iconOffset,
1301 y + 7 + iconOffset },
1306 g.setColor(SHEET_COLOUR);
1307 fillRect(g, lastSSX, y + 4 + iconOffset, (x * charWidth) - lastSSX,
1312 void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX, int x,
1313 int y, int iconOffset, int startRes, int column, boolean validRes,
1316 int sCol = (lastSSX / charWidth)
1317 + hiddenColumns.visibleToAbsoluteColumn(startRes);
1319 int x2 = (x * charWidth);
1321 if (USE_FILL_ROUND_RECT || isVectorRendering())
1323 // draw glyph line behind helix (visible in EPS or SVG output)
1324 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1326 g.setColor(HELIX_COLOUR);
1328 int ofs = charWidth / 2;
1329 // Off by 1 offset when drawing rects and ovals
1330 // to offscreen image on the MAC
1331 fillRoundRect(g, lastSSX, y + 3 + iconOffset, x2 - x1 - 1, 8, 8, 8);
1332 if (sCol == 0 || row[sCol - 1] == null
1333 || row[sCol - 1].secondaryStructure != 'H')
1338 fillRoundRect(g, lastSSX, y + 3 + iconOffset, x2 - x1 - ofs, 8, 0,
1341 if (!validRes || row[column] == null
1342 || row[column].secondaryStructure != 'H')
1348 fillRoundRect(g, lastSSX + ofs, y + 3 + iconOffset, x2 - x1 - ofs,
1355 boolean leftEnd = sCol == 0 || row[sCol - 1] == null
1356 || row[sCol - 1].secondaryStructure != 'H';
1357 boolean rightEnd = !validRes || row[column] == null
1358 || row[column].secondaryStructure != 'H';
1360 if (leftEnd || rightEnd)
1362 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1364 g.setColor(HELIX_COLOUR);
1368 fillArc(g, lastSSX, y + 3 + iconOffset, charWidth, 8, 90, 180);
1369 x1 += charWidth / 2;
1374 fillArc(g, ((x - 1) * charWidth), y + 3 + iconOffset, charWidth, 8,
1376 x2 -= charWidth / 2;
1379 fillRect(g, x1, y + 3 + iconOffset, x2 - x1, 8);
1382 void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
1383 Annotation[] aa_annotations, int sRes, int eRes, int y, float min,
1384 float max, int graphHeight)
1386 if (sRes > aa_annotations.length)
1390 Stroke roundStroke = new BasicStroke(1, BasicStroke.CAP_ROUND,
1391 BasicStroke.JOIN_ROUND);
1392 Stroke squareStroke = new BasicStroke(1, BasicStroke.CAP_SQUARE,
1393 BasicStroke.JOIN_MITER);
1394 Graphics2D g2d = (Graphics2D) g;
1395 Stroke prevStroke = g2d.getStroke();
1396 g2d.setStroke(roundStroke);
1400 // Adjustment for fastpaint to left
1406 eRes = Math.min(eRes, aa_annotations.length);
1409 float range = max - min;
1414 y2 = y - (int) ((0 - min / range) * graphHeight);
1417 g.setColor(Color.gray);
1418 drawLine(g, squareStroke, x * charWidth, y2, (eRes - sRes) * charWidth,
1426 eRes = Math.min(eRes, aa_annotations.length);
1429 int aaMax = aa_annotations.length - 1;
1431 while (x <= eRes - sRes)
1434 if (hasHiddenColumns)
1436 column = hiddenColumns.visibleToAbsoluteColumn(column);
1444 if (aa_annotations[column] == null)
1450 if (aa_annotations[column].colour == null)
1452 g.setColor(Color.black);
1456 g.setColor(aa_annotations[column].colour);
1459 boolean previousValueExists = column > 0
1460 && aa_annotations[column - 1] != null;
1461 float previousValue = previousValueExists
1462 ? aa_annotations[column - 1].value
1464 float thisValue = aa_annotations[column].value;
1465 boolean nextValueExists = aa_annotations.length > column + 1
1466 && aa_annotations[column + 1] != null;
1467 float nextValue = nextValueExists ? aa_annotations[column + 1].value
1470 // check for standalone value
1471 if (!previousValueExists && !nextValueExists)
1473 y2 = y - yValueToPixelHeight(thisValue, min, range, graphHeight);
1474 drawLine(g, x * charWidth + charWidth / 4, y2,
1475 x * charWidth + 3 * charWidth / 4, y2);
1480 if (!previousValueExists)
1486 y1 = y - yValueToPixelHeight(previousValue, min, range, graphHeight);
1487 y2 = y - yValueToPixelHeight(thisValue, min, range, graphHeight);
1491 // only draw an initial half-line
1492 drawLine(g, x * charWidth, y1 + (y2 - y1) / 2,
1493 x * charWidth + charWidth / 2, y2);
1496 else if (x == eRes - sRes)
1498 // this is one past the end to draw -- only draw a half line
1499 drawLine(g, (x - 1) * charWidth + charWidth / 2, y1,
1500 x * charWidth - 1, y1 + (y2 - y1) / 2);
1505 drawLine(g, (x - 1) * charWidth + charWidth / 2, y1,
1506 x * charWidth + charWidth / 2, y2);
1511 if (_aa.threshold != null)
1513 g.setColor(_aa.threshold.colour);
1514 Graphics2D g2 = (Graphics2D) g;
1515 y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
1516 drawLine(g, dashedLine(charWidth), 0, y2, (eRes - sRes) * charWidth,
1519 g2d.setStroke(prevStroke);
1522 private static double log2 = Math.log(2);
1524 // Cached dashed line Strokes
1525 private static Map<Integer, Stroke> dashedLineLookup = new HashMap<>();
1528 * Returns a dashed line stroke as close to 6-4 pixels as fits within the
1529 * charWidth. This allows translations of multiples of charWidth without
1530 * disrupting the dashed line. The exact values are 0.6-0.4 proportions of
1531 * charWidth for charWidth under 16. For charWidth 16 or over, the number of
1532 * dashes doubles as charWidth doubles.
1535 * @return Stroke with appropriate dashed line fitting exactly within the
1538 private static Stroke dashedLine(int charWidth)
1540 if (!dashedLineLookup.containsKey(charWidth))
1542 int power2 = charWidth >= 8 ? (int) (Math.log(charWidth) / log2) : 2;
1543 float width = ((float) charWidth) / ((float) Math.pow(2, power2 - 3));
1544 float segment1 = width * 0.6f;
1545 float segment2 = width - segment1;
1546 dashedLineLookup.put(charWidth, new BasicStroke(1,
1547 BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 3f, new float[]
1548 { segment1, segment2 }, 0f));
1550 return dashedLineLookup.get(charWidth);
1553 private static int yValueToPixelHeight(float value, float min,
1554 float range, int graphHeight)
1556 return (int) (((value - min) / range) * graphHeight);
1559 @SuppressWarnings("unused")
1560 void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
1561 Annotation[] aa_annotations, int sRes, int eRes, float min,
1562 float max, int y, boolean renderHistogram, boolean renderProfile,
1563 boolean normaliseProfile)
1565 if (sRes > aa_annotations.length)
1569 Font ofont = g.getFont();
1570 eRes = Math.min(eRes, aa_annotations.length);
1572 int x = 0, y1 = y, y2 = y;
1574 float range = max - min;
1578 y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
1581 g.setColor(Color.gray);
1583 drawLine(g, x, y2, (eRes - sRes) * charWidth, y2);
1586 int aaMax = aa_annotations.length - 1;
1587 while (x < eRes - sRes)
1590 if (hasHiddenColumns)
1592 column = hiddenColumns.visibleToAbsoluteColumn(column);
1600 if (aa_annotations[column] == null)
1605 if (aa_annotations[column].colour == null)
1607 g.setColor(Color.black);
1611 g.setColor(aa_annotations[column].colour);
1614 y1 = y - (int) (((aa_annotations[column].value - min) / (range))
1617 if (renderHistogram)
1621 fillRect(g, x * charWidth, y2, charWidth, y1 - y2);
1625 fillRect(g, x * charWidth, y1, charWidth, y2 - y1);
1628 // draw profile if available
1633 * {profile type, #values, total count, char1, pct1, char2, pct2...}
1635 int profl[] = getProfileFor(_aa, column);
1637 // just try to draw the logo if profl is not null
1638 if (profl != null && profl[2] != 0)
1640 boolean isStructureProfile = profl[0] == AlignmentAnnotation.STRUCTURE_PROFILE;
1641 boolean isCdnaProfile = profl[0] == AlignmentAnnotation.CDNA_PROFILE;
1642 float ht = normaliseProfile ? y - _aa.graphHeight : y1;
1643 final double normaliseFactor = normaliseProfile ? _aa.graphHeight
1647 * Render a single base for a sequence profile, a base pair for
1648 * structure profile, and a triplet for a cdna profile
1650 char[] dc = new char[isStructureProfile ? 2
1651 : (isCdnaProfile ? 3 : 1)];
1653 // lm is not necessary - we can just use fm - could be off by no more
1655 // LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
1656 // Console.info(asc + " " + dec + " " + (asc -
1658 // + " " + (dec - lm.getDescent()));
1660 double asc = fm.getAscent();
1661 double dec = fm.getDescent();
1662 double fht = fm.getHeight();
1664 double scale = 1f / (normaliseProfile ? profl[2] : 100f);
1665 // float ofontHeight = 1f / fm.getAscent();// magnify to fill box
1668 * Traverse the character(s)/percentage data in the array
1673 // profl[1] is the number of values in the profile
1674 for (int i = 0, c = 3, last = profl[1]; i < last; i++)
1678 if (isStructureProfile)
1680 // todo can we encode a structure pair as an int, like codons?
1681 dc[0] = (char) profl[c++];
1682 dc[1] = (char) profl[c++];
1685 else if (isCdnaProfile)
1687 CodingUtils.decodeCodon2(profl[c++], dc);
1692 dc[0] = (char) profl[c++];
1695 // next profl[] position is profile % for the character(s)
1697 int percent = profl[c++];
1700 // failsafe in case a count rounds down to 0%
1703 double newHeight = normaliseFactor * scale * percent;
1706 * Set character colour as per alignment colour scheme; use the
1707 * codon translation if a cDNA profile
1709 Color colour = null;
1712 final String codonTranslation = ResidueProperties
1714 colour = profcolour.findColour(codonTranslation.charAt(0),
1719 colour = profcolour.findColour(dc[0], column, null);
1721 g.setColor(colour == Color.white ? Color.lightGray : colour);
1723 double sx = 1f * charWidth / fm.charsWidth(dc, 0, dc.length);
1724 double sy = newHeight / asc;
1725 double newAsc = asc * sy;
1726 double newDec = dec * sy;
1727 // it is not necessary to recalculate lm for the new font.
1728 // note: lm.getBaselineOffsets()[lm.getBaselineIndex()]) must be 0
1729 // by definition. Was:
1730 // int hght = (int) (ht + (newAsc - newDec);
1731 // - lm.getBaselineOffsets()[lm.getBaselineIndex()]));
1733 if (Platform.isJS())
1736 * SwingJS does not implement font.deriveFont()
1737 * so use a scaling transform to draw instead,
1738 * this is off by a very small amount
1740 final int hght = (int) (ht2 + (newAsc - newDec));
1741 Graphics2D gg = (Graphics2D) g;
1742 int xShift = (int) Math.round(x * charWidth / sx);
1743 int yShift = (int) Math.round(hght / sy);
1744 gg.transform(AffineTransform.getScaleInstance(sx, sy));
1745 gg.drawString(s, xShift, yShift);
1747 AffineTransform.getScaleInstance(1D / sx, 1D / sy));
1757 // Java ('normal') method is to scale the font to fit
1759 final int hght = (int) (ht + (newAsc - newDec));
1761 .deriveFont(AffineTransform.getScaleInstance(sx, sy));
1763 g.drawChars(dc, 0, dc.length, x * charWidth, hght);
1773 if (_aa.threshold != null)
1775 g.setColor(_aa.threshold.colour);
1776 Stroke s = new BasicStroke(1, BasicStroke.CAP_SQUARE,
1777 BasicStroke.JOIN_ROUND, 3f, new float[]
1781 - ((_aa.threshold.value - min) / range) * _aa.graphHeight);
1782 drawLine(g, s, 0, y2, (eRes - sRes) * charWidth, y2);
1786 // used by overview window
1787 public void drawGraph(Graphics g, AlignmentAnnotation _aa,
1788 Annotation[] aa_annotations, int width, int y, int sRes, int eRes)
1790 eRes = Math.min(eRes, aa_annotations.length);
1791 g.setColor(Color.white);
1792 fillRect(g, 0, 0, width, y);
1793 g.setColor(new Color(0, 0, 180));
1797 for (int j = sRes; j < eRes; j++)
1799 if (aa_annotations[j] != null)
1801 if (aa_annotations[j].colour == null)
1803 g.setColor(Color.black);
1807 g.setColor(aa_annotations[j].colour);
1810 height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
1816 fillRect(g, x, y - height, charWidth, height);
1822 Color getNotCanonicalColor(char lastss)
1828 return new Color(255, 125, 5);
1832 return new Color(245, 115, 10);
1836 return new Color(235, 135, 15);
1840 return new Color(225, 105, 20);
1844 return new Color(215, 145, 30);
1848 return new Color(205, 95, 35);
1852 return new Color(195, 155, 45);
1856 return new Color(185, 85, 55);
1860 return new Color(175, 165, 65);
1864 return new Color(170, 75, 75);
1868 return new Color(160, 175, 85);
1872 return new Color(150, 65, 95);
1876 return new Color(140, 185, 105);
1880 return new Color(130, 55, 110);
1884 return new Color(120, 195, 120);
1888 return new Color(110, 45, 130);
1892 return new Color(100, 205, 140);
1896 return new Color(90, 35, 150);
1900 return new Color(85, 215, 160);
1904 return new Color(75, 25, 170);
1908 return new Color(65, 225, 180);
1912 return new Color(55, 15, 185);
1916 return new Color(45, 235, 195);
1920 return new Color(35, 5, 205);
1924 return new Color(25, 245, 215);
1928 return new Color(15, 0, 225);
1932 return new Color(10, 255, 235);
1936 return new Color(5, 150, 245);
1940 return new Color(0, 80, 255);
1943 Console.info("This is not a interaction : " + lastss);
1949 private void fillPolygon(Graphics g, int[] xpoints, int[] ypoints, int n)
1952 g.fillPolygon(xpoints, ypoints, n);
1956 private void fillRect(Graphics g, int a, int b, int c, int d)
1958 fillRect(g, false, a, b, c, d);
1961 private void fillRect(Graphics g, int a, int b, int c, int d)
1964 g.fillRect(a, b, c, d);
1967 private void fillRoundRect(Graphics g, int a, int b, int c, int d, int e,
1971 g.fillRoundRect(a, b, c, d, e, f);
1974 private void fillArc(Graphics g, int a, int b, int c, int d, int e, int f)
1977 g.fillArc(a, b, c, d, e, f);
1980 private void drawLine(Graphics g, Stroke s, int a, int b, int c, int d)
1982 Graphics2D g2d = (Graphics2D) g;
1983 Stroke p = g2d.getStroke();
1985 drawLine(g, a, b, c, d);
1989 private void drawLine(Graphics g, int a, int b, int c, int d)
1992 g.drawLine(a, b, c, d);
1995 private void setAntialias(Graphics g)
1997 setAntialias(g, false);
2000 private void setAntialias(Graphics g, boolean text)
2002 if (isVectorRendering())
2004 // no need to antialias vector drawings
2007 if (Cache.getDefault("ANTI_ALIAS", true))
2009 Graphics2D g2d = (Graphics2D) g;
2012 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
2013 this.textAntialiasMethod);
2017 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
2018 RenderingHints.VALUE_ANTIALIAS_ON);
2023 private void unsetAntialias(Graphics g)
2025 if (isVectorRendering())
2027 // no need to antialias vector drawings
2030 Graphics2D g2d = (Graphics2D) g;
2031 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
2032 RenderingHints.VALUE_ANTIALIAS_OFF);
2035 public void setVectorRendering(boolean b)
2037 renderingVectors = b;
2040 public boolean isVectorRendering()
2042 return renderingVectors;