Clover icon

Coverage Report

  1. Project Clover database Wed May 27 2026 17:16:46 BST
  2. Package jalview.renderer

File AnnotationRenderer.java

 

Coverage histogram

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

Code metrics

328
794
26
1
2,035
1,512
431
0.54
30.54
26
16.58

Classes

Class Line # Actions
AnnotationRenderer 66 794 431
0.5984320659.8%
 

Contributing tests

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