Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package jalview.renderer

File AnnotationRenderer.java

 

Coverage histogram

../../img/srcFileCovDistChart6.png
37% of files have more coverage

Code metrics

310
768
26
1
1,948
1,446
416
0.54
29.54
26
16

Classes

Class Line # Actions
AnnotationRenderer 62 768 416
0.5135869451.4%
 

Contributing tests

This file is covered by 185 tests. .

Source view

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 java.awt.BasicStroke;
24    import java.awt.Color;
25    import java.awt.Font;
26    import java.awt.FontMetrics;
27    import java.awt.Graphics;
28    import java.awt.Graphics2D;
29    import java.awt.Image;
30    import java.awt.RenderingHints;
31    import java.awt.Stroke;
32    import java.awt.geom.AffineTransform;
33    import java.awt.image.ImageObserver;
34    import java.util.BitSet;
35    import java.util.Hashtable;
36   
37    import org.jfree.graphics2d.svg.SVGGraphics2D;
38    import org.jibble.epsgraphics.EpsGraphics2D;
39   
40    import jalview.analysis.AAFrequency;
41    import jalview.analysis.AlignmentUtils;
42    import jalview.analysis.CodingUtils;
43    import jalview.analysis.Rna;
44    import jalview.analysis.StructureFrequency;
45    import jalview.api.AlignViewportI;
46    import jalview.bin.Cache;
47    import jalview.bin.Console;
48    import jalview.datamodel.AlignmentAnnotation;
49    import jalview.datamodel.Annotation;
50    import jalview.datamodel.ColumnSelection;
51    import jalview.datamodel.HiddenColumns;
52    import jalview.datamodel.ProfilesI;
53    import jalview.renderer.api.AnnotationRendererFactoryI;
54    import jalview.renderer.api.AnnotationRowRendererI;
55    import jalview.schemes.ColourSchemeI;
56    import jalview.schemes.NucleotideColourScheme;
57    import jalview.schemes.ResidueProperties;
58    import jalview.schemes.ZappoColourScheme;
59    import jalview.util.MessageManager;
60    import jalview.util.Platform;
61   
 
62    public class AnnotationRenderer
63    {
64    private static final int UPPER_TO_LOWER = 'a' - 'A'; // 32
65   
66    private static final int CHAR_A = 'A'; // 65
67   
68    private static final int CHAR_Z = 'Z'; // 90
69   
70    /**
71    * flag indicating if timing and redraw parameter info should be output
72    */
73    private final boolean debugRedraw;
74   
75    private int charWidth, endRes, charHeight;
76   
77    private boolean validCharWidth, hasHiddenColumns;
78   
79    private FontMetrics fm;
80   
81    private final boolean USE_FILL_ROUND_RECT = Platform.isAMacAndNotJS();
82   
83    boolean av_renderHistogram = true, av_renderProfile = true,
84    av_normaliseProfile = false;
85   
86    ResidueShaderI profcolour = null;
87   
88    private ColumnSelection columnSelection;
89   
90    private HiddenColumns hiddenColumns;
91   
92    private ProfilesI hconsensus;
93   
94    private ProfilesI hSSconsensus;
95   
96    private Hashtable<String, Object>[] complementConsensus;
97   
98    private Hashtable<String, Object>[] hStrucConsensus;
99   
100    private boolean av_ignoreGapsConsensus;
101   
102    private boolean renderingVectors = false;
103   
104    private boolean glyphLineDrawn = false;
105   
106    /**
107    * attributes set from AwtRenderPanelI
108    */
109    /**
110    * old image used when data is currently being calculated and cannot be
111    * rendered
112    */
113    private Image fadedImage;
114   
115    /**
116    * panel being rendered into
117    */
118    private ImageObserver annotationPanel;
119   
120    /**
121    * width of image to render in panel
122    */
123    private int imgWidth;
124   
125    /**
126    * offset to beginning of visible area
127    */
128    private int sOffset;
129   
130    /**
131    * offset to end of visible area
132    */
133    private int visHeight;
134   
135    /**
136    * indicate if the renderer should only render the visible portion of the
137    * annotation given the current view settings
138    */
139    private boolean useClip = true;
140   
141    /**
142    * master flag indicating if renderer should ever try to clip. not enabled for
143    * jalview 2.8.1
144    */
145    private boolean canClip = false;
146   
 
147  491 toggle public AnnotationRenderer()
148    {
149  491 this(false);
150    }
151   
152    /**
153    * Create a new annotation Renderer
154    *
155    * @param debugRedraw
156    * flag indicating if timing and redraw parameter info should be
157    * output
158    */
 
159  491 toggle public AnnotationRenderer(boolean debugRedraw)
160    {
161  491 this.debugRedraw = debugRedraw;
162    }
163   
164    /**
165    * Remove any references and resources when this object is no longer required
166    */
 
167  253 toggle public void dispose()
168    {
169  253 hiddenColumns = null;
170  253 hconsensus = null;
171  253 hSSconsensus = null;
172  253 complementConsensus = null;
173  253 hStrucConsensus = null;
174  253 fadedImage = null;
175  253 annotationPanel = null;
176  253 rendererFactoryI = null;
177    }
178   
 
179  0 toggle void drawStemAnnot(Graphics g, Annotation[] row_annotations, int lastSSX,
180    int x, int y, int iconOffset, int startRes, int column,
181    boolean validRes, boolean validEnd)
182    {
183  0 int sCol = (lastSSX / charWidth)
184    + hiddenColumns.visibleToAbsoluteColumn(startRes);
185  0 int x1 = lastSSX;
186  0 int x2 = (x * charWidth);
187   
188  0 char dc = (column == 0 || row_annotations[column - 1] == null) ? ' '
189    : row_annotations[column - 1].secondaryStructure;
190   
191  0 boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
192    || dc != row_annotations[sCol - 1].secondaryStructure
193    || !validEnd;
194  0 boolean diffdownstream = !validRes || !validEnd
195    || row_annotations[column] == null
196    || dc != row_annotations[column].secondaryStructure;
197   
198  0 if (diffupstream || diffdownstream)
199    {
200    // draw glyphline under arrow
201  0 drawGlyphLine(g, lastSSX, x, y, iconOffset);
202    }
203  0 g.setColor(STEM_COLOUR);
204   
205  0 if (column > 0 && Rna.isClosingParenthesis(dc))
206    {
207  0 if (diffupstream)
208    // if (validRes && column>1 && row_annotations[column-2]!=null &&
209    // dc.equals(row_annotations[column-2].displayCharacter))
210    {
211    /*
212    * if new annotation with a closing base pair half of the stem,
213    * display a backward arrow
214    */
215  0 fillPolygon(g, new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
216    new int[]
217    { y + iconOffset + 1, y + 13 + iconOffset,
218    y + 7 + iconOffset },
219    3);
220  0 x1 += 5;
221    }
222  0 if (diffdownstream)
223    {
224  0 x2 -= 1;
225    }
226    }
227    else
228    {
229    // display a forward arrow
230  0 if (diffdownstream)
231    {
232    /*
233    * if annotation ending with an opeing base pair half of the stem,
234    * display a forward arrow
235    */
236  0 fillPolygon(g, new int[] { x2 - 6, x2 - 6, x2 - 1 },
237    new int[]
238    { y + iconOffset + 1, y + 13 + iconOffset,
239    y + 7 + iconOffset },
240    3);
241  0 x2 -= 5;
242    }
243  0 if (diffupstream)
244    {
245  0 x1 += 1;
246    }
247    }
248    // draw arrow body
249  0 unsetAntialias(g);
250  0 fillRect(g, x1, y + 4 + iconOffset, x2 - x1, 6);
251    }
252   
 
253  0 toggle void drawNotCanonicalAnnot(Graphics g, Color nonCanColor,
254    Annotation[] row_annotations, int lastSSX, int x, int y,
255    int iconOffset, int startRes, int column, boolean validRes,
256    boolean validEnd)
257    {
258    // Console.info(nonCanColor);
259   
260  0 int sCol = (lastSSX / charWidth)
261    + hiddenColumns.visibleToAbsoluteColumn(startRes);
262  0 int x1 = lastSSX;
263  0 int x2 = (x * charWidth);
264   
265  0 String dc = (column == 0 || row_annotations[column - 1] == null) ? ""
266    : row_annotations[column - 1].displayCharacter;
267   
268  0 boolean diffupstream = sCol == 0 || row_annotations[sCol - 1] == null
269    || !dc.equals(row_annotations[sCol - 1].displayCharacter)
270    || !validEnd;
271  0 boolean diffdownstream = !validRes || !validEnd
272    || row_annotations[column] == null
273    || !dc.equals(row_annotations[column].displayCharacter);
274    // Console.info("Column "+column+" diff up:
275    // "+diffupstream+"
276    // down:"+diffdownstream);
277    // If a closing base pair half of the stem, display a backward arrow
278  0 if (diffupstream || diffdownstream)
279    {
280    // draw glyphline under arrow
281  0 drawGlyphLine(g, lastSSX, x, y, iconOffset);
282    }
283  0 g.setColor(nonCanColor);
284  0 if (column > 0 && Rna.isClosingParenthesis(dc))
285    {
286   
287  0 if (diffupstream)
288    // if (validRes && column>1 && row_annotations[column-2]!=null &&
289    // dc.equals(row_annotations[column-2].displayCharacter))
290    {
291  0 fillPolygon(g, new int[] { lastSSX + 5, lastSSX + 5, lastSSX },
292    new int[]
293    { y + iconOffset + 1, y + 13 + iconOffset,
294    y + 7 + iconOffset },
295    3);
296  0 x1 += 5;
297    }
298  0 if (diffdownstream)
299    {
300  0 x2 -= 1;
301    }
302    }
303    else
304    {
305   
306    // display a forward arrow
307  0 if (diffdownstream)
308    {
309  0 fillPolygon(g, new int[] { x2 - 6, x2 - 6, x2 - 1 },
310    new int[]
311    { y + iconOffset + 1, y + 13 + iconOffset,
312    y + 7 + iconOffset },
313    3);
314  0 x2 -= 5;
315    }
316  0 if (diffupstream)
317    {
318  0 x1 += 1;
319    }
320    }
321    // draw arrow body
322  0 unsetAntialias(g);
323  0 fillRect(g, x1, y + 4 + iconOffset, x2 - x1, 6);
324    }
325   
326    // public void updateFromAnnotationPanel(FontMetrics annotFM, AlignViewportI
327    // av)
 
328  2323 toggle public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
329    AlignViewportI av)
330    {
331  2323 fm = annotPanel.getFontMetrics();
332  2323 annotationPanel = annotPanel;
333  2323 fadedImage = annotPanel.getFadedImage();
334  2323 imgWidth = annotPanel.getFadedImageWidth();
335    // visible area for rendering
336  2323 int[] bounds = annotPanel.getVisibleVRange();
337  2323 if (bounds != null)
338    {
339  2043 sOffset = bounds[0];
340  2043 visHeight = bounds[1];
341  2043 if (visHeight == 0)
342    {
343  0 useClip = false;
344    }
345    else
346    {
347  2043 useClip = canClip;
348    }
349    }
350    else
351    {
352  280 useClip = false;
353    }
354   
355  2323 rendererFactoryI = AnnotationRendererFactory.getRendererFactory();
356  2323 updateFromAlignViewport(av);
357    }
358   
 
359  2323 toggle public void updateFromAlignViewport(AlignViewportI av)
360    {
361  2323 charWidth = av.getCharWidth();
362  2323 endRes = av.getRanges().getEndRes();
363  2323 charHeight = av.getCharHeight();
364  2323 hasHiddenColumns = av.hasHiddenColumns();
365  2323 validCharWidth = av.isValidCharWidth();
366  2323 av_renderHistogram = av.isShowConsensusHistogram();
367  2323 av_renderProfile = av.isShowSequenceLogo();
368  2323 av_normaliseProfile = av.isNormaliseSequenceLogo();
369  2323 profcolour = av.getResidueShading();
370  2323 if (profcolour == null || profcolour.getColourScheme() == null)
371    {
372    /*
373    * Use default colour for sequence logo if
374    * the alignment has no colourscheme set
375    * (would like to use user preference but n/a for applet)
376    */
377  2071 ColourSchemeI col = av.getAlignment().isNucleotide()
378    ? new NucleotideColourScheme()
379    : new ZappoColourScheme();
380  2071 profcolour = new ResidueShader(col);
381    }
382  2323 columnSelection = av.getColumnSelection();
383  2323 hiddenColumns = av.getAlignment().getHiddenColumns();
384  2323 hconsensus = av.getSequenceConsensusHash();
385  2323 hSSconsensus = av.getSequenceSSConsensusHash();
386  2323 complementConsensus = av.getComplementConsensusHash();
387  2323 hStrucConsensus = av.getRnaStructureConsensusHash();
388  2323 av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
389    }
390   
391    /**
392    * Returns profile data; the first element is the profile type, the second is
393    * the number of distinct values, the third the total count, and the remainder
394    * depend on the profile type.
395    *
396    * @param aa
397    * @param column
398    * @return
399    */
 
400  91351 toggle int[] getProfileFor(AlignmentAnnotation aa, int column)
401    {
402    // TODO : consider refactoring the global alignment calculation
403    // properties/rendering attributes as a global 'alignment group' which holds
404    // all vis settings for the alignment as a whole rather than a subset
405    //
406  91351 if (aa.autoCalculated && (aa.label.startsWith("Consensus")
407    || aa.label.startsWith("cDNA Consensus")))
408    {
409  91351 boolean forComplement = aa.label.startsWith("cDNA Consensus");
410  91351 if (aa.groupRef != null && aa.groupRef.consensusData != null
411    && aa.groupRef.isShowSequenceLogo())
412    {
413    // TODO? group consensus for cDNA complement
414  27909 return AAFrequency.extractProfile(
415    aa.groupRef.consensusData.get(column),
416    aa.groupRef.getIgnoreGapsConsensus());
417    }
418    // TODO extend annotation row to enable dynamic and static profile data to
419    // be stored
420  63442 if (aa.groupRef == null && aa.sequenceRef == null)
421    {
422  63442 if (forComplement)
423    {
424  0 return AAFrequency.extractCdnaProfile(complementConsensus[column],
425    av_ignoreGapsConsensus);
426    }
427    else
428    {
429  63442 return AAFrequency.extractProfile(hconsensus.get(column),
430    av_ignoreGapsConsensus);
431    }
432    }
433    }
434   
435  0 else if (aa.autoCalculated && aa.label.startsWith(
436    MessageManager.getString("label.ssconsensus_label")))
437    {
438  0 return AAFrequency.extractProfile(hSSconsensus.get(column),
439    av_ignoreGapsConsensus);
440   
441    }
442   
443    else
444    {
445  0 if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
446    {
447    // TODO implement group structure consensus
448    /*
449    * if (aa.groupRef != null && aa.groupRef.consensusData != null &&
450    * aa.groupRef.isShowSequenceLogo()) { //TODO check what happens for
451    * group selections return StructureFrequency.extractProfile(
452    * aa.groupRef.consensusData[column], aa.groupRef
453    * .getIgnoreGapsConsensus()); }
454    */
455    // TODO extend annotation row to enable dynamic and static profile data
456    // to
457    // be stored
458  0 if (aa.groupRef == null && aa.sequenceRef == null
459    && hStrucConsensus != null
460    && hStrucConsensus.length > column)
461    {
462  0 return StructureFrequency.extractProfile(hStrucConsensus[column],
463    av_ignoreGapsConsensus);
464    }
465    }
466    }
467  0 return null;
468    }
469   
470    boolean rna = false;
471   
472    private AnnotationRendererFactoryI rendererFactoryI;
473   
474    /**
475    * Render the annotation rows associated with an alignment.
476    *
477    * @param annotPanel
478    * container frame
479    * @param av
480    * data and view settings to render
481    * @param g
482    * destination for graphics
483    * @param activeRow
484    * row where a mouse event occured (or -1)
485    * @param startRes
486    * first column that will be drawn
487    * @param endRes
488    * last column that will be drawn
489    * @return true if the fadedImage was used for any alignment annotation rows
490    * currently being calculated
491    */
 
492  2323 toggle public boolean drawComponent(AwtRenderPanelI annotPanel,
493    AlignViewportI av, Graphics g, int activeRow, int startRes,
494    int endRes)
495    {
496  2323 if (g instanceof EpsGraphics2D || g instanceof SVGGraphics2D)
497    {
498  14 this.setVectorRendering(true);
499    }
500  2323 Graphics2D g2d = (Graphics2D) g;
501   
502  2323 long stime = System.currentTimeMillis();
503  2323 boolean usedFaded = false;
504    // NOTES:
505    // AnnotationPanel needs to implement: ImageObserver, access to
506    // AlignViewport
507  2323 updateFromAwtRenderPanel(annotPanel, av);
508  2323 fm = g.getFontMetrics();
509  2323 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
510    // int temp = 0;
511  2323 if (aa == null)
512    {
513  0 return false;
514    }
515  2323 int x = 0, y = 0;
516  2323 int column = 0;
517  2323 char lastSS;
518  2323 int lastSSX;
519  2323 int iconOffset = 0;
520  2323 boolean validRes = false;
521  2323 boolean validEnd = false;
522  2323 boolean labelAllCols = false;
523    // boolean centreColLabels;
524    // boolean centreColLabelsDef = av.isCentreColumnLabels();
525  2323 boolean scaleColLabel = false;
526  2323 final AlignmentAnnotation consensusAnnot = av
527    .getAlignmentConsensusAnnotation();
528  2323 final AlignmentAnnotation structConsensusAnnot = av
529    .getAlignmentStrucConsensusAnnotation();
530  2323 final AlignmentAnnotation complementConsensusAnnot = av
531    .getComplementConsensusAnnotation();
532  2323 final AlignmentAnnotation ssConsensusAnnot = av
533    .getAlignmentSecondaryStructureConsensusAnnotation();
534   
535  2323 BitSet graphGroupDrawn = new BitSet();
536  2323 int charOffset = 0; // offset for a label
537    // \u03B2 \u03B1
538    // debug ints
539  2323 int yfrom = 0, f_i = 0, yto = 0, f_to = 0;
540  2323 boolean clipst = false, clipend = false;
541  15202 for (int i = 0; i < aa.length; i++)
542    {
543  12879 AlignmentAnnotation row = aa[i];
544  12879 boolean renderHistogram = true;
545  12879 boolean renderProfile = false;
546  12879 boolean normaliseProfile = false;
547  12879 boolean isRNA = row.isRNA();
548   
549    // check if this is a consensus annotation row and set the display
550    // settings appropriately
551    // TODO: generalise this to have render styles for consensus/profile
552    // data
553  12879 if (row.groupRef != null && row == row.groupRef.getConsensus())
554    {
555  447 renderHistogram = row.groupRef.isShowConsensusHistogram();
556  447 renderProfile = row.groupRef.isShowSequenceLogo();
557  447 normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
558    }
559  12432 else if (row == consensusAnnot || row == structConsensusAnnot
560    || row == complementConsensusAnnot || row == ssConsensusAnnot)
561    {
562  2323 renderHistogram = av_renderHistogram;
563  2323 renderProfile = av_renderProfile;
564  2323 normaliseProfile = av_normaliseProfile;
565    }
566   
567  12879 Annotation[] row_annotations = row.annotations;
568  12879 if (!row.visible)
569    {
570  1021 continue;
571    }
572    // centreColLabels = row.centreColLabels || centreColLabelsDef;
573  11858 labelAllCols = row.showAllColLabels;
574  11858 scaleColLabel = row.scaleColLabel;
575  11858 lastSS = ' ';
576  11858 lastSSX = 0;
577   
578  11858 if (!useClip || ((y - charHeight) < visHeight
579    && (y + row.height + charHeight * 2) >= sOffset))
580    {// if_in_visible_region
581  11858 if (!clipst)
582    {
583  2314 clipst = true;
584  2314 yfrom = y;
585  2314 f_i = i;
586    }
587  11858 yto = y;
588  11858 f_to = i;
589  11858 if (row.graph > 0)
590    {
591  10045 if (row.graphGroup > -1 && graphGroupDrawn.get(row.graphGroup))
592    {
593  0 continue;
594    }
595   
596    // this is so that we draw the characters below the graph
597  10045 y += row.height;
598   
599  10045 if (row.hasText)
600    {
601  9351 iconOffset = charHeight - fm.getDescent();
602  9351 y -= charHeight;
603    }
604    }
605  1813 else if (row.hasText)
606    {
607  1540 iconOffset = charHeight - fm.getDescent();
608   
609    }
610    else
611    {
612  273 iconOffset = 0;
613    }
614   
615  11858 if (row.autoCalculated && av.isCalculationInProgress(row))
616    {
617  325 y += charHeight;
618  325 usedFaded = true;
619  325 g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0,
620    y - row.height, imgWidth, y, annotationPanel);
621  325 g.setColor(Color.black);
622    // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
623   
624  325 continue;
625    }
626   
627    /*
628    * else if (annotationPanel.av.updatingConservation &&
629    * aa[i].label.equals("Conservation")) {
630    *
631    * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
632    * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
633    * annotationPanel.imgWidth, y, annotationPanel);
634    *
635    * g.setColor(Color.black); //
636    * g.drawString("Calculating Conservation.....",20, y-row.height/2);
637    *
638    * continue; } else if (annotationPanel.av.updatingConservation &&
639    * aa[i].label.equals("Quality")) {
640    *
641    * y += charHeight; g.drawImage(annotationPanel.fadedImage, 0, y -
642    * row.height, annotationPanel.imgWidth, y, 0, y - row.height,
643    * annotationPanel.imgWidth, y, annotationPanel);
644    * g.setColor(Color.black); // /
645    * g.drawString("Calculating Quality....",20, y-row.height/2);
646    *
647    * continue; }
648    */
649   
650    // first pass sets up state for drawing continuation from left-hand
651    // column
652    // of startRes
653   
654    // flag used for vector rendition
655  11533 this.glyphLineDrawn = false;
656  11533 x = (startRes == 0) ? 0 : -1;
657  596538 while (x < endRes - startRes)
658    {
659  585003 if (hasHiddenColumns)
660    {
661  136512 column = hiddenColumns.visibleToAbsoluteColumn(startRes + x);
662  136512 if (column > row_annotations.length - 1)
663    {
664  0 break;
665    }
666    }
667    else
668    {
669  448490 column = startRes + x;
670    }
671   
672  584999 if ((row_annotations == null)
673    || (row_annotations.length <= column)
674    || (row_annotations[column] == null))
675    {
676  103949 validRes = false;
677    }
678    else
679    {
680  481049 validRes = true;
681    }
682  584999 final String displayChar = validRes
683    ? row_annotations[column].displayCharacter
684    : null;
685  585002 if (x > -1)
686    {
687  580098 unsetAntialias(g);
688  580108 if (activeRow == i)
689    {
690  0 g.setColor(Color.red);
691   
692  0 if (columnSelection != null)
693    {
694  0 if (columnSelection.contains(column))
695    {
696  0 fillRect(g, x * charWidth, y, charWidth, charHeight);
697    }
698    }
699    }
700  580106 if (row.getInvalidStrucPos() > x)
701    {
702  0 g.setColor(Color.orange);
703  0 fillRect(g, x * charWidth, y, charWidth, charHeight);
704    }
705  580104 else if (row.getInvalidStrucPos() == x)
706    {
707  0 g.setColor(Color.orange.darker());
708  0 fillRect(g, x * charWidth, y, charWidth, charHeight);
709    }
710  580099 if (validCharWidth && validRes && displayChar != null
711    && (displayChar.length() > 0))
712    {
713    // Graphics2D gg = (g);
714  346488 float fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
715    displayChar.length());
716   
717    /*
718    * shrink label width to fit in column, if that is
719    * both configured and necessary
720    */
721  346488 boolean scaledToFit = false;
722  346488 float fmScaling = 1f;
723  346487 if (scaleColLabel && fmWidth > charWidth)
724    {
725  0 scaledToFit = true;
726  0 fmScaling = charWidth;
727  0 fmScaling /= fmWidth;
728    // and update the label's width to reflect the scaling.
729  0 fmWidth = charWidth;
730    }
731   
732  346487 charOffset = (int) ((charWidth - fmWidth) / 2f);
733   
734  346488 if (row_annotations[column].colour == null)
735    {
736  156958 g2d.setColor(Color.black);
737    }
738    else
739    {
740  189530 g2d.setColor(row_annotations[column].colour);
741    }
742   
743    /*
744    * draw the label, unless it is the same secondary structure
745    * symbol (excluding RNA Helix) as the previous column
746    */
747  346488 final int xPos = (x * charWidth) + charOffset;
748  346489 final int yPos = y + iconOffset;
749   
750    /*
751    * translate to drawing position _before_ applying any scaling
752    */
753  346488 g2d.translate(xPos, yPos);
754  346489 if (scaledToFit)
755    {
756    /*
757    * use a scaling transform to make the label narrower
758    * (JalviewJS doesn't have Font.deriveFont(AffineTransform))
759    */
760  0 g2d.transform(
761    AffineTransform.getScaleInstance(fmScaling, 1.0));
762    }
763  346489 setAntialias(g);
764  346492 if (column == 0 || row.graph > 0)
765    {
766  338115 g2d.drawString(displayChar, 0, 0);
767    }
768  8375 else if (row_annotations[column - 1] == null || (labelAllCols
769    || !displayChar.equals(
770    row_annotations[column - 1].displayCharacter)
771    || (displayChar.length() < 2
772    && row_annotations[column].secondaryStructure == ' ')))
773    {
774  8375 g2d.drawString(displayChar, 0, 0);
775    }
776  346497 if (scaledToFit)
777    {
778    /*
779    * undo scaling before translating back
780    * (restoring saved transform does NOT work in JS PDFGraphics!)
781    */
782  0 g2d.transform(AffineTransform
783    .getScaleInstance(1D / fmScaling, 1.0));
784    }
785  346496 g2d.translate(-xPos, -yPos);
786    }
787    }
788  585005 if (row.hasIcons)
789    {
790  62638 char ss = validRes ? row_annotations[column].secondaryStructure
791    : '-';
792   
793  62638 if (ss == '(')
794    {
795    // distinguish between forward/backward base-pairing
796  0 if (displayChar.indexOf(')') > -1)
797    {
798   
799  0 ss = ')';
800   
801    }
802    }
803  62638 if (ss == '[')
804    {
805  0 if ((displayChar.indexOf(']') > -1))
806    {
807  0 ss = ']';
808   
809    }
810    }
811  62638 if (ss == '{')
812    {
813    // distinguish between forward/backward base-pairing
814  0 if (displayChar.indexOf('}') > -1)
815    {
816  0 ss = '}';
817   
818    }
819    }
820  62638 if (ss == '<')
821    {
822    // distinguish between forward/backward base-pairing
823  0 if (displayChar.indexOf('<') > -1)
824    {
825  0 ss = '>';
826   
827    }
828    }
829  62638 if (isRNA && (ss >= CHAR_A) && (ss <= CHAR_Z))
830    {
831    // distinguish between forward/backward base-pairing
832  0 int ssLowerCase = ss + UPPER_TO_LOWER;
833    // TODO would .equals() be safer here? or charAt(0)?
834  0 if (displayChar.indexOf(ssLowerCase) > -1)
835    {
836  0 ss = (char) ssLowerCase;
837    }
838    }
839   
840  62638 if (!validRes || (ss != lastSS))
841    {
842   
843  48457 if (x > -1)
844    {
845   
846    // int nb_annot = x - temp;
847    // Console.info("\t type :"+lastSS+"\t x
848    // :"+x+"\t nbre
849    // annot :"+nb_annot);
850  47684 switch (lastSS)
851    {
852  0 case '(': // Stem case for RNA secondary structure
853  0 case ')': // and opposite direction
854  0 drawStemAnnot(g, row_annotations, lastSSX, x, y,
855    iconOffset, startRes, column, validRes, validEnd);
856    // temp = x;
857  0 break;
858   
859  812 case 'H':
860  812 if (!isRNA)
861    {
862  812 drawHelixAnnot(g, row_annotations, lastSSX, x, y,
863    iconOffset, startRes, column, validRes,
864    validEnd);
865  812 break;
866    }
867    // no break if isRNA - falls through to drawNotCanonicalAnnot!
868  4361 case 'E':
869  4361 if (!isRNA)
870    {
871  4361 drawSheetAnnot(g, row_annotations, lastSSX, x, y,
872    iconOffset, startRes, column, validRes,
873    validEnd);
874  4361 break;
875    }
876    // no break if isRNA - fall through to drawNotCanonicalAnnot!
877   
878  0 case '{':
879  0 case '}':
880  0 case '[':
881  0 case ']':
882  0 case '>':
883  0 case '<':
884  0 case 'A':
885  0 case 'a':
886  0 case 'B':
887  0 case 'b':
888  0 case 'C':
889  0 case 'c':
890  0 case 'D':
891  0 case 'd':
892  0 case 'e':
893  0 case 'F':
894  0 case 'f':
895  0 case 'G':
896  0 case 'g':
897  0 case 'h':
898  0 case 'I':
899  0 case 'i':
900  0 case 'J':
901  0 case 'j':
902  0 case 'K':
903  0 case 'k':
904  0 case 'L':
905  0 case 'l':
906  0 case 'M':
907  0 case 'm':
908  0 case 'N':
909  0 case 'n':
910  0 case 'O':
911  0 case 'o':
912  0 case 'P':
913  0 case 'p':
914  0 case 'Q':
915  0 case 'q':
916  0 case 'R':
917  0 case 'r':
918  0 case 'S':
919  0 case 's':
920  0 case 'T':
921  0 case 't':
922  0 case 'U':
923  0 case 'u':
924  0 case 'V':
925  0 case 'v':
926  0 case 'W':
927  0 case 'w':
928  0 case 'X':
929  0 case 'x':
930  0 case 'Y':
931  0 case 'y':
932  0 case 'Z':
933  0 case 'z':
934   
935  0 Color nonCanColor = getNotCanonicalColor(lastSS);
936  0 drawNotCanonicalAnnot(g, nonCanColor, row_annotations,
937    lastSSX, x, y, iconOffset, startRes, column,
938    validRes, validEnd);
939    // temp = x;
940  0 break;
941  42511 default:
942  42511 if (isVectorRendering())
943    {
944    // draw single full width glyphline
945  0 drawGlyphLine(g, lastSSX, endRes - x, y, iconOffset);
946    // disable more glyph lines
947  0 this.glyphLineDrawn = true;
948    }
949    else
950    {
951  42511 drawGlyphLine(g, lastSSX, x, y, iconOffset);
952    }
953  42511 break;
954    }
955    }
956  48457 if (validRes)
957    {
958  5263 lastSS = ss;
959    }
960    else
961    {
962  43194 lastSS = ' ';
963    }
964  48457 if (x > -1)
965    {
966  47684 lastSSX = (x * charWidth);
967    }
968    }
969    }
970  585003 column++;
971  585004 x++;
972    }
973  11533 if (column >= row_annotations.length)
974    {
975  1692 column = row_annotations.length - 1;
976  1692 validEnd = false;
977    }
978    else
979    {
980  9841 validEnd = true;
981    }
982  11533 if ((row_annotations == null) || (row_annotations.length <= column)
983    || (row_annotations[column] == null))
984    {
985  2166 validRes = false;
986    }
987    else
988    {
989  9367 validRes = true;
990    }
991    // x ++;
992   
993  11533 if (row.hasIcons)
994    {
995  1043 switch (lastSS)
996    {
997   
998  0 case 'H':
999  0 if (!isRNA)
1000    {
1001  0 drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1002    startRes, column, validRes, validEnd);
1003  0 break;
1004    }
1005    // no break if isRNA - fall through to drawNotCanonicalAnnot!
1006   
1007  90 case 'E':
1008  90 if (!isRNA)
1009    {
1010  90 drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1011    startRes, column, validRes, validEnd);
1012  90 break;
1013    }
1014    // no break if isRNA - fall through to drawNotCanonicalAnnot!
1015   
1016  0 case '(':
1017  0 case ')': // Stem case for RNA secondary structure
1018   
1019  0 drawStemAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1020    startRes, column, validRes, validEnd);
1021   
1022  0 break;
1023  0 case '{':
1024  0 case '}':
1025  0 case '[':
1026  0 case ']':
1027  0 case '>':
1028  0 case '<':
1029  0 case 'A':
1030  0 case 'a':
1031  0 case 'B':
1032  0 case 'b':
1033  0 case 'C':
1034  0 case 'c':
1035  0 case 'D':
1036  0 case 'd':
1037  0 case 'e':
1038  0 case 'F':
1039  0 case 'f':
1040  0 case 'G':
1041  0 case 'g':
1042  0 case 'h':
1043  0 case 'I':
1044  0 case 'i':
1045  0 case 'J':
1046  0 case 'j':
1047  0 case 'K':
1048  0 case 'k':
1049  0 case 'L':
1050  0 case 'l':
1051  0 case 'M':
1052  0 case 'm':
1053  0 case 'N':
1054  0 case 'n':
1055  0 case 'O':
1056  0 case 'o':
1057  0 case 'P':
1058  0 case 'p':
1059  0 case 'Q':
1060  0 case 'q':
1061  0 case 'R':
1062  0 case 'r':
1063  0 case 'T':
1064  0 case 't':
1065  0 case 'U':
1066  0 case 'u':
1067  0 case 'V':
1068  0 case 'v':
1069  0 case 'W':
1070  0 case 'w':
1071  0 case 'X':
1072  0 case 'x':
1073  0 case 'Y':
1074  0 case 'y':
1075  0 case 'Z':
1076  0 case 'z':
1077    // Console.info(lastSS);
1078  0 Color nonCanColor = getNotCanonicalColor(lastSS);
1079  0 drawNotCanonicalAnnot(g, nonCanColor, row_annotations, lastSSX,
1080    x, y, iconOffset, startRes, column, validRes, validEnd);
1081  0 break;
1082  953 default:
1083  953 if (isVectorRendering())
1084    {
1085    // draw single full width glyphline
1086  0 drawGlyphLine(g, lastSSX, endRes - x, y, iconOffset);
1087    // disable more glyph lines
1088  0 this.glyphLineDrawn = true;
1089    }
1090    else
1091    {
1092  953 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1093    }
1094  953 break;
1095    }
1096    }
1097   
1098  11533 if (row.graph > 0 && row.graphHeight > 0)
1099    {
1100  9720 if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1101    {
1102  549 if (row.graphGroup > -1 && !graphGroupDrawn.get(row.graphGroup))
1103    {
1104    // TODO: JAL-1291 revise rendering model so the graphGroup map is
1105    // computed efficiently for all visible labels
1106  0 float groupmax = -999999, groupmin = 9999999;
1107  0 for (int gg = 0; gg < aa.length; gg++)
1108    {
1109  0 if (aa[gg].graphGroup != row.graphGroup)
1110    {
1111  0 continue;
1112    }
1113   
1114  0 if (aa[gg] != row)
1115    {
1116  0 aa[gg].visible = false;
1117    }
1118  0 if (aa[gg].graphMax > groupmax)
1119    {
1120  0 groupmax = aa[gg].graphMax;
1121    }
1122  0 if (aa[gg].graphMin < groupmin)
1123    {
1124  0 groupmin = aa[gg].graphMin;
1125    }
1126    }
1127   
1128  0 for (int gg = 0; gg < aa.length; gg++)
1129    {
1130  0 if (aa[gg].graphGroup == row.graphGroup)
1131    {
1132  0 drawLineGraph(g, aa[gg], aa[gg].annotations, startRes,
1133    endRes, y, groupmin, groupmax, row.graphHeight);
1134    }
1135    }
1136   
1137  0 graphGroupDrawn.set(row.graphGroup);
1138    }
1139    else
1140    {
1141  549 drawLineGraph(g, row, row_annotations, startRes, endRes, y,
1142    row.graphMin, row.graphMax, row.graphHeight);
1143    }
1144    }
1145  9171 else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1146    {
1147  9026 drawBarGraph(g, row, row_annotations, startRes, endRes,
1148    row.graphMin, row.graphMax, y, renderHistogram,
1149    renderProfile, normaliseProfile);
1150    }
1151    else
1152    {
1153  145 AnnotationRowRendererI renderer = rendererFactoryI
1154    .getRendererFor(row);
1155  145 if (renderer != null)
1156    {
1157  145 renderer.renderRow(g, charWidth, charHeight, hasHiddenColumns,
1158    av, hiddenColumns, columnSelection, row,
1159    row_annotations, startRes, endRes, row.graphMin,
1160    row.graphMax, y);
1161    }
1162  145 if (debugRedraw)
1163    {
1164  0 if (renderer == null)
1165    {
1166  0 System.err
1167    .println("No renderer found for " + row.toString());
1168    }
1169    else
1170    {
1171  0 Console.warn(
1172    "rendered with " + renderer.getClass().toString());
1173    }
1174    }
1175   
1176    }
1177    }
1178    }
1179    else
1180    {
1181  0 if (clipst && !clipend)
1182    {
1183  0 clipend = true;
1184    }
1185    } // end if_in_visible_region
1186  11533 if (row.graph > 0 && row.hasText)
1187    {
1188  9026 y += charHeight;
1189    }
1190   
1191  11533 if (row.graph == 0)
1192    {
1193  1813 y += aa[i].height;
1194    }
1195    }
1196  2323 if (debugRedraw)
1197    {
1198  0 if (canClip)
1199    {
1200  0 if (clipst)
1201    {
1202  0 Console.warn("Start clip at : " + yfrom + " (index " + f_i + ")");
1203    }
1204  0 if (clipend)
1205    {
1206  0 Console.warn("End clip at : " + yto + " (index " + f_to + ")");
1207    }
1208    }
1209  0 ;
1210  0 Console.warn("Annotation Rendering time:"
1211    + (System.currentTimeMillis() - stime));
1212    }
1213  2323 ;
1214   
1215  2323 return !usedFaded;
1216    }
1217   
1218    public static final Color GLYPHLINE_COLOR = Color.gray;
1219   
1220    public static final Color SHEET_COLOUR = Color.green;
1221   
1222    public static final Color HELIX_COLOUR = Color.red;
1223   
1224    public static final Color STEM_COLOUR = Color.blue;
1225   
1226    // private Color sdNOTCANONICAL_COLOUR;
1227   
 
1228  48724 toggle void drawGlyphLine(Graphics g, int lastSSX, int x, int y, int iconOffset)
1229    {
1230  48724 if (glyphLineDrawn)
1231    {
1232    // if we've drawn a single long glyphline for an export, don't draw the
1233    // bits
1234  0 return;
1235    }
1236  48724 unsetAntialias(g);
1237  48724 g.setColor(GLYPHLINE_COLOR);
1238  48724 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
1239    }
1240   
 
1241  4451 toggle void drawSheetAnnot(Graphics g, Annotation[] row,
1242   
1243    int lastSSX, int x, int y, int iconOffset, int startRes,
1244    int column, boolean validRes, boolean validEnd)
1245    {
1246  4451 if (!validEnd || !validRes || row == null || row[column] == null
1247    || row[column].secondaryStructure != 'E')
1248    {
1249    // draw the glyphline underneath
1250  4448 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1251   
1252  4448 g.setColor(SHEET_COLOUR);
1253  4448 fillRect(g, lastSSX, y + 4 + iconOffset,
1254    (x * charWidth) - lastSSX - 4, 6);
1255  4448 fillPolygon(g,
1256    new int[]
1257    { (x * charWidth) - 6, (x * charWidth) - 6,
1258    (x * charWidth - 1) },
1259    new int[]
1260    { y + iconOffset + 1, y + 13 + iconOffset,
1261    y + 7 + iconOffset },
1262    3);
1263    }
1264    else
1265    {
1266  3 g.setColor(SHEET_COLOUR);
1267  3 fillRect(g, lastSSX, y + 4 + iconOffset, (x * charWidth) - lastSSX,
1268    6);
1269    }
1270    }
1271   
 
1272  812 toggle void drawHelixAnnot(Graphics g, Annotation[] row, int lastSSX, int x,
1273    int y, int iconOffset, int startRes, int column, boolean validRes,
1274    boolean validEnd)
1275    {
1276  812 int sCol = (lastSSX / charWidth)
1277    + hiddenColumns.visibleToAbsoluteColumn(startRes);
1278  812 int x1 = lastSSX;
1279  812 int x2 = (x * charWidth);
1280   
1281  812 if (USE_FILL_ROUND_RECT || isVectorRendering())
1282    {
1283    // draw glyph line behind helix (visible in EPS or SVG output)
1284  0 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1285   
1286  0 g.setColor(HELIX_COLOUR);
1287  0 setAntialias(g);
1288  0 int ofs = charWidth / 2;
1289    // Off by 1 offset when drawing rects and ovals
1290    // to offscreen image on the MAC
1291  0 fillRoundRect(g, lastSSX, y + 3 + iconOffset, x2 - x1 - 1, 8, 8, 8);
1292  0 if (sCol == 0 || row[sCol - 1] == null
1293    || row[sCol - 1].secondaryStructure != 'H')
1294    {
1295    }
1296    else
1297    {
1298  0 fillRoundRect(g, lastSSX, y + 3 + iconOffset, x2 - x1 - ofs, 8, 0,
1299    0);
1300    }
1301  0 if (!validRes || row[column] == null
1302    || row[column].secondaryStructure != 'H')
1303    {
1304   
1305    }
1306    else
1307    {
1308  0 fillRoundRect(g, lastSSX + ofs, y + 3 + iconOffset, x2 - x1 - ofs,
1309    8, 0, 0);
1310    }
1311   
1312  0 return;
1313    }
1314   
1315  812 boolean leftEnd = sCol == 0 || row[sCol - 1] == null
1316    || row[sCol - 1].secondaryStructure != 'H';
1317  812 boolean rightEnd = !validRes || row[column] == null
1318    || row[column].secondaryStructure != 'H';
1319   
1320  812 if (leftEnd || rightEnd)
1321    {
1322  812 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1323    }
1324  812 g.setColor(HELIX_COLOUR);
1325   
1326  812 if (leftEnd)
1327    {
1328  812 fillArc(g, lastSSX, y + 3 + iconOffset, charWidth, 8, 90, 180);
1329  812 x1 += charWidth / 2;
1330    }
1331   
1332  812 if (rightEnd)
1333    {
1334  812 fillArc(g, ((x - 1) * charWidth), y + 3 + iconOffset, charWidth, 8,
1335    270, 180);
1336  812 x2 -= charWidth / 2;
1337    }
1338   
1339  812 fillRect(g, x1, y + 3 + iconOffset, x2 - x1, 8);
1340    }
1341   
 
1342  549 toggle void drawLineGraph(Graphics g, AlignmentAnnotation _aa,
1343    Annotation[] aa_annotations, int sRes, int eRes, int y, float min,
1344    float max, int graphHeight)
1345    {
1346  549 if (sRes > aa_annotations.length)
1347    {
1348  1 return;
1349    }
1350  548 Stroke roundStroke = new BasicStroke(1, BasicStroke.CAP_ROUND,
1351    BasicStroke.JOIN_ROUND);
1352  548 Stroke squareStroke = new BasicStroke(1, BasicStroke.CAP_SQUARE,
1353    BasicStroke.JOIN_MITER);
1354  548 Graphics2D g2d = (Graphics2D) g;
1355  548 Stroke prevStroke = g2d.getStroke();
1356  548 g2d.setStroke(roundStroke);
1357   
1358  548 int x = 0;
1359   
1360    // Adjustment for fastpaint to left
1361  548 if (eRes < endRes)
1362    {
1363  0 eRes++;
1364    }
1365   
1366  548 eRes = Math.min(eRes, aa_annotations.length);
1367   
1368  548 if (sRes == 0)
1369    {
1370  546 x++;
1371    }
1372   
1373  548 int y1 = y, y2 = y;
1374  548 float range = max - min;
1375   
1376    // //Draw origin
1377  548 if (min < 0)
1378    {
1379  0 y2 = y - (int) ((0 - min / range) * graphHeight);
1380    }
1381   
1382  548 g.setColor(Color.gray);
1383  548 drawLine(g, squareStroke, x * charWidth - charWidth, y2,
1384    (eRes - sRes) * charWidth, y2);
1385   
1386  548 eRes = Math.min(eRes, aa_annotations.length);
1387   
1388  548 int column;
1389  548 int aaMax = aa_annotations.length - 1;
1390   
1391  24377 while (x < eRes - sRes)
1392    {
1393  23829 column = sRes + x;
1394  23829 if (hasHiddenColumns)
1395    {
1396  8832 column = hiddenColumns.visibleToAbsoluteColumn(column);
1397    }
1398   
1399  23829 if (column > aaMax)
1400    {
1401  0 break;
1402    }
1403   
1404  23829 if (aa_annotations[column] == null)
1405    {
1406  8935 x++;
1407  8935 continue;
1408    }
1409   
1410  14894 if (aa_annotations[column].colour == null)
1411    {
1412  3300 g.setColor(Color.black);
1413    }
1414    else
1415    {
1416  11594 g.setColor(aa_annotations[column].colour);
1417    }
1418   
1419  14894 if (aa_annotations[column - 1] == null
1420    && aa_annotations.length > column + 1
1421    && aa_annotations[column + 1] == null)
1422    {
1423    // standalone value
1424  1835 y1 = y - (int) (((aa_annotations[column].value - min) / range)
1425    * graphHeight);
1426  1835 drawLine(g, x * charWidth + charWidth / 4, y1,
1427    x * charWidth + 3 * charWidth / 4, y1);
1428  1835 x++;
1429  1835 continue;
1430    }
1431   
1432  13059 if (aa_annotations[column - 1] == null)
1433    {
1434  185 x++;
1435  185 continue;
1436    }
1437   
1438  12874 y1 = y - (int) (((aa_annotations[column - 1].value - min) / range)
1439    * graphHeight);
1440  12874 y2 = y - (int) (((aa_annotations[column].value - min) / range)
1441    * graphHeight);
1442   
1443  12874 drawLine(g, (x - 1) * charWidth + charWidth / 2, y1,
1444    x * charWidth + charWidth / 2, y2);
1445  12874 x++;
1446    }
1447   
1448  548 if (_aa.threshold != null)
1449    {
1450  0 g.setColor(_aa.threshold.colour);
1451  0 Graphics2D g2 = (Graphics2D) g;
1452  0 Stroke s = new BasicStroke(1, BasicStroke.CAP_SQUARE,
1453    BasicStroke.JOIN_ROUND, 3f, new float[]
1454    { 5f, 3f }, 0f);
1455   
1456  0 y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
1457  0 drawLine(g, s, 0, y2, (eRes - sRes) * charWidth, y2);
1458    }
1459  548 g2d.setStroke(prevStroke);
1460    }
1461   
 
1462  9026 toggle @SuppressWarnings("unused")
1463    void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
1464    Annotation[] aa_annotations, int sRes, int eRes, float min,
1465    float max, int y, boolean renderHistogram, boolean renderProfile,
1466    boolean normaliseProfile)
1467    {
1468  9026 if (sRes > aa_annotations.length)
1469    {
1470  0 return;
1471    }
1472  9026 Font ofont = g.getFont();
1473  9026 eRes = Math.min(eRes, aa_annotations.length);
1474   
1475  9026 int x = 0, y1 = y, y2 = y;
1476   
1477  9026 float range = max - min;
1478   
1479  9026 if (min < 0)
1480    {
1481  0 y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
1482    }
1483   
1484  9026 g.setColor(Color.gray);
1485   
1486  9026 drawLine(g, x, y2, (eRes - sRes) * charWidth, y2);
1487   
1488  9026 int column;
1489  9026 int aaMax = aa_annotations.length - 1;
1490  447869 while (x < eRes - sRes)
1491    {
1492  438843 column = sRes + x;
1493  438843 if (hasHiddenColumns)
1494    {
1495  127404 column = hiddenColumns.visibleToAbsoluteColumn(column);
1496    }
1497   
1498  438845 if (column > aaMax)
1499    {
1500  0 break;
1501    }
1502   
1503  438846 if (aa_annotations[column] == null)
1504    {
1505  1535 x++;
1506  1535 continue;
1507    }
1508  437310 if (aa_annotations[column].colour == null)
1509    {
1510  157098 g.setColor(Color.black);
1511    }
1512    else
1513    {
1514  280212 g.setColor(aa_annotations[column].colour);
1515    }
1516   
1517  437309 y1 = y - (int) (((aa_annotations[column].value - min) / (range))
1518    * _aa.graphHeight);
1519   
1520  437310 if (renderHistogram)
1521    {
1522  437309 if (y1 - y2 > 0)
1523    {
1524  0 fillRect(g, x * charWidth, y2, charWidth, y1 - y2);
1525    }
1526    else
1527    {
1528  437308 fillRect(g, x * charWidth, y1, charWidth, y2 - y1);
1529    }
1530    }
1531    // draw profile if available
1532  437308 if (renderProfile)
1533    {
1534   
1535    /*
1536    * {profile type, #values, total count, char1, pct1, char2, pct2...}
1537    */
1538  91351 int profl[] = getProfileFor(_aa, column);
1539   
1540    // just try to draw the logo if profl is not null
1541  91351 if (profl != null && profl[2] != 0)
1542    {
1543  91351 boolean isStructureProfile = profl[0] == AlignmentAnnotation.STRUCTURE_PROFILE;
1544  91351 boolean isCdnaProfile = profl[0] == AlignmentAnnotation.CDNA_PROFILE;
1545  91351 float ht = normaliseProfile ? y - _aa.graphHeight : y1;
1546  91351 final double normaliseFactor = normaliseProfile ? _aa.graphHeight
1547    : (y2 - y1);
1548   
1549    /**
1550    * Render a single base for a sequence profile, a base pair for
1551    * structure profile, and a triplet for a cdna profile
1552    */
1553  91351 char[] dc = new char[isStructureProfile ? 2
1554  91351 : (isCdnaProfile ? 3 : 1)];
1555   
1556    // lm is not necessary - we can just use fm - could be off by no more
1557    // than 0.5 px
1558    // LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
1559    // Console.info(asc + " " + dec + " " + (asc -
1560    // lm.getAscent())
1561    // + " " + (dec - lm.getDescent()));
1562   
1563  91351 double asc = fm.getAscent();
1564  91351 double dec = fm.getDescent();
1565  91351 double fht = fm.getHeight();
1566   
1567  91351 double scale = 1f / (normaliseProfile ? profl[2] : 100f);
1568    // float ofontHeight = 1f / fm.getAscent();// magnify to fill box
1569   
1570    /*
1571    * Traverse the character(s)/percentage data in the array
1572    */
1573   
1574  91351 float ht2 = ht;
1575   
1576    // profl[1] is the number of values in the profile
1577  265682 for (int i = 0, c = 3, last = profl[1]; i < last; i++)
1578    {
1579   
1580  174332 String s;
1581  174333 if (isStructureProfile)
1582    {
1583    // todo can we encode a structure pair as an int, like codons?
1584  0 dc[0] = (char) profl[c++];
1585  0 dc[1] = (char) profl[c++];
1586  0 s = new String(dc);
1587    }
1588  174332 else if (isCdnaProfile)
1589    {
1590  0 CodingUtils.decodeCodon2(profl[c++], dc);
1591  0 s = new String(dc);
1592    }
1593    else
1594    {
1595  174333 dc[0] = (char) profl[c++];
1596  174333 s = new String(dc);
1597    }
1598    // next profl[] position is profile % for the character(s)
1599   
1600  174332 int percent = profl[c++];
1601  174333 if (percent == 0)
1602    {
1603    // failsafe in case a count rounds down to 0%
1604  0 continue;
1605    }
1606  174332 double newHeight = normaliseFactor * scale * percent;
1607   
1608    /*
1609    * Set character colour as per alignment colour scheme; use the
1610    * codon translation if a cDNA profile
1611    */
1612  174332 Color colour = null;
1613  174333 if (isCdnaProfile)
1614    {
1615  0 final String codonTranslation = ResidueProperties
1616    .codonTranslate(s);
1617  0 colour = profcolour.findColour(codonTranslation.charAt(0),
1618    column, null);
1619    }
1620  174329 if (_aa.label == MessageManager
1621    .getString("label.ssconsensus_label"))
1622    {
1623  0 colour = AlignmentUtils
1624    .getSecondaryStructureAnnotationColour(dc[0]);
1625    }
1626    else
1627    {
1628   
1629  174329 colour = profcolour.findColour(dc[0], column, null);
1630   
1631    }
1632  174331 g.setColor(colour == Color.white ? Color.lightGray : colour);
1633   
1634    // Debug - render boxes around characters
1635    // g.setColor(Color.red);
1636    // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
1637    // (int)(scl));
1638    // g.setColor(profcolour.findColour(dc[0]).darker());
1639   
1640  174328 double sx = 1f * charWidth / fm.charsWidth(dc, 0, dc.length);
1641  174329 double sy = newHeight / asc;
1642  174329 double newAsc = asc * sy;
1643  174329 double newDec = dec * sy;
1644    // it is not necessary to recalculate lm for the new font.
1645    // note: lm.getBaselineOffsets()[lm.getBaselineIndex()]) must be 0
1646    // by definition. Was:
1647    // int hght = (int) (ht + (newAsc - newDec);
1648    // - lm.getBaselineOffsets()[lm.getBaselineIndex()]));
1649   
1650  174329 if (Platform.isJS())
1651    {
1652    /*
1653    * SwingJS does not implement font.deriveFont()
1654    * so use a scaling transform to draw instead,
1655    * this is off by a very small amount
1656    */
1657  0 final int hght = (int) (ht2 + (newAsc - newDec));
1658  0 Graphics2D gg = (Graphics2D) g;
1659  0 int xShift = (int) Math.round(x * charWidth / sx);
1660  0 int yShift = (int) Math.round(hght / sy);
1661  0 gg.transform(AffineTransform.getScaleInstance(sx, sy));
1662  0 gg.drawString(s, xShift, yShift);
1663  0 gg.transform(
1664    AffineTransform.getScaleInstance(1D / sx, 1D / sy));
1665  0 ht2 += newHeight;
1666    }
1667    else
1668    /**
1669    * Java only
1670    *
1671    * @j2sIgnore
1672    */
1673    {
1674    // Java ('normal') method is to scale the font to fit
1675   
1676  174329 final int hght = (int) (ht + (newAsc - newDec));
1677  174331 Font font = ofont
1678    .deriveFont(AffineTransform.getScaleInstance(sx, sy));
1679  174327 g.setFont(font);
1680  174330 g.drawChars(dc, 0, dc.length, x * charWidth, hght);
1681  174330 g.setFont(ofont);
1682   
1683  174330 ht += newHeight;
1684    }
1685    }
1686    }
1687    }
1688  437307 x++;
1689    }
1690  9026 if (_aa.threshold != null)
1691    {
1692  0 g.setColor(_aa.threshold.colour);
1693  0 Stroke s = new BasicStroke(1, BasicStroke.CAP_SQUARE,
1694    BasicStroke.JOIN_ROUND, 3f, new float[]
1695    { 5f, 3f }, 0f);
1696   
1697  0 y2 = (int) (y
1698    - ((_aa.threshold.value - min) / range) * _aa.graphHeight);
1699  0 drawLine(g, s, 0, y2, (eRes - sRes) * charWidth, y2);
1700    }
1701    }
1702   
1703    // used by overview window
 
1704  0 toggle public void drawGraph(Graphics g, AlignmentAnnotation _aa,
1705    Annotation[] aa_annotations, int width, int y, int sRes, int eRes)
1706    {
1707  0 eRes = Math.min(eRes, aa_annotations.length);
1708  0 g.setColor(Color.white);
1709  0 fillRect(g, 0, 0, width, y);
1710  0 g.setColor(new Color(0, 0, 180));
1711   
1712  0 int x = 0, height;
1713   
1714  0 for (int j = sRes; j < eRes; j++)
1715    {
1716  0 if (aa_annotations[j] != null)
1717    {
1718  0 if (aa_annotations[j].colour == null)
1719    {
1720  0 g.setColor(Color.black);
1721    }
1722    else
1723    {
1724  0 g.setColor(aa_annotations[j].colour);
1725    }
1726   
1727  0 height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
1728  0 if (height > y)
1729    {
1730  0 height = y;
1731    }
1732   
1733  0 fillRect(g, x, y - height, charWidth, height);
1734    }
1735  0 x += charWidth;
1736    }
1737    }
1738   
 
1739  0 toggle Color getNotCanonicalColor(char lastss)
1740    {
1741  0 switch (lastss)
1742    {
1743  0 case '{':
1744  0 case '}':
1745  0 return new Color(255, 125, 5);
1746   
1747  0 case '[':
1748  0 case ']':
1749  0 return new Color(245, 115, 10);
1750   
1751  0 case '>':
1752  0 case '<':
1753  0 return new Color(235, 135, 15);
1754   
1755  0 case 'A':
1756  0 case 'a':
1757  0 return new Color(225, 105, 20);
1758   
1759  0 case 'B':
1760  0 case 'b':
1761  0 return new Color(215, 145, 30);
1762   
1763  0 case 'C':
1764  0 case 'c':
1765  0 return new Color(205, 95, 35);
1766   
1767  0 case 'D':
1768  0 case 'd':
1769  0 return new Color(195, 155, 45);
1770   
1771  0 case 'E':
1772  0 case 'e':
1773  0 return new Color(185, 85, 55);
1774   
1775  0 case 'F':
1776  0 case 'f':
1777  0 return new Color(175, 165, 65);
1778   
1779  0 case 'G':
1780  0 case 'g':
1781  0 return new Color(170, 75, 75);
1782   
1783  0 case 'H':
1784  0 case 'h':
1785  0 return new Color(160, 175, 85);
1786   
1787  0 case 'I':
1788  0 case 'i':
1789  0 return new Color(150, 65, 95);
1790   
1791  0 case 'J':
1792  0 case 'j':
1793  0 return new Color(140, 185, 105);
1794   
1795  0 case 'K':
1796  0 case 'k':
1797  0 return new Color(130, 55, 110);
1798   
1799  0 case 'L':
1800  0 case 'l':
1801  0 return new Color(120, 195, 120);
1802   
1803  0 case 'M':
1804  0 case 'm':
1805  0 return new Color(110, 45, 130);
1806   
1807  0 case 'N':
1808  0 case 'n':
1809  0 return new Color(100, 205, 140);
1810   
1811  0 case 'O':
1812  0 case 'o':
1813  0 return new Color(90, 35, 150);
1814   
1815  0 case 'P':
1816  0 case 'p':
1817  0 return new Color(85, 215, 160);
1818   
1819  0 case 'Q':
1820  0 case 'q':
1821  0 return new Color(75, 25, 170);
1822   
1823  0 case 'R':
1824  0 case 'r':
1825  0 return new Color(65, 225, 180);
1826   
1827  0 case 'S':
1828  0 case 's':
1829  0 return new Color(55, 15, 185);
1830   
1831  0 case 'T':
1832  0 case 't':
1833  0 return new Color(45, 235, 195);
1834   
1835  0 case 'U':
1836  0 case 'u':
1837  0 return new Color(35, 5, 205);
1838   
1839  0 case 'V':
1840  0 case 'v':
1841  0 return new Color(25, 245, 215);
1842   
1843  0 case 'W':
1844  0 case 'w':
1845  0 return new Color(15, 0, 225);
1846   
1847  0 case 'X':
1848  0 case 'x':
1849  0 return new Color(10, 255, 235);
1850   
1851  0 case 'Y':
1852  0 case 'y':
1853  0 return new Color(5, 150, 245);
1854   
1855  0 case 'Z':
1856  0 case 'z':
1857  0 return new Color(0, 80, 255);
1858   
1859  0 default:
1860  0 Console.info("This is not a interaction : " + lastss);
1861  0 return null;
1862   
1863    }
1864    }
1865   
 
1866  4448 toggle private void fillPolygon(Graphics g, int[] xpoints, int[] ypoints, int n)
1867    {
1868  4448 setAntialias(g);
1869  4448 g.fillPolygon(xpoints, ypoints, n);
1870    }
1871   
1872    /*
1873    private void fillRect(Graphics g, int a, int b, int c, int d)
1874    {
1875    fillRect(g, false, a, b, c, d);
1876    }*/
1877   
 
1878  442567 toggle private void fillRect(Graphics g, int a, int b, int c, int d)
1879    {
1880  442567 unsetAntialias(g);
1881  442566 g.fillRect(a, b, c, d);
1882    }
1883   
 
1884  0 toggle private void fillRoundRect(Graphics g, int a, int b, int c, int d, int e,
1885    int f)
1886    {
1887  0 setAntialias(g);
1888  0 g.fillRoundRect(a, b, c, d, e, f);
1889    }
1890   
 
1891  1624 toggle private void fillArc(Graphics g, int a, int b, int c, int d, int e, int f)
1892    {
1893  1624 setAntialias(g);
1894  1624 g.fillArc(a, b, c, d, e, f);
1895    }
1896   
 
1897  548 toggle private void drawLine(Graphics g, Stroke s, int a, int b, int c, int d)
1898    {
1899  548 Graphics2D g2d = (Graphics2D) g;
1900  548 Stroke p = g2d.getStroke();
1901  548 g2d.setStroke(s);
1902  548 drawLine(g, a, b, c, d);
1903  548 g2d.setStroke(p);
1904    }
1905   
 
1906  24283 toggle private void drawLine(Graphics g, int a, int b, int c, int d)
1907    {
1908  24283 setAntialias(g);
1909  24283 g.drawLine(a, b, c, d);
1910    }
1911   
 
1912  376843 toggle private void setAntialias(Graphics g)
1913    {
1914  376846 if (isVectorRendering())
1915    {
1916    // no need to antialias vector drawings
1917  6650 return;
1918    }
1919  370194 if (Cache.getDefault("ANTI_ALIAS", true))
1920    {
1921  48670 Graphics2D g2d = (Graphics2D) g;
1922  48676 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1923    RenderingHints.VALUE_ANTIALIAS_ON);
1924    }
1925    }
1926   
 
1927  1071383 toggle private void unsetAntialias(Graphics g)
1928    {
1929  1071390 if (isVectorRendering())
1930    {
1931    // no need to antialias vector drawings
1932  17584 return;
1933    }
1934  1053813 Graphics2D g2d = (Graphics2D) g;
1935  1053815 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1936    RenderingHints.VALUE_ANTIALIAS_OFF);
1937    }
1938   
 
1939  14 toggle public void setVectorRendering(boolean b)
1940    {
1941  14 renderingVectors = b;
1942    }
1943   
 
1944  1492485 toggle public boolean isVectorRendering()
1945    {
1946  1492486 return renderingVectors;
1947    }
1948    }