Clover icon

Coverage Report

  1. Project Clover database Fri Jul 3 2026 13:27:51 BST
  2. Package jalview.renderer

File AnnotationRenderer.java

 

Coverage histogram

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

Code metrics

320
779
26
1
1,992
1,477
428
0.55
29.96
26
16.46

Classes

Class Line # Actions
AnnotationRenderer 64 779 428
0.514666751.5%
 

Contributing tests

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