1 package jalview.renderer;
3 import jalview.analysis.AAFrequency;
4 import jalview.analysis.StructureFrequency;
5 import jalview.api.AlignViewportI;
6 import jalview.datamodel.AlignmentAnnotation;
7 import jalview.datamodel.Annotation;
8 import jalview.datamodel.ColumnSelection;
9 import jalview.schemes.ColourSchemeI;
11 import java.awt.BasicStroke;
12 import java.awt.Color;
14 import java.awt.FontMetrics;
15 import java.awt.Graphics;
16 import java.awt.Graphics2D;
17 import java.awt.Image;
18 import java.awt.font.LineMetrics;
19 import java.awt.geom.AffineTransform;
20 import java.awt.image.ImageObserver;
21 import java.util.Hashtable;
23 import com.stevesoft.pat.Regex;
25 public class AnnotationRenderer
28 public AnnotationRenderer()
30 // TODO Auto-generated constructor stub
33 public void drawStemAnnot(Graphics g, Annotation[] row_annotations,
34 int lastSSX, int x, int y, int iconOffset, int startRes,
35 int column, boolean validRes, boolean validEnd)
37 g.setColor(STEM_COLOUR);
38 int sCol = (lastSSX / charWidth) + startRes;
40 int x2 = (x * charWidth);
41 Regex closeparen = new Regex("(\\))");
43 String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
44 : row_annotations[column - 1].displayCharacter;
46 boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
47 || !dc.equals(row_annotations[sCol - 1].displayCharacter);
48 boolean diffdownstream = !validRes || !validEnd
49 || row_annotations[column] == null
50 || !dc.equals(row_annotations[column].displayCharacter);
51 // System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
52 // If a closing base pair half of the stem, display a backward arrow
53 if (column > 0 && closeparen.search(dc))
56 // if (validRes && column>1 && row_annotations[column-2]!=null &&
57 // dc.equals(row_annotations[column-2].displayCharacter))
59 g.fillPolygon(new int[]
60 { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
61 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
71 // display a forward arrow
74 g.fillPolygon(new int[]
75 { x2 - 5, x2 - 5, x2 }, new int[]
76 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
85 g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
88 private int charWidth, endRes, charHeight;
90 private boolean validCharWidth, hasHiddenColumns;
92 private FontMetrics fm;
94 private final boolean MAC = new jalview.util.Platform().isAMac();
96 boolean av_renderHistogram = true, av_renderProfile = true,
97 av_normaliseProfile = false;
99 ColourSchemeI profcolour = null;
101 private ColumnSelection columnSelection;
103 private Hashtable[] hconsensus;
105 private Hashtable[] hStrucConsensus;
107 private boolean av_ignoreGapsConsensus;
110 * attributes set from AwtRenderPanelI
113 * old image used when data is currently being calculated and cannot be
116 private Image fadedImage;
119 * panel being rendered into
121 private ImageObserver annotationPanel;
124 * width of image to render in panel
126 private int imgWidth;
128 // public void updateFromAnnotationPanel(FontMetrics annotFM, AlignViewportI
130 public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
133 fm = annotPanel.getFontMetrics();
134 annotationPanel = annotPanel;
135 fadedImage = annotPanel.getFadedImage();
136 imgWidth = annotPanel.getFadedImageWidth();
137 updateFromAlignViewport(av);
140 public void updateFromAlignViewport(AlignViewportI av)
142 charWidth = av.getCharWidth();
143 endRes = av.getEndRes();
144 charHeight = av.getCharHeight();
145 hasHiddenColumns = av.hasHiddenColumns();
146 validCharWidth = av.isValidCharWidth();
147 av_renderHistogram = av.isShowConsensusHistogram();
148 av_renderProfile = av.isShowSequenceLogo();
149 av_normaliseProfile = av.isNormaliseSequenceLogo();
150 profcolour = av.getGlobalColourScheme();
151 if (profcolour == null)
153 // Set the default colour for sequence logo if the alignnent has no
155 profcolour = av.getAlignment().isNucleotide() ? new jalview.schemes.NucleotideColourScheme()
156 : new jalview.schemes.ZappoColourScheme();
158 columnSelection = av.getColumnSelection();
159 hconsensus = av.getSequenceConsensusHash();// hconsensus;
160 hStrucConsensus = av.getRnaStructureConsensusHash(); // hStrucConsensus;
161 av_ignoreGapsConsensus = av.getIgnoreGapsConsensus();
164 public int[] getProfileFor(AlignmentAnnotation aa, int column)
166 // TODO : consider refactoring the global alignment calculation
167 // properties/rendering attributes as a global 'alignment group' which holds
168 // all vis settings for the alignment as a whole rather than a subset
170 if (aa.autoCalculated && aa.label.startsWith("Consensus"))
172 if (aa.groupRef != null && aa.groupRef.consensusData != null
173 && aa.groupRef.isShowSequenceLogo())
175 return AAFrequency.extractProfile(
176 aa.groupRef.consensusData[column],
177 aa.groupRef.getIgnoreGapsConsensus());
179 // TODO extend annotation row to enable dynamic and static profile data to
181 if (aa.groupRef == null && aa.sequenceRef == null && av_renderProfile)
183 return AAFrequency.extractProfile(hconsensus[column],
184 av_ignoreGapsConsensus);
189 if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
191 // TODO implement group structure consensus
193 * if (aa.groupRef != null && aa.groupRef.consensusData != null &&
194 * aa.groupRef.isShowSequenceLogo()) { //TODO check what happens for
195 * group selections return StructureFrequency.extractProfile(
196 * aa.groupRef.consensusData[column], aa.groupRef
197 * .getIgnoreGapsConsensus()); }
199 // TODO extend annotation row to enable dynamic and static profile data
202 if (aa.groupRef == null && aa.sequenceRef == null
203 && av_renderProfile && hStrucConsensus != null
204 && hStrucConsensus.length > column)
206 return StructureFrequency.extractProfile(hStrucConsensus[column],
207 av_ignoreGapsConsensus);
215 * Render the annotation rows associated with an alignment.
219 * data and view settings to render
221 * destination for graphics
223 * row where a mouse event occured (or -1)
225 * first column that will be drawn
227 * last column that will be drawn
228 * @return true if the fadedImage was used for any alignment annotation rows currently being calculated
230 public boolean drawComponent(AwtRenderPanelI annotPanel, AlignViewportI av,
231 Graphics g, int activeRow, int startRes, int endRes)
233 boolean usedFaded=false;
235 // AnnotationPanel needs to implement: ImageObserver, access to
237 updateFromAwtRenderPanel(annotPanel, av);
238 fm = g.getFontMetrics();
239 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
246 boolean validRes = false;
247 boolean validEnd = false;
248 boolean labelAllCols = false;
249 boolean centreColLabels, centreColLabelsDef = av
250 .getCentreColumnLabels();
251 boolean scaleColLabel = false;
252 boolean[] graphGroupDrawn = new boolean[aa.length];
253 int charOffset = 0; // offset for a label
254 float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
256 Font ofont = g.getFont();
258 for (int i = 0; i < aa.length; i++)
260 AlignmentAnnotation row = aa[i];
261 Annotation[] row_annotations=row.annotations;
266 centreColLabels = row.centreColLabels || centreColLabelsDef;
267 labelAllCols = row.showAllColLabels;
268 scaleColLabel = row.scaleColLabel;
273 if (row.graphGroup > -1 && graphGroupDrawn[row.graphGroup])
278 // this is so that we draw the characters below the graph
283 iconOffset = charHeight - fm.getDescent();
287 else if (row.hasText)
289 iconOffset = charHeight - fm.getDescent();
297 if (row.autoCalculated && av.isCalculationInProgress(row))
301 g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
302 - row.height, imgWidth, y, annotationPanel);
303 g.setColor(Color.black);
304 // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
310 * else if (annotationPanel.av.updatingConservation &&
311 * aa[i].label.equals("Conservation")) {
313 * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
314 * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
315 * annotationPanel.imgWidth, y, annotationPanel);
317 * g.setColor(Color.black); //
318 * g.drawString("Calculating Conservation.....",20, y-row.height/2);
320 * continue; } else if (annotationPanel.av.updatingConservation &&
321 * aa[i].label.equals("Quality")) {
323 * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
324 * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
325 * annotationPanel.imgWidth, y, annotationPanel); g.setColor(Color.black);
326 * // / g.drawString("Calculating Quality....",20, y-row.height/2);
330 // first pass sets up state for drawing continuation from left-hand column
332 x = (startRes == 0) ? 0 : -1;
333 while (x < endRes - startRes)
335 if (hasHiddenColumns)
337 column = columnSelection.adjustForHiddenColumns(startRes + x);
338 if (column > row_annotations.length - 1)
345 column = startRes + x;
348 if ((row_annotations == null) || (row_annotations.length <= column)
349 || (row_annotations[column] == null))
361 g.setColor(Color.red);
363 if (columnSelection != null)
365 for (int n = 0; n < columnSelection.size(); n++)
367 int v = columnSelection.columnAt(n);
371 g.fillRect(x * charWidth, y, charWidth, charHeight);
376 if (!row.isValidStruc())
378 g.setColor(Color.orange);
379 g.fillRect((int) row.getInvalidStrucPos() * charWidth, y,
380 charWidth, charHeight);
384 && row_annotations[column].displayCharacter != null
385 && (row_annotations[column].displayCharacter.length() > 0))
388 if (centreColLabels || scaleColLabel)
390 fmWidth = fm.charsWidth(
391 row_annotations[column].displayCharacter
393 row_annotations[column].displayCharacter.length());
397 // justify the label and scale to fit in column
398 if (fmWidth > charWidth)
400 // scale only if the current font isn't already small enough
401 fmScaling = charWidth;
402 fmScaling /= fmWidth;
403 g.setFont(ofont.deriveFont(AffineTransform
404 .getScaleInstance(fmScaling, 1.0)));
405 // and update the label's width to reflect the scaling.
413 .charWidth(row_annotations[column].displayCharacter
416 charOffset = (int) ((charWidth - fmWidth) / 2f);
418 if (row_annotations[column].colour == null)
419 g.setColor(Color.black);
421 g.setColor(row_annotations[column].colour);
423 if (column == 0 || row.graph > 0)
425 g.drawString(row_annotations[column].displayCharacter,
426 (x * charWidth) + charOffset, y + iconOffset);
428 else if (row_annotations[column - 1] == null
430 || !row_annotations[column].displayCharacter
431 .equals(row_annotations[column - 1].displayCharacter) || (row_annotations[column].displayCharacter
432 .length() < 2 && row_annotations[column].secondaryStructure == ' ')))
434 g.drawString(row_annotations[column].displayCharacter, x
435 * charWidth + charOffset, y + iconOffset);
442 char ss = validRes ? row_annotations[column].secondaryStructure
446 // distinguish between forward/backward base-pairing
447 if (row_annotations[column].displayCharacter.indexOf(')') > -1)
452 if (!validRes || (ss != lastSS))
459 drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
460 column, validRes, validEnd);
464 drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
465 column, validRes, validEnd);
468 case 'S': // Stem case for RNA secondary structure
469 case 's': // and opposite direction
470 drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
471 column, validRes, validEnd);
475 g.setColor(Color.gray);
476 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth)
492 lastSSX = (x * charWidth);
499 if (column >= row_annotations.length)
501 column = row_annotations.length - 1;
508 if ((row_annotations == null) || (row_annotations.length <= column)
509 || (row_annotations[column] == null))
525 drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
526 column, validRes, validEnd);
530 drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
531 column, validRes, validEnd);
534 case 'S': // Stem case for RNA secondary structure
535 drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
536 column, validRes, validEnd);
539 drawGlyphLine(g, row_annotations, lastSSX, x, y, iconOffset, startRes,
540 column, validRes, validEnd);
545 if (row.graph > 0 && row.graphHeight > 0)
547 if (row.graph == AlignmentAnnotation.LINE_GRAPH)
549 if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
551 float groupmax = -999999, groupmin = 9999999;
552 for (int gg = 0; gg < aa.length; gg++)
554 if (aa[gg].graphGroup != row.graphGroup)
561 aa[gg].visible = false;
564 if (aa[gg].graphMax > groupmax)
566 groupmax = aa[gg].graphMax;
568 if (aa[gg].graphMin < groupmin)
570 groupmin = aa[gg].graphMin;
574 for (int gg = 0; gg < aa.length; gg++)
576 if (aa[gg].graphGroup == row.graphGroup)
578 drawLineGraph(g, aa[gg], aa[gg].annotations, startRes, endRes, y, groupmin,
579 groupmax, row.graphHeight);
583 graphGroupDrawn[row.graphGroup] = true;
587 drawLineGraph(g, row, row_annotations, startRes, endRes, y, row.graphMin,
588 row.graphMax, row.graphHeight);
591 else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
593 drawBarGraph(g, row, row_annotations, startRes, endRes, row.graphMin,
598 if (row.graph > 0 && row.hasText)
611 private final Color GLYPHLINE_COLOR = Color.gray;
613 private final Color SHEET_COLOUR = Color.green;
615 private final Color HELIX_COLOUR = Color.red;
617 private final Color STEM_COLOUR = Color.blue;
619 public void drawGlyphLine(Graphics g, Annotation[] row,
620 int lastSSX, int x, int y, int iconOffset, int startRes,
621 int column, boolean validRes, boolean validEnd)
623 g.setColor(GLYPHLINE_COLOR);
624 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
627 public void drawSheetAnnot(Graphics g, Annotation[] row,
628 int lastSSX, int x, int y, int iconOffset, int startRes,
629 int column, boolean validRes, boolean validEnd)
631 g.setColor(SHEET_COLOUR);
633 if (!validEnd || !validRes || row==null || row[column] == null
634 || row[column].secondaryStructure != 'E')
636 g.fillRect(lastSSX, y + 4 + iconOffset,
637 (x * charWidth) - lastSSX - 4, 7);
638 g.fillPolygon(new int[]
639 { (x * charWidth) - 4, (x * charWidth) - 4, (x * charWidth) },
641 { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
646 g.fillRect(lastSSX, y + 4 + iconOffset,
647 (x + 1) * charWidth - lastSSX, 7);
652 public void drawHelixAnnot(Graphics g, Annotation[] row,
653 int lastSSX, int x, int y, int iconOffset, int startRes,
654 int column, boolean validRes, boolean validEnd)
656 g.setColor(HELIX_COLOUR);
658 int sCol = (lastSSX / charWidth) + startRes;
660 int x2 = (x * charWidth);
664 int ofs = charWidth / 2;
665 // Off by 1 offset when drawing rects and ovals
666 // to offscreen image on the MAC
667 g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1, 8, 8, 8);
668 if (sCol == 0 || row[sCol - 1] == null
669 || row[sCol - 1].secondaryStructure != 'H')
674 // g.setColor(Color.orange);
675 g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1 - ofs + 1, 8,
678 if (!validRes || row[column] == null
679 || row[column].secondaryStructure != 'H')
685 // g.setColor(Color.magenta);
686 g.fillRoundRect(lastSSX + ofs, y + 4 + iconOffset, x2 - x1 - ofs
694 if (sCol == 0 || row[sCol - 1] == null
695 || row[sCol - 1].secondaryStructure != 'H')
697 g.fillArc(lastSSX, y + 4 + iconOffset, charWidth, 8, 90, 180);
701 if (!validRes || row[column] == null
702 || row[column].secondaryStructure != 'H')
704 g.fillArc((x * charWidth) - charWidth, y + 4 + iconOffset, charWidth,
709 g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
712 public void drawLineGraph(Graphics g, AlignmentAnnotation _aa, Annotation[] aa_annotations, int sRes,
713 int eRes, int y, float min, float max, int graphHeight)
715 if (sRes > aa_annotations.length)
722 // Adjustment for fastpaint to left
728 eRes = Math.min(eRes, aa_annotations.length);
736 float range = max - min;
741 y2 = y - (int) ((0 - min / range) * graphHeight);
744 g.setColor(Color.gray);
745 g.drawLine(x - charWidth, y2, (eRes - sRes + 1) * charWidth, y2);
747 eRes = Math.min(eRes, aa_annotations.length);
750 int aaMax = aa_annotations.length - 1;
752 while (x < eRes - sRes)
755 if (hasHiddenColumns)
757 column = columnSelection.adjustForHiddenColumns(column);
765 if (aa_annotations[column] == null
766 || aa_annotations[column - 1] == null)
772 if (aa_annotations[column].colour == null)
773 g.setColor(Color.black);
775 g.setColor(aa_annotations[column].colour);
778 - (int) (((aa_annotations[column - 1].value - min) / range) * graphHeight);
780 - (int) (((aa_annotations[column].value - min) / range) * graphHeight);
782 g.drawLine(x * charWidth - charWidth / 2, y1, x * charWidth
783 + charWidth / 2, y2);
787 if (_aa.threshold != null)
789 g.setColor(_aa.threshold.colour);
790 Graphics2D g2 = (Graphics2D) g;
791 g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
792 BasicStroke.JOIN_ROUND, 3f, new float[]
795 y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
796 g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
797 g2.setStroke(new BasicStroke());
801 public void drawBarGraph(Graphics g, AlignmentAnnotation _aa, Annotation[] aa_annotations, int sRes,
802 int eRes, float min, float max, int y)
804 if (sRes > aa_annotations.length)
808 Font ofont = g.getFont();
809 eRes = Math.min(eRes, aa_annotations.length);
811 int x = 0, y1 = y, y2 = y;
813 float range = max - min;
817 y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
820 g.setColor(Color.gray);
822 g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
825 int aaMax = aa_annotations.length - 1;
826 boolean renderHistogram = true, renderProfile = true, normaliseProfile = false;
827 // if (aa.autoCalculated && aa.label.startsWith("Consensus"))
829 // TODO: generalise this to have render styles for consensus/profile data
830 if (_aa.groupRef != null)
832 renderHistogram = _aa.groupRef.isShowConsensusHistogram();
833 renderProfile = _aa.groupRef.isShowSequenceLogo();
834 normaliseProfile = _aa.groupRef.isNormaliseSequenceLogo();
838 renderHistogram = av_renderHistogram;
839 renderProfile = av_renderProfile;
840 normaliseProfile = av_normaliseProfile;
843 while (x < eRes - sRes)
846 if (hasHiddenColumns)
848 column = columnSelection.adjustForHiddenColumns(column);
856 if (aa_annotations[column] == null)
861 if (aa_annotations[column].colour == null)
862 g.setColor(Color.black);
864 g.setColor(aa_annotations[column].colour);
867 - (int) (((aa_annotations[column].value - min) / (range)) * _aa.graphHeight);
873 g.fillRect(x * charWidth, y2, charWidth, y1 - y2);
877 g.fillRect(x * charWidth, y1, charWidth, y2 - y1);
880 // draw profile if available
884 int profl[] = getProfileFor(_aa, column);
885 // just try to draw the logo if profl is not null
886 if (profl != null && profl[1] != 0)
888 float ht = normaliseProfile ? y - _aa.graphHeight : y1;
889 double htn = normaliseProfile ? _aa.graphHeight : (y2 - y1);// aa.graphHeight;
896 * profl.length == 74 indicates that the profile of a secondary
897 * structure conservation row was accesed. Therefore dc gets length 2,
898 * to have space for a basepair instead of just a single nucleotide
900 if (profl.length == 74)
908 LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
909 double scale = 1f / (normaliseProfile ? profl[1] : 100f);
910 float ofontHeight = 1f / lm.getAscent();// magnify to fill box
912 for (int c = 2; c < profl[0];)
914 dc[0] = (char) profl[c++];
916 if (_aa.label.startsWith("StrucConsensus"))
918 dc[1] = (char) profl[c++];
922 wdth /= fm.charsWidth(dc, 0, dc.length);
926 scl = htn * scale * profl[c++];
927 lm = ofont.getLineMetrics(dc, 0, 1, g.getFontMetrics()
928 .getFontRenderContext());
929 g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
930 wdth, scl / lm.getAscent())));
931 lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
933 // Debug - render boxes around characters
934 // g.setColor(Color.red);
935 // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
937 // g.setColor(profcolour.findColour(dc[0]).darker());
938 g.setColor(profcolour.findColour(dc[0], column,null));
940 hght = (ht + (scl - lm.getDescent() - lm.getBaselineOffsets()[lm
941 .getBaselineIndex()]));
943 g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
951 if (_aa.threshold != null)
953 g.setColor(_aa.threshold.colour);
954 Graphics2D g2 = (Graphics2D) g;
955 g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
956 BasicStroke.JOIN_ROUND, 3f, new float[]
959 y2 = (int) (y - ((_aa.threshold.value - min) / range) * _aa.graphHeight);
960 g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
961 g2.setStroke(new BasicStroke());
965 // used by overview window
966 public void drawGraph(Graphics g, AlignmentAnnotation _aa, Annotation[] aa_annotations, int width,
967 int y, int sRes, int eRes)
969 eRes = Math.min(eRes, aa_annotations.length);
970 g.setColor(Color.white);
971 g.fillRect(0, 0, width, y);
972 g.setColor(new Color(0, 0, 180));
976 for (int j = sRes; j < eRes; j++)
978 if (aa_annotations[j] != null)
980 if (aa_annotations[j].colour == null)
981 g.setColor(Color.black);
983 g.setColor(aa_annotations[j].colour);
985 height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
991 g.fillRect(x, y - height, charWidth, height);