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.ColumnSelection;
8 import jalview.schemes.ColourSchemeI;
10 import java.awt.BasicStroke;
11 import java.awt.Color;
13 import java.awt.FontMetrics;
14 import java.awt.Graphics;
15 import java.awt.Graphics2D;
16 import java.awt.Image;
17 import java.awt.font.LineMetrics;
18 import java.awt.geom.AffineTransform;
19 import java.awt.image.ImageObserver;
20 import java.util.Hashtable;
22 import com.stevesoft.pat.Regex;
24 public class AnnotationRenderer
27 public AnnotationRenderer()
29 // TODO Auto-generated constructor stub
32 public void drawStemAnnot(Graphics g, AlignmentAnnotation row,
33 int lastSSX, int x, int y, int iconOffset, int startRes,
34 int column, boolean validRes, boolean validEnd)
36 g.setColor(STEM_COLOUR);
37 int sCol = (lastSSX / charWidth) + startRes;
39 int x2 = (x * charWidth);
40 Regex closeparen = new Regex("(\\))");
42 String dc = (column == 0 || row.annotations[column - 1] == null) ? ""
43 : row.annotations[column - 1].displayCharacter;
45 boolean diffupstream = sCol == 0 || row.annotations[sCol - 1] == null
46 || !dc.equals(row.annotations[sCol - 1].displayCharacter);
47 boolean diffdownstream = !validRes || !validEnd
48 || row.annotations[column] == null
49 || !dc.equals(row.annotations[column].displayCharacter);
50 // System.out.println("Column "+column+" diff up: "+diffupstream+" down:"+diffdownstream);
51 // If a closing base pair half of the stem, display a backward arrow
52 if (column > 0 && closeparen.search(dc))
55 // if (validRes && column>1 && row.annotations[column-2]!=null &&
56 // dc.equals(row.annotations[column-2].displayCharacter))
58 g.fillPolygon(new int[]
59 { lastSSX + 5, lastSSX + 5, lastSSX }, new int[]
60 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
70 // display a forward arrow
73 g.fillPolygon(new int[]
74 { x2 - 5, x2 - 5, x2 }, new int[]
75 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset }, 3);
84 g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
87 private int charWidth, endRes, charHeight;
89 private boolean validCharWidth, hasHiddenColumns;
91 private FontMetrics fm;
93 private boolean MAC = new jalview.util.Platform().isAMac();
95 boolean av_renderHistogram = true, av_renderProfile = true,
96 av_normaliseProfile = false;
98 ColourSchemeI profcolour = null;
100 private ColumnSelection columnSelection;
102 private Hashtable[] hconsensus;
104 private Hashtable[] hStrucConsensus;
106 private boolean av_ignoreGapsConsensus;
109 * attributes set from AwtRenderPanelI
112 * old image used when data is currently being calculated and cannot be
115 private Image fadedImage;
118 * panel being rendered into
120 private ImageObserver annotationPanel;
123 * width of image to render in panel
125 private int imgWidth;
127 // public void updateFromAnnotationPanel(FontMetrics annotFM, AlignViewportI
129 public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
132 fm = annotPanel.getFontMetrics();
133 annotationPanel = annotPanel;
134 fadedImage = annotPanel.getFadedImage();
135 imgWidth = annotPanel.getFadedImageWidth();
136 updateFromAlignViewport(av);
139 public void updateFromAlignViewport(AlignViewportI av)
141 charWidth = av.getCharWidth();
142 endRes = av.getEndRes();
143 charHeight = av.getCharHeight();
144 hasHiddenColumns = av.hasHiddenColumns();
145 validCharWidth = av.isValidCharWidth();
146 av_renderHistogram = av.isShowConsensusHistogram();
147 av_renderProfile = av.isShowSequenceLogo();
148 av_normaliseProfile = av.isNormaliseSequenceLogo();
149 profcolour = av.getGlobalColourScheme();
150 if (profcolour == null)
152 // Set the default colour for sequence logo if the alignnent has no
154 profcolour = av.getAlignment().isNucleotide() ? new jalview.schemes.NucleotideColourScheme()
155 : new jalview.schemes.ZappoColourScheme();
157 columnSelection = av.getColumnSelection();
158 hconsensus = av.getSequenceConsensusHash();// hconsensus;
159 hStrucConsensus = av.getRnaStructureConsensusHash(); // hStrucConsensus;
160 av_ignoreGapsConsensus = av.getIgnoreGapsConsensus();
163 public int[] getProfileFor(AlignmentAnnotation aa, int column)
165 // TODO : consider refactoring the global alignment calculation
166 // properties/rendering attributes as a global 'alignment group' which holds
167 // all vis settings for the alignment as a whole rather than a subset
169 if (aa.autoCalculated && aa.label.startsWith("Consensus"))
171 if (aa.groupRef != null && aa.groupRef.consensusData != null
172 && aa.groupRef.isShowSequenceLogo())
174 return AAFrequency.extractProfile(
175 aa.groupRef.consensusData[column],
176 aa.groupRef.getIgnoreGapsConsensus());
178 // TODO extend annotation row to enable dynamic and static profile data to
180 if (aa.groupRef == null && aa.sequenceRef == null && av_renderProfile)
182 return AAFrequency.extractProfile(hconsensus[column],
183 av_ignoreGapsConsensus);
188 if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
190 // TODO implement group structure consensus
192 * if (aa.groupRef != null && aa.groupRef.consensusData != null &&
193 * aa.groupRef.isShowSequenceLogo()) { //TODO check what happens for
194 * group selections return StructureFrequency.extractProfile(
195 * aa.groupRef.consensusData[column], aa.groupRef
196 * .getIgnoreGapsConsensus()); }
198 // TODO extend annotation row to enable dynamic and static profile data
201 if (aa.groupRef == null && aa.sequenceRef == null
202 && av_renderProfile && hStrucConsensus != null
203 && hStrucConsensus.length > column)
205 return StructureFrequency.extractProfile(hStrucConsensus[column],
206 av_ignoreGapsConsensus);
216 * @param annotationPanel
225 public void drawComponent(AwtRenderPanelI annotPanel, AlignViewportI av,
226 Graphics g, int activeRow, int startRes, int endRes)
229 // AnnotationPanel needs to implement: ImageObserver, access to
231 updateFromAwtRenderPanel(annotPanel, av);
232 fm = g.getFontMetrics();
233 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
240 boolean validRes = false;
241 boolean validEnd = false;
242 boolean labelAllCols = false;
243 boolean centreColLabels, centreColLabelsDef = av
244 .getCentreColumnLabels();
245 boolean scaleColLabel = false;
246 boolean[] graphGroupDrawn = new boolean[aa.length];
247 int charOffset = 0; // offset for a label
248 float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
250 Font ofont = g.getFont();
252 for (int i = 0; i < aa.length; i++)
254 AlignmentAnnotation row = aa[i];
260 centreColLabels = row.centreColLabels || centreColLabelsDef;
261 labelAllCols = row.showAllColLabels;
262 scaleColLabel = row.scaleColLabel;
267 if (row.graphGroup > -1 && graphGroupDrawn[row.graphGroup])
272 // this is so that we draw the characters below the graph
277 iconOffset = charHeight - fm.getDescent();
281 else if (row.hasText)
283 iconOffset = charHeight - fm.getDescent();
291 if (aa[i].autoCalculated && av.isCalculationInProgress(aa[i]))
295 g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0, y
296 - row.height, imgWidth, y, annotationPanel);
297 g.setColor(Color.black);
298 // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
304 * else if (annotationPanel.av.updatingConservation &&
305 * aa[i].label.equals("Conservation")) {
307 * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
308 * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
309 * annotationPanel.imgWidth, y, annotationPanel);
311 * g.setColor(Color.black); //
312 * g.drawString("Calculating Conservation.....",20, y-row.height/2);
314 * continue; } else if (annotationPanel.av.updatingConservation &&
315 * aa[i].label.equals("Quality")) {
317 * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
318 * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
319 * annotationPanel.imgWidth, y, annotationPanel); g.setColor(Color.black);
320 * // / g.drawString("Calculating Quality....",20, y-row.height/2);
324 // first pass sets up state for drawing continuation from left-hand column
326 x = (startRes == 0) ? 0 : -1;
327 while (x < endRes - startRes)
329 if (hasHiddenColumns)
331 column = columnSelection.adjustForHiddenColumns(startRes + x);
332 if (column > row.annotations.length - 1)
339 column = startRes + x;
342 if ((row.annotations == null) || (row.annotations.length <= column)
343 || (row.annotations[column] == null))
355 g.setColor(Color.red);
357 if (columnSelection != null)
359 for (int n = 0; n < columnSelection.size(); n++)
361 int v = columnSelection.columnAt(n);
365 g.fillRect(x * charWidth, y, charWidth, charHeight);
370 if (!row.isValidStruc())
372 g.setColor(Color.orange);
373 g.fillRect((int) row.getInvalidStrucPos() * charWidth, y,
374 charWidth, charHeight);
378 && row.annotations[column].displayCharacter != null
379 && (row.annotations[column].displayCharacter.length() > 0))
382 if (centreColLabels || scaleColLabel)
384 fmWidth = (float) fm.charsWidth(
385 row.annotations[column].displayCharacter
387 row.annotations[column].displayCharacter.length());
391 // justify the label and scale to fit in column
392 if (fmWidth > charWidth)
394 // scale only if the current font isn't already small enough
395 fmScaling = charWidth;
396 fmScaling /= fmWidth;
397 g.setFont(ofont.deriveFont(AffineTransform
398 .getScaleInstance(fmScaling, 1.0)));
399 // and update the label's width to reflect the scaling.
407 .charWidth(row.annotations[column].displayCharacter
410 charOffset = (int) ((charWidth - fmWidth) / 2f);
412 if (row.annotations[column].colour == null)
413 g.setColor(Color.black);
415 g.setColor(row.annotations[column].colour);
417 if (column == 0 || row.graph > 0)
419 g.drawString(row.annotations[column].displayCharacter,
420 (x * charWidth) + charOffset, y + iconOffset);
422 else if (row.annotations[column - 1] == null
424 || !row.annotations[column].displayCharacter
425 .equals(row.annotations[column - 1].displayCharacter) || (row.annotations[column].displayCharacter
426 .length() < 2 && row.annotations[column].secondaryStructure == ' ')))
428 g.drawString(row.annotations[column].displayCharacter, x
429 * charWidth + charOffset, y + iconOffset);
436 char ss = validRes ? row.annotations[column].secondaryStructure
440 // distinguish between forward/backward base-pairing
441 if (row.annotations[column].displayCharacter.indexOf(')') > -1)
446 if (!validRes || (ss != lastSS))
453 drawHelixAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
454 column, validRes, validEnd);
458 drawSheetAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
459 column, validRes, validEnd);
462 case 'S': // Stem case for RNA secondary structure
463 case 's': // and opposite direction
464 drawStemAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
465 column, validRes, validEnd);
469 g.setColor(Color.gray);
470 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth)
486 lastSSX = (x * charWidth);
493 if (column >= row.annotations.length)
495 column = row.annotations.length - 1;
502 if ((row.annotations == null) || (row.annotations.length <= column)
503 || (row.annotations[column] == null))
519 drawHelixAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
520 column, validRes, validEnd);
524 drawSheetAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
525 column, validRes, validEnd);
528 case 'S': // Stem case for RNA secondary structure
529 drawStemAnnot(g, row, lastSSX, x, y, iconOffset, startRes,
530 column, validRes, validEnd);
533 drawGlyphLine(g, row, lastSSX, x, y, iconOffset, startRes,
534 column, validRes, validEnd);
539 if (row.graph > 0 && row.graphHeight > 0)
541 if (row.graph == AlignmentAnnotation.LINE_GRAPH)
543 if (row.graphGroup > -1 && !graphGroupDrawn[row.graphGroup])
545 float groupmax = -999999, groupmin = 9999999;
546 for (int gg = 0; gg < aa.length; gg++)
548 if (aa[gg].graphGroup != row.graphGroup)
555 aa[gg].visible = false;
558 if (aa[gg].graphMax > groupmax)
560 groupmax = aa[gg].graphMax;
562 if (aa[gg].graphMin < groupmin)
564 groupmin = aa[gg].graphMin;
568 for (int gg = 0; gg < aa.length; gg++)
570 if (aa[gg].graphGroup == row.graphGroup)
572 drawLineGraph(g, aa[gg], startRes, endRes, y, groupmin,
573 groupmax, row.graphHeight);
577 graphGroupDrawn[row.graphGroup] = true;
581 drawLineGraph(g, row, startRes, endRes, y, row.graphMin,
582 row.graphMax, row.graphHeight);
585 else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
587 drawBarGraph(g, row, startRes, endRes, row.graphMin,
592 if (row.graph > 0 && row.hasText)
604 private Color GLYPHLINE_COLOR = Color.gray;
606 private Color SHEET_COLOUR = Color.green;
608 private Color HELIX_COLOUR = Color.red;
610 private Color STEM_COLOUR = Color.blue;
612 public void drawGlyphLine(Graphics g, AlignmentAnnotation row,
613 int lastSSX, int x, int y, int iconOffset, int startRes,
614 int column, boolean validRes, boolean validEnd)
616 g.setColor(GLYPHLINE_COLOR);
617 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
620 public void drawSheetAnnot(Graphics g, AlignmentAnnotation row,
621 int lastSSX, int x, int y, int iconOffset, int startRes,
622 int column, boolean validRes, boolean validEnd)
624 g.setColor(SHEET_COLOUR);
626 if (!validEnd || !validRes || row.annotations[column] == null
627 || row.annotations[column].secondaryStructure != 'E')
629 g.fillRect(lastSSX, y + 4 + iconOffset,
630 (x * charWidth) - lastSSX - 4, 7);
631 g.fillPolygon(new int[]
632 { (x * charWidth) - 4, (x * charWidth) - 4, (x * charWidth) },
634 { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
639 g.fillRect(lastSSX, y + 4 + iconOffset,
640 (x + 1) * charWidth - lastSSX, 7);
645 public void drawHelixAnnot(Graphics g, AlignmentAnnotation row,
646 int lastSSX, int x, int y, int iconOffset, int startRes,
647 int column, boolean validRes, boolean validEnd)
649 g.setColor(HELIX_COLOUR);
651 int sCol = (lastSSX / charWidth) + startRes;
653 int x2 = (x * charWidth);
657 int ofs = charWidth / 2;
658 // Off by 1 offset when drawing rects and ovals
659 // to offscreen image on the MAC
660 g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1, 8, 8, 8);
661 if (sCol == 0 || row.annotations[sCol - 1] == null
662 || row.annotations[sCol - 1].secondaryStructure != 'H')
667 // g.setColor(Color.orange);
668 g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1 - ofs + 1, 8,
671 if (!validRes || row.annotations[column] == null
672 || row.annotations[column].secondaryStructure != 'H')
678 // g.setColor(Color.magenta);
679 g.fillRoundRect(lastSSX + ofs, y + 4 + iconOffset, x2 - x1 - ofs
687 if (sCol == 0 || row.annotations[sCol - 1] == null
688 || row.annotations[sCol - 1].secondaryStructure != 'H')
690 g.fillArc(lastSSX, y + 4 + iconOffset, charWidth, 8, 90, 180);
694 if (!validRes || row.annotations[column] == null
695 || row.annotations[column].secondaryStructure != 'H')
697 g.fillArc((x * charWidth) - charWidth, y + 4 + iconOffset, charWidth,
702 g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
705 public void drawLineGraph(Graphics g, AlignmentAnnotation aa, int sRes,
706 int eRes, int y, float min, float max, int graphHeight)
708 if (sRes > aa.annotations.length)
715 // Adjustment for fastpaint to left
721 eRes = Math.min(eRes, aa.annotations.length);
729 float range = max - min;
734 y2 = y - (int) ((0 - min / range) * graphHeight);
737 g.setColor(Color.gray);
738 g.drawLine(x - charWidth, y2, (eRes - sRes + 1) * charWidth, y2);
740 eRes = Math.min(eRes, aa.annotations.length);
743 int aaMax = aa.annotations.length - 1;
745 while (x < eRes - sRes)
748 if (hasHiddenColumns)
750 column = columnSelection.adjustForHiddenColumns(column);
758 if (aa.annotations[column] == null
759 || aa.annotations[column - 1] == null)
765 if (aa.annotations[column].colour == null)
766 g.setColor(Color.black);
768 g.setColor(aa.annotations[column].colour);
771 - (int) (((aa.annotations[column - 1].value - min) / range) * graphHeight);
773 - (int) (((aa.annotations[column].value - min) / range) * graphHeight);
775 g.drawLine(x * charWidth - charWidth / 2, y1, x * charWidth
776 + charWidth / 2, y2);
780 if (aa.threshold != null)
782 g.setColor(aa.threshold.colour);
783 Graphics2D g2 = (Graphics2D) g;
784 g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
785 BasicStroke.JOIN_ROUND, 3f, new float[]
788 y2 = (int) (y - ((aa.threshold.value - min) / range) * graphHeight);
789 g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
790 g2.setStroke(new BasicStroke());
794 public void drawBarGraph(Graphics g, AlignmentAnnotation aa, int sRes,
795 int eRes, float min, float max, int y)
797 if (sRes > aa.annotations.length)
801 Font ofont = g.getFont();
802 eRes = Math.min(eRes, aa.annotations.length);
804 int x = 0, y1 = y, y2 = y;
806 float range = max - min;
810 y2 = y - (int) ((0 - min / (range)) * aa.graphHeight);
813 g.setColor(Color.gray);
815 g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
818 int aaMax = aa.annotations.length - 1;
819 boolean renderHistogram = true, renderProfile = true, normaliseProfile = false;
820 // if (aa.autoCalculated && aa.label.startsWith("Consensus"))
822 // TODO: generalise this to have render styles for consensus/profile data
823 if (aa.groupRef != null)
825 renderHistogram = aa.groupRef.isShowConsensusHistogram();
826 renderProfile = aa.groupRef.isShowSequenceLogo();
827 normaliseProfile = aa.groupRef.isNormaliseSequenceLogo();
831 renderHistogram = av_renderHistogram;
832 renderProfile = av_renderProfile;
833 normaliseProfile = av_normaliseProfile;
836 while (x < eRes - sRes)
839 if (hasHiddenColumns)
841 column = columnSelection.adjustForHiddenColumns(column);
849 if (aa.annotations[column] == null)
854 if (aa.annotations[column].colour == null)
855 g.setColor(Color.black);
857 g.setColor(aa.annotations[column].colour);
860 - (int) (((aa.annotations[column].value - min) / (range)) * aa.graphHeight);
866 g.fillRect(x * charWidth, y2, charWidth, y1 - y2);
870 g.fillRect(x * charWidth, y1, charWidth, y2 - y1);
873 // draw profile if available
874 if (renderProfile && aa.annotations[column].value != 0)
877 int profl[] = getProfileFor(aa, column);
878 // just try to draw the logo if profl is not null
879 if (profl != null && profl[1] != 0)
881 float ht = normaliseProfile ? y - aa.graphHeight : y1;
882 double htn = normaliseProfile ? aa.graphHeight : (y2 - y1);// aa.graphHeight;
889 * profl.length == 74 indicates that the profile of a secondary
890 * structure conservation row was accesed. Therefore dc gets length 2,
891 * to have space for a basepair instead of just a single nucleotide
893 if (profl.length == 74)
901 LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
902 double scale = 1f / (normaliseProfile ? profl[1] : 100f);
903 float ofontHeight = 1f / lm.getAscent();// magnify to fill box
905 for (int c = 2; c < profl[0];)
907 dc[0] = (char) profl[c++];
909 if (aa.label.startsWith("StrucConsensus"))
911 dc[1] = (char) profl[c++];
915 wdth /= (float) fm.charsWidth(dc, 0, dc.length);
919 scl = ((double) htn) * scale * ((double) profl[c++]);
920 lm = ofont.getLineMetrics(dc, 0, 1, g.getFontMetrics()
921 .getFontRenderContext());
922 g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
923 wdth, scl / lm.getAscent())));
924 lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
926 // Debug - render boxes around characters
927 // g.setColor(Color.red);
928 // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
930 // g.setColor(profcolour.findColour(dc[0]).darker());
931 g.setColor(profcolour.findColour(dc[0], column));
933 hght = (ht + (scl - lm.getDescent() - lm.getBaselineOffsets()[lm
934 .getBaselineIndex()]));
936 g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
944 if (aa.threshold != null)
946 g.setColor(aa.threshold.colour);
947 Graphics2D g2 = (Graphics2D) g;
948 g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
949 BasicStroke.JOIN_ROUND, 3f, new float[]
952 y2 = (int) (y - ((aa.threshold.value - min) / range) * aa.graphHeight);
953 g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
954 g2.setStroke(new BasicStroke());
958 // used by overview window
959 public void drawGraph(Graphics g, AlignmentAnnotation aa, int width,
960 int y, int sRes, int eRes)
962 eRes = Math.min(eRes, aa.annotations.length);
963 g.setColor(Color.white);
964 g.fillRect(0, 0, width, y);
965 g.setColor(new Color(0, 0, 180));
969 for (int j = sRes; j < eRes; j++)
971 if (aa.annotations[j] != null)
973 if (aa.annotations[j].colour == null)
974 g.setColor(Color.black);
976 g.setColor(aa.annotations[j].colour);
978 height = (int) ((aa.annotations[j].value / aa.graphMax) * y);
984 g.fillRect(x, y - height, charWidth, height);