JAL-3485 constants for CDNA_CONSENSUS, STRUCTURE_CONSENSUS added
[jalview.git] / src / jalview / renderer / AnnotationRenderer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.renderer;
22
23 import jalview.analysis.AAFrequency;
24 import jalview.analysis.CodingUtils;
25 import jalview.analysis.Rna;
26 import jalview.analysis.StructureFrequency;
27 import jalview.api.AlignViewportI;
28 import jalview.datamodel.AlignmentAnnotation;
29 import jalview.datamodel.Annotation;
30 import jalview.datamodel.ColumnSelection;
31 import jalview.datamodel.HiddenColumns;
32 import jalview.datamodel.ProfilesI;
33 import jalview.schemes.ColourSchemeI;
34 import jalview.schemes.NucleotideColourScheme;
35 import jalview.schemes.ResidueProperties;
36 import jalview.schemes.ZappoColourScheme;
37 import jalview.util.Platform;
38 import jalview.viewmodel.AlignmentViewport.AutoAnnotation;
39
40 import java.awt.BasicStroke;
41 import java.awt.Color;
42 import java.awt.Font;
43 import java.awt.FontMetrics;
44 import java.awt.Graphics;
45 import java.awt.Graphics2D;
46 import java.awt.Image;
47 import java.awt.font.LineMetrics;
48 import java.awt.geom.AffineTransform;
49 import java.awt.image.ImageObserver;
50 import java.util.BitSet;
51 import java.util.Hashtable;
52
53 public class AnnotationRenderer
54 {
55   private static final int UPPER_TO_LOWER = 'a' - 'A'; // 32
56
57   private static final int CHAR_A = 'A'; // 65
58
59   private static final int CHAR_Z = 'Z'; // 90
60
61   /**
62    * flag indicating if timing and redraw parameter info should be output
63    */
64   private final boolean debugRedraw;
65
66   private int charWidth, endRes, charHeight;
67
68   private boolean validCharWidth, hasHiddenColumns;
69
70   private FontMetrics fm;
71
72   private final boolean MAC = Platform.isAMac();
73
74   boolean av_renderHistogram = true, av_renderProfile = true,
75           av_normaliseProfile = false;
76
77   ResidueShaderI profcolour = null;
78
79   private ColumnSelection columnSelection;
80
81   private HiddenColumns hiddenColumns;
82
83   private ProfilesI hconsensus;
84
85   private Hashtable[] complementConsensus;
86
87   private Hashtable[] hStrucConsensus;
88
89   private boolean av_ignoreGapsConsensus;
90
91   /**
92    * attributes set from AwtRenderPanelI
93    */
94   /**
95    * old image used when data is currently being calculated and cannot be
96    * rendered
97    */
98   private Image fadedImage;
99
100   /**
101    * panel being rendered into
102    */
103   private ImageObserver annotationPanel;
104
105   /**
106    * width of image to render in panel
107    */
108   private int imgWidth;
109
110   /**
111    * offset to beginning of visible area
112    */
113   private int sOffset;
114
115   /**
116    * offset to end of visible area
117    */
118   private int visHeight;
119
120   /**
121    * indicate if the renderer should only render the visible portion of the
122    * annotation given the current view settings
123    */
124   private boolean useClip = true;
125
126   /**
127    * master flag indicating if renderer should ever try to clip. not enabled for
128    * jalview 2.8.1
129    */
130   private boolean canClip = false;
131
132   public AnnotationRenderer()
133   {
134     this(false);
135   }
136
137   /**
138    * Create a new annotation Renderer
139    * 
140    * @param debugRedraw
141    *          flag indicating if timing and redraw parameter info should be
142    *          output
143    */
144   public AnnotationRenderer(boolean debugRedraw)
145   {
146     this.debugRedraw = debugRedraw;
147   }
148
149   /**
150    * Remove any references and resources when this object is no longer required
151    */
152   public void dispose()
153   {
154     hconsensus = null;
155     complementConsensus = null;
156     hStrucConsensus = null;
157     fadedImage = null;
158     annotationPanel = null;
159   }
160
161   void drawStemAnnot(Graphics g, Annotation[] row_annotations, int lastSSX,
162           int x, int y, int iconOffset, int startRes, int column,
163           boolean validRes, boolean validEnd)
164   {
165     g.setColor(STEM_COLOUR);
166     int sCol = (lastSSX / charWidth)
167             + hiddenColumns.visibleToAbsoluteColumn(startRes);
168     int x1 = lastSSX;
169     int x2 = (x * charWidth);
170
171     char dc = (column == 0 || row_annotations[column - 1] == null) ? ' '
172             : row_annotations[column - 1].secondaryStructure;
173
174     boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
175             || dc != row_annotations[sCol - 1].secondaryStructure;
176     boolean diffdownstream = !validRes || !validEnd
177             || row_annotations[column] == null
178             || dc != row_annotations[column].secondaryStructure;
179
180     if (column > 0 && Rna.isClosingParenthesis(dc))
181     {
182       if (diffupstream)
183       // if (validRes && column>1 && row_annotations[column-2]!=null &&
184       // dc.equals(row_annotations[column-2].displayCharacter))
185       {
186         /*
187          * if new annotation with a closing base pair half of the stem, 
188          * display a backward arrow
189          */
190         g.fillPolygon(new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
191                 new int[]
192                 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
193                 3);
194         x1 += 5;
195       }
196       if (diffdownstream)
197       {
198         x2 -= 1;
199       }
200     }
201     else
202     {
203       // display a forward arrow
204       if (diffdownstream)
205       {
206         /*
207          * if annotation ending with an opeing base pair half of the stem, 
208          * display a forward arrow
209          */
210         g.fillPolygon(new int[] { x2 - 5, x2 - 5, x2 },
211                 new int[]
212                 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
213                 3);
214         x2 -= 5;
215       }
216       if (diffupstream)
217       {
218         x1 += 1;
219       }
220     }
221     // draw arrow body
222     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
223   }
224
225   void drawNotCanonicalAnnot(Graphics g, Color nonCanColor,
226           Annotation[] row_annotations, int lastSSX, int x, int y,
227           int iconOffset, int startRes, int column, boolean validRes,
228           boolean validEnd)
229   {
230     // System.out.println(nonCanColor);
231
232     g.setColor(nonCanColor);
233     int sCol = (lastSSX / charWidth)
234             + hiddenColumns.visibleToAbsoluteColumn(startRes);
235     int x1 = lastSSX;
236     int x2 = (x * charWidth);
237
238     String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
239             : row_annotations[column - 1].displayCharacter;
240
241     boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
242             || !dc.equals(row_annotations[sCol - 1].displayCharacter);
243     boolean diffdownstream = !validRes || !validEnd
244             || row_annotations[column] == null
245             || !dc.equals(row_annotations[column].displayCharacter);
246     // System.out.println("Column "+column+" diff up: "+diffupstream+"
247     // down:"+diffdownstream);
248     // If a closing base pair half of the stem, display a backward arrow
249     if (column > 0 && Rna.isClosingParenthesis(dc))
250     {
251
252       if (diffupstream)
253       // if (validRes && column>1 && row_annotations[column-2]!=null &&
254       // dc.equals(row_annotations[column-2].displayCharacter))
255       {
256         g.fillPolygon(new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
257                 new int[]
258                 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
259                 3);
260         x1 += 5;
261       }
262       if (diffdownstream)
263       {
264         x2 -= 1;
265       }
266     }
267     else
268     {
269
270       // display a forward arrow
271       if (diffdownstream)
272       {
273         g.fillPolygon(new int[] { x2 - 5, x2 - 5, x2 },
274                 new int[]
275                 { y + iconOffset, y + 14 + iconOffset, y + 8 + iconOffset },
276                 3);
277         x2 -= 5;
278       }
279       if (diffupstream)
280       {
281         x1 += 1;
282       }
283     }
284     // draw arrow body
285     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 7);
286   }
287
288   // public void updateFromAnnotationPanel(FontMetrics annotFM, AlignViewportI
289   // av)
290   public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
291           AlignViewportI av)
292   {
293     fm = annotPanel.getFontMetrics();
294     annotationPanel = annotPanel;
295     fadedImage = annotPanel.getFadedImage();
296     imgWidth = annotPanel.getFadedImageWidth();
297     // visible area for rendering
298     int[] bounds = annotPanel.getVisibleVRange();
299     if (bounds != null)
300     {
301       sOffset = bounds[0];
302       visHeight = bounds[1];
303       if (visHeight == 0)
304       {
305         useClip = false;
306       }
307       else
308       {
309         useClip = canClip;
310       }
311     }
312     else
313     {
314       useClip = false;
315     }
316
317     updateFromAlignViewport(av);
318   }
319
320   public void updateFromAlignViewport(AlignViewportI av)
321   {
322     charWidth = av.getCharWidth();
323     endRes = av.getRanges().getEndRes();
324     charHeight = av.getCharHeight();
325     hasHiddenColumns = av.hasHiddenColumns();
326     validCharWidth = av.isValidCharWidth();
327     av_renderHistogram = av.isShowConsensusHistogram();
328     av_renderProfile = av.isShowSequenceLogo();
329     av_normaliseProfile = av.isNormaliseSequenceLogo();
330     profcolour = av.getResidueShading();
331     if (profcolour == null || profcolour.getColourScheme() == null)
332     {
333       /*
334        * Use default colour for sequence logo if 
335        * the alignment has no colourscheme set
336        * (would like to use user preference but n/a for applet)
337        */
338       ColourSchemeI col = av.getAlignment().isNucleotide()
339               ? new NucleotideColourScheme()
340               : new ZappoColourScheme();
341       profcolour = new ResidueShader(col);
342     }
343     columnSelection = av.getColumnSelection();
344     hiddenColumns = av.getAlignment().getHiddenColumns();
345     hconsensus = av.getSequenceConsensusHash();
346     complementConsensus = av.getComplementConsensusHash();
347     hStrucConsensus = av.getRnaStructureConsensusHash();
348     av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
349   }
350
351   /**
352    * Returns profile data; the first element is the profile type, the second is
353    * the number of distinct values, the third the total count, and the remainder
354    * depend on the profile type.
355    * 
356    * @param aa
357    * @param column
358    * @return
359    */
360   int[] getProfileFor(AlignmentAnnotation aa, int column)
361   {
362     // TODO : consider refactoring the global alignment calculation
363     // properties/rendering attributes as a global 'alignment group' which holds
364     // all vis settings for the alignment as a whole rather than a subset
365     //
366     if (aa.autoCalculated
367             && (aa.label.startsWith(AutoAnnotation.CONSENSUS.label)
368                     || aa.label.startsWith(
369                             AutoAnnotation.CDNA_CONSENSUS.label)))
370     {
371       boolean forComplement = aa.label
372               .startsWith(AutoAnnotation.CDNA_CONSENSUS.label);
373       if (aa.groupRef != null && aa.groupRef.consensusData != null
374               && aa.groupRef.isShowSequenceLogo())
375       {
376         // TODO? group consensus for cDNA complement
377         return AAFrequency.extractProfile(
378                 aa.groupRef.consensusData.get(column),
379                 aa.groupRef.getIgnoreGapsConsensus());
380       }
381       // TODO extend annotation row to enable dynamic and static profile data to
382       // be stored
383       if (aa.groupRef == null && aa.sequenceRef == null)
384       {
385         if (forComplement)
386         {
387           return AAFrequency.extractCdnaProfile(complementConsensus[column],
388                   av_ignoreGapsConsensus);
389         }
390         else
391         {
392           return AAFrequency.extractProfile(hconsensus.get(column),
393                   av_ignoreGapsConsensus);
394         }
395       }
396     }
397     else
398     {
399       if (aa.autoCalculated && aa.label
400               .startsWith(AutoAnnotation.STRUCTURE_CONSENSUS.label))
401       {
402         // TODO implement group structure consensus
403         /*
404          * if (aa.groupRef != null && aa.groupRef.consensusData != null &&
405          * aa.groupRef.isShowSequenceLogo()) { //TODO check what happens for
406          * group selections return StructureFrequency.extractProfile(
407          * aa.groupRef.consensusData[column], aa.groupRef
408          * .getIgnoreGapsConsensus()); }
409          */
410         // TODO extend annotation row to enable dynamic and static profile data
411         // to
412         // be stored
413         if (aa.groupRef == null && aa.sequenceRef == null
414                 && hStrucConsensus != null
415                 && hStrucConsensus.length > column)
416         {
417           return StructureFrequency.extractProfile(hStrucConsensus[column],
418                   av_ignoreGapsConsensus);
419         }
420       }
421     }
422     return null;
423   }
424
425   boolean rna = false;
426
427   /**
428    * Render the annotation rows associated with an alignment.
429    * 
430    * @param annotPanel
431    *          container frame
432    * @param av
433    *          data and view settings to render
434    * @param g
435    *          destination for graphics
436    * @param activeRow
437    *          row where a mouse event occured (or -1)
438    * @param startRes
439    *          first column that will be drawn
440    * @param endRes
441    *          last column that will be drawn
442    * @return true if the fadedImage was used for any alignment annotation rows
443    *         currently being calculated
444    */
445   public boolean drawComponent(AwtRenderPanelI annotPanel,
446           AlignViewportI av, Graphics g, int activeRow, int startRes,
447           int endRes)
448   {
449     long stime = System.currentTimeMillis();
450     boolean usedFaded = false;
451     // NOTES:
452     // AnnotationPanel needs to implement: ImageObserver, access to
453     // AlignViewport
454     updateFromAwtRenderPanel(annotPanel, av);
455     fm = g.getFontMetrics();
456     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
457     int temp = 0;
458     if (aa == null)
459     {
460       return false;
461     }
462     int x = 0, y = 0;
463     int column = 0;
464     char lastSS;
465     int lastSSX;
466     int iconOffset = 0;
467     boolean validRes = false;
468     boolean validEnd = false;
469     boolean labelAllCols = false;
470     boolean centreColLabels;
471     boolean centreColLabelsDef = av.isCentreColumnLabels();
472     boolean scaleColLabel = false;
473     final AlignmentAnnotation consensusAnnot = av
474             .getAlignmentConsensusAnnotation();
475     final AlignmentAnnotation structConsensusAnnot = av
476             .getAlignmentStrucConsensusAnnotation();
477     final AlignmentAnnotation complementConsensusAnnot = av
478             .getComplementConsensusAnnotation();
479     boolean renderHistogram = true, renderProfile = true,
480             normaliseProfile = false, isRNA = rna;
481
482     BitSet graphGroupDrawn = new BitSet();
483     int charOffset = 0; // offset for a label
484     float fmWidth, fmScaling = 1f; // scaling for a label to fit it into a
485     // column.
486     Font ofont = g.getFont();
487     // \u03B2 \u03B1
488     // debug ints
489     int yfrom = 0, f_i = 0, yto = 0, f_to = 0;
490     boolean clipst = false, clipend = false;
491     for (int i = 0; i < aa.length; i++)
492     {
493       AlignmentAnnotation row = aa[i];
494       isRNA = row.isRNA();
495       {
496         // check if this is a consensus annotation row and set the display
497         // settings appropriately
498         // TODO: generalise this to have render styles for consensus/profile
499         // data
500         if (row.groupRef != null && row == row.groupRef.getConsensus())
501         {
502           renderHistogram = row.groupRef.isShowConsensusHistogram();
503           renderProfile = row.groupRef.isShowSequenceLogo();
504           normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
505         }
506         else if (row == consensusAnnot || row == structConsensusAnnot
507                 || row == complementConsensusAnnot)
508         {
509           renderHistogram = av_renderHistogram;
510           renderProfile = av_renderProfile;
511           normaliseProfile = av_normaliseProfile;
512         }
513         else
514         {
515           renderHistogram = true;
516           // don't need to set render/normaliseProfile since they are not
517           // currently used in any other annotation track renderer
518         }
519       }
520       Annotation[] row_annotations = row.annotations;
521       if (!row.visible)
522       {
523         continue;
524       }
525       centreColLabels = row.centreColLabels || centreColLabelsDef;
526       labelAllCols = row.showAllColLabels;
527       scaleColLabel = row.scaleColLabel;
528       lastSS = ' ';
529       lastSSX = 0;
530
531       if (!useClip || ((y - charHeight) < visHeight
532               && (y + row.height + charHeight * 2) >= sOffset))
533       {// if_in_visible_region
534         if (!clipst)
535         {
536           clipst = true;
537           yfrom = y;
538           f_i = i;
539         }
540         yto = y;
541         f_to = i;
542         if (row.graph > 0)
543         {
544           if (row.graphGroup > -1 && graphGroupDrawn.get(row.graphGroup))
545           {
546             continue;
547           }
548
549           // this is so that we draw the characters below the graph
550           y += row.height;
551
552           if (row.hasText)
553           {
554             iconOffset = charHeight - fm.getDescent();
555             y -= charHeight;
556           }
557         }
558         else if (row.hasText)
559         {
560           iconOffset = charHeight - fm.getDescent();
561
562         }
563         else
564         {
565           iconOffset = 0;
566         }
567
568         if (row.autoCalculated && av.isCalculationInProgress(row))
569         {
570           y += charHeight;
571           usedFaded = true;
572           g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0,
573                   y - row.height, imgWidth, y, annotationPanel);
574           g.setColor(Color.black);
575           // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
576
577           continue;
578         }
579
580         /*
581          * else if (annotationPanel.av.updatingConservation &&
582          * aa[i].label.equals("Conservation")) {
583          * 
584          * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
585          * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
586          * annotationPanel.imgWidth, y, annotationPanel);
587          * 
588          * g.setColor(Color.black); //
589          * g.drawString("Calculating Conservation.....",20, y-row.height/2);
590          * 
591          * continue; } else if (annotationPanel.av.updatingConservation &&
592          * aa[i].label.equals("Quality")) {
593          * 
594          * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
595          * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
596          * annotationPanel.imgWidth, y, annotationPanel);
597          * g.setColor(Color.black); // /
598          * g.drawString("Calculating Quality....",20, y-row.height/2);
599          * 
600          * continue; }
601          */
602         // first pass sets up state for drawing continuation from left-hand
603         // column
604         // of startRes
605         x = (startRes == 0) ? 0 : -1;
606         while (x < endRes - startRes)
607         {
608           if (hasHiddenColumns)
609           {
610             column = hiddenColumns.visibleToAbsoluteColumn(startRes + x);
611             if (column > row_annotations.length - 1)
612             {
613               break;
614             }
615           }
616           else
617           {
618             column = startRes + x;
619           }
620
621           if ((row_annotations == null)
622                   || (row_annotations.length <= column)
623                   || (row_annotations[column] == null))
624           {
625             validRes = false;
626           }
627           else
628           {
629             validRes = true;
630           }
631           final String displayChar = validRes
632                   ? row_annotations[column].displayCharacter
633                   : null;
634           if (x > -1)
635           {
636             if (activeRow == i)
637             {
638               g.setColor(Color.red);
639
640               if (columnSelection != null)
641               {
642                 if (columnSelection.contains(column))
643                 {
644                   g.fillRect(x * charWidth, y, charWidth, charHeight);
645                 }
646               }
647             }
648             if (row.getInvalidStrucPos() > x)
649             {
650               g.setColor(Color.orange);
651               g.fillRect(x * charWidth, y, charWidth, charHeight);
652             }
653             else if (row.getInvalidStrucPos() == x)
654             {
655               g.setColor(Color.orange.darker());
656               g.fillRect(x * charWidth, y, charWidth, charHeight);
657             }
658             if (validCharWidth && validRes && displayChar != null
659                     && (displayChar.length() > 0))
660             {
661
662               fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
663                       displayChar.length());
664               if (/* centreColLabels || */scaleColLabel)
665               {
666                 // fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
667                 // displayChar.length());
668                 //
669                 // if (scaleColLabel)
670                 // {
671                 // justify the label and scale to fit in column
672                 if (fmWidth > charWidth)
673                 {
674                   // scale only if the current font isn't already small enough
675                   fmScaling = charWidth;
676                   fmScaling /= fmWidth;
677                   g.setFont(ofont.deriveFont(AffineTransform
678                           .getScaleInstance(fmScaling, 1.0)));
679                   // and update the label's width to reflect the scaling.
680                   fmWidth = charWidth;
681                 }
682                 // }
683               }
684               // TODO is it ok to use width of / show all characters here?
685               // else
686               // {
687               // fmWidth = fm.charWidth(displayChar.charAt(0));
688               // }
689               charOffset = (int) ((charWidth - fmWidth) / 2f);
690
691               if (row_annotations[column].colour == null)
692               {
693                 g.setColor(Color.black);
694               }
695               else
696               {
697                 g.setColor(row_annotations[column].colour);
698               }
699
700               if (column == 0 || row.graph > 0)
701               {
702                 g.drawString(displayChar, (x * charWidth) + charOffset,
703                         y + iconOffset);
704               }
705               else if (row_annotations[column - 1] == null || (labelAllCols
706                       || !displayChar.equals(
707                               row_annotations[column - 1].displayCharacter)
708                       || (displayChar.length() < 2
709                               && row_annotations[column].secondaryStructure == ' ')))
710               {
711                 g.drawString(displayChar, x * charWidth + charOffset,
712                         y + iconOffset);
713               }
714               g.setFont(ofont);
715             }
716           }
717           if (row.hasIcons)
718           {
719             char ss = validRes ? row_annotations[column].secondaryStructure
720                     : '-';
721
722             if (ss == '(')
723             {
724               // distinguish between forward/backward base-pairing
725               if (displayChar.indexOf(')') > -1)
726               {
727
728                 ss = ')';
729
730               }
731             }
732             if (ss == '[')
733             {
734               if ((displayChar.indexOf(']') > -1))
735               {
736                 ss = ']';
737
738               }
739             }
740             if (ss == '{')
741             {
742               // distinguish between forward/backward base-pairing
743               if (displayChar.indexOf('}') > -1)
744               {
745                 ss = '}';
746
747               }
748             }
749             if (ss == '<')
750             {
751               // distinguish between forward/backward base-pairing
752               if (displayChar.indexOf('<') > -1)
753               {
754                 ss = '>';
755
756               }
757             }
758             if (isRNA && (ss >= CHAR_A) && (ss <= CHAR_Z))
759             {
760               // distinguish between forward/backward base-pairing
761               int ssLowerCase = ss + UPPER_TO_LOWER;
762               // TODO would .equals() be safer here? or charAt(0)?
763               if (displayChar.indexOf(ssLowerCase) > -1)
764               {
765                 ss = (char) ssLowerCase;
766               }
767             }
768
769             if (!validRes || (ss != lastSS))
770             {
771
772               if (x > -1)
773               {
774
775                 int nb_annot = x - temp;
776                 // System.out.println("\t type :"+lastSS+"\t x :"+x+"\t nbre
777                 // annot :"+nb_annot);
778                 switch (lastSS)
779                 {
780                 case '(': // Stem case for RNA secondary structure
781                 case ')': // and opposite direction
782                   drawStemAnnot(g, row_annotations, lastSSX, x, y,
783                           iconOffset, startRes, column, validRes, validEnd);
784                   temp = x;
785                   break;
786
787                 case 'H':
788                   if (!isRNA)
789                   {
790                     drawHelixAnnot(g, row_annotations, lastSSX, x, y,
791                             iconOffset, startRes, column, validRes,
792                             validEnd);
793                     break;
794                   }
795                   // no break if isRNA - falls through to drawNotCanonicalAnnot!
796                 case 'E':
797                   if (!isRNA)
798                   {
799                     drawSheetAnnot(g, row_annotations, lastSSX, x, y,
800                             iconOffset, startRes, column, validRes,
801                             validEnd);
802                     break;
803                   }
804                   // no break if isRNA - fall through to drawNotCanonicalAnnot!
805
806                 case '{':
807                 case '}':
808                 case '[':
809                 case ']':
810                 case '>':
811                 case '<':
812                 case 'A':
813                 case 'a':
814                 case 'B':
815                 case 'b':
816                 case 'C':
817                 case 'c':
818                 case 'D':
819                 case 'd':
820                 case 'e':
821                 case 'F':
822                 case 'f':
823                 case 'G':
824                 case 'g':
825                 case 'h':
826                 case 'I':
827                 case 'i':
828                 case 'J':
829                 case 'j':
830                 case 'K':
831                 case 'k':
832                 case 'L':
833                 case 'l':
834                 case 'M':
835                 case 'm':
836                 case 'N':
837                 case 'n':
838                 case 'O':
839                 case 'o':
840                 case 'P':
841                 case 'p':
842                 case 'Q':
843                 case 'q':
844                 case 'R':
845                 case 'r':
846                 case 'S':
847                 case 's':
848                 case 'T':
849                 case 't':
850                 case 'U':
851                 case 'u':
852                 case 'V':
853                 case 'v':
854                 case 'W':
855                 case 'w':
856                 case 'X':
857                 case 'x':
858                 case 'Y':
859                 case 'y':
860                 case 'Z':
861                 case 'z':
862
863                   Color nonCanColor = getNotCanonicalColor(lastSS);
864                   drawNotCanonicalAnnot(g, nonCanColor, row_annotations,
865                           lastSSX, x, y, iconOffset, startRes, column,
866                           validRes, validEnd);
867                   temp = x;
868                   break;
869                 default:
870                   g.setColor(Color.gray);
871                   g.fillRect(lastSSX, y + 6 + iconOffset,
872                           (x * charWidth) - lastSSX, 2);
873                   temp = x;
874                   break;
875                 }
876               }
877               if (validRes)
878               {
879                 lastSS = ss;
880               }
881               else
882               {
883                 lastSS = ' ';
884               }
885               if (x > -1)
886               {
887                 lastSSX = (x * charWidth);
888               }
889             }
890           }
891           column++;
892           x++;
893         }
894         if (column >= row_annotations.length)
895         {
896           column = row_annotations.length - 1;
897           validEnd = false;
898         }
899         else
900         {
901           validEnd = true;
902         }
903         if ((row_annotations == null) || (row_annotations.length <= column)
904                 || (row_annotations[column] == null))
905         {
906           validRes = false;
907         }
908         else
909         {
910           validRes = true;
911         }
912         // x ++;
913
914         if (row.hasIcons)
915         {
916           switch (lastSS)
917           {
918
919           case 'H':
920             if (!isRNA)
921             {
922               drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
923                       startRes, column, validRes, validEnd);
924               break;
925             }
926             // no break if isRNA - fall through to drawNotCanonicalAnnot!
927
928           case 'E':
929             if (!isRNA)
930             {
931               drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
932                       startRes, column, validRes, validEnd);
933               break;
934             }
935             // no break if isRNA - fall through to drawNotCanonicalAnnot!
936
937           case '(':
938           case ')': // Stem case for RNA secondary structure
939
940             drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
941                     startRes, column, validRes, validEnd);
942
943             break;
944           case '{':
945           case '}':
946           case '[':
947           case ']':
948           case '>':
949           case '<':
950           case 'A':
951           case 'a':
952           case 'B':
953           case 'b':
954           case 'C':
955           case 'c':
956           case 'D':
957           case 'd':
958           case 'e':
959           case 'F':
960           case 'f':
961           case 'G':
962           case 'g':
963           case 'h':
964           case 'I':
965           case 'i':
966           case 'J':
967           case 'j':
968           case 'K':
969           case 'k':
970           case 'L':
971           case 'l':
972           case 'M':
973           case 'm':
974           case 'N':
975           case 'n':
976           case 'O':
977           case 'o':
978           case 'P':
979           case 'p':
980           case 'Q':
981           case 'q':
982           case 'R':
983           case 'r':
984           case 'T':
985           case 't':
986           case 'U':
987           case 'u':
988           case 'V':
989           case 'v':
990           case 'W':
991           case 'w':
992           case 'X':
993           case 'x':
994           case 'Y':
995           case 'y':
996           case 'Z':
997           case 'z':
998             // System.out.println(lastSS);
999             Color nonCanColor = getNotCanonicalColor(lastSS);
1000             drawNotCanonicalAnnot(g, nonCanColor, row_annotations, lastSSX,
1001                     x, y, iconOffset, startRes, column, validRes, validEnd);
1002             break;
1003           default:
1004             drawGlyphLine(g, row_annotations, lastSSX, x, y, iconOffset,
1005                     startRes, column, validRes, validEnd);
1006             break;
1007           }
1008         }
1009
1010         if (row.graph > 0 && row.graphHeight > 0)
1011         {
1012           if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1013           {
1014             if (row.graphGroup > -1 && !graphGroupDrawn.get(row.graphGroup))
1015             {
1016               // TODO: JAL-1291 revise rendering model so the graphGroup map is
1017               // computed efficiently for all visible labels
1018               float groupmax = -999999, groupmin = 9999999;
1019               for (int gg = 0; gg < aa.length; gg++)
1020               {
1021                 if (aa[gg].graphGroup != row.graphGroup)
1022                 {
1023                   continue;
1024                 }
1025
1026                 if (aa[gg] != row)
1027                 {
1028                   aa[gg].visible = false;
1029                 }
1030                 if (aa[gg].graphMax > groupmax)
1031                 {
1032                   groupmax = aa[gg].graphMax;
1033                 }
1034                 if (aa[gg].graphMin < groupmin)
1035                 {
1036                   groupmin = aa[gg].graphMin;
1037                 }
1038               }
1039
1040               for (int gg = 0; gg < aa.length; gg++)
1041               {
1042                 if (aa[gg].graphGroup == row.graphGroup)
1043                 {
1044                   drawLineGraph(g, aa[gg], aa[gg].annotations, startRes,
1045                           endRes, y, groupmin, groupmax, row.graphHeight);
1046                 }
1047               }
1048
1049               graphGroupDrawn.set(row.graphGroup);
1050             }
1051             else
1052             {
1053               drawLineGraph(g, row, row_annotations, startRes, endRes, y,
1054                       row.graphMin, row.graphMax, row.graphHeight);
1055             }
1056           }
1057           else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1058           {
1059             drawBarGraph(g, row, row_annotations, startRes, endRes,
1060                     row.graphMin, row.graphMax, y, renderHistogram,
1061                     renderProfile, normaliseProfile);
1062           }
1063         }
1064       }
1065       else
1066       {
1067         if (clipst && !clipend)
1068         {
1069           clipend = true;
1070         }
1071       } // end if_in_visible_region
1072       if (row.graph > 0 && row.hasText)
1073       {
1074         y += charHeight;
1075       }
1076
1077       if (row.graph == 0)
1078       {
1079         y += aa[i].height;
1080       }
1081     }
1082     if (debugRedraw)
1083     {
1084       if (canClip)
1085       {
1086         if (clipst)
1087         {
1088           System.err.println(
1089                   "Start clip at : " + yfrom + " (index " + f_i + ")");
1090         }
1091         if (clipend)
1092         {
1093           System.err.println(
1094                   "End clip at : " + yto + " (index " + f_to + ")");
1095         }
1096       }
1097       ;
1098       System.err.println("Annotation Rendering time:"
1099               + (System.currentTimeMillis() - stime));
1100     }
1101     ;
1102
1103     return !usedFaded;
1104   }
1105
1106   public static final Color GLYPHLINE_COLOR = Color.gray;
1107
1108   public static final Color SHEET_COLOUR = Color.green;
1109
1110   public static final Color HELIX_COLOUR = Color.red;
1111
1112   public static final Color STEM_COLOUR = Color.blue;
1113
1114   private Color sdNOTCANONICAL_COLOUR;
1115
1116   void drawGlyphLine(Graphics g, Annotation[] row, int lastSSX, int x,
1117           int y, int iconOffset, int startRes, int column, boolean validRes,
1118           boolean validEnd)
1119   {
1120     g.setColor(GLYPHLINE_COLOR);
1121     g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
1122   }
1123
1124   void drawSheetAnnot(Graphics g, Annotation[] row,
1125
1126           int lastSSX, int x, int y, int iconOffset, int startRes,
1127           int column, boolean validRes, boolean validEnd)
1128   {
1129     g.setColor(SHEET_COLOUR);
1130
1131     if (!validEnd || !validRes || row == null || row[column] == null
1132             || row[column].secondaryStructure != 'E')
1133     {
1134       g.fillRect(lastSSX, y + 4 + iconOffset, (x * charWidth) - lastSSX - 4,
1135               7);
1136       g.fillPolygon(
1137               new int[]
1138               { (x * charWidth) - 4, (x * charWidth) - 4, (x * charWidth) },
1139               new int[]
1140               { y + iconOffset, y + 14 + iconOffset, y + 7 + iconOffset },
1141               3);
1142     }
1143     else
1144     {
1145       g.fillRect(lastSSX, y + 4 + iconOffset, (x + 1) * charWidth - lastSSX,
1146               7);
1147     }
1148
1149   }
1150
1151   void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX, int x,
1152           int y, int iconOffset, int startRes, int column, boolean validRes,
1153           boolean validEnd)
1154   {
1155     g.setColor(HELIX_COLOUR);
1156
1157     int sCol = (lastSSX / charWidth)
1158             + hiddenColumns.visibleToAbsoluteColumn(startRes);
1159     int x1 = lastSSX;
1160     int x2 = (x * charWidth);
1161
1162     if (MAC)
1163     {
1164       int ofs = charWidth / 2;
1165       // Off by 1 offset when drawing rects and ovals
1166       // to offscreen image on the MAC
1167       g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1, 8, 8, 8);
1168       if (sCol == 0 || row[sCol - 1] == null
1169               || row[sCol - 1].secondaryStructure != 'H')
1170       {
1171       }
1172       else
1173       {
1174         // g.setColor(Color.orange);
1175         g.fillRoundRect(lastSSX, y + 4 + iconOffset, x2 - x1 - ofs + 1, 8,
1176                 0, 0);
1177       }
1178       if (!validRes || row[column] == null
1179               || row[column].secondaryStructure != 'H')
1180       {
1181
1182       }
1183       else
1184       {
1185         // g.setColor(Color.magenta);
1186         g.fillRoundRect(lastSSX + ofs, y + 4 + iconOffset,
1187                 x2 - x1 - ofs + 1, 8, 0, 0);
1188
1189       }
1190
1191       return;
1192     }
1193
1194     if (sCol == 0 || row[sCol - 1] == null
1195             || row[sCol - 1].secondaryStructure != 'H')
1196     {
1197       g.fillArc(lastSSX, y + 4 + iconOffset, charWidth, 8, 90, 180);
1198       x1 += charWidth / 2;
1199     }
1200
1201     if (!validRes || row[column] == null
1202             || row[column].secondaryStructure != 'H')
1203     {
1204       g.fillArc((x * charWidth) - charWidth, y + 4 + iconOffset, charWidth,
1205               8, 270, 180);
1206       x2 -= charWidth / 2;
1207     }
1208
1209     g.fillRect(x1, y + 4 + iconOffset, x2 - x1, 8);
1210   }
1211
1212   void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
1213           Annotation[] aa_annotations, int sRes, int eRes, int y, float min,
1214           float max, int graphHeight)
1215   {
1216     if (sRes > aa_annotations.length)
1217     {
1218       return;
1219     }
1220
1221     int x = 0;
1222
1223     // Adjustment for fastpaint to left
1224     if (eRes < endRes)
1225     {
1226       eRes++;
1227     }
1228
1229     eRes = Math.min(eRes, aa_annotations.length);
1230
1231     if (sRes == 0)
1232     {
1233       x++;
1234     }
1235
1236     int y1 = y, y2 = y;
1237     float range = max - min;
1238
1239     // //Draw origin
1240     if (min < 0)
1241     {
1242       y2 = y - (int) ((0 - min / range) * graphHeight);
1243     }
1244
1245     g.setColor(Color.gray);
1246     g.drawLine(x - charWidth, y2, (eRes - sRes + 1) * charWidth, y2);
1247
1248     eRes = Math.min(eRes, aa_annotations.length);
1249
1250     int column;
1251     int aaMax = aa_annotations.length - 1;
1252
1253     while (x < eRes - sRes)
1254     {
1255       column = sRes + x;
1256       if (hasHiddenColumns)
1257       {
1258         column = hiddenColumns.visibleToAbsoluteColumn(column);
1259       }
1260
1261       if (column > aaMax)
1262       {
1263         break;
1264       }
1265
1266       if (aa_annotations[column] == null
1267               || aa_annotations[column - 1] == null)
1268       {
1269         x++;
1270         continue;
1271       }
1272
1273       if (aa_annotations[column].colour == null)
1274       {
1275         g.setColor(Color.black);
1276       }
1277       else
1278       {
1279         g.setColor(aa_annotations[column].colour);
1280       }
1281
1282       y1 = y - (int) (((aa_annotations[column - 1].value - min) / range)
1283               * graphHeight);
1284       y2 = y - (int) (((aa_annotations[column].value - min) / range)
1285               * graphHeight);
1286
1287       g.drawLine(x * charWidth - charWidth / 2, y1,
1288               x * charWidth + charWidth / 2, y2);
1289       x++;
1290     }
1291
1292     if (_aa.threshold != null)
1293     {
1294       g.setColor(_aa.threshold.colour);
1295       Graphics2D g2 = (Graphics2D) g;
1296       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1297               BasicStroke.JOIN_ROUND, 3f, new float[]
1298               { 5f, 3f }, 0f));
1299
1300       y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
1301       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
1302       g2.setStroke(new BasicStroke());
1303     }
1304   }
1305
1306   void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
1307           Annotation[] aa_annotations, int sRes, int eRes, float min,
1308           float max, int y, boolean renderHistogram, boolean renderProfile,
1309           boolean normaliseProfile)
1310   {
1311     if (sRes > aa_annotations.length)
1312     {
1313       return;
1314     }
1315     Font ofont = g.getFont();
1316     eRes = Math.min(eRes, aa_annotations.length);
1317
1318     int x = 0, y1 = y, y2 = y;
1319
1320     float range = max - min;
1321
1322     if (min < 0)
1323     {
1324       y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
1325     }
1326
1327     g.setColor(Color.gray);
1328
1329     g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
1330
1331     int column;
1332     int aaMax = aa_annotations.length - 1;
1333     while (x < eRes - sRes)
1334     {
1335       column = sRes + x;
1336       if (hasHiddenColumns)
1337       {
1338         column = hiddenColumns.visibleToAbsoluteColumn(column);
1339       }
1340
1341       if (column > aaMax)
1342       {
1343         break;
1344       }
1345
1346       if (aa_annotations[column] == null)
1347       {
1348         x++;
1349         continue;
1350       }
1351       if (aa_annotations[column].colour == null)
1352       {
1353         g.setColor(Color.black);
1354       }
1355       else
1356       {
1357         g.setColor(aa_annotations[column].colour);
1358       }
1359
1360       y1 = y - (int) (((aa_annotations[column].value - min) / (range))
1361               * _aa.graphHeight);
1362
1363       if (renderHistogram)
1364       {
1365         if (y1 - y2 > 0)
1366         {
1367           g.fillRect(x * charWidth, y2, charWidth, y1 - y2);
1368         }
1369         else
1370         {
1371           g.fillRect(x * charWidth, y1, charWidth, y2 - y1);
1372         }
1373       }
1374       // draw profile if available
1375       if (renderProfile)
1376       {
1377
1378         /*
1379          * {profile type, #values, total count, char1, pct1, char2, pct2...}
1380          */
1381         int profl[] = getProfileFor(_aa, column);
1382
1383         // just try to draw the logo if profl is not null
1384         if (profl != null && profl[2] != 0)
1385         {
1386           boolean isStructureProfile = profl[0] == AlignmentAnnotation.STRUCTURE_PROFILE;
1387           boolean isCdnaProfile = profl[0] == AlignmentAnnotation.CDNA_PROFILE;
1388           float ht = normaliseProfile ? y - _aa.graphHeight : y1;
1389           double htn = normaliseProfile ? _aa.graphHeight : (y2 - y1);// aa.graphHeight;
1390           double hght;
1391           float wdth;
1392           double ht2 = 0;
1393           char[] dc;
1394
1395           /**
1396            * Render a single base for a sequence profile, a base pair for
1397            * structure profile, and a triplet for a cdna profile
1398            */
1399           dc = new char[isStructureProfile ? 2 : (isCdnaProfile ? 3 : 1)];
1400
1401           LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
1402           double scale = 1f / (normaliseProfile ? profl[2] : 100f);
1403           float ofontHeight = 1f / lm.getAscent();// magnify to fill box
1404           double scl = 0.0;
1405
1406           /*
1407            * Traverse the character(s)/percentage data in the array
1408            */
1409           int c = 3;
1410           int valuesProcessed = 0;
1411           // profl[1] is the number of values in the profile
1412           while (valuesProcessed < profl[1])
1413           {
1414             if (isStructureProfile)
1415             {
1416               // todo can we encode a structure pair as an int, like codons?
1417               dc[0] = (char) profl[c++];
1418               dc[1] = (char) profl[c++];
1419             }
1420             else if (isCdnaProfile)
1421             {
1422               dc = CodingUtils.decodeCodon(profl[c++]);
1423             }
1424             else
1425             {
1426               dc[0] = (char) profl[c++];
1427             }
1428
1429             wdth = charWidth;
1430             wdth /= fm.charsWidth(dc, 0, dc.length);
1431
1432             ht += scl;
1433             // next profl[] position is profile % for the character(s)
1434             scl = htn * scale * profl[c++];
1435             lm = ofont.getLineMetrics(dc, 0, 1,
1436                     g.getFontMetrics().getFontRenderContext());
1437             Font font = ofont.deriveFont(AffineTransform
1438                     .getScaleInstance(wdth, scl / lm.getAscent()));
1439             g.setFont(font);
1440             lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
1441
1442             // Debug - render boxes around characters
1443             // g.setColor(Color.red);
1444             // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
1445             // (int)(scl));
1446             // g.setColor(profcolour.findColour(dc[0]).darker());
1447
1448             /*
1449              * Set character colour as per alignment colour scheme; use the
1450              * codon translation if a cDNA profile
1451              */
1452             Color colour = null;
1453             if (isCdnaProfile)
1454             {
1455               final String codonTranslation = ResidueProperties
1456                       .codonTranslate(new String(dc));
1457               colour = profcolour.findColour(codonTranslation.charAt(0),
1458                       column, null);
1459             }
1460             else
1461             {
1462               colour = profcolour.findColour(dc[0], column, null);
1463             }
1464             g.setColor(colour == Color.white ? Color.lightGray : colour);
1465
1466             hght = (ht + (scl - lm.getDescent()
1467                     - lm.getBaselineOffsets()[lm.getBaselineIndex()]));
1468
1469             g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
1470             valuesProcessed++;
1471           }
1472           g.setFont(ofont);
1473         }
1474       }
1475       x++;
1476     }
1477     if (_aa.threshold != null)
1478     {
1479       g.setColor(_aa.threshold.colour);
1480       Graphics2D g2 = (Graphics2D) g;
1481       g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
1482               BasicStroke.JOIN_ROUND, 3f, new float[]
1483               { 5f, 3f }, 0f));
1484
1485       y2 = (int) (y
1486               - ((_aa.threshold.value - min) / range) * _aa.graphHeight);
1487       g.drawLine(0, y2, (eRes - sRes) * charWidth, y2);
1488       g2.setStroke(new BasicStroke());
1489     }
1490   }
1491
1492   // used by overview window
1493   public void drawGraph(Graphics g, AlignmentAnnotation _aa,
1494           Annotation[] aa_annotations, int width, int y, int sRes, int eRes)
1495   {
1496     eRes = Math.min(eRes, aa_annotations.length);
1497     g.setColor(Color.white);
1498     g.fillRect(0, 0, width, y);
1499     g.setColor(new Color(0, 0, 180));
1500
1501     int x = 0, height;
1502
1503     for (int j = sRes; j < eRes; j++)
1504     {
1505       if (aa_annotations[j] != null)
1506       {
1507         if (aa_annotations[j].colour == null)
1508         {
1509           g.setColor(Color.black);
1510         }
1511         else
1512         {
1513           g.setColor(aa_annotations[j].colour);
1514         }
1515
1516         height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
1517         if (height > y)
1518         {
1519           height = y;
1520         }
1521
1522         g.fillRect(x, y - height, charWidth, height);
1523       }
1524       x += charWidth;
1525     }
1526   }
1527
1528   Color getNotCanonicalColor(char lastss)
1529   {
1530     switch (lastss)
1531     {
1532     case '{':
1533     case '}':
1534       return new Color(255, 125, 5);
1535
1536     case '[':
1537     case ']':
1538       return new Color(245, 115, 10);
1539
1540     case '>':
1541     case '<':
1542       return new Color(235, 135, 15);
1543
1544     case 'A':
1545     case 'a':
1546       return new Color(225, 105, 20);
1547
1548     case 'B':
1549     case 'b':
1550       return new Color(215, 145, 30);
1551
1552     case 'C':
1553     case 'c':
1554       return new Color(205, 95, 35);
1555
1556     case 'D':
1557     case 'd':
1558       return new Color(195, 155, 45);
1559
1560     case 'E':
1561     case 'e':
1562       return new Color(185, 85, 55);
1563
1564     case 'F':
1565     case 'f':
1566       return new Color(175, 165, 65);
1567
1568     case 'G':
1569     case 'g':
1570       return new Color(170, 75, 75);
1571
1572     case 'H':
1573     case 'h':
1574       return new Color(160, 175, 85);
1575
1576     case 'I':
1577     case 'i':
1578       return new Color(150, 65, 95);
1579
1580     case 'J':
1581     case 'j':
1582       return new Color(140, 185, 105);
1583
1584     case 'K':
1585     case 'k':
1586       return new Color(130, 55, 110);
1587
1588     case 'L':
1589     case 'l':
1590       return new Color(120, 195, 120);
1591
1592     case 'M':
1593     case 'm':
1594       return new Color(110, 45, 130);
1595
1596     case 'N':
1597     case 'n':
1598       return new Color(100, 205, 140);
1599
1600     case 'O':
1601     case 'o':
1602       return new Color(90, 35, 150);
1603
1604     case 'P':
1605     case 'p':
1606       return new Color(85, 215, 160);
1607
1608     case 'Q':
1609     case 'q':
1610       return new Color(75, 25, 170);
1611
1612     case 'R':
1613     case 'r':
1614       return new Color(65, 225, 180);
1615
1616     case 'S':
1617     case 's':
1618       return new Color(55, 15, 185);
1619
1620     case 'T':
1621     case 't':
1622       return new Color(45, 235, 195);
1623
1624     case 'U':
1625     case 'u':
1626       return new Color(35, 5, 205);
1627
1628     case 'V':
1629     case 'v':
1630       return new Color(25, 245, 215);
1631
1632     case 'W':
1633     case 'w':
1634       return new Color(15, 0, 225);
1635
1636     case 'X':
1637     case 'x':
1638       return new Color(10, 255, 235);
1639
1640     case 'Y':
1641     case 'y':
1642       return new Color(5, 150, 245);
1643
1644     case 'Z':
1645     case 'z':
1646       return new Color(0, 80, 255);
1647
1648     default:
1649       System.out.println("This is not a interaction : " + lastss);
1650       return null;
1651
1652     }
1653   }
1654 }