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.Collections;
36 import java.util.HashMap;
37 import java.util.Hashtable;
38 import java.util.List;
41 import org.jfree.graphics2d.svg.SVGGraphics2D;
42 import org.jibble.epsgraphics.EpsGraphics2D;
44 import jalview.analysis.AAFrequency;
45 import jalview.analysis.CodingUtils;
46 import jalview.analysis.Rna;
47 import jalview.analysis.StructureFrequency;
48 import jalview.api.AlignViewportI;
49 import jalview.bin.Cache;
50 import jalview.bin.Console;
51 import jalview.datamodel.AlignmentAnnotation;
52 import jalview.datamodel.Annotation;
53 import jalview.datamodel.ColumnSelection;
54 import jalview.datamodel.HiddenColumns;
55 import jalview.datamodel.ProfilesI;
56 import jalview.datamodel.annotations.AnnotationColouringI;
57 import jalview.renderer.api.AnnotationRendererFactoryI;
58 import jalview.renderer.api.AnnotationRowRendererI;
59 import jalview.schemes.ColourSchemeI;
60 import jalview.schemes.NucleotideColourScheme;
61 import jalview.schemes.ResidueProperties;
62 import jalview.schemes.ZappoColourScheme;
63 import jalview.util.Platform;
65 public class AnnotationRenderer
67 private static final int UPPER_TO_LOWER = 'a' - 'A'; // 32
69 private static final int CHAR_A = 'A'; // 65
71 private static final int CHAR_Z = 'Z'; // 90
74 * flag indicating if timing and redraw parameter info should be output
76 private final boolean debugRedraw;
78 private int charWidth, endRes, charHeight;
80 private boolean validCharWidth, hasHiddenColumns;
82 private FontMetrics fm;
84 private final boolean USE_FILL_ROUND_RECT = Platform.isAMacAndNotJS();
86 boolean av_renderHistogram = true, av_renderProfile = true,
87 av_normaliseProfile = false;
89 ResidueShaderI profcolour = null;
91 private ColumnSelection columnSelection;
93 private HiddenColumns hiddenColumns;
95 private ProfilesI hconsensus;
97 private Hashtable<String, Object>[] complementConsensus;
99 private Hashtable<String, Object>[] hStrucConsensus;
101 private boolean av_ignoreGapsConsensus;
103 private boolean renderingVectors = false;
105 private boolean glyphLineDrawn = false;
108 * attributes set from AwtRenderPanelI
111 * old image used when data is currently being calculated and cannot be
114 private Image fadedImage;
117 * panel being rendered into
119 private ImageObserver annotationPanel;
122 * width of image to render in panel
124 private int imgWidth;
127 * offset to beginning of visible area
132 * offset to end of visible area
134 private int visHeight;
137 * indicate if the renderer should only render the visible portion of the
138 * annotation given the current view settings
140 private boolean useClip = true;
143 * master flag indicating if renderer should ever try to clip. not enabled for
146 private boolean canClip = false;
149 * Property to set text antialiasing method
151 private static final String TEXT_ANTIALIAS_METHOD = "TEXT_ANTIALIAS_METHOD";
153 private static final Object textAntialiasMethod;
157 final String textAntialiasMethodPref = Cache
158 .getDefault(TEXT_ANTIALIAS_METHOD, "GASP");
159 switch (textAntialiasMethodPref)
162 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
165 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_GASP;
168 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HBGR;
171 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB;
174 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VBGR;
177 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB;
180 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_OFF;
183 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT;
186 jalview.bin.Console.warn(TEXT_ANTIALIAS_METHOD + " value '"
187 + textAntialiasMethodPref
188 + "' not recognised, defaulting to 'GASP'. See https://docs.oracle.com/javase/8/docs/api/java/awt/RenderingHints.html#KEY_TEXT_ANTIALIASING");
189 textAntialiasMethod = RenderingHints.VALUE_TEXT_ANTIALIAS_GASP;
193 public AnnotationRenderer()
199 * Create a new annotation Renderer
202 * flag indicating if timing and redraw parameter info should be
205 public AnnotationRenderer(boolean debugRedraw)
207 this.debugRedraw = debugRedraw;
211 * Remove any references and resources when this object is no longer required
213 public void dispose()
215 hiddenColumns = null;
217 complementConsensus = null;
218 hStrucConsensus = null;
220 annotationPanel = null;
221 rendererFactoryI = null;
224 void drawStemAnnot(Graphics g, Annotation[] row_annotations, int lastSSX,
225 int x, int y, int iconOffset, int startRes, int column,
226 boolean validRes, boolean validEnd)
228 int sCol = (lastSSX / charWidth)
229 + hiddenColumns.visibleToAbsoluteColumn(startRes);
231 int x2 = (x * charWidth);
233 char dc = (column == 0 || row_annotations[column - 1] == null) ? ' '
234 : row_annotations[column - 1].secondaryStructure;
236 boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
237 || dc != row_annotations[sCol - 1].secondaryStructure
239 boolean diffdownstream = !validRes || !validEnd
240 || row_annotations[column] == null
241 || dc != row_annotations[column].secondaryStructure;
243 if (diffupstream || diffdownstream)
245 // draw glyphline under arrow
246 drawGlyphLine(g, lastSSX, x, y, iconOffset);
248 g.setColor(STEM_COLOUR);
250 if (column > 0 && Rna.isClosingParenthesis(dc))
253 // if (validRes && column>1 && row_annotations[column-2]!=null &&
254 // dc.equals(row_annotations[column-2].displayCharacter))
257 * if new annotation with a closing base pair half of the stem,
258 * display a backward arrow
260 fillPolygon(g, new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
262 { y + iconOffset + 1, y + 13 + iconOffset,
263 y + 7 + iconOffset },
274 // display a forward arrow
278 * if annotation ending with an opeing base pair half of the stem,
279 * display a forward arrow
281 fillPolygon(g, new int[] { x2 - 6, x2 - 6, x2 - 1 },
283 { y + iconOffset + 1, y + 13 + iconOffset,
284 y + 7 + iconOffset },
295 fillRect(g, x1, y + 4 + iconOffset, x2 - x1, 6);
298 void drawNotCanonicalAnnot(Graphics g, Color nonCanColor,
299 Annotation[] row_annotations, int lastSSX, int x, int y,
300 int iconOffset, int startRes, int column, boolean validRes,
303 // Console.info(nonCanColor);
305 int sCol = (lastSSX / charWidth)
306 + hiddenColumns.visibleToAbsoluteColumn(startRes);
308 int x2 = (x * charWidth);
310 String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
311 : row_annotations[column - 1].displayCharacter;
313 boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
314 || !dc.equals(row_annotations[sCol - 1].displayCharacter)
316 boolean diffdownstream = !validRes || !validEnd
317 || row_annotations[column] == null
318 || !dc.equals(row_annotations[column].displayCharacter);
319 // Console.info("Column "+column+" diff up:
321 // down:"+diffdownstream);
322 // If a closing base pair half of the stem, display a backward arrow
323 if (diffupstream || diffdownstream)
325 // draw glyphline under arrow
326 drawGlyphLine(g, lastSSX, x, y, iconOffset);
328 g.setColor(nonCanColor);
329 if (column > 0 && Rna.isClosingParenthesis(dc))
333 // if (validRes && column>1 && row_annotations[column-2]!=null &&
334 // dc.equals(row_annotations[column-2].displayCharacter))
336 fillPolygon(g, new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
338 { y + iconOffset + 1, y + 13 + iconOffset,
339 y + 7 + iconOffset },
351 // display a forward arrow
354 fillPolygon(g, new int[] { x2 - 6, x2 - 6, x2 - 1 },
356 { y + iconOffset + 1, y + 13 + iconOffset,
357 y + 7 + iconOffset },
368 fillRect(g, x1, y + 4 + iconOffset, x2 - x1, 6);
371 // public void updateFromAnnotationPanel(FontMetrics annotFM, AlignViewportI
373 public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
376 fm = annotPanel.getFontMetrics();
377 annotationPanel = annotPanel;
378 fadedImage = annotPanel.getFadedImage();
379 imgWidth = annotPanel.getFadedImageWidth();
380 // visible area for rendering
381 int[] bounds = annotPanel.getVisibleVRange();
385 visHeight = bounds[1];
400 rendererFactoryI = AnnotationRendererFactory.getRendererFactory();
401 updateFromAlignViewport(av);
404 public void updateFromAlignViewport(AlignViewportI av)
406 charWidth = av.getCharWidth();
407 endRes = av.getRanges().getEndRes();
408 charHeight = av.getCharHeight();
409 hasHiddenColumns = av.hasHiddenColumns();
410 validCharWidth = av.isValidCharWidth();
411 av_renderHistogram = av.isShowConsensusHistogram();
412 av_renderProfile = av.isShowSequenceLogo();
413 av_normaliseProfile = av.isNormaliseSequenceLogo();
414 profcolour = av.getResidueShading();
415 if (profcolour == null || profcolour.getColourScheme() == null)
418 * Use default colour for sequence logo if
419 * the alignment has no colourscheme set
420 * (would like to use user preference but n/a for applet)
422 ColourSchemeI col = av.getAlignment().isNucleotide()
423 ? new NucleotideColourScheme()
424 : new ZappoColourScheme();
425 profcolour = new ResidueShader(col);
427 columnSelection = av.getColumnSelection();
428 hiddenColumns = av.getAlignment().getHiddenColumns();
429 hconsensus = av.getSequenceConsensusHash();
430 complementConsensus = av.getComplementConsensusHash();
431 hStrucConsensus = av.getRnaStructureConsensusHash();
432 av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
436 * Returns profile data; the first element is the profile type, the second is
437 * the number of distinct values, the third the total count, and the remainder
438 * depend on the profile type.
444 int[] getProfileFor(AlignmentAnnotation aa, int column)
446 // TODO : consider refactoring the global alignment calculation
447 // properties/rendering attributes as a global 'alignment group' which holds
448 // all vis settings for the alignment as a whole rather than a subset
450 if (aa.autoCalculated && (aa.label.startsWith("Consensus")
451 || aa.label.startsWith("cDNA Consensus")))
453 boolean forComplement = aa.label.startsWith("cDNA Consensus");
454 if (aa.groupRef != null && aa.groupRef.consensusData != null
455 && aa.groupRef.isShowSequenceLogo())
457 // TODO? group consensus for cDNA complement
458 return AAFrequency.extractProfile(
459 aa.groupRef.consensusData.get(column),
460 aa.groupRef.getIgnoreGapsConsensus());
462 // TODO extend annotation row to enable dynamic and static profile data to
464 if (aa.groupRef == null && aa.sequenceRef == null)
468 return AAFrequency.extractCdnaProfile(complementConsensus[column],
469 av_ignoreGapsConsensus);
473 return AAFrequency.extractProfile(hconsensus.get(column),
474 av_ignoreGapsConsensus);
480 if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
482 // TODO implement group structure consensus
484 * if (aa.groupRef != null && aa.groupRef.consensusData != null &&
485 * aa.groupRef.isShowSequenceLogo()) { //TODO check what happens for
486 * group selections return StructureFrequency.extractProfile(
487 * aa.groupRef.consensusData[column], aa.groupRef
488 * .getIgnoreGapsConsensus()); }
490 // TODO extend annotation row to enable dynamic and static profile data
493 if (aa.groupRef == null && aa.sequenceRef == null
494 && hStrucConsensus != null
495 && hStrucConsensus.length > column)
497 return StructureFrequency.extractProfile(hStrucConsensus[column],
498 av_ignoreGapsConsensus);
507 private AnnotationRendererFactoryI rendererFactoryI;
510 * Render the annotation rows associated with an alignment.
515 * data and view settings to render
517 * destination for graphics
519 * row where a mouse event occured (or -1)
521 * first column that will be drawn
523 * last column that will be drawn
524 * @return true if the fadedImage was used for any alignment annotation rows
525 * currently being calculated
527 public boolean drawComponent(AwtRenderPanelI annotPanel,
528 AlignViewportI av, Graphics g, int activeRow, int startRes,
531 return drawComponent(annotPanel, av, g, activeRow, startRes, endRes,
535 public boolean drawComponent(AwtRenderPanelI annotPanel,
536 AlignViewportI av, Graphics g, int activeRow, int startRes,
537 int endRes, boolean forExport)
539 if (g instanceof EpsGraphics2D || g instanceof SVGGraphics2D)
541 this.setVectorRendering(true);
543 Graphics2D g2d = (Graphics2D) g;
545 long stime = System.currentTimeMillis();
546 boolean usedFaded = false;
548 // AnnotationPanel needs to implement: ImageObserver, access to
550 updateFromAwtRenderPanel(annotPanel, av);
551 fm = g.getFontMetrics();
552 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
563 boolean validRes = false;
564 boolean validEnd = false;
565 boolean labelAllCols = false;
566 // boolean centreColLabels;
567 // boolean centreColLabelsDef = av.isCentreColumnLabels();
568 boolean scaleColLabel = false;
569 final AlignmentAnnotation consensusAnnot = av
570 .getAlignmentConsensusAnnotation();
571 final AlignmentAnnotation structConsensusAnnot = av
572 .getAlignmentStrucConsensusAnnotation();
573 final AlignmentAnnotation complementConsensusAnnot = av
574 .getComplementConsensusAnnotation();
576 BitSet graphGroupDrawn = new BitSet();
579 int yfrom = 0, f_i = 0, yto = 0, f_to = 0;
580 boolean clipst = false, clipend = false;
581 for (int i = 0; i < aa.length; i++)
583 AlignmentAnnotation row = aa[i];
584 boolean renderHistogram = true;
585 boolean renderProfile = false;
586 boolean normaliseProfile = false;
587 boolean isRNA = row.isRNA();
589 // check if this is a consensus annotation row and set the display
590 // settings appropriately
591 // TODO: generalise this to have render styles for consensus/profile
593 if (row.groupRef != null && row == row.groupRef.getConsensus())
595 renderHistogram = row.groupRef.isShowConsensusHistogram();
596 renderProfile = row.groupRef.isShowSequenceLogo();
597 normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
599 else if (row == consensusAnnot || row == structConsensusAnnot
600 || row == complementConsensusAnnot)
602 renderHistogram = av_renderHistogram;
603 renderProfile = av_renderProfile;
604 normaliseProfile = av_normaliseProfile;
607 Annotation[] row_annotations = row.annotations;
612 // centreColLabels = row.centreColLabels || centreColLabelsDef;
613 labelAllCols = row.showAllColLabels;
614 scaleColLabel = row.scaleColLabel;
618 if (!useClip || ((y - charHeight) < visHeight
619 && (y + row.height + charHeight * 2) >= sOffset))
620 {// if_in_visible_region
631 if (row.graphGroup > -1 && graphGroupDrawn.get(row.graphGroup))
636 // this is so that we draw the characters below the graph
641 iconOffset = charHeight - fm.getDescent();
645 else if (row.hasText)
647 iconOffset = charHeight - fm.getDescent();
655 if (row.autoCalculated && av.isCalculationInProgress(row))
659 g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0,
660 y - row.height, imgWidth, y, annotationPanel);
661 g.setColor(Color.black);
662 // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
668 * else if (annotationPanel.av.updatingConservation &&
669 * aa[i].label.equals("Conservation")) {
671 * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
672 * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
673 * annotationPanel.imgWidth, y, annotationPanel);
675 * g.setColor(Color.black); //
676 * g.drawString("Calculating Conservation.....",20, y-row.height/2);
678 * continue; } else if (annotationPanel.av.updatingConservation &&
679 * aa[i].label.equals("Quality")) {
681 * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
682 * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
683 * annotationPanel.imgWidth, y, annotationPanel);
684 * g.setColor(Color.black); // /
685 * g.drawString("Calculating Quality....",20, y-row.height/2);
690 // first pass sets up state for drawing continuation from left-hand
694 // flag used for vector rendition
695 this.glyphLineDrawn = false;
696 x = (startRes == 0) ? 0 : -1;
698 while (x < endRes - startRes)
700 if (hasHiddenColumns)
702 column = hiddenColumns.visibleToAbsoluteColumn(startRes + x);
703 if (column > row_annotations.length - 1)
710 column = startRes + x;
713 if ((row_annotations == null)
714 || (row_annotations.length <= column)
715 || (row_annotations[column] == null))
723 final String displayChar = validRes
724 ? row_annotations[column].displayCharacter
731 g.setColor(Color.red);
733 if (columnSelection != null)
735 if (columnSelection.contains(column))
737 fillRect(g, x * charWidth, y, charWidth, charHeight);
741 if (row.getInvalidStrucPos() > x)
743 g.setColor(Color.orange);
744 fillRect(g, x * charWidth, y, charWidth, charHeight);
746 else if (row.getInvalidStrucPos() == x)
748 g.setColor(Color.orange.darker());
749 fillRect(g, x * charWidth, y, charWidth, charHeight);
751 if (validCharWidth && validRes && displayChar != null
752 && (displayChar.length() > 0))
754 float fmWidth = fm.stringWidth(displayChar);
757 * shrink label width to fit in column, if that is
758 * both configured and necessary
760 boolean scaledToFit = false;
761 float fmScaling = 1f;
762 if (scaleColLabel && fmWidth > charWidth)
765 fmScaling = (float) charWidth / fmWidth;
766 // and update the label's width to reflect the scaling.
770 float charOffset = (charWidth - fmWidth) / 2f;
772 if (row_annotations[column].colour == null)
774 g2d.setColor(Color.black);
778 g2d.setColor(row_annotations[column].colour);
782 * draw the label, unless it is the same secondary structure
783 * symbol (excluding RNA Helix) as the previous column
785 final float xPos = (x * charWidth) + charOffset;
786 final float yPos = y + iconOffset;
788 // Act on a copy of the Graphics2d object
789 Graphics2D g2dCopy = (Graphics2D) g2d.create();
790 // Clip to this annotation line (particularly width).
791 // This removes artifacts from text when side scrolling
792 // (particularly in wrap format), but can result in clipped
793 // characters until a full paint is drawn.
794 // Add charHeight allowance above and below annotation for
795 // character overhang.
796 // If we're in an image export, set the clip width to be the
797 // entire width of the annotation.
799 int clipWidth = forExport
800 ? row_annotations.length * charWidth - 1
802 g2dCopy.setClip(0, (int) yPos - 2 * charHeight, clipWidth,
805 * translate to drawing position _before_ applying any scaling
807 g2dCopy.translate(xPos, yPos);
811 * use a scaling transform to make the label narrower
812 * (JalviewJS doesn't have Font.deriveFont(AffineTransform))
815 AffineTransform.getScaleInstance(fmScaling, 1.0));
817 setAntialias(g2dCopy, true);
818 if (column == 0 || row.graph > 0)
820 g2dCopy.drawString(displayChar, 0, 0);
822 else if (row_annotations[column - 1] == null || (labelAllCols
823 || !displayChar.equals(
824 row_annotations[column - 1].displayCharacter)
825 || (displayChar.length() < 2
826 && row_annotations[column].secondaryStructure == ' ')))
828 g2dCopy.drawString(displayChar, 0, 0);
835 char ss = validRes ? row_annotations[column].secondaryStructure
840 // distinguish between forward/backward base-pairing
841 if (displayChar.indexOf(')') > -1)
850 if ((displayChar.indexOf(']') > -1))
858 // distinguish between forward/backward base-pairing
859 if (displayChar.indexOf('}') > -1)
867 // distinguish between forward/backward base-pairing
868 if (displayChar.indexOf('<') > -1)
874 if (isRNA && (ss >= CHAR_A) && (ss <= CHAR_Z))
876 // distinguish between forward/backward base-pairing
877 int ssLowerCase = ss + UPPER_TO_LOWER;
878 // TODO would .equals() be safer here? or charAt(0)?
879 if (displayChar.indexOf(ssLowerCase) > -1)
881 ss = (char) ssLowerCase;
885 if (!validRes || (ss != lastSS))
891 // int nb_annot = x - temp;
892 // Console.info("\t type :"+lastSS+"\t x
894 // annot :"+nb_annot);
897 case '(': // Stem case for RNA secondary structure
898 case ')': // and opposite direction
899 drawStemAnnot(g, row_annotations, lastSSX, x, y,
900 iconOffset, startRes, column, validRes, validEnd);
907 drawHelixAnnot(g, row_annotations, lastSSX, x, y,
908 iconOffset, startRes, column, validRes,
912 // no break if isRNA - falls through to drawNotCanonicalAnnot!
916 drawSheetAnnot(g, row_annotations, lastSSX, x, y,
917 iconOffset, startRes, column, validRes,
921 // no break if isRNA - fall through to drawNotCanonicalAnnot!
980 Color nonCanColor = getNotCanonicalColor(lastSS);
981 drawNotCanonicalAnnot(g, nonCanColor, row_annotations,
982 lastSSX, x, y, iconOffset, startRes, column,
987 if (isVectorRendering())
989 // draw single full width glyphline
990 drawGlyphLine(g, lastSSX, endRes - x, y, iconOffset);
991 // disable more glyph lines
992 this.glyphLineDrawn = true;
996 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1011 lastSSX = (x * charWidth);
1018 if (column >= row_annotations.length)
1020 column = row_annotations.length - 1;
1027 if ((row_annotations == null) || (row_annotations.length <= column)
1028 || (row_annotations[column] == null))
1046 drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1047 startRes, column, validRes, validEnd);
1050 // no break if isRNA - fall through to drawNotCanonicalAnnot!
1055 drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1056 startRes, column, validRes, validEnd);
1059 // no break if isRNA - fall through to drawNotCanonicalAnnot!
1062 case ')': // Stem case for RNA secondary structure
1064 drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1065 startRes, column, validRes, validEnd);
1122 // Console.info(lastSS);
1123 Color nonCanColor = getNotCanonicalColor(lastSS);
1124 drawNotCanonicalAnnot(g, nonCanColor, row_annotations, lastSSX,
1125 x, y, iconOffset, startRes, column, validRes, validEnd);
1128 if (isVectorRendering())
1130 // draw single full width glyphline
1131 drawGlyphLine(g, lastSSX, endRes - x, y, iconOffset);
1132 // disable more glyph lines
1133 this.glyphLineDrawn = true;
1137 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1143 if (row.graph > 0 && row.graphHeight > 0)
1145 if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1147 if (row.graphGroup > -1 && !graphGroupDrawn.get(row.graphGroup))
1149 // TODO: JAL-1291 revise rendering model so the graphGroup map is
1150 // computed efficiently for all visible labels
1151 float groupmax = -999999, groupmin = 9999999;
1152 for (int gg = 0; gg < aa.length; gg++)
1154 if (aa[gg].graphGroup != row.graphGroup)
1161 aa[gg].visible = false;
1163 if (aa[gg].graphMax > groupmax)
1165 groupmax = aa[gg].graphMax;
1167 if (aa[gg].graphMin < groupmin)
1169 groupmin = aa[gg].graphMin;
1173 for (int gg = 0; gg < aa.length; gg++)
1175 if (aa[gg].graphGroup == row.graphGroup)
1177 drawLineGraph(g, aa[gg], aa[gg].annotations, startRes,
1178 endRes, y, groupmin, groupmax, row.graphHeight);
1182 graphGroupDrawn.set(row.graphGroup);
1186 drawLineGraph(g, row, row_annotations, startRes, endRes, y,
1187 row.graphMin, row.graphMax, row.graphHeight);
1190 else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1192 drawBarGraph(g, row, row_annotations, startRes, endRes,
1193 row.graphMin, row.graphMax, y, renderHistogram,
1194 renderProfile, normaliseProfile);
1198 AnnotationRowRendererI renderer = rendererFactoryI
1199 .getRendererFor(row);
1200 if (renderer != null)
1202 renderer.renderRow(g, charWidth, charHeight, hasHiddenColumns,
1203 av, hiddenColumns, columnSelection, row,
1204 row_annotations, startRes, endRes, row.graphMin,
1205 row.graphMax, y, isVectorRendering());
1209 if (renderer == null)
1212 .println("No renderer found for " + row.toString());
1217 "rendered with " + renderer.getClass().toString());
1226 if (clipst && !clipend)
1230 } // end if_in_visible_region
1231 if (row.graph > 0 && row.hasText)
1247 Console.warn("Start clip at : " + yfrom + " (index " + f_i + ")");
1251 Console.warn("End clip at : " + yto + " (index " + f_to + ")");
1255 Console.warn("Annotation Rendering time:"
1256 + (System.currentTimeMillis() - stime));
1263 public static final Color GLYPHLINE_COLOR = Color.gray;
1265 public static final Color SHEET_COLOUR = Color.green;
1267 public static final Color HELIX_COLOUR = Color.red;
1269 public static final Color STEM_COLOUR = Color.blue;
1271 // private Color sdNOTCANONICAL_COLOUR;
1273 void drawGlyphLine(Graphics g, int lastSSX, int x, int y, int iconOffset)
1277 // if we've drawn a single long glyphline for an export, don't draw the
1282 g.setColor(GLYPHLINE_COLOR);
1283 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
1286 void drawSheetAnnot(Graphics g, Annotation[] row,
1288 int lastSSX, int x, int y, int iconOffset, int startRes,
1289 int column, boolean validRes, boolean validEnd)
1291 if (!validEnd || !validRes || row == null || row[column] == null
1292 || row[column].secondaryStructure != 'E')
1294 // draw the glyphline underneath
1295 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1297 g.setColor(SHEET_COLOUR);
1298 fillRect(g, lastSSX, y + 4 + iconOffset,
1299 (x * charWidth) - lastSSX - 4, 6);
1302 { (x * charWidth) - 6, (x * charWidth) - 6,
1303 (x * charWidth - 1) },
1305 { y + iconOffset + 1, y + 13 + iconOffset,
1306 y + 7 + iconOffset },
1311 g.setColor(SHEET_COLOUR);
1312 fillRect(g, lastSSX, y + 4 + iconOffset, (x * charWidth) - lastSSX,
1317 void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX, int x,
1318 int y, int iconOffset, int startRes, int column, boolean validRes,
1321 int sCol = (lastSSX / charWidth)
1322 + hiddenColumns.visibleToAbsoluteColumn(startRes);
1324 int x2 = (x * charWidth);
1326 if (USE_FILL_ROUND_RECT || isVectorRendering())
1328 // draw glyph line behind helix (visible in EPS or SVG output)
1329 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1331 g.setColor(HELIX_COLOUR);
1333 int ofs = charWidth / 2;
1334 // Off by 1 offset when drawing rects and ovals
1335 // to offscreen image on the MAC
1336 fillRoundRect(g, lastSSX, y + 3 + iconOffset, x2 - x1 - 1, 8, 8, 8);
1337 if (sCol == 0 || row[sCol - 1] == null
1338 || row[sCol - 1].secondaryStructure != 'H')
1343 fillRoundRect(g, lastSSX, y + 3 + iconOffset, x2 - x1 - ofs, 8, 0,
1346 if (!validRes || row[column] == null
1347 || row[column].secondaryStructure != 'H')
1353 fillRoundRect(g, lastSSX + ofs, y + 3 + iconOffset, x2 - x1 - ofs,
1360 boolean leftEnd = sCol == 0 || row[sCol - 1] == null
1361 || row[sCol - 1].secondaryStructure != 'H';
1362 boolean rightEnd = !validRes || row[column] == null
1363 || row[column].secondaryStructure != 'H';
1365 if (leftEnd || rightEnd)
1367 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1369 g.setColor(HELIX_COLOUR);
1373 fillArc(g, lastSSX, y + 3 + iconOffset, charWidth, 8, 90, 180);
1374 x1 += charWidth / 2;
1379 fillArc(g, ((x - 1) * charWidth), y + 3 + iconOffset, charWidth, 8,
1381 x2 -= charWidth / 2;
1384 fillRect(g, x1, y + 3 + iconOffset, x2 - x1, 8);
1387 void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
1388 Annotation[] aa_annotations, int sRes, int eRes, int y, float min,
1389 float max, int graphHeight)
1391 if (sRes > aa_annotations.length)
1395 Stroke roundStroke = new BasicStroke(1, BasicStroke.CAP_ROUND,
1396 BasicStroke.JOIN_ROUND);
1397 Stroke squareStroke = new BasicStroke(1, BasicStroke.CAP_SQUARE,
1398 BasicStroke.JOIN_MITER);
1399 Graphics2D g2d = (Graphics2D) g;
1400 Stroke prevStroke = g2d.getStroke();
1401 g2d.setStroke(roundStroke);
1405 // Adjustment for fastpaint to left
1411 eRes = Math.min(eRes, aa_annotations.length);
1414 float range = max - min;
1419 y2 = y - (int) ((0 - min / range) * graphHeight);
1422 g.setColor(Color.gray);
1423 drawLine(g, squareStroke, x * charWidth, y2, (eRes - sRes) * charWidth,
1431 eRes = Math.min(eRes, aa_annotations.length);
1434 int aaMax = aa_annotations.length - 1;
1436 while (x <= eRes - sRes)
1439 if (hasHiddenColumns)
1441 column = hiddenColumns.visibleToAbsoluteColumn(column);
1449 if (aa_annotations[column] == null)
1455 boolean individualColour = false;
1456 if (aa_annotations[column].colour == null)
1458 g.setColor(Color.black);
1462 g.setColor(aa_annotations[column].colour);
1463 individualColour = true;
1466 boolean value1Exists = column > 0
1467 && aa_annotations[column - 1] != null;
1469 Color color1 = null;
1472 value1 = aa_annotations[column - 1].value;
1473 color1 = aa_annotations[column - 1].colour;
1475 float value2 = aa_annotations[column].value;
1476 boolean nextValueExists = aa_annotations.length > column + 1
1477 && aa_annotations[column + 1] != null;
1479 // check for standalone value
1480 if (!value1Exists && !nextValueExists)
1482 y2 = y - yValueToPixelHeight(value2, min, range, graphHeight);
1483 drawLine(g, x * charWidth + charWidth / 4, y2,
1484 x * charWidth + 3 * charWidth / 4, y2);
1495 y1 = y - yValueToPixelHeight(value1, min, range, graphHeight);
1496 y2 = y - yValueToPixelHeight(value2, min, range, graphHeight);
1500 int a1 = (x - 1) * charWidth + charWidth / 2;
1502 int a2 = x * charWidth + charWidth / 2;
1506 // only draw an initial half-line
1508 b1 = y1 + (y2 - y1) / 2;
1509 v1 = value1 + (value2 - value1) / 2;
1511 else if (x == eRes - sRes)
1513 // this is one past the end to draw -- only draw the first half of the
1515 a2 = x * charWidth - 1;
1516 b2 = y1 + (y2 - y1) / 2;
1517 v2 = value1 + (value2 - value1) / 2;
1522 AnnotationColouringI ac = aa_annotations[column]
1523 .getAnnotationColouring();
1524 List<Map.Entry<Float, Color>> valCols = ac == null ? null
1525 : ac.rangeColours(v1, v2);
1526 if (valCols != null)
1528 drawSegmentedLine(g, valCols, a1, b1, a2, b2);
1532 drawLine(g, a1, b1, a2, b2);
1537 if (_aa.threshold != null)
1539 g.setColor(_aa.threshold.colour);
1540 Graphics2D g2 = (Graphics2D) g;
1541 y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
1542 drawLine(g, dashedLine(charWidth), 0, y2, (eRes - sRes) * charWidth,
1545 g2d.setStroke(prevStroke);
1548 private static double log2 = Math.log(2);
1550 // Cached dashed line Strokes
1551 private static Map<Integer, Stroke> dashedLineLookup = new HashMap<>();
1554 * Returns a dashed line stroke as close to 6-4 pixels as fits within the
1555 * charWidth. This allows translations of multiples of charWidth without
1556 * disrupting the dashed line. The exact values are 0.6-0.4 proportions of
1557 * charWidth for charWidth under 16. For charWidth 16 or over, the number of
1558 * dashes doubles as charWidth doubles.
1561 * @return Stroke with appropriate dashed line fitting exactly within the
1564 private static Stroke dashedLine(int charWidth)
1566 if (!dashedLineLookup.containsKey(charWidth))
1568 int power2 = charWidth >= 8 ? (int) (Math.log(charWidth) / log2) : 2;
1569 float width = ((float) charWidth) / ((float) Math.pow(2, power2 - 3));
1570 float segment1 = width * 0.6f;
1571 float segment2 = width - segment1;
1572 dashedLineLookup.put(charWidth, new BasicStroke(1,
1573 BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 3f, new float[]
1574 { segment1, segment2 }, 0f));
1576 return dashedLineLookup.get(charWidth);
1579 private void drawSegmentedLine(Graphics g,
1580 List<Map.Entry<Float, Color>> valCols, int x1, int y1, int x2,
1583 if (valCols == null || valCols.size() == 0)
1587 // let's only go forwards+up|down -- try and avoid providing right to left
1597 Collections.reverse(valCols);
1599 Graphics2D g2d = (Graphics2D) g.create();
1601 boolean reverse = yd > 0; // reverse => line going DOWN (y increasing)
1602 Map.Entry<Float, Color> firstValCol = valCols.remove(0);
1603 float firstVal = firstValCol.getKey();
1604 Color firstCol = firstValCol.getValue();
1606 yy1 = reverse ? (int) Math.ceil(y1 + firstVal * yd)
1607 : (int) Math.floor(y1 + firstVal * yd);
1608 Color thisCol = firstCol;
1609 for (int i = 0; i < valCols.size(); i++)
1611 Map.Entry<Float, Color> valCol = valCols.get(i);
1612 float val = valCol.getKey();
1613 Color col = valCol.getValue();
1615 int clipW = x2 - x1 + 2;
1619 if (reverse) // line going down
1621 yy2 = (int) Math.ceil(y1 + val * yd);
1622 g2d.setColor(thisCol);
1627 // highest segment, don't clip at the top
1631 if (i == valCols.size() - 1)
1633 // lowest segment, don't clip at the bottom
1637 else // line going up (or level)
1639 yy2 = (int) Math.floor(y1 + val * yd);
1640 // g2d.setColor(Color.cyan); g2d.drawRect(x1 - 1, yy1, x2 - x1 + 1, yy2
1648 // lowest segment, don't clip at the bottom
1651 if (i == valCols.size() - 1)
1653 // highest segment, don't clip at the top
1658 g2d.setClip(clipX, clipY, clipW, clipH);
1659 drawLine(g2d, x1, y1, x2, y2);
1666 private static int yValueToPixelHeight(float value, float min,
1667 float range, int graphHeight)
1669 return (int) (((value - min) / range) * graphHeight);
1672 @SuppressWarnings("unused")
1673 void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
1674 Annotation[] aa_annotations, int sRes, int eRes, float min,
1675 float max, int y, boolean renderHistogram, boolean renderProfile,
1676 boolean normaliseProfile)
1678 if (sRes > aa_annotations.length)
1682 Font ofont = g.getFont();
1683 eRes = Math.min(eRes, aa_annotations.length);
1685 int x = 0, y1 = y, y2 = y;
1687 float range = max - min;
1691 y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
1694 g.setColor(Color.gray);
1696 drawLine(g, x, y2, (eRes - sRes) * charWidth, y2);
1699 int aaMax = aa_annotations.length - 1;
1700 while (x < eRes - sRes)
1703 if (hasHiddenColumns)
1705 column = hiddenColumns.visibleToAbsoluteColumn(column);
1713 if (aa_annotations[column] == null)
1718 if (aa_annotations[column].colour == null)
1720 g.setColor(Color.black);
1724 g.setColor(aa_annotations[column].colour);
1727 y1 = y - (int) (((aa_annotations[column].value - min) / (range))
1730 if (renderHistogram)
1734 fillRect(g, x * charWidth, y2, charWidth, y1 - y2);
1738 fillRect(g, x * charWidth, y1, charWidth, y2 - y1);
1741 // draw profile if available
1746 * {profile type, #values, total count, char1, pct1, char2, pct2...}
1748 int profl[] = getProfileFor(_aa, column);
1750 // just try to draw the logo if profl is not null
1751 if (profl != null && profl[2] != 0)
1753 boolean isStructureProfile = profl[0] == AlignmentAnnotation.STRUCTURE_PROFILE;
1754 boolean isCdnaProfile = profl[0] == AlignmentAnnotation.CDNA_PROFILE;
1755 float ht = normaliseProfile ? y - _aa.graphHeight : y1;
1756 final double normaliseFactor = normaliseProfile ? _aa.graphHeight
1760 * Render a single base for a sequence profile, a base pair for
1761 * structure profile, and a triplet for a cdna profile
1763 char[] dc = new char[isStructureProfile ? 2
1764 : (isCdnaProfile ? 3 : 1)];
1766 // lm is not necessary - we can just use fm - could be off by no more
1768 // LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
1769 // Console.info(asc + " " + dec + " " + (asc -
1771 // + " " + (dec - lm.getDescent()));
1773 double asc = fm.getAscent();
1774 double dec = fm.getDescent();
1775 double fht = fm.getHeight();
1777 double scale = 1f / (normaliseProfile ? profl[2] : 100f);
1778 // float ofontHeight = 1f / fm.getAscent();// magnify to fill box
1781 * Traverse the character(s)/percentage data in the array
1786 // profl[1] is the number of values in the profile
1787 for (int i = 0, c = 3, last = profl[1]; i < last; i++)
1791 if (isStructureProfile)
1793 // todo can we encode a structure pair as an int, like codons?
1794 dc[0] = (char) profl[c++];
1795 dc[1] = (char) profl[c++];
1798 else if (isCdnaProfile)
1800 CodingUtils.decodeCodon2(profl[c++], dc);
1805 dc[0] = (char) profl[c++];
1808 // next profl[] position is profile % for the character(s)
1810 int percent = profl[c++];
1813 // failsafe in case a count rounds down to 0%
1816 double newHeight = normaliseFactor * scale * percent;
1819 * Set character colour as per alignment colour scheme; use the
1820 * codon translation if a cDNA profile
1822 Color colour = null;
1825 final String codonTranslation = ResidueProperties
1827 colour = profcolour.findColour(codonTranslation.charAt(0),
1832 colour = profcolour.findColour(dc[0], column, null);
1834 g.setColor(colour == Color.white ? Color.lightGray : colour);
1836 double sx = 1f * charWidth / fm.charsWidth(dc, 0, dc.length);
1837 double sy = newHeight / asc;
1838 double newAsc = asc * sy;
1839 double newDec = dec * sy;
1840 // it is not necessary to recalculate lm for the new font.
1841 // note: lm.getBaselineOffsets()[lm.getBaselineIndex()]) must be 0
1842 // by definition. Was:
1843 // int hght = (int) (ht + (newAsc - newDec);
1844 // - lm.getBaselineOffsets()[lm.getBaselineIndex()]));
1846 if (Platform.isJS())
1849 * SwingJS does not implement font.deriveFont()
1850 * so use a scaling transform to draw instead,
1851 * this is off by a very small amount
1853 final int hght = (int) (ht2 + (newAsc - newDec));
1854 Graphics2D gg = (Graphics2D) g;
1855 int xShift = (int) Math.round(x * charWidth / sx);
1856 int yShift = (int) Math.round(hght / sy);
1857 gg.transform(AffineTransform.getScaleInstance(sx, sy));
1858 gg.drawString(s, xShift, yShift);
1860 AffineTransform.getScaleInstance(1D / sx, 1D / sy));
1870 // Java ('normal') method is to scale the font to fit
1872 final int hght = (int) (ht + (newAsc - newDec));
1874 .deriveFont(AffineTransform.getScaleInstance(sx, sy));
1876 g.drawChars(dc, 0, dc.length, x * charWidth, hght);
1886 if (_aa.threshold != null)
1888 g.setColor(_aa.threshold.colour);
1889 Stroke s = new BasicStroke(1, BasicStroke.CAP_SQUARE,
1890 BasicStroke.JOIN_ROUND, 3f, new float[]
1894 - ((_aa.threshold.value - min) / range) * _aa.graphHeight);
1895 drawLine(g, s, 0, y2, (eRes - sRes) * charWidth, y2);
1899 // used by overview window
1900 public void drawGraph(Graphics g, AlignmentAnnotation _aa,
1901 Annotation[] aa_annotations, int width, int y, int sRes, int eRes)
1903 eRes = Math.min(eRes, aa_annotations.length);
1904 g.setColor(Color.white);
1905 fillRect(g, 0, 0, width, y);
1906 g.setColor(new Color(0, 0, 180));
1910 for (int j = sRes; j < eRes; j++)
1912 if (aa_annotations[j] != null)
1914 if (aa_annotations[j].colour == null)
1916 g.setColor(Color.black);
1920 g.setColor(aa_annotations[j].colour);
1923 height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
1929 fillRect(g, x, y - height, charWidth, height);
1935 Color getNotCanonicalColor(char lastss)
1941 return new Color(255, 125, 5);
1945 return new Color(245, 115, 10);
1949 return new Color(235, 135, 15);
1953 return new Color(225, 105, 20);
1957 return new Color(215, 145, 30);
1961 return new Color(205, 95, 35);
1965 return new Color(195, 155, 45);
1969 return new Color(185, 85, 55);
1973 return new Color(175, 165, 65);
1977 return new Color(170, 75, 75);
1981 return new Color(160, 175, 85);
1985 return new Color(150, 65, 95);
1989 return new Color(140, 185, 105);
1993 return new Color(130, 55, 110);
1997 return new Color(120, 195, 120);
2001 return new Color(110, 45, 130);
2005 return new Color(100, 205, 140);
2009 return new Color(90, 35, 150);
2013 return new Color(85, 215, 160);
2017 return new Color(75, 25, 170);
2021 return new Color(65, 225, 180);
2025 return new Color(55, 15, 185);
2029 return new Color(45, 235, 195);
2033 return new Color(35, 5, 205);
2037 return new Color(25, 245, 215);
2041 return new Color(15, 0, 225);
2045 return new Color(10, 255, 235);
2049 return new Color(5, 150, 245);
2053 return new Color(0, 80, 255);
2056 Console.info("This is not a interaction : " + lastss);
2062 private void fillPolygon(Graphics g, int[] xpoints, int[] ypoints, int n)
2065 g.fillPolygon(xpoints, ypoints, n);
2069 private void fillRect(Graphics g, int a, int b, int c, int d)
2071 fillRect(g, false, a, b, c, d);
2074 private void fillRect(Graphics g, int a, int b, int c, int d)
2077 g.fillRect(a, b, c, d);
2080 private void fillRoundRect(Graphics g, int a, int b, int c, int d, int e,
2084 g.fillRoundRect(a, b, c, d, e, f);
2087 private void fillArc(Graphics g, int a, int b, int c, int d, int e, int f)
2090 g.fillArc(a, b, c, d, e, f);
2093 private void drawLine(Graphics g, Stroke s, int a, int b, int c, int d)
2095 Graphics2D g2d = (Graphics2D) g;
2096 Stroke p = g2d.getStroke();
2098 drawLine(g, a, b, c, d);
2102 private void drawLine(Graphics g, int x1, int y1, int x2, int y2)
2105 g.drawLine(x1, y1, x2, y2);
2108 private void setAntialias(Graphics g)
2110 setAntialias(g, false);
2113 private void setAntialias(Graphics g, boolean text)
2115 if (isVectorRendering())
2117 // no need to antialias vector drawings
2120 if (Cache.getDefault("ANTI_ALIAS", true))
2122 Graphics2D g2d = (Graphics2D) g;
2125 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
2126 this.textAntialiasMethod);
2130 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
2131 RenderingHints.VALUE_ANTIALIAS_ON);
2136 private void unsetAntialias(Graphics g)
2138 if (isVectorRendering())
2140 // no need to antialias vector drawings
2143 Graphics2D g2d = (Graphics2D) g;
2144 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
2145 RenderingHints.VALUE_ANTIALIAS_OFF);
2148 public void setVectorRendering(boolean b)
2150 renderingVectors = b;
2153 public boolean isVectorRendering()
2155 return renderingVectors;