Clover icon

Coverage Report

  1. Project Clover database Thu Nov 7 2024 10:11:34 GMT
  2. Package jalview.renderer

File AnnotationRenderer.java

 

Coverage histogram

../../img/srcFileCovDistChart5.png
43% of files have more coverage

Code metrics

318
776
26
1
1,989
1,473
426
0.55
29.85
26
16.38

Classes

Class Line # Actions
AnnotationRenderer 64 776 426
0.5071428450.7%
 

Contributing tests

This file is covered by 184 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  492 toggle public AnnotationRenderer()
150    {
151  492 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  492 toggle public AnnotationRenderer(boolean debugRedraw)
162    {
163  492 this.debugRedraw = debugRedraw;
164    }
165   
166    /**
167    * Remove any references and resources when this object is no longer required
168    */
 
169  253 toggle public void dispose()
170    {
171  253 hiddenColumns = null;
172  253 hconsensus = null;
173  253 hSSconsensus = null;
174  253 complementConsensus = null;
175  253 hStrucConsensus = null;
176  253 fadedImage = null;
177  253 annotationPanel = null;
178  253 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  2463 toggle public void updateFromAwtRenderPanel(AwtRenderPanelI annotPanel,
331    AlignViewportI av)
332    {
333  2463 fm = annotPanel.getFontMetrics();
334  2463 annotationPanel = annotPanel;
335  2463 fadedImage = annotPanel.getFadedImage();
336  2463 imgWidth = annotPanel.getFadedImageWidth();
337    // visible area for rendering
338  2463 int[] bounds = annotPanel.getVisibleVRange();
339  2463 if (bounds != null)
340    {
341  2206 sOffset = bounds[0];
342  2206 visHeight = bounds[1];
343  2206 if (visHeight == 0)
344    {
345  0 useClip = false;
346    }
347    else
348    {
349  2206 useClip = canClip;
350    }
351    }
352    else
353    {
354  257 useClip = false;
355    }
356   
357  2463 rendererFactoryI = AnnotationRendererFactory.getRendererFactory();
358  2463 updateFromAlignViewport(av);
359    }
360   
 
361  2463 toggle public void updateFromAlignViewport(AlignViewportI av)
362    {
363  2463 charWidth = av.getCharWidth();
364  2463 endRes = av.getRanges().getEndRes();
365  2463 charHeight = av.getCharHeight();
366  2463 hasHiddenColumns = av.hasHiddenColumns();
367  2463 validCharWidth = av.isValidCharWidth();
368  2463 av_renderHistogram = av.isShowConsensusHistogram();
369  2463 av_renderProfile = av.isShowSequenceLogo();
370  2463 av_normaliseProfile = av.isNormaliseSequenceLogo();
371  2463 profcolour = av.getResidueShading();
372  2463 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  2186 ColourSchemeI col = av.getAlignment().isNucleotide()
380    ? new NucleotideColourScheme()
381    : new ZappoColourScheme();
382  2186 profcolour = new ResidueShader(col);
383    }
384  2463 columnSelection = av.getColumnSelection();
385  2463 hiddenColumns = av.getAlignment().getHiddenColumns();
386  2463 hconsensus = av.getSequenceConsensusHash();
387  2463 hSSconsensus = av.getSequenceSSConsensusHash();
388  2463 complementConsensus = av.getComplementConsensusHash();
389  2463 hStrucConsensus = av.getRnaStructureConsensusHash();
390  2463 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  108957 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  108957 if (aa.autoCalculated && (aa.label.startsWith("Consensus")
409    || aa.label.startsWith("cDNA Consensus")))
410    {
411  108957 boolean forComplement = aa.label.startsWith("cDNA Consensus");
412  108957 if (aa.groupRef != null && aa.groupRef.consensusData != null
413    && aa.groupRef.isShowSequenceLogo())
414    {
415    // TODO? group consensus for cDNA complement
416  44541 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  64416 if (aa.groupRef == null && aa.sequenceRef == null)
423    {
424  64416 if (forComplement)
425    {
426  0 return AAFrequency.extractCdnaProfile(complementConsensus[column],
427    av_ignoreGapsConsensus);
428    }
429    else
430    {
431  64416 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  2463 toggle public boolean drawComponent(AwtRenderPanelI annotPanel,
521    AlignViewportI av, Graphics g, int activeRow, int startRes,
522    int endRes)
523    {
524  2463 if (g instanceof EpsGraphics2D || g instanceof SVGGraphics2D)
525    {
526  14 this.setVectorRendering(true);
527    }
528  2463 Graphics2D g2d = (Graphics2D) g;
529   
530  2463 long stime = System.currentTimeMillis();
531  2463 boolean usedFaded = false;
532    // NOTES:
533    // AnnotationPanel needs to implement: ImageObserver, access to
534    // AlignViewport
535  2463 updateFromAwtRenderPanel(annotPanel, av);
536  2463 fm = g.getFontMetrics();
537  2463 AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
538    // int temp = 0;
539  2463 if (aa == null)
540    {
541  0 return false;
542    }
543  2463 int x = 0, y = 0;
544  2463 int column = 0;
545  2463 char lastSS;
546  2463 int lastSSX;
547  2463 int iconOffset = 0;
548  2463 boolean validRes = false;
549  2463 boolean validEnd = false;
550  2463 boolean labelAllCols = false;
551    // boolean centreColLabels;
552    // boolean centreColLabelsDef = av.isCentreColumnLabels();
553  2463 boolean scaleColLabel = false;
554  2463 final AlignmentAnnotation consensusAnnot = av
555    .getAlignmentConsensusAnnotation();
556  2463 final AlignmentAnnotation structConsensusAnnot = av
557    .getAlignmentStrucConsensusAnnotation();
558  2463 final AlignmentAnnotation complementConsensusAnnot = av
559    .getComplementConsensusAnnotation();
560  2463 final List<AlignmentAnnotation> ssConsensusAnnot = av
561    .getAlignmentSecondaryStructureConsensusAnnotation();
562   
563  2463 BitSet graphGroupDrawn = new BitSet();
564  2463 int charOffset = 0; // offset for a label
565    // \u03B2 \u03B1
566    // debug ints
567  2463 int yfrom = 0, f_i = 0, yto = 0, f_to = 0;
568  2463 boolean clipst = false, clipend = false;
569  15863 for (int i = 0; i < aa.length; i++)
570    {
571  13401 AlignmentAnnotation row = aa[i];
572  13401 boolean renderHistogram = true;
573  13401 boolean renderProfile = false;
574  13401 boolean normaliseProfile = false;
575  13401 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  13401 if (row.groupRef != null && (row == row.groupRef.getConsensus()
582    || (row.groupRef.getSSConsensus(null) != null
583    && row.groupRef.getSSConsensus(null).contains(row))))
584    {
585  711 renderHistogram = row.groupRef.isShowConsensusHistogram();
586  711 renderProfile = row.groupRef.isShowSequenceLogo();
587  711 normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
588    }
589  12690 else if (row == consensusAnnot || row == structConsensusAnnot
590    || row == complementConsensusAnnot
591    || (ssConsensusAnnot != null
592    && ssConsensusAnnot.contains(row)))
593    {
594  2463 renderHistogram = av_renderHistogram;
595  2463 renderProfile = av_renderProfile;
596  2463 normaliseProfile = av_normaliseProfile;
597    }
598   
599  13401 Annotation[] row_annotations = row.annotations;
600  13401 if (!row.visible)
601    {
602  1172 continue;
603    }
604    // centreColLabels = row.centreColLabels || centreColLabelsDef;
605  12229 labelAllCols = row.showAllColLabels;
606  12229 scaleColLabel = row.scaleColLabel;
607  12229 lastSS = ' ';
608  12229 lastSSX = 0;
609   
610  12229 if (!useClip || ((y - charHeight) < visHeight
611    && (y + row.height + charHeight * 2) >= sOffset))
612    {// if_in_visible_region
613  12229 if (!clipst)
614    {
615  2453 clipst = true;
616  2453 yfrom = y;
617  2453 f_i = i;
618    }
619  12229 yto = y;
620  12229 f_to = i;
621  12229 if (row.graph > 0)
622    {
623  10319 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  10319 y += row.height;
630   
631  10319 if (row.hasText)
632    {
633  9673 iconOffset = charHeight - fm.getDescent();
634  9673 y -= charHeight;
635    }
636    }
637  1910 else if (row.hasText)
638    {
639  1648 iconOffset = charHeight - fm.getDescent();
640   
641    }
642    else
643    {
644  262 iconOffset = 0;
645    }
646   
647  12229 if (row.autoCalculated && av.isCalculationInProgress(row))
648    {
649  218 y += charHeight;
650  218 usedFaded = true;
651  218 g.drawImage(fadedImage, 0, y - row.height, imgWidth, y, 0,
652    y - row.height, imgWidth, y, annotationPanel);
653  218 g.setColor(Color.black);
654    // g.drawString("Calculating "+aa[i].label+"....",20, y-row.height/2);
655   
656  218 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  12011 this.glyphLineDrawn = false;
688  12011 x = (startRes == 0) ? 0 : -1;
689  624989 while (x < endRes - startRes)
690    {
691  612978 if (hasHiddenColumns)
692    {
693  144732 column = hiddenColumns.visibleToAbsoluteColumn(startRes + x);
694  144732 if (column > row_annotations.length - 1)
695    {
696  0 break;
697    }
698    }
699    else
700    {
701  468246 column = startRes + x;
702    }
703   
704  612978 if ((row_annotations == null)
705    || (row_annotations.length <= column)
706    || (row_annotations[column] == null))
707    {
708  107805 validRes = false;
709    }
710    else
711    {
712  505173 validRes = true;
713    }
714  612978 final String displayChar = validRes
715    ? row_annotations[column].displayCharacter
716    : null;
717  612978 if (x > -1)
718    {
719  607678 unsetAntialias(g);
720  607678 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  607678 if (row.getInvalidStrucPos() > x)
733    {
734  0 g.setColor(Color.orange);
735  0 fillRect(g, x * charWidth, y, charWidth, charHeight);
736    }
737  607677 else if (row.getInvalidStrucPos() == x)
738    {
739  0 g.setColor(Color.orange.darker());
740  0 fillRect(g, x * charWidth, y, charWidth, charHeight);
741    }
742  607677 if (validCharWidth && validRes && displayChar != null
743    && (displayChar.length() > 0))
744    {
745    // Graphics2D gg = (g);
746  363792 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  363792 boolean scaledToFit = false;
754  363792 float fmScaling = 1f;
755  363792 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  363792 charOffset = (int) ((charWidth - fmWidth) / 2f);
765   
766  363793 if (row_annotations[column].colour == null)
767    {
768  153000 g2d.setColor(Color.black);
769    }
770    else
771    {
772  210793 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  363793 final int xPos = (x * charWidth) + charOffset;
780  363793 final int yPos = y + iconOffset;
781   
782    /*
783    * translate to drawing position _before_ applying any scaling
784    */
785  363793 g2d.translate(xPos, yPos);
786  363792 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  363792 setAntialias(g);
796  363793 if (column == 0 || row.graph > 0)
797    {
798  354844 g2d.drawString(displayChar, 0, 0);
799    }
800  8949 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  8949 g2d.drawString(displayChar, 0, 0);
807    }
808  363793 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  363792 g2d.translate(-xPos, -yPos);
818    }
819    }
820  612978 if (row.hasIcons)
821    {
822  65310 char ss = validRes ? row_annotations[column].secondaryStructure
823    : '-';
824   
825  65310 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  65310 if (ss == '[')
836    {
837  0 if ((displayChar.indexOf(']') > -1))
838    {
839  0 ss = ']';
840   
841    }
842    }
843  65310 if (ss == '{')
844    {
845    // distinguish between forward/backward base-pairing
846  0 if (displayChar.indexOf('}') > -1)
847    {
848  0 ss = '}';
849   
850    }
851    }
852  65310 if (ss == '<')
853    {
854    // distinguish between forward/backward base-pairing
855  0 if (displayChar.indexOf('<') > -1)
856    {
857  0 ss = '>';
858   
859    }
860    }
861  65310 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  65310 if (!validRes || (ss != lastSS))
873    {
874   
875  50177 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  49350 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  866 case 'H':
892  866 if (!isRNA)
893    {
894  866 drawHelixAnnot(g, row_annotations, lastSSX, x, y,
895    iconOffset, startRes, column, validRes,
896    validEnd);
897  866 break;
898    }
899    // no break if isRNA - falls through to drawNotCanonicalAnnot!
900  4405 case 'E':
901  4405 if (!isRNA)
902    {
903  4405 drawSheetAnnot(g, row_annotations, lastSSX, x, y,
904    iconOffset, startRes, column, validRes,
905    validEnd);
906  4405 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  44079 default:
974  44079 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  44079 drawGlyphLine(g, lastSSX, x, y, iconOffset);
984    }
985  44079 break;
986    }
987    }
988  50177 if (validRes)
989    {
990  5380 lastSS = ss;
991    }
992    else
993    {
994  44797 lastSS = ' ';
995    }
996  50177 if (x > -1)
997    {
998  49350 lastSSX = (x * charWidth);
999    }
1000    }
1001    }
1002  612978 column++;
1003  612978 x++;
1004    }
1005  12011 if (column >= row_annotations.length)
1006    {
1007  1772 column = row_annotations.length - 1;
1008  1772 validEnd = false;
1009    }
1010    else
1011    {
1012  10239 validEnd = true;
1013    }
1014  12011 if ((row_annotations == null) || (row_annotations.length <= column)
1015    || (row_annotations[column] == null))
1016    {
1017  2340 validRes = false;
1018    }
1019    else
1020    {
1021  9671 validRes = true;
1022    }
1023    // x ++;
1024   
1025  12011 if (row.hasIcons)
1026    {
1027  1086 switch (lastSS)
1028    {
1029   
1030  0 case 'H':
1031  0 if (!isRNA)
1032    {
1033  0 drawHelixAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1034    startRes, column, validRes, validEnd);
1035  0 break;
1036    }
1037    // no break if isRNA - fall through to drawNotCanonicalAnnot!
1038   
1039  109 case 'E':
1040  109 if (!isRNA)
1041    {
1042  109 drawSheetAnnot(g, row_annotations, lastSSX, x, y, iconOffset,
1043    startRes, column, validRes, validEnd);
1044  109 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  977 default:
1115  977 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  977 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1125    }
1126  977 break;
1127    }
1128    }
1129   
1130  12011 if (row.graph > 0 && row.graphHeight > 0)
1131    {
1132  10101 if (row.graph == AlignmentAnnotation.LINE_GRAPH)
1133    {
1134  513 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  513 drawLineGraph(g, row, row_annotations, startRes, endRes, y,
1174    row.graphMin, row.graphMax, row.graphHeight);
1175    }
1176    }
1177  9588 else if (row.graph == AlignmentAnnotation.BAR_GRAPH)
1178    {
1179  9455 drawBarGraph(g, row, row_annotations, startRes, endRes,
1180    row.graphMin, row.graphMax, y, renderHistogram,
1181    renderProfile, normaliseProfile);
1182    }
1183    else
1184    {
1185  133 AnnotationRowRendererI renderer = rendererFactoryI
1186    .getRendererFor(row);
1187  133 if (renderer != null)
1188    {
1189  133 renderer.renderRow(g, charWidth, charHeight, hasHiddenColumns,
1190    av, hiddenColumns, columnSelection, row,
1191    row_annotations, startRes, endRes, row.graphMin,
1192    row.graphMax, y);
1193    }
1194  133 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  12010 if (row.graph > 0 && row.hasText)
1219    {
1220  9454 y += charHeight;
1221    }
1222   
1223  12010 if (row.graph == 0)
1224    {
1225  1910 y += aa[i].height;
1226    }
1227    }
1228  2462 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  2462 ;
1246   
1247  2462 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  50433 toggle void drawGlyphLine(Graphics g, int lastSSX, int x, int y, int iconOffset)
1261    {
1262  50433 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  50433 unsetAntialias(g);
1269  50433 g.setColor(GLYPHLINE_COLOR);
1270  50433 g.fillRect(lastSSX, y + 6 + iconOffset, (x * charWidth) - lastSSX, 2);
1271    }
1272   
 
1273  4514 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  4514 if (!validEnd || !validRes || row == null || row[column] == null
1279    || row[column].secondaryStructure != 'E')
1280    {
1281    // draw the glyphline underneath
1282  4511 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1283   
1284  4511 g.setColor(SHEET_COLOUR);
1285  4511 fillRect(g, lastSSX, y + 4 + iconOffset,
1286    (x * charWidth) - lastSSX - 4, 6);
1287  4511 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  3 g.setColor(SHEET_COLOUR);
1299  3 fillRect(g, lastSSX, y + 4 + iconOffset, (x * charWidth) - lastSSX,
1300    6);
1301    }
1302    }
1303   
 
1304  866 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  866 int sCol = (lastSSX / charWidth)
1309    + hiddenColumns.visibleToAbsoluteColumn(startRes);
1310  866 int x1 = lastSSX;
1311  866 int x2 = (x * charWidth);
1312   
1313  866 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  866 boolean leftEnd = sCol == 0 || row[sCol - 1] == null
1348    || row[sCol - 1].secondaryStructure != 'H';
1349  866 boolean rightEnd = !validRes || row[column] == null
1350    || row[column].secondaryStructure != 'H';
1351   
1352  866 if (leftEnd || rightEnd)
1353    {
1354  866 drawGlyphLine(g, lastSSX, x, y, iconOffset);
1355    }
1356  866 g.setColor(HELIX_COLOUR);
1357   
1358  866 if (leftEnd)
1359    {
1360  866 fillArc(g, lastSSX, y + 3 + iconOffset, charWidth, 8, 90, 180);
1361  866 x1 += charWidth / 2;
1362    }
1363   
1364  866 if (rightEnd)
1365    {
1366  866 fillArc(g, ((x - 1) * charWidth), y + 3 + iconOffset, charWidth, 8,
1367    270, 180);
1368  866 x2 -= charWidth / 2;
1369    }
1370   
1371  866 fillRect(g, x1, y + 3 + iconOffset, x2 - x1, 8);
1372    }
1373   
 
1374  513 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  513 if (sRes > aa_annotations.length)
1379    {
1380  1 return;
1381    }
1382  512 Stroke roundStroke = new BasicStroke(1, BasicStroke.CAP_ROUND,
1383    BasicStroke.JOIN_ROUND);
1384  512 Stroke squareStroke = new BasicStroke(1, BasicStroke.CAP_SQUARE,
1385    BasicStroke.JOIN_MITER);
1386  512 Graphics2D g2d = (Graphics2D) g;
1387  512 Stroke prevStroke = g2d.getStroke();
1388  512 g2d.setStroke(roundStroke);
1389   
1390  512 int x = 0;
1391   
1392    // Adjustment for fastpaint to left
1393  512 if (eRes < endRes)
1394    {
1395  0 eRes++;
1396    }
1397   
1398  512 eRes = Math.min(eRes, aa_annotations.length);
1399   
1400  512 if (sRes == 0)
1401    {
1402  510 x++;
1403    }
1404   
1405  512 int y1 = y, y2 = y;
1406  512 float range = max - min;
1407   
1408    // //Draw origin
1409  512 if (min < 0)
1410    {
1411  0 y2 = y - (int) ((0 - min / range) * graphHeight);
1412    }
1413   
1414  512 g.setColor(Color.gray);
1415  512 drawLine(g, squareStroke, x * charWidth - charWidth, y2,
1416    (eRes - sRes) * charWidth, y2);
1417   
1418  512 eRes = Math.min(eRes, aa_annotations.length);
1419   
1420  512 int column;
1421  512 int aaMax = aa_annotations.length - 1;
1422   
1423  23078 while (x < eRes - sRes)
1424    {
1425  22566 column = sRes + x;
1426  22566 if (hasHiddenColumns)
1427    {
1428  8032 column = hiddenColumns.visibleToAbsoluteColumn(column);
1429    }
1430   
1431  22566 if (column > aaMax)
1432    {
1433  0 break;
1434    }
1435   
1436  22566 if (aa_annotations[column] == null)
1437    {
1438  8263 x++;
1439  8263 continue;
1440    }
1441   
1442  14303 if (aa_annotations[column].colour == null)
1443    {
1444  3318 g.setColor(Color.black);
1445    }
1446    else
1447    {
1448  10985 g.setColor(aa_annotations[column].colour);
1449    }
1450   
1451  14303 if (aa_annotations[column - 1] == null
1452    && aa_annotations.length > column + 1
1453    && aa_annotations[column + 1] == null)
1454    {
1455    // standalone value
1456  1668 y1 = y - (int) (((aa_annotations[column].value - min) / range)
1457    * graphHeight);
1458  1668 drawLine(g, x * charWidth + charWidth / 4, y1,
1459    x * charWidth + 3 * charWidth / 4, y1);
1460  1668 x++;
1461  1668 continue;
1462    }
1463   
1464  12635 if (aa_annotations[column - 1] == null)
1465    {
1466  225 x++;
1467  225 continue;
1468    }
1469   
1470  12410 y1 = y - (int) (((aa_annotations[column - 1].value - min) / range)
1471    * graphHeight);
1472  12410 y2 = y - (int) (((aa_annotations[column].value - min) / range)
1473    * graphHeight);
1474   
1475  12410 drawLine(g, (x - 1) * charWidth + charWidth / 2, y1,
1476    x * charWidth + charWidth / 2, y2);
1477  12410 x++;
1478    }
1479   
1480  512 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  512 g2d.setStroke(prevStroke);
1492    }
1493   
 
1494  9455 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  9455 if (sRes > aa_annotations.length)
1501    {
1502  0 return;
1503    }
1504  9455 Font ofont = g.getFont();
1505  9455 eRes = Math.min(eRes, aa_annotations.length);
1506   
1507  9455 int x = 0, y1 = y, y2 = y;
1508   
1509  9455 float range = max - min;
1510   
1511  9455 if (min < 0)
1512    {
1513  0 y2 = y - (int) ((0 - min / (range)) * _aa.graphHeight);
1514    }
1515   
1516  9455 g.setColor(Color.gray);
1517   
1518  9455 drawLine(g, x, y2, (eRes - sRes) * charWidth, y2);
1519   
1520  9455 int column;
1521  9455 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  9455 boolean _aa_is_dssp3 = _aa.autoCalculated && _aa.label
1528    .startsWith(Constants.SECONDARY_STRUCTURE_CONSENSUS_LABEL);
1529   
1530  471919 while (x < eRes - sRes)
1531    {
1532  462465 column = sRes + x;
1533  462465 if (hasHiddenColumns)
1534    {
1535  136449 column = hiddenColumns.visibleToAbsoluteColumn(column);
1536    }
1537   
1538  462465 if (column > aaMax)
1539    {
1540  0 break;
1541    }
1542   
1543  462466 if (aa_annotations[column] == null)
1544    {
1545  2323 x++;
1546  2323 continue;
1547    }
1548  460143 if (aa_annotations[column].colour == null)
1549    {
1550  153065 g.setColor(Color.black);
1551    }
1552    else
1553    {
1554  307078 g.setColor(aa_annotations[column].colour);
1555    }
1556   
1557  460142 y1 = y - (int) (((aa_annotations[column].value - min) / (range))
1558    * _aa.graphHeight);
1559   
1560  460143 if (renderHistogram)
1561    {
1562  460142 if (y1 - y2 > 0)
1563    {
1564  0 fillRect(g, x * charWidth, y2, charWidth, y1 - y2);
1565    }
1566    else
1567    {
1568  460143 fillRect(g, x * charWidth, y1, charWidth, y2 - y1);
1569    }
1570    }
1571    // draw profile if available
1572  460143 if (renderProfile)
1573    {
1574   
1575    /*
1576    * {profile type, #values, total count, char1, pct1, char2, pct2...}
1577    */
1578  108957 int profl[] = getProfileFor(_aa, column);
1579   
1580    // just try to draw the logo if profl is not null
1581  108956 if (profl != null && profl[2] != 0)
1582    {
1583    // TODO do we ever have different profiles for different columns ?
1584    // Move up to where we set _aa_is_dssp3
1585  108956 boolean isStructureProfile = profl[0] == AlignmentAnnotation.STRUCTURE_PROFILE;
1586  108956 boolean isCdnaProfile = profl[0] == AlignmentAnnotation.CDNA_PROFILE;
1587  108956 float ht = normaliseProfile ? y - _aa.graphHeight : y1;
1588  108956 final double normaliseFactor = normaliseProfile ? _aa.graphHeight
1589    : (y2 - y1);
1590   
1591    /**
1592    * Render a single base for a sequence profile, a base pair for
1593    * structure profile, and a triplet for a cdna profile
1594    */
1595  108956 char[] dc = new char[isStructureProfile ? 2
1596  108956 : (isCdnaProfile ? 3 : 1)];
1597   
1598    // lm is not necessary - we can just use fm - could be off by no more
1599    // than 0.5 px
1600    // LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
1601    // Console.info(asc + " " + dec + " " + (asc -
1602    // lm.getAscent())
1603    // + " " + (dec - lm.getDescent()));
1604   
1605  108956 double asc = fm.getAscent();
1606  108956 double dec = fm.getDescent();
1607  108956 double fht = fm.getHeight();
1608   
1609  108956 double scale = 1f / (normaliseProfile ? profl[2] : 100f);
1610    // float ofontHeight = 1f / fm.getAscent();// magnify to fill box
1611   
1612    /*
1613    * Traverse the character(s)/percentage data in the array
1614    */
1615   
1616  108956 float ht2 = ht;
1617   
1618    // profl[1] is the number of values in the profile
1619  305036 for (int i = 0, c = 3, last = profl[1]; i < last; i++)
1620    {
1621   
1622  196080 String s;
1623  196080 if (isStructureProfile)
1624    {
1625    // todo can we encode a structure pair as an int, like codons?
1626  0 dc[0] = (char) profl[c++];
1627  0 dc[1] = (char) profl[c++];
1628  0 s = new String(dc);
1629    }
1630  196080 else if (isCdnaProfile)
1631    {
1632  0 CodingUtils.decodeCodon2(profl[c++], dc);
1633  0 s = new String(dc);
1634    }
1635    else
1636    {
1637  196080 dc[0] = (char) profl[c++];
1638  196080 s = new String(dc);
1639    }
1640    // next profl[] position is profile % for the character(s)
1641   
1642  196080 int percent = profl[c++];
1643  196080 if (percent == 0)
1644    {
1645    // failsafe in case a count rounds down to 0%
1646  0 continue;
1647    }
1648  196080 double newHeight = normaliseFactor * scale * percent;
1649   
1650    /*
1651    * Set character colour as per alignment colour scheme; use the
1652    * codon translation if a cDNA profile
1653    */
1654  196080 Color colour = null;
1655  196080 if (isCdnaProfile)
1656    {
1657  0 final String codonTranslation = ResidueProperties
1658    .codonTranslate(s);
1659  0 colour = profcolour.findColour(codonTranslation.charAt(0),
1660    column, null);
1661    }
1662  196080 else if (_aa_is_dssp3)
1663    {
1664    // TODO - would be nice to get rid of the special 'findSSColour'
1665    // here ?
1666  0 colour = profcolour.findSSColour(dc[0], column);
1667    }
1668    else
1669    {
1670   
1671  196080 colour = profcolour.findColour(dc[0], column, null);
1672    }
1673  196080 g.setColor(colour == Color.white ? Color.lightGray : colour);
1674   
1675    // Debug - render boxes around characters
1676    // g.setColor(Color.red);
1677    // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
1678    // (int)(scl));
1679    // g.setColor(profcolour.findColour(dc[0]).darker());
1680   
1681  196080 double sx = 1f * charWidth / fm.charsWidth(dc, 0, dc.length);
1682  196080 double sy = newHeight / asc;
1683  196080 double newAsc = asc * sy;
1684  196080 double newDec = dec * sy;
1685    // it is not necessary to recalculate lm for the new font.
1686    // note: lm.getBaselineOffsets()[lm.getBaselineIndex()]) must be 0
1687    // by definition. Was:
1688    // int hght = (int) (ht + (newAsc - newDec);
1689    // - lm.getBaselineOffsets()[lm.getBaselineIndex()]));
1690   
1691  196080 if (Platform.isJS())
1692    {
1693    /*
1694    * SwingJS does not implement font.deriveFont()
1695    * so use a scaling transform to draw instead,
1696    * this is off by a very small amount
1697    */
1698  0 final int hght = (int) (ht2 + (newAsc - newDec));
1699  0 Graphics2D gg = (Graphics2D) g;
1700  0 int xShift = (int) Math.round(x * charWidth / sx);
1701  0 int yShift = (int) Math.round(hght / sy);
1702  0 gg.transform(AffineTransform.getScaleInstance(sx, sy));
1703  0 gg.drawString(s, xShift, yShift);
1704  0 gg.transform(
1705    AffineTransform.getScaleInstance(1D / sx, 1D / sy));
1706  0 ht2 += newHeight;
1707    }
1708    else
1709    /**
1710    * Java only
1711    *
1712    * @j2sIgnore
1713    */
1714    {
1715    // Java ('normal') method is to scale the font to fit
1716   
1717  196080 final int hght = (int) (ht + (newAsc - newDec));
1718  196080 Font font = ofont
1719    .deriveFont(AffineTransform.getScaleInstance(sx, sy));
1720  196076 g.setFont(font);
1721  196077 g.drawChars(dc, 0, dc.length, x * charWidth, hght);
1722  196080 g.setFont(ofont);
1723   
1724  196080 ht += newHeight;
1725    }
1726    }
1727    }
1728    }
1729  460141 x++;
1730    }
1731  9454 if (_aa.threshold != null)
1732    {
1733  0 g.setColor(_aa.threshold.colour);
1734  0 Stroke s = new BasicStroke(1, BasicStroke.CAP_SQUARE,
1735    BasicStroke.JOIN_ROUND, 3f, new float[]
1736    { 5f, 3f }, 0f);
1737   
1738  0 y2 = (int) (y
1739    - ((_aa.threshold.value - min) / range) * _aa.graphHeight);
1740  0 drawLine(g, s, 0, y2, (eRes - sRes) * charWidth, y2);
1741    }
1742    }
1743   
1744    // used by overview window
 
1745  0 toggle public void drawGraph(Graphics g, AlignmentAnnotation _aa,
1746    Annotation[] aa_annotations, int width, int y, int sRes, int eRes)
1747    {
1748  0 eRes = Math.min(eRes, aa_annotations.length);
1749  0 g.setColor(Color.white);
1750  0 fillRect(g, 0, 0, width, y);
1751  0 g.setColor(new Color(0, 0, 180));
1752   
1753  0 int x = 0, height;
1754   
1755  0 for (int j = sRes; j < eRes; j++)
1756    {
1757  0 if (aa_annotations[j] != null)
1758    {
1759  0 if (aa_annotations[j].colour == null)
1760    {
1761  0 g.setColor(Color.black);
1762    }
1763    else
1764    {
1765  0 g.setColor(aa_annotations[j].colour);
1766    }
1767   
1768  0 height = (int) ((aa_annotations[j].value / _aa.graphMax) * y);
1769  0 if (height > y)
1770    {
1771  0 height = y;
1772    }
1773   
1774  0 fillRect(g, x, y - height, charWidth, height);
1775    }
1776  0 x += charWidth;
1777    }
1778    }
1779   
 
1780  0 toggle Color getNotCanonicalColor(char lastss)
1781    {
1782  0 switch (lastss)
1783    {
1784  0 case '{':
1785  0 case '}':
1786  0 return new Color(255, 125, 5);
1787   
1788  0 case '[':
1789  0 case ']':
1790  0 return new Color(245, 115, 10);
1791   
1792  0 case '>':
1793  0 case '<':
1794  0 return new Color(235, 135, 15);
1795   
1796  0 case 'A':
1797  0 case 'a':
1798  0 return new Color(225, 105, 20);
1799   
1800  0 case 'B':
1801  0 case 'b':
1802  0 return new Color(215, 145, 30);
1803   
1804  0 case 'C':
1805  0 case 'c':
1806  0 return new Color(205, 95, 35);
1807   
1808  0 case 'D':
1809  0 case 'd':
1810  0 return new Color(195, 155, 45);
1811   
1812  0 case 'E':
1813  0 case 'e':
1814  0 return new Color(185, 85, 55);
1815   
1816  0 case 'F':
1817  0 case 'f':
1818  0 return new Color(175, 165, 65);
1819   
1820  0 case 'G':
1821  0 case 'g':
1822  0 return new Color(170, 75, 75);
1823   
1824  0 case 'H':
1825  0 case 'h':
1826  0 return new Color(160, 175, 85);
1827   
1828  0 case 'I':
1829  0 case 'i':
1830  0 return new Color(150, 65, 95);
1831   
1832  0 case 'J':
1833  0 case 'j':
1834  0 return new Color(140, 185, 105);
1835   
1836  0 case 'K':
1837  0 case 'k':
1838  0 return new Color(130, 55, 110);
1839   
1840  0 case 'L':
1841  0 case 'l':
1842  0 return new Color(120, 195, 120);
1843   
1844  0 case 'M':
1845  0 case 'm':
1846  0 return new Color(110, 45, 130);
1847   
1848  0 case 'N':
1849  0 case 'n':
1850  0 return new Color(100, 205, 140);
1851   
1852  0 case 'O':
1853  0 case 'o':
1854  0 return new Color(90, 35, 150);
1855   
1856  0 case 'P':
1857  0 case 'p':
1858  0 return new Color(85, 215, 160);
1859   
1860  0 case 'Q':
1861  0 case 'q':
1862  0 return new Color(75, 25, 170);
1863   
1864  0 case 'R':
1865  0 case 'r':
1866  0 return new Color(65, 225, 180);
1867   
1868  0 case 'S':
1869  0 case 's':
1870  0 return new Color(55, 15, 185);
1871   
1872  0 case 'T':
1873  0 case 't':
1874  0 return new Color(45, 235, 195);
1875   
1876  0 case 'U':
1877  0 case 'u':
1878  0 return new Color(35, 5, 205);
1879   
1880  0 case 'V':
1881  0 case 'v':
1882  0 return new Color(25, 245, 215);
1883   
1884  0 case 'W':
1885  0 case 'w':
1886  0 return new Color(15, 0, 225);
1887   
1888  0 case 'X':
1889  0 case 'x':
1890  0 return new Color(10, 255, 235);
1891   
1892  0 case 'Y':
1893  0 case 'y':
1894  0 return new Color(5, 150, 245);
1895   
1896  0 case 'Z':
1897  0 case 'z':
1898  0 return new Color(0, 80, 255);
1899   
1900  0 default:
1901  0 Console.info("This is not a interaction : " + lastss);
1902  0 return null;
1903   
1904    }
1905    }
1906   
 
1907  4511 toggle private void fillPolygon(Graphics g, int[] xpoints, int[] ypoints, int n)
1908    {
1909  4511 setAntialias(g);
1910  4511 g.fillPolygon(xpoints, ypoints, n);
1911    }
1912   
1913    /*
1914    private void fillRect(Graphics g, int a, int b, int c, int d)
1915    {
1916    fillRect(g, false, a, b, c, d);
1917    }*/
1918   
 
1919  465523 toggle private void fillRect(Graphics g, int a, int b, int c, int d)
1920    {
1921  465523 unsetAntialias(g);
1922  465523 g.fillRect(a, b, c, d);
1923    }
1924   
 
1925  0 toggle private void fillRoundRect(Graphics g, int a, int b, int c, int d, int e,
1926    int f)
1927    {
1928  0 setAntialias(g);
1929  0 g.fillRoundRect(a, b, c, d, e, f);
1930    }
1931   
 
1932  1732 toggle private void fillArc(Graphics g, int a, int b, int c, int d, int e, int f)
1933    {
1934  1732 setAntialias(g);
1935  1732 g.fillArc(a, b, c, d, e, f);
1936    }
1937   
 
1938  512 toggle private void drawLine(Graphics g, Stroke s, int a, int b, int c, int d)
1939    {
1940  512 Graphics2D g2d = (Graphics2D) g;
1941  512 Stroke p = g2d.getStroke();
1942  512 g2d.setStroke(s);
1943  512 drawLine(g, a, b, c, d);
1944  512 g2d.setStroke(p);
1945    }
1946   
 
1947  24045 toggle private void drawLine(Graphics g, int a, int b, int c, int d)
1948    {
1949  24045 setAntialias(g);
1950  24045 g.drawLine(a, b, c, d);
1951    }
1952   
 
1953  394081 toggle private void setAntialias(Graphics g)
1954    {
1955  394081 if (isVectorRendering())
1956    {
1957    // no need to antialias vector drawings
1958  6650 return;
1959    }
1960  387430 if (Cache.getDefault("ANTI_ALIAS", true))
1961    {
1962  48078 Graphics2D g2d = (Graphics2D) g;
1963  48079 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1964    RenderingHints.VALUE_ANTIALIAS_ON);
1965    }
1966    }
1967   
 
1968  1123630 toggle private void unsetAntialias(Graphics g)
1969    {
1970  1123630 if (isVectorRendering())
1971    {
1972    // no need to antialias vector drawings
1973  17584 return;
1974    }
1975  1106046 Graphics2D g2d = (Graphics2D) g;
1976  1106046 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1977    RenderingHints.VALUE_ANTIALIAS_OFF);
1978    }
1979   
 
1980  14 toggle public void setVectorRendering(boolean b)
1981    {
1982  14 renderingVectors = b;
1983    }
1984   
 
1985  1563629 toggle public boolean isVectorRendering()
1986    {
1987  1563628 return renderingVectors;
1988    }
1989    }