001package com.hfg.graphics;
002
003
004import java.awt.*;
005import java.awt.Frame;
006import java.awt.font.FontRenderContext;
007import java.awt.font.TextLayout;
008import java.awt.image.BufferedImage;
009import java.awt.geom.AffineTransform;
010import java.io.*;
011import java.util.*;
012import java.util.List;
013import javax.imageio.ImageIO;
014
015
016import com.hfg.bio.Strand;
017import com.hfg.html.*;
018import com.hfg.html.attribute.Shape;
019import com.hfg.javascript.PopupMenuJS;
020import com.hfg.javascript.TooltipJS;
021import com.hfg.math.NumUtil;
022import com.hfg.svg.SVG;
023import com.hfg.util.collection.CollectionUtil;
024import com.hfg.image.ImageIO_Util;
025
026
027//------------------------------------------------------------------------------
028/**
029 * An evidence Track of data to display on genomic coordinates.
030 *
031 * @author J. Alex Taylor, hairyfatguy.com
032 */
033//------------------------------------------------------------------------------
034// com.hfg XML/HTML Coding Library
035//
036// This library is free software; you can redistribute it and/or
037// modify it under the terms of the GNU Lesser General Public
038// License as published by the Free Software Foundation; either
039// version 2.1 of the License, or (at your option) any later version.
040//
041// This library is distributed in the hope that it will be useful,
042// but WITHOUT ANY WARRANTY; without even the implied warranty of
043// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
044// Lesser General Public License for more details.
045//
046// You should have received a copy of the GNU Lesser General Public
047// License along with this library; if not, write to the Free Software
048// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
049//
050// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
051// jataylor@hairyfatguy.com
052//------------------------------------------------------------------------------
053// TODO:
054//  - Vertically center in the track.
055
056public class Track extends Rectangle implements Comparable<Track>
057{
058
059   //##########################################################################
060   // PRIVATE FIELDS
061   //##########################################################################
062
063   private String mName;
064   private String mDescription;
065   private String mURL;
066   private List<Gene2D> mForwardGenes;
067   private List<Gene2D> mReverseGenes;
068   private List<HighlightRegion>   mHighlightRegions;
069   private Boolean mShowId;
070   private boolean mShowSpeciesInTooltip = true;
071   private boolean mShowLocationInTooltip = true;
072
073   private Color  mBackgroundColor = Color.white;
074//   private Color  mForwardStrandColor = Color.blue;
075//   private Color  mReverseStrandColor = new Color(150, 150, 255);
076   private Color  mAxisColor  = Color.gray;
077   private Color  mLabelColor = Color.black;
078   private Color  mHighlightColor = new Color(150, 250, 150);
079//   private Font    mLabelFont = new Font("Monospaced", Font.PLAIN, 9);
080   private Font    mLabelFont = sDefaultLabelFont;
081
082   private int    mXAxisStartValue;
083   private int    mXAxisEndValue;
084   private double mXScalingFactor;
085   private Map<Gene2D, String>    mForwardLineMap;
086   private Map<Gene2D, String>    mReverseLineMap;
087   private int    mNumForwardLines;
088   private int    mNumReverseLines;
089   private List<Integer> mForwardLineHeights;
090   private List<Integer> mReverseLineHeights;
091
092   private int    mImageWidthInPixels = 700;
093   private int    mForwardImageHeightInPixels;
094   private int    mReverseImageHeightInPixels;
095   private int    mLineHeightInPixels = 10;
096   private int    mLinePaddingInPixels = 3;
097   private int    mDescriptionWidthInPixels = 120;
098   private int    mMinimumBufferInPixels = 3;
099   private int    mRightBufferInPixels = 60;
100
101   private int    mXAxisImageHeightInPixels;
102
103
104   // Calculated
105   private int    mMajorTickStep;
106   private int    mMinorTickStep;
107
108   private ImageMap mForwardImageMap = new ImageMap();
109   private ImageMap mReverseImageMap = new ImageMap();
110   private Script   mJavascript = new Script();
111
112   private static FontRenderContext sFRC;
113   private static Font sDefaultLabelFont = Font.decode("Courier-PLAIN-9");
114   static
115   {
116      Frame frame = new Frame();
117      frame.addNotify();
118      Image image =frame.createImage(1, 1);
119      sFRC = ((Graphics2D) image.getGraphics()).getFontRenderContext();
120   }
121
122
123   //##########################################################################
124   // CONSTRUCTORS
125   //##########################################################################
126
127   //--------------------------------------------------------------------------
128   public Track()
129   {
130   }
131
132
133   //--------------------------------------------------------------------------
134   public Track(String inName)
135   {
136      this();
137      mName = inName;
138   }
139
140
141   //##########################################################################
142   // PUBLIC METHODS
143   //##########################################################################
144
145
146   //--------------------------------------------------------------------------
147   @Override
148   public boolean equals(Object o2)
149   {
150      return (o2 instanceof Track && compareTo((Track)o2) == 0 ? true : false);
151   }
152
153   //--------------------------------------------------------------------------
154   public int compareTo(Track inTrack2)
155   {
156      int returnValue = 0;
157
158      if (this != inTrack2)
159      {
160         returnValue = this.getName().compareTo(inTrack2.getName());
161      }
162
163      return returnValue;
164   }
165
166
167   //--------------------------------------------------------------------------
168   public boolean hasGenesOnStrand(Strand inStrand)
169   {
170      return areAnyGenesInDisplayRegion(Strand.FORWARD == inStrand ? mForwardGenes : mReverseGenes);
171   }
172
173
174   //--------------------------------------------------------------------------
175   public void addHighlightRegion(int start, int end)
176   {
177      if (null == mHighlightRegions)
178      {
179         mHighlightRegions = new ArrayList<HighlightRegion>();
180      }
181
182      mHighlightRegions.add(new HighlightRegion(start, end));
183   }
184
185   //--------------------------------------------------------------------------
186   public void setGenes(Collection<Gene2D> inValues)
187   {
188      if (mForwardGenes != null) mForwardGenes.clear();
189      if (mReverseGenes != null) mReverseGenes.clear();
190      if (CollectionUtil.hasValues(inValues))
191      {
192         for (Gene2D gene : inValues)
193         {
194            addGene(gene);
195         }
196      }
197   }
198
199   //--------------------------------------------------------------------------
200   public void addGene(Gene2D inValue)
201   {
202      if (inValue.getStrand() == Strand.FORWARD
203          || (null == inValue.getStrand()
204              && inValue.getEndLocation() > inValue.getStartLocation()))
205      {
206         if (null == mForwardGenes)
207         {
208            mForwardGenes = new ArrayList<Gene2D>();
209         }
210
211         mForwardGenes.add(inValue);
212      }
213      else
214      {
215         if (null == mReverseGenes)
216         {
217            mReverseGenes = new ArrayList<Gene2D>();
218         }
219
220         mReverseGenes.add(inValue);
221      }
222   }
223
224   //--------------------------------------------------------------------------
225   public Collection<Gene2D> getGenes()
226   {
227      Collection<Gene2D> genes = new ArrayList<Gene2D>();
228      if (CollectionUtil.hasValues(mForwardGenes)) genes.addAll(mForwardGenes);
229      if (CollectionUtil.hasValues(mReverseGenes)) genes.addAll(mReverseGenes);
230
231      return genes;
232   }
233
234   //--------------------------------------------------------------------------
235   public void setName(String inValue)
236   {
237      mName = inValue;
238   }
239
240   //--------------------------------------------------------------------------
241   public String getName()
242   {
243      return mName;
244   }
245
246
247   //--------------------------------------------------------------------------
248   public void setURL(String inValue)
249   {
250      mURL = inValue;
251   }
252
253   //--------------------------------------------------------------------------
254   public void setDescription(String inValue)
255   {
256      mDescription = inValue;
257   }
258
259   //--------------------------------------------------------------------------
260   public String getDescription()
261   {
262      return mDescription;
263   }
264
265   //--------------------------------------------------------------------------
266   public void setBackgroundColor(Color inValue)
267   {
268      mBackgroundColor = inValue;
269   }
270
271   //--------------------------------------------------------------------------
272   public Color getBackgroundColor()
273   {
274      return mBackgroundColor;
275   }
276/*
277   //--------------------------------------------------------------------------
278   public void setForwardStrandColor(Color inValue)
279   {
280      mForwardStrandColor = inValue;
281   }
282
283   //--------------------------------------------------------------------------
284   public Color getForwardStrandColor()
285   {
286      return mForwardStrandColor;
287   }
288
289   //--------------------------------------------------------------------------
290   public void setReverseStrandColor(Color inValue)
291   {
292      mReverseStrandColor = inValue;
293   }
294
295   //--------------------------------------------------------------------------
296   public Color getReverseStrandColor()
297   {
298      return mReverseStrandColor;
299   }
300*/
301   //--------------------------------------------------------------------------
302   public void setLabelColor(Color inValue)
303   {
304      mLabelColor = inValue;
305   }
306
307   //--------------------------------------------------------------------------
308   public Color getLabelColor()
309   {
310      return mLabelColor;
311   }
312
313   //--------------------------------------------------------------------------
314   public void setAxisColor(Color inValue)
315   {
316      mAxisColor = inValue;
317   }
318
319   //--------------------------------------------------------------------------
320   public Color getAxisColor()
321   {
322      return mLabelColor;
323   }
324
325   //--------------------------------------------------------------------------
326   public void setHighlightColor(Color inValue)
327   {
328      mHighlightColor = inValue;
329   }
330
331   //--------------------------------------------------------------------------
332   public Color getHighlightColor()
333   {
334      return mHighlightColor;
335   }
336
337   //--------------------------------------------------------------------------
338   public void setLabelFont(Font inValue)
339   {
340      mLabelFont = inValue;
341   }
342
343   //--------------------------------------------------------------------------
344   public void setXAxisStartValue(int inValue)
345   {
346      mXAxisStartValue = inValue;
347   }
348
349   //--------------------------------------------------------------------------
350   public void setXAxisEndValue(int inValue)
351   {
352      mXAxisEndValue = inValue;
353   }
354
355   //--------------------------------------------------------------------------
356   public int getForwardStrandImageHeight()
357   {
358      return mForwardImageHeightInPixels;
359   }
360
361   //--------------------------------------------------------------------------
362   public int getReverseStrandImageHeight()
363   {
364      return mReverseImageHeightInPixels;
365   }
366
367   //--------------------------------------------------------------------------
368   public void setImageWidth(int inValue)
369   {
370      mImageWidthInPixels = inValue;
371   }
372
373   //--------------------------------------------------------------------------
374   public int getImageWidth()
375   {
376      return mImageWidthInPixels;
377   }
378
379   //--------------------------------------------------------------------------
380   public Script getJavascript()
381   {
382      return mJavascript;
383   }
384
385   //--------------------------------------------------------------------------
386   public ImageMap getImageMap(Strand inStrand)
387   {
388      return (inStrand == Strand.FORWARD ? mForwardImageMap : mReverseImageMap);
389   }
390
391   //--------------------------------------------------------------------------
392   public void setShowId(boolean inValue)
393   {
394      mShowId = inValue;
395   }
396
397   //--------------------------------------------------------------------------
398   public void draw(Graphics2D g2, Strand inStrand)
399   {
400      if (inStrand == Strand.FORWARD) mForwardImageMap = new ImageMap();
401      else                            mReverseImageMap = new ImageMap();
402      // TODO: Split mJavascript into forward and reverse?
403
404      // Save settings
405      Paint origPaint = g2.getPaint();
406      Font  origFont  = g2.getFont();
407
408      // Save inital location.
409      AffineTransform origTransform = g2.getTransform();
410
411      // Enable anti-aliasing
412//      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
413//                          RenderingHints.VALUE_ANTIALIAS_ON);
414
415
416
417      int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
418                                                              mReverseImageHeightInPixels);
419
420      // Paint the background
421      setSize(mImageWidthInPixels, imageHeightInPixels);
422      g2.setPaint(getBackgroundColor());
423      g2.fill(this.getBounds());
424
425      // Draw the line separating the description from the alignment
426      g2.setPaint(mAxisColor);
427//      g2.drawLine(mDescriptionWidthInPixels - mMinimumBufferInPixels, 0,
428//                  mDescriptionWidthInPixels - mMinimumBufferInPixels, imageHeightInPixels - 2);
429      g2.fillRect(mDescriptionWidthInPixels - (2 * mMinimumBufferInPixels), 0,
430                  mMinimumBufferInPixels, imageHeightInPixels - 2);
431
432
433      // Write the Track name
434      g2.setPaint(mLabelColor);
435      g2.setFont(mLabelFont);
436      FontRenderContext frc = g2.getFontRenderContext();
437      TextLayout layout = new TextLayout(mName, mLabelFont, frc);
438      Rectangle textBounds = (g2 instanceof SvgGraphics2D ? TextUtil.getStringRect(mName, mLabelFont) : layout.getBounds().getBounds());
439      int xOffset = (int) (mDescriptionWidthInPixels - textBounds.getWidth()) -  2 * mLinePaddingInPixels - mMinimumBufferInPixels;
440      int yOffset = (int) (imageHeightInPixels/2 + textBounds.getHeight()/2);
441
442      g2.drawString(mName, xOffset, yOffset);
443
444
445      if (mURL != null)
446      {
447         // Set image map data
448         ImageMap imageMap = (inStrand == Strand.FORWARD ? mForwardImageMap : mReverseImageMap);
449         Area area = imageMap.addArea();
450         area.setHref(mURL);
451         area.setShape(Shape.RECT);
452
453         area.setCoords(xOffset + "," + (yOffset - textBounds.getHeight()) + ","
454                        + (xOffset + textBounds.getWidth()) + "," + yOffset);
455      }
456
457
458
459      drawHighlightRegions(g2, imageHeightInPixels);
460
461      drawGenes(g2, inStrand);
462
463
464
465      // Return to orig. location
466      g2.setTransform(origTransform);
467
468      // Restore settings
469      g2.setPaint(origPaint);
470      g2.setFont(origFont);
471   }
472
473
474   //--------------------------------------------------------------------------
475   public Image getImage(Strand inStrand)
476   {
477      initialize(inStrand);
478
479      int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
480                                                              mReverseImageHeightInPixels);
481
482      Frame frame = new Frame();
483      frame.addNotify();
484      Image image = frame.createImage(mImageWidthInPixels, imageHeightInPixels);
485
486      Graphics2D g2 = (Graphics2D) image.getGraphics();
487      draw(g2, inStrand);
488
489      return image;
490   }
491
492   //--------------------------------------------------------------------------
493   public BufferedImage getBufferedImage(Strand inStrand)
494   {
495      initialize(inStrand);
496
497      int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
498            mReverseImageHeightInPixels);
499
500      Frame frame = new Frame();
501      frame.addNotify();
502      BufferedImage bufferedImage = new BufferedImage(mImageWidthInPixels,
503                                                      imageHeightInPixels,
504                                                      BufferedImage.TYPE_INT_RGB);
505
506      Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics();
507      draw(g2, inStrand);
508
509      return bufferedImage;
510   }
511
512   //--------------------------------------------------------------------------
513   public BufferedImage getXAxisBufferedImage()
514   {
515      Frame frame = new Frame();
516      frame.addNotify();
517      BufferedImage bufferedImage = new BufferedImage(mImageWidthInPixels,
518                                                      mXAxisImageHeightInPixels,
519                                                      BufferedImage.TYPE_INT_RGB);
520
521      Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics();
522      drawXAxis(g2);
523
524      return bufferedImage;
525   }
526
527   //--------------------------------------------------------------------------
528   public void writeImageAsJpeg(OutputStream inStream, Strand inStrand)
529   throws IOException
530   {
531      BufferedImage bufferedImage = getBufferedImage(inStrand);
532
533      ImageIO_Util.writeBufferedImageAsJpeg(bufferedImage, inStream);
534   }
535
536   //--------------------------------------------------------------------------
537   public void writeXAxisImageAsJpeg(OutputStream inStream)
538   throws IOException
539   {
540      mXAxisImageHeightInPixels = getLabelWidthInPixels(mXAxisEndValue + "") + 7;
541
542      BufferedImage bufferedImage = getXAxisBufferedImage();
543
544      ImageIO_Util.writeBufferedImageAsJpeg(bufferedImage, inStream);
545   }
546
547   //--------------------------------------------------------------------------
548   public void writeImageAsPng(OutputStream inStream, Strand inStrand)
549   throws IOException
550   {
551      BufferedImage bufferedImage = getBufferedImage(inStrand);
552      ImageIO.write(bufferedImage, "png", inStream);
553   }
554
555   //--------------------------------------------------------------------------
556   public SVG writeImageAsSvg(Strand inStrand)
557   {
558      if (mLabelFont == sDefaultLabelFont)
559      {
560         setLabelFont(Font.decode("Courier-PLAIN-8"));
561      }
562
563      initialize(inStrand);
564
565      int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
566            mReverseImageHeightInPixels);
567
568      SvgGraphics2D g2 = new SvgGraphics2D();
569      draw(g2, inStrand);
570      SVG svgTag = g2.getRootTag().setWidth(mImageWidthInPixels).setHeight(imageHeightInPixels);
571      svgTag.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing
572      return svgTag;
573   }
574
575
576   //##########################################################################
577   // PRIVATE METHODS
578   //##########################################################################
579
580   //--------------------------------------------------------------------------
581   private void initialize(Strand inStrand)
582   {
583      List<Gene2D> genes = (inStrand == Strand.FORWARD ? mForwardGenes : mReverseGenes);
584
585      if (genes != null)
586      {
587         for (Gene2D gene : genes)
588         {
589            initializeGene(gene);
590         }
591      }
592
593      // Figure out how many lines high the image will be and set the image height.
594      createLineMap(inStrand);
595   }
596
597   //--------------------------------------------------------------------------
598   private void initializeGene(Gene2D inGene)
599   {
600      inGene.setDisplayRegion(mXAxisStartValue, mXAxisEndValue);
601      inGene.setXScalingFactor(getXScalingFactor());
602//      inGene.setForwardStrandColor(getForwardStrandColor());
603//      inGene.setReverseStrandColor(getReverseStrandColor());
604
605      if (mLabelFont != null)
606      {
607         inGene.setLabelFont(mLabelFont);
608      }
609
610      if (mShowId != null)
611      {
612         inGene.setShowId(mShowId);
613      }
614   }
615
616   //--------------------------------------------------------------------------
617   private boolean areAnyGenesInDisplayRegion(List<Gene2D> inGenes)
618   {
619      boolean result = false;
620      if (inGenes != null)
621      {
622         for (Gene2D gene : inGenes)
623         {
624            gene.setDisplayRegion(mXAxisStartValue, mXAxisEndValue);
625            if (gene.inDisplayRegion())
626            {
627               result = true;
628               break;
629            }
630         }
631      }
632
633      return result;
634   }
635
636   //--------------------------------------------------------------------------
637   private double getXScalingFactor()
638   {
639      if (0 == mXScalingFactor)
640      {
641         mXScalingFactor = (double)(mImageWidthInPixels - mDescriptionWidthInPixels - mRightBufferInPixels)
642                                    /(mXAxisEndValue - mXAxisStartValue + 1);
643      }
644
645      return mXScalingFactor;
646   }
647
648   //--------------------------------------------------------------------------
649   private int getScaledPosition(int inPosition)
650   {
651      int xOffset = (int) ((inPosition - mXAxisStartValue) * getXScalingFactor());
652
653      return xOffset;
654   }
655
656   //--------------------------------------------------------------------------
657   private Gene2D getLastGeneForLevel(int inLevel, List<List<Gene2D>> inLevelLists)
658   {
659      Gene2D outGene = null;
660
661      if (inLevelLists.size() >= inLevel)
662      {
663         List<Gene2D> levelList = inLevelLists.get(inLevel - 1);
664         outGene = levelList.get(levelList.size() - 1);
665      }
666
667      return outGene;
668   }
669
670   //--------------------------------------------------------------------------
671   private void addGeneToLevel(Gene2D inGene, int inLevel, List<List<Gene2D>> inLevelLists)
672   {
673      List<Gene2D> levelList = null;
674
675      if (inLevelLists.size() < inLevel)
676      {
677         levelList = new ArrayList<Gene2D>();
678         inLevelLists.add(levelList);
679      }
680      else
681      {
682         levelList = inLevelLists.get(inLevel - 1);
683      }
684
685      levelList.add(inGene);
686   }
687
688
689   //--------------------------------------------------------------------------
690   private boolean genesAreTooCloseTogether(Gene2D inGene1, Gene2D inGene2)
691   {
692      boolean resultValue = false;
693
694      if (getScaledPosition(inGene1.getRight()) + (mShowId && inGene1.getShowId() ? inGene1.getLabelWidthInPixels() : 0)
695          + mMinimumBufferInPixels > getScaledPosition(inGene2.getLeft()))
696      {
697         resultValue = true;
698      }
699
700      return resultValue;
701   }
702
703   //--------------------------------------------------------------------------
704   private void createLineMap(Strand inStrand)
705   {
706      Map<Gene2D, String> lineMap = null;
707      List<List<Gene2D>> levelLists = new ArrayList<>();
708
709      List<Gene2D> genes = (inStrand == Strand.FORWARD ? mForwardGenes : mReverseGenes);
710
711      if (genes != null)
712      {
713         Collections.sort(genes);
714
715         lineMap = new HashMap<>();
716
717         for (Gene2D gene : genes)
718         {
719            if (gene.inDisplayRegion())
720            {
721               int level = 1;
722               boolean assigningLevel = true;
723               while (assigningLevel)
724               {
725                  Gene2D lastGene = getLastGeneForLevel(level, levelLists);
726                  if (lastGene != null
727                        && genesAreTooCloseTogether(lastGene, gene))
728                  {
729                     level++;
730                  }
731                  else
732                  {
733                     addGeneToLevel(gene, level, levelLists);
734                     lineMap.put(gene, level + "");
735                     assigningLevel = false;
736                  }
737               }
738            }
739
740         }
741      }
742
743      if (inStrand == Strand.FORWARD)
744      {
745         mForwardLineMap = lineMap;
746         mNumForwardLines = levelLists.size();
747         mForwardLineHeights = determineLineHeights(levelLists);
748//         mForwardImageHeightInPixels = mNumForwardLines * (mLineHeightInPixels + mLinePaddingInPixels);
749         mForwardImageHeightInPixels = (int) NumUtil.sum(mForwardLineHeights) + mForwardLineHeights.size() * mLinePaddingInPixels;
750      }
751      else
752      {
753         mReverseLineMap = lineMap;
754         mNumReverseLines = levelLists.size();
755         mReverseLineHeights = determineLineHeights(levelLists);
756//         mReverseImageHeightInPixels = mNumReverseLines * (mLineHeightInPixels + mLinePaddingInPixels);
757         mReverseImageHeightInPixels = (int) NumUtil.sum(mReverseLineHeights) + mReverseLineHeights.size() * mLinePaddingInPixels;
758      }
759
760   }
761
762   //--------------------------------------------------------------------------
763   private List<Integer> determineLineHeights(List<List<Gene2D>> inLevelLists)
764   {
765      List<Integer> lineHeights = new ArrayList<>(inLevelLists.size());
766
767      for (List<Gene2D> level : inLevelLists)
768      {
769         double maxLineHeight = 0;
770         for (Gene2D gene : level)
771         {
772            if (gene.getHeightInPixels() > maxLineHeight) maxLineHeight = gene.getHeightInPixels();
773         }
774
775         lineHeights.add((int)maxLineHeight);
776      }
777
778
779      return lineHeights;
780   }
781
782   //--------------------------------------------------------------------------
783   private int getYOffsetForLine(int inLine, Strand inStrand)
784   {
785      int yOffset = 0;
786
787      List<Integer> lineHeights = (inStrand == Strand.FORWARD ? mForwardLineHeights : mReverseLineHeights);
788      for (int i = 0; i < inLine; i++)
789      {
790         yOffset -= lineHeights.get(i);
791      }
792
793      yOffset -= inLine * mLinePaddingInPixels;
794
795      return yOffset;
796   }
797
798   //--------------------------------------------------------------------------
799   private void drawHighlightRegions(Graphics2D g2, int inImageHeight)
800   {
801      if (mHighlightRegions != null)
802      {
803         for (HighlightRegion region : mHighlightRegions)
804         {
805            int xOffset = mDescriptionWidthInPixels + (int)((region.getStart() - mXAxisStartValue) * getXScalingFactor());
806            int width = (int) ((region.getEnd() - region.getStart() - 1) * getXScalingFactor());
807            if (width < 1) width = 1;
808            g2.setPaint(mHighlightColor);
809
810            g2.fillRect(xOffset, 0, width, inImageHeight);
811         }
812
813      }
814   }
815
816   //--------------------------------------------------------------------------
817   private void drawGenes(Graphics2D g2, Strand inStrand)
818   {
819      TooltipJS tooltip = new TooltipJS();
820
821      int imageHeightInPixels;
822      List<Gene2D> genes;
823      Map<Gene2D, String> lineMap;
824      ImageMap imageMap;
825      if (inStrand == Strand.FORWARD)
826      {
827         imageHeightInPixels = mForwardImageHeightInPixels;
828         genes = mForwardGenes;
829         lineMap = mForwardLineMap;
830         imageMap = mForwardImageMap;
831      }
832      else
833      {
834         imageHeightInPixels = mReverseImageHeightInPixels;
835         genes = mReverseGenes;
836         lineMap = mReverseLineMap;
837         imageMap = mReverseImageMap;
838      }
839
840      // Move to the bottom left of the genomic display
841      g2.translate(mDescriptionWidthInPixels, imageHeightInPixels);
842
843      if (genes != null)
844      {
845         // Save inital location.
846         AffineTransform origTransform = g2.getTransform();
847
848         for (Gene2D gene : genes)
849         {
850            if (gene.inDisplayRegion())
851            {
852               // Move to gene location
853
854               // What line is the gene on? Move to upper left of gene.
855               int line = Integer.parseInt(lineMap.get(gene));
856               if (inStrand == Strand.REVERSE)
857               {
858                  line = mNumReverseLines - line + 1;
859               }
860
861               //XXXX           int xOffset = (int) ((gene.getStartLocation() - mXAxisStartValue) * getXScalingFactor());
862               int geneLeft = (int) (gene.getLeft() * mXScalingFactor);
863               int bound = (int) (mXAxisStartValue * mXScalingFactor);
864               int xOffset = geneLeft - bound;
865//               int xOffset = (int) ((gene.getLeft() - mXAxisStartValue) * getXScalingFactor());
866               // Don't back up into the description region.
867               if (xOffset < 0) xOffset = 0;
868        /*       // Don't go off the right side of the display region.
869               if (xOffset > mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels)
870               {
871                  xOffset = mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels;
872               }
873        */
874
875//               int yOffset = -line * (mLineHeightInPixels + mLinePaddingInPixels);
876               int yOffset = getYOffsetForLine(line, inStrand);
877
878               g2.translate(xOffset, yOffset);
879
880               gene.draw(g2);
881
882               // Return to orig. location
883               g2.setTransform(origTransform);
884
885               // Set image map data
886               Area area = imageMap.addArea();
887               setAreaURL(area, gene);
888
889               tooltip.addTooltip(area, gene.getTooltipContent());
890               area.setShape(Shape.RECT);
891
892               int x1 = mDescriptionWidthInPixels + xOffset;
893               if (x1 < mDescriptionWidthInPixels) x1 = mDescriptionWidthInPixels;
894               int x2 = mDescriptionWidthInPixels + xOffset + gene.getScaledWidthInPixels()
895                        + (mShowId ? gene.getLabelWidthInPixels() : 0);
896               if (x2 > mImageWidthInPixels)
897               {
898                  x2 = mImageWidthInPixels;
899               }
900               area.setCoords(x1 + "," + (imageHeightInPixels + yOffset) + ","
901                              + x2 + "," + (imageHeightInPixels + yOffset + gene.getHeightInPixels() - 1));
902            }
903         }
904      }
905   }
906
907   //--------------------------------------------------------------------------
908   private void setAreaURL(Area inArea, Gene2D inGene)
909   {
910      List<Link> urls = inGene.getURLs();
911      if (CollectionUtil.hasValues(urls))
912      {
913         if (1 == urls.size())
914         {
915            Link link = urls.get(0);
916            inArea.setHref(link.getURL());
917         }
918         else
919         {
920            PopupMenuJS menu = new PopupMenuJS();
921            menu.setMenuTitle(inGene.getId());
922            for (Link link : urls)
923            {
924               menu.addLink(link);
925            }
926
927             // Add the menu to the track's javascript
928            mJavascript.addContent(menu.generateMenuJavascript());
929
930            inArea.setHref(menu.getDisplayMenuJavascript());
931         }
932      }
933   }
934
935   //--------------------------------------------------------------------------
936   public void drawXAxis(Graphics2D g2)
937   {
938      // Save settings
939      Paint origPaint = g2.getPaint();
940      Font  origFont  = g2.getFont();
941
942      // Save inital location.
943      AffineTransform origTransform = g2.getTransform();
944
945      // Enable anti-aliasing
946      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
947                          RenderingHints.VALUE_ANTIALIAS_ON);
948
949      // Paint the background
950      g2.setPaint(Color.white);
951      g2.fillRect(0, 0, mImageWidthInPixels, mXAxisImageHeightInPixels);
952
953
954      drawHighlightRegions(g2, mXAxisImageHeightInPixels);
955
956      // Draw the x-axis line
957      g2.setPaint(mAxisColor);
958      g2.drawLine(mDescriptionWidthInPixels, 0,
959                  mImageWidthInPixels - mRightBufferInPixels, 0);
960
961      determineTickSize();
962      double lastXTranslation = 0;
963      int yOffset = 2;
964
965      for (Integer tickValue : getTickValues())
966      {
967         if (tickValue%mMajorTickStep == 0
968             || tickValue == mXAxisStartValue
969             || tickValue == mXAxisEndValue)
970         {
971            yOffset = 4;
972
973            g2.setFont(mLabelFont);
974            FontRenderContext frc = g2.getFontRenderContext();
975            TextLayout layout = new TextLayout(tickValue + "", mLabelFont, frc);
976            double xTranslation = mDescriptionWidthInPixels
977                               + ((tickValue - mXAxisStartValue) * getXScalingFactor())
978                               + layout.getBounds().getHeight() / 2;
979
980            if (xTranslation - lastXTranslation > 8
981                && (tickValue == mXAxisEndValue || xTranslation < mImageWidthInPixels - mRightBufferInPixels - 10))
982            {
983               double yTranslation = yOffset + layout.getAdvance() + 1;
984               g2.translate(xTranslation, yTranslation);
985               g2.rotate(-Math.PI / 2);
986               g2.drawString(tickValue + "", 0, 0);
987               g2.setTransform(origTransform);
988               lastXTranslation = xTranslation;
989            }
990         }
991         else if (tickValue%mMinorTickStep == 0)
992         {
993            yOffset = 2;
994         }
995         else
996         {
997            continue;
998         }
999
1000         int xOffset = mDescriptionWidthInPixels
1001                       + (int) ((tickValue - mXAxisStartValue) * getXScalingFactor());
1002         g2.drawLine(xOffset, 0,
1003                     xOffset, yOffset);
1004      }
1005
1006
1007      // Return to orig. location
1008      g2.setTransform(origTransform);
1009
1010      // Restore settings
1011      g2.setPaint(origPaint);
1012      g2.setFont(origFont);
1013   }
1014
1015   //--------------------------------------------------------------------------
1016   private void determineTickSize()
1017   {
1018      int xAxisWidth = mXAxisEndValue - mXAxisStartValue + 1;
1019      String widthString = xAxisWidth + "";
1020      mMajorTickStep = (int) Math.pow(10, widthString.length() - 1);
1021      mMinorTickStep = mMajorTickStep / 10;
1022   }
1023
1024   //--------------------------------------------------------------------------
1025   private List<Integer> getTickValues()
1026   {
1027      List<Integer> tickValues = new ArrayList<>();
1028
1029      // First add the major ticks
1030      tickValues.add(mXAxisStartValue);
1031
1032      for (int tickValue = (int) (Math.ceil(mXAxisStartValue / mMajorTickStep) * mMajorTickStep);
1033           tickValue < mXAxisEndValue;
1034           tickValue += mMajorTickStep)
1035      {
1036         if (tickValue > mXAxisStartValue)
1037         {
1038            tickValues.add(tickValue);
1039         }
1040      }
1041
1042      tickValues.add(mXAxisEndValue);
1043
1044      // Now add the minor ticks
1045      for (int tickValue = (int) (Math.ceil(mXAxisStartValue / mMinorTickStep) * mMinorTickStep);
1046           tickValue < mXAxisEndValue;
1047           tickValue += mMinorTickStep)
1048      {
1049         if (tickValue%mMajorTickStep != 0
1050             && tickValue != mXAxisStartValue)
1051         {
1052            tickValues.add(tickValue);
1053         }
1054      }
1055
1056      return tickValues;
1057   }
1058
1059   //--------------------------------------------------------------------------
1060   public int getLabelWidthInPixels(String inLabel)
1061   {
1062      int widthInPixels = 0;
1063
1064      if (inLabel != null
1065            && inLabel.length() > 0)
1066      {
1067         TextLayout layout = new TextLayout(inLabel, mLabelFont, sFRC);
1068         widthInPixels = (int) layout.getBounds().getWidth() + 1;
1069      }
1070
1071      return widthInPixels;
1072   }
1073
1074
1075
1076
1077   //--------------------------------------------------------------------------
1078   private class HighlightRegion
1079   {
1080      private int mStart;
1081      private int mEnd;
1082
1083      //-----------------------------------------------------------------------
1084      public HighlightRegion(int inStart, int inEnd)
1085      {
1086         if (inEnd < inStart)
1087         {
1088            mStart = inEnd;
1089            mEnd = inStart;
1090         }
1091         else
1092         {
1093            mStart = inStart;
1094            mEnd = inEnd;
1095         }
1096      }
1097
1098      //-----------------------------------------------------------------------
1099      public int getStart()
1100      {
1101         return mStart;
1102      }
1103
1104      //-----------------------------------------------------------------------
1105      public int getEnd()
1106      {
1107         return mEnd;
1108      }
1109   }
1110
1111}
1112