001package com.hfg.graphics;
002
003
004import java.awt.Color;
005import java.awt.Dimension;
006import java.awt.Font;
007import java.awt.Frame;
008import java.awt.Graphics2D;
009import java.awt.Image;
010import java.awt.Paint;
011import java.awt.Point;
012import java.awt.Rectangle;
013import java.awt.font.FontRenderContext;
014import java.awt.font.TextLayout;
015import java.util.ArrayList;
016import java.util.List;
017
018import com.hfg.bio.seq.Exon;
019import com.hfg.bio.Strand;
020import com.hfg.bio.seq.SeqMappingCoverage;
021import com.hfg.bio.taxonomy.ncbi.NCBITaxon;
022import com.hfg.html.Link;
023import com.hfg.svg.SvgNode;
024import com.hfg.svg.SvgGroup;
025import com.hfg.svg.SvgRect;
026import com.hfg.svg.SvgText;
027import com.hfg.util.collection.CollectionUtil;
028import com.hfg.util.StringBuilderPlus;
029import com.hfg.javascript.TooltipJS;
030
031//------------------------------------------------------------------------------
032/**
033 Simple 2D representation of a gene.
034 <div>
035  @author J. Alex Taylor, hairyfatguy.com
036 </div>
037 */
038//------------------------------------------------------------------------------
039// com.hfg XML/HTML Coding Library
040//
041// This library is free software; you can redistribute it and/or
042// modify it under the terms of the GNU Lesser General Public
043// License as published by the Free Software Foundation; either
044// version 2.1 of the License, or (at your option) any later version.
045//
046// This library is distributed in the hope that it will be useful,
047// but WITHOUT ANY WARRANTY; without even the implied warranty of
048// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
049// Lesser General Public License for more details.
050//
051// You should have received a copy of the GNU Lesser General Public
052// License along with this library; if not, write to the Free Software
053// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
054//
055// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
056// jataylor@hairyfatguy.com
057//------------------------------------------------------------------------------
058
059public class Gene2D extends Rectangle implements Comparable<Gene2D>
060{
061
062   //##########################################################################
063   // PRIVATE FIELDS
064   //##########################################################################
065
066   private String     mId;
067   private String     mDescription;
068   private List<Link> mURLs;
069   private int        mStartLocation;
070   private int        mEndLocation;
071   private Strand     mStrand;
072   private List<Exon> mExons;
073   private NCBITaxon  mSpecies;
074   private SeqMappingCoverage mSeqMappingCoverage;
075
076   private int     mDisplayStart;
077   private int     mDisplayEnd;
078   private boolean mInDisplayRegion;
079
080   private boolean mShowId = true;
081   private int     mLabelWidthInPixels = -1;
082   private Color   mColor;
083//   private Font    mLabelFont = new Font("Monospaced", Font.PLAIN, 9);
084   private Font    mLabelFont = Font.decode("Courier-PLAIN-9");
085
086   private double mXScalingFactor;
087
088   private int    mHeightInPixels = 10;
089
090   private static Color DEFAULT_COLOR = Color.black;
091
092   private static FontRenderContext sFRC;
093
094   static
095   {
096      Frame frame = new Frame();
097      frame.addNotify();
098      Image image =frame.createImage(1, 1);
099      sFRC = ((Graphics2D) image.getGraphics()).getFontRenderContext();
100   }
101
102   //##########################################################################
103   // CONSTRUCTORS
104   //##########################################################################
105
106   //--------------------------------------------------------------------------
107   public Gene2D()
108   {
109
110   }
111
112
113   //##########################################################################
114   // PUBLIC METHODS
115   //##########################################################################
116
117   //--------------------------------------------------------------------------
118   public void addExon(Exon inValue)
119   {
120      if (null == mExons)
121      {
122         mExons = new ArrayList<>();
123      }
124
125      mExons.add(inValue);
126
127      if (mStrand == null && inValue.getStrand() != null)
128      {
129         mStrand = inValue.getStrand();
130      }
131
132      if (mStartLocation == 0
133          || inValue.getLeft() < mStartLocation)
134      {
135         mStartLocation = inValue.getLeft();
136      }
137
138      if (mEndLocation == 0
139          || inValue.getRight() > mEndLocation)
140      {
141         mEndLocation = inValue.getRight();
142      }
143   }
144
145   //--------------------------------------------------------------------------
146   public List<Exon> getExons()
147   {
148      return mExons;
149   }
150
151   //--------------------------------------------------------------------------
152   public void setId(String inValue)
153   {
154      mId = inValue;
155      mLabelWidthInPixels = -1;
156   }
157
158   //--------------------------------------------------------------------------
159   public String getId()
160   {
161      return mId;
162   }
163
164   //--------------------------------------------------------------------------
165   public void setDescription(String inValue)
166   {
167      mDescription = inValue;
168   }
169
170   //--------------------------------------------------------------------------
171   public String getDescription()
172   {
173      return mDescription;
174   }
175
176
177   //--------------------------------------------------------------------------
178   public void setSeqMappingCoverage(SeqMappingCoverage inValue)
179   {
180      mSeqMappingCoverage = inValue;
181   }
182
183   //--------------------------------------------------------------------------
184   public SeqMappingCoverage getSeqMappingCoverage()
185   {
186      return mSeqMappingCoverage;
187   }
188
189   //--------------------------------------------------------------------------
190   public void setSpecies(NCBITaxon inValue)
191   {
192      mSpecies = inValue;
193   }
194
195   //--------------------------------------------------------------------------
196   public NCBITaxon getSpecies()
197   {
198      return mSpecies;
199   }
200
201
202   //--------------------------------------------------------------------------
203   /**
204    Add a URL to the gene. If more than one URL is defined, a popup menu will be
205    shown.
206    @param inValue the link to associate with the gene
207    */
208   public void addURL(Link inValue)
209   {
210      if (null == mURLs)
211      {
212         mURLs = new ArrayList<>(2);
213      }
214
215      mURLs.add(inValue);
216   }
217
218   //--------------------------------------------------------------------------
219   public List<Link> getURLs()
220   {
221      return mURLs;
222   }
223
224
225   //--------------------------------------------------------------------------
226   /**
227    Basepair location of the start of the gene.
228    @return the 1-based location
229    */
230   public int getStartLocation()
231   {
232      return mStartLocation;
233   }
234
235   //--------------------------------------------------------------------------
236   public void setStartLocation(int inValue)
237   {
238      mStartLocation = inValue;
239   }
240
241   //--------------------------------------------------------------------------
242   public int getEndLocation()
243   {
244      return mEndLocation;
245   }
246
247   //--------------------------------------------------------------------------
248   public void setEndLocation(int inValue)
249   {
250      mEndLocation = inValue;
251   }
252
253
254   //--------------------------------------------------------------------------
255   /**
256    The leftmost basepair of the gene (the start location if the gene is on the
257    forward strand or the end location if the gene is on the reverse strand).
258    @return the 1-based left location
259    */
260   public int getLeft()
261   {
262      return Math.min(mStartLocation, mEndLocation);
263   }
264
265   //--------------------------------------------------------------------------
266   /**
267    The rightmost basepair of the gene (the end location if the gene is on the
268    forward strand or the start location if the gene is on the reverse strand).
269    @return the 1-based right location
270    */
271   public int getRight()
272   {
273      return Math.max(mStartLocation, mEndLocation);
274   }
275
276   //--------------------------------------------------------------------------
277   public Strand getStrand()
278   {
279      return mStrand;
280   }
281
282   //--------------------------------------------------------------------------
283   public void setStrand(Strand inValue)
284   {
285      mStrand = inValue;
286   }
287
288   //--------------------------------------------------------------------------
289   public void setColor(Color inValue)
290   {
291      mColor = inValue;
292   }
293
294   //--------------------------------------------------------------------------
295   public Color getColor()
296   {
297      return mColor;
298   }
299
300   //--------------------------------------------------------------------------
301   public void setLabelFont(Font inValue)
302   {
303      mLabelFont = inValue;
304   }
305
306   //--------------------------------------------------------------------------
307   public void setXScalingFactor(double inValue)
308   {
309      mXScalingFactor = inValue;
310   }
311
312   //--------------------------------------------------------------------------
313   public void setShowId(boolean inValue)
314   {
315      mShowId = inValue;
316   }
317
318   //--------------------------------------------------------------------------
319   public boolean getShowId()
320   {
321      return mShowId;
322   }
323
324   //--------------------------------------------------------------------------
325   public void setDisplayRegion(int inDisplayStart, int inDisplayEnd)
326   {
327      mDisplayStart = Math.max(getLeft(), inDisplayStart);
328      mDisplayEnd   = Math.min(getRight(), inDisplayEnd);
329      mInDisplayRegion = (mDisplayEnd >= mDisplayStart ? true : false);
330   }
331
332   //--------------------------------------------------------------------------
333   public boolean inDisplayRegion()
334   {
335      return mInDisplayRegion;
336   }
337
338   //--------------------------------------------------------------------------
339   public SvgNode toSVG()
340   {
341      SvgGroup group = new SvgGroup();
342
343      if (mInDisplayRegion)
344      {
345         Color color = (mColor != null ? mColor : DEFAULT_COLOR);
346
347         // Draw the gene line.
348         int width = getScaledWidthInPixels(mDisplayStart, mDisplayEnd);
349         group.addRect(new Rectangle(new Point(0, mHeightInPixels/2),
350                                               new Dimension(width, 1))).setFill(color);
351
352         if (mShowId && mId != null)
353         {
354            Rectangle textBoundBox = TextUtil.getStringRect(mId, mLabelFont);
355            SvgText label = group.addText(mId, mLabelFont, new Point(width + 1, (int) (mHeightInPixels/2f + textBoundBox.getHeight()/2f) - 2));
356
357            TooltipJS tooltip = new TooltipJS();
358            tooltip.addTooltip(label, getTooltipContent());
359         }
360
361         List<SvgRect> exons = drawExons();
362         if (CollectionUtil.hasValues(exons))
363         {
364            group.addSubtags(exons);
365         }
366
367         TooltipJS tooltip = new TooltipJS();
368         tooltip.addTooltip(group, getTooltipContent());
369      }
370
371      return group;
372   }
373
374   //--------------------------------------------------------------------------
375   public void draw(Graphics2D g2)
376   {
377      if (mInDisplayRegion)
378      {
379         // Save settings
380         Paint origPaint = g2.getPaint();
381         Font  origFont  = g2.getFont();
382
383         Color color = (mColor != null ? mColor : DEFAULT_COLOR);
384         g2.setPaint(color);
385
386         // Draw the gene line.
387//         System.out.println(getId() + ": " + mDisplayStart + "-" + mDisplayEnd);/////////////////////////
388         int width = getScaledWidthInPixels(mDisplayStart, mDisplayEnd);
389         g2.fillRect(0, mHeightInPixels/2, width, 1);
390
391         if (mShowId && mId != null)
392         {
393            FontRenderContext frc = g2.getFontRenderContext();
394            TextLayout layout = new TextLayout(mId, mLabelFont, frc);
395            g2.drawString(mId, width + 1, (int) (mHeightInPixels/2 + layout.getBounds().getHeight()/2));
396         }
397
398         drawExons(g2);
399
400         // Restore settings
401         g2.setPaint(origPaint);
402         g2.setFont(origFont);
403      }
404   }
405
406   //--------------------------------------------------------------------------
407   public int getScaledWidthInPixels()
408   {
409      return getScaledWidthInPixels(getDisplayBoundedValue(getLeft()),
410                                    getDisplayBoundedValue(getRight()));
411   }
412
413
414
415   //--------------------------------------------------------------------------
416   public void setHeightInPixels(int inValue)
417   {
418      mHeightInPixels = inValue;
419   }
420
421   //--------------------------------------------------------------------------
422   public int getHeightInPixels()
423   {
424      return mHeightInPixels;
425   }
426
427
428   //--------------------------------------------------------------------------
429   public int getLabelWidthInPixels()
430   {
431      if (mLabelWidthInPixels == -1)
432      {
433         if (mId != null
434             && mId.length() > 0)
435         {
436            TextLayout layout = new TextLayout(mId, mLabelFont, sFRC);
437            mLabelWidthInPixels = (int) layout.getBounds().getWidth() + 1;
438         }
439         else
440         {
441            mLabelWidthInPixels = 0;
442         }
443      }
444
445      return mLabelWidthInPixels;
446   }
447
448
449   //--------------------------------------------------------------------------
450   public String getTooltipContent()
451   {
452      StringBuilderPlus text = new StringBuilderPlus().setDelimiter("<br />");
453
454      if (getId() != null
455          && (null == getDescription()
456              || getDescription().indexOf(getId()) < 0))
457      {
458         text.append(getId());
459      }
460
461      if (getDescription() != null)
462      {
463         text.delimitedAppend(getDescription());
464      }
465
466      if (mSeqMappingCoverage != null)
467      {
468         text.delimitedAppend("<i>Mapping: ");
469         if (mSeqMappingCoverage.getMappedLength() != null)
470         {
471            text.append(mSeqMappingCoverage.getMappedLength());
472         }
473         else
474         {
475            text.append("?");
476         }
477
478         text.append("/");
479
480         if (mSeqMappingCoverage.getQueryLength() != null)
481         {
482            text.append(mSeqMappingCoverage.getQueryLength());
483         }
484         else
485         {
486            text.append("?");
487         }
488         
489         if (mSeqMappingCoverage.getPercentIdentity() != null)
490         {
491            text.append(String.format(" (%d%% identity)", mSeqMappingCoverage.getPercentIdentity().intValue()));
492         }
493         
494         text.append("</i>");
495      }
496
497      if (getSpecies() != null)
498      {
499         text.delimitedAppend("<i>");
500         text.append(getSpecies().getScientificName());
501         text.append("</i>");
502      }
503
504      text.delimitedAppend("<i>");
505      if (getStrand() != null)
506      {
507         text.append("Strand: (");
508         text.append(getStrand().getSymbol());
509         text.append(") ");
510      }
511      text.append("bp.: ");
512      text.append(getLeft());
513      text.append(getLeft() != getRight() ?" - " + getRight() : "");
514      text.append("</i>");
515
516      return text.toString();
517   }
518
519   //--------------------------------------------------------------------------
520   @Override
521   public boolean equals(Object o2)
522   {
523      return (o2 instanceof Gene2D && compareTo((Gene2D)o2) == 0 ? true : false);
524   }
525
526   //--------------------------------------------------------------------------
527   public int compareTo(Gene2D gene2)
528   {
529      int returnValue = 0;
530
531      if (this != gene2)
532      {
533         if (getLeft() > gene2.getLeft())
534         {
535            returnValue = 1;
536         }
537         else if (getLeft() < gene2.getLeft())
538         {
539            returnValue = -1;
540         }
541         else
542         {
543            if (getRight() > gene2.getRight())
544            {
545               returnValue = 1;
546            }
547            else if (getRight() < gene2.getRight())
548            {
549               returnValue = -1;
550            }
551            else
552            {
553               // Somewhat arbitrary but this method will really only need to
554               // be called to order by position. Returning 0 when the objects
555               // are not the same can screw up hashes.
556               returnValue = 1;
557            }
558         }
559      }
560
561      return returnValue;
562   }
563
564   //##########################################################################
565   // PRIVATE METHODS
566   //##########################################################################
567
568
569   //--------------------------------------------------------------------------
570   private boolean isExonInDisplayRegion(Exon inExon)
571   {
572      boolean returnValue = false;
573
574      if (inExon.getRight() >= mDisplayStart
575            && inExon.getLeft() <= mDisplayEnd)
576      {
577         returnValue = true;
578      }
579
580      return returnValue;
581   }
582
583   //--------------------------------------------------------------------------
584   private void drawExons(Graphics2D g2)
585   {
586
587      if (mExons != null)
588      {
589         // Save settings
590         Paint origPaint = g2.getPaint();
591
592         for (Exon exon : mExons)
593         {
594            if (isExonInDisplayRegion(exon))
595            {
596               // Calculate exon location
597               int xOffset = getBoundedAndScaledLocation(exon.getLeft());
598
599               // Draw the exon
600               int width = getScaledWidthInPixels(getDisplayBoundedValue(exon.getLeft()),
601                                                  getDisplayBoundedValue(exon.getRight()));
602
603               if (exon.getColor() != null)
604               {
605                  g2.setPaint(exon.getColor());
606               }
607
608               g2.fillRect(xOffset, 0, width, mHeightInPixels);
609
610               if (exon.getColor() != null)
611               {
612                  // Restore settings
613                  g2.setPaint(origPaint);
614               }
615            }
616         }
617      }
618
619   }
620
621   //--------------------------------------------------------------------------
622   private List<SvgRect> drawExons()
623   {
624      List<SvgRect> exons = null;
625      if (mExons != null)
626      {
627         exons = new ArrayList<>(mExons.size());
628         for (Exon exon : mExons)
629         {
630            if (isExonInDisplayRegion(exon))
631            {
632               // Calculate exon location
633               int xOffset = getBoundedAndScaledLocation(exon.getLeft());
634
635               // Draw the exon
636               int width = getScaledWidthInPixels(getDisplayBoundedValue(exon.getLeft()),
637                                                  getDisplayBoundedValue(exon.getRight()));
638
639               SvgRect svgExon = new SvgRect(new Rectangle(new Point(xOffset, 0),
640                                                               new Dimension(width, mHeightInPixels)));
641
642               Color color = (mColor != null ? mColor : DEFAULT_COLOR);
643               if (exon.getColor() != null)
644               {
645                  color = exon.getColor();
646               }
647               svgExon.setFill(color);
648
649               exons.add(svgExon);
650            }
651         }
652      }
653
654      return exons;
655   }
656
657   //--------------------------------------------------------------------------
658   private int getScaledWidthInPixels(int inStart, int inEnd)
659   {
660      int leftPixel = (int) (inStart * mXScalingFactor);
661      int rightPixel = (int) (inEnd * mXScalingFactor);
662      int width = rightPixel - leftPixel + 1;
663 //     System.out.println("Exon: " + inStart + "-" + inEnd + "   " + leftPixel + "-" + rightPixel + "  " + mXScalingFactor + " (" + width + ")");/////////////////////////
664 //     int width = Math.abs((int) ((inEnd - inStart + 1) * mXScalingFactor));
665
666      // Minimum width of 1
667      return (0 >= width ? 1 : width);
668   }
669
670   //--------------------------------------------------------------------------
671   private int getBoundedAndScaledLocation(int inValue)
672   {
673      int bound = (int) (getDisplayBoundedValue(inValue) * mXScalingFactor);
674      int start = (int) (mDisplayStart * mXScalingFactor);
675      return bound - start;
676//      return (int)((getDisplayBoundedValue(inValue) - mDisplayStart) * mXScalingFactor);
677   }
678
679
680   //--------------------------------------------------------------------------
681   private int getDisplayBoundedValue(int inValue)
682   {
683      int value = inValue;
684
685      if (value < mDisplayStart)
686      {
687         value = mDisplayStart;
688      }
689      else if (value > mDisplayEnd)
690      {
691         value = mDisplayEnd;
692      }
693
694      return value;
695   }
696
697}
698