All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hfg.graphics.Track2D Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.graphics;


import java.awt.*;
import java.awt.Frame;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.awt.geom.AffineTransform;
import java.util.*;
import java.util.List;
import java.io.*;

import com.hfg.bio.Strand;
import com.hfg.html.*;
import com.hfg.html.attribute.Shape;
import com.hfg.javascript.PopupMenuJS;
import com.hfg.javascript.TooltipJS;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.image.ImageIO_Util;
import com.hfg.util.mime.MimeType;
import com.hfg.svg.*;
import com.hfg.xml.XMLNode;

import javax.imageio.ImageIO;

//------------------------------------------------------------------------------
/**
 * An evidence Track of data to display on genomic coordinates.
 *
 * @author J. Alex Taylor, hairyfatguy.com
 */
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
// TODO:
//  - Vertically center in the track.

public class Track2D extends Rectangle implements Comparable
{

   //##########################################################################
   // PRIVATE FIELDS
   //##########################################################################

   private String                mName;
   private String                mDescription;
   private String                mURL;
   private List          mForwardGenes;
   private List          mReverseGenes;
   private List mHighlightRegions;
   private Boolean               mShowId;
   private boolean               mShowSpeciesInTooltip  = true;
   private boolean               mShowLocationInTooltip = true;
   private ColorScale            mPctIdColorScale;
   
   private Color  mBackgroundColor = Color.white;
//   private Color  mForwardStrandColor = Color.blue;
//   private Color  mReverseStrandColor = new Color(150, 150, 255);
   private Color  mAxisColor  = Color.gray;
   private Color  mLabelColor = Color.black;
   private Color  mHighlightColor = new Color(150, 250, 150);
   private Font    mLabelFont = sDefaultLabelFont;

   private int    mXAxisStartValue;
   private int    mXAxisEndValue;
   private double mXScalingFactor;
   private Map    mForwardLineMap;
   private Map    mReverseLineMap;
   private int    mNumForwardLines;
   private int    mNumReverseLines;
   private List mForwardLineHeights;
   private List mReverseLineHeights;

   private int    mImageWidthInPixels = 700;
   private int    mForwardImageHeightInPixels;
   private int    mReverseImageHeightInPixels;
   private int    mLineHeightInPixels = 10;
   private int    mLinePaddingInPixels = 3;
   private int    mDescriptionWidthInPixels = 120;
   private int    mMinimumBufferInPixels = 3;
   private int    mRightBufferInPixels = 60;

   private int    mXAxisImageHeightInPixels;

//   private Font      mFont       = Font.decode("Arial-PLAIN-10");


   // Calculated
   private int    mMajorTickStep;
   private int    mMinorTickStep;

   private ImageMap mForwardImageMap = new ImageMap();
   private ImageMap mReverseImageMap = new ImageMap();
   private Script   mJavascript = new Script(MimeType.TEXT_JAVASCRIPT);

   private static FontRenderContext sFRC;
//   private static Font sDefaultLabelFont = Font.decode("Courier-PLAIN-9");
   private static Font sDefaultLabelFont = new Font("Monospaced", Font.PLAIN, 8);
   static
   {
      Frame frame = new Frame();
      frame.addNotify();
      Image image =frame.createImage(1, 1);
      sFRC = ((Graphics2D) image.getGraphics()).getFontRenderContext();
   }


   //##########################################################################
   // CONSTRUCTORS
   //##########################################################################

   //--------------------------------------------------------------------------
   public Track2D()
   {
   }


   //--------------------------------------------------------------------------
   public Track2D(String inName)
   {
      this();
      mName = inName;
   }


   //##########################################################################
   // PUBLIC METHODS
   //##########################################################################


   //--------------------------------------------------------------------------
   @Override
   public boolean equals(Object o2)
   {
      return (o2 instanceof Track2D && compareTo((Track2D)o2) == 0);
   }

   //--------------------------------------------------------------------------
   public int compareTo(Track2D inTrack2)
   {
      int returnValue = 0;

      if (this != inTrack2)
      {
         returnValue = this.getName().compareTo(inTrack2.getName());
      }

      return returnValue;
   }


   //--------------------------------------------------------------------------
   public boolean hasGenesOnStrand(Strand inStrand)
   {
      return areAnyGenesInDisplayRegion(Strand.FORWARD == inStrand ? mForwardGenes : mReverseGenes);
   }


   //--------------------------------------------------------------------------
   public void addHighlightRegion(int start, int end)
   {
      if (null == mHighlightRegions)
      {
         mHighlightRegions = new ArrayList();
      }

      mHighlightRegions.add(new HighlightRegion(start, end));
   }

   //--------------------------------------------------------------------------
   public void setGenes(Collection inValues)
   {
      if (mForwardGenes != null) mForwardGenes.clear();
      if (mReverseGenes != null) mReverseGenes.clear();
      if (CollectionUtil.hasValues(inValues))
      {
         for (Gene2D gene : inValues)
         {
            addGene(gene);
         }
      }
   }

   //--------------------------------------------------------------------------
   public void addGene(Gene2D inValue)
   {
      if (inValue.getStrand() == Strand.FORWARD
          || (null == inValue.getStrand()
              && inValue.getEndLocation() > inValue.getStartLocation()))
      {
         if (null == mForwardGenes)
         {
            mForwardGenes = new ArrayList();
         }

         mForwardGenes.add(inValue);
      }
      else
      {
         if (null == mReverseGenes)
         {
            mReverseGenes = new ArrayList();
         }

         mReverseGenes.add(inValue);
      }
   }

   //--------------------------------------------------------------------------
   public Collection getGenes()
   {
      Collection genes = new ArrayList();
      if (CollectionUtil.hasValues(mForwardGenes)) genes.addAll(mForwardGenes);
      if (CollectionUtil.hasValues(mReverseGenes)) genes.addAll(mReverseGenes);

      return genes;
   }

   //--------------------------------------------------------------------------
   public void setName(String inValue)
   {
      mName = inValue;
   }

   //--------------------------------------------------------------------------
   public String getName()
   {
      return mName;
   }


   //--------------------------------------------------------------------------
   public void setURL(String inValue)
   {
      mURL = inValue;
   }

   //--------------------------------------------------------------------------
   public void setDescription(String inValue)
   {
      mDescription = inValue;
   }

   //--------------------------------------------------------------------------
   public String getDescription()
   {
      return mDescription;
   }

   //--------------------------------------------------------------------------
   public void setBackgroundColor(Color inValue)
   {
      mBackgroundColor = inValue;
   }

   //--------------------------------------------------------------------------
   public Color getBackgroundColor()
   {
      return mBackgroundColor;
   }

   //--------------------------------------------------------------------------
   public void setPctIdColorScale(ColorScale inValue)
   {
      mPctIdColorScale = inValue;
   }

/*
   //--------------------------------------------------------------------------
   public void setForwardStrandColor(Color inValue)
   {
      mForwardStrandColor = inValue;
   }

   //--------------------------------------------------------------------------
   public Color getForwardStrandColor()
   {
      return mForwardStrandColor;
   }

   //--------------------------------------------------------------------------
   public void setReverseStrandColor(Color inValue)
   {
      mReverseStrandColor = inValue;
   }

   //--------------------------------------------------------------------------
   public Color getReverseStrandColor()
   {
      return mReverseStrandColor;
   }
*/
   //--------------------------------------------------------------------------
   public void setLabelColor(Color inValue)
   {
      mLabelColor = inValue;
   }

   //--------------------------------------------------------------------------
   public Color getLabelColor()
   {
      return mLabelColor;
   }

   //--------------------------------------------------------------------------
   public void setAxisColor(Color inValue)
   {
      mAxisColor = inValue;
   }

   //--------------------------------------------------------------------------
   public Color getAxisColor()
   {
      return mLabelColor;
   }

   //--------------------------------------------------------------------------
   public void setHighlightColor(Color inValue)
   {
      mHighlightColor = inValue;
   }

   //--------------------------------------------------------------------------
   public Color getHighlightColor()
   {
      return mHighlightColor;
   }

   //--------------------------------------------------------------------------
   public void setLabelFont(Font inValue)
   {
      mLabelFont = inValue;
   }

   //--------------------------------------------------------------------------
   public void setXAxisStartValue(int inValue)
   {
      mXAxisStartValue = inValue;
   }

   //--------------------------------------------------------------------------
   public void setXAxisEndValue(int inValue)
   {
      mXAxisEndValue = inValue;
   }

   //--------------------------------------------------------------------------
   public int getForwardStrandImageHeight()
   {
      return mForwardImageHeightInPixels;
   }

   //--------------------------------------------------------------------------
   public int getReverseStrandImageHeight()
   {
      return mReverseImageHeightInPixels;
   }

   //--------------------------------------------------------------------------
   public void setImageWidth(int inValue)
   {
      mImageWidthInPixels = inValue;
   }

   //--------------------------------------------------------------------------
   public int getImageWidth()
   {
      return mImageWidthInPixels;
   }

   //--------------------------------------------------------------------------
   public Script getJavascript()
   {
      return mJavascript;
   }

   //--------------------------------------------------------------------------
   public ImageMap getImageMap(Strand inStrand)
   {
      return (inStrand == Strand.FORWARD ? mForwardImageMap : mReverseImageMap);
   }

   //--------------------------------------------------------------------------
   public void setShowId(boolean inValue)
   {
      mShowId = inValue;
   }

   //--------------------------------------------------------------------------
   public XMLNode toSVG(Strand inStrand)
   {
      initialize(inStrand);

      SVG svg = new SVG();
      svg.setFont(mLabelFont);
      svg.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing

      SvgGroup group = svg.addGroup();
      group.setAttribute("transform", "scale(1)");


      int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
                                                              mReverseImageHeightInPixels);
      // Paint the background
      SvgRect background = group.addRect(new Rectangle(new Point(0, 0),
                                                       new Dimension(mImageWidthInPixels, imageHeightInPixels)));
      background.setFill(getBackgroundColor());
      // background.setStyle("stroke-width: 0.5; stroke: red;"); // For debugging

      // Draw the line separating the description from the alignment
      SvgRect separator = group.addRect(new Rectangle(new Point(mDescriptionWidthInPixels - (2 * mMinimumBufferInPixels), 0),
                                                      new Dimension(mMinimumBufferInPixels, imageHeightInPixels - 2)));
      separator.setFill(mAxisColor);

      // Write the Track name
      Rectangle textBoundBox = TextUtil.getStringRect(mName, mLabelFont);
      int xOffset = (int) (mDescriptionWidthInPixels - textBoundBox.getWidth() -  (2 * mLinePaddingInPixels) - mMinimumBufferInPixels);
      int yOffset = (int) (imageHeightInPixels/2 + textBoundBox.getHeight()/2);
      group.addText(mName, mLabelFont, new Point(xOffset, yOffset));

      // Add highlights
      SvgGroup highlightGroup = generateHighlightRegions(imageHeightInPixels);
      if (highlightGroup != null) group.addSubtag(highlightGroup);

      // Add genes
      group.addSubtag(generateGenes(inStrand));

      return svg;
   }
/*
   //--------------------------------------------------------------------------
   public SVG writeImageAsSvg(Strand inStrand)
   {
      if (mLabelFont == sDefaultLabelFont)
      {
         setLabelFont(Font.decode("Courier-PLAIN-8"));
      }

      initialize(inStrand);

      int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
            mReverseImageHeightInPixels);

      SvgGraphics2D g2 = new SvgGraphics2D();
      draw(g2, inStrand);
      SVG svgTag = g2.getRootTag().setWidth(mImageWidthInPixels).setHeight(imageHeightInPixels);
      svgTag.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing
      return svgTag;
   }
*/

   //--------------------------------------------------------------------------
   public SVG getXAxisAsSVG()
   {
      mXAxisImageHeightInPixels = (int) TextUtil.getStringRect(mXAxisEndValue + "", mLabelFont).getWidth() + 7;

      SVG svg = new SVG();
      svg.setFont(mLabelFont);
      svg.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing

      SvgGroup group = svg.addGroup();
      group.setAttribute("transform", "scale(1)");


      // Paint the background
      SvgRect background = group.addRect(new Rectangle(new Point(0, 0),
                                                       new Dimension(mImageWidthInPixels, mXAxisImageHeightInPixels)));
      background.setFill(getBackgroundColor());
      // background.setStyle("stroke-width: 0.5; stroke: red;"); // For debugging

      // Draw the x-axis line
      SvgLine axisLine = group.addLine(new Point(mDescriptionWidthInPixels, 1),
                                       new Point(mImageWidthInPixels - mRightBufferInPixels, 1));
//      axisLine.setFill(mAxisColor);

      // Add highlights
      SvgGroup highlightGroup = generateHighlightRegions(mXAxisImageHeightInPixels);
      if (highlightGroup != null) group.addSubtag(highlightGroup);
/*
      // Write the Track name
      Rectangle textBoundBox = TextUtil.getStringRect(mName, mLabelFont);
      int xOffset = (int) (mDescriptionWidthInPixels - textBoundBox.getWidth() -  (2 * mLinePaddingInPixels) - mMinimumBufferInPixels);
      int yOffset = (int) (imageHeightInPixels/2 + textBoundBox.getHeight()/2);
      group.addText(mName, mLabelFont, new Point(xOffset, yOffset));
*/

      determineTickSize();
      double lastXTranslation = 0;
      int yOffset = 2;

      for (Integer tickValue : getTickValues())
      {
         if (tickValue%mMajorTickStep == 0
             || tickValue == mXAxisStartValue
             || tickValue == mXAxisEndValue)
         {
            yOffset = 6;

            Rectangle textBoundBox = TextUtil.getStringRect(tickValue + "", mLabelFont);
            double xTranslation = mDescriptionWidthInPixels
                               + ((tickValue - mXAxisStartValue) * getXScalingFactor())
                               - textBoundBox.getHeight() / 4;

            if (xTranslation - lastXTranslation > 8)
            {
               SvgText label = group.addText(tickValue + "", mLabelFont, new Point((int)xTranslation, yOffset + 5));
               label.setTransform("rotate(45 " + (int) (xTranslation - 3) + " " + (yOffset + 5) + ")");
               lastXTranslation = xTranslation;
            }
/*
            if (xTranslation - lastXTranslation > 8
                && (tickValue == mXAxisEndValue || xTranslation < mImageWidthInPixels - mRightBufferInPixels - 10))
            {
               double yTranslation = yOffset + layout.getAdvance() + 1;
               g2.translate(xTranslation, yTranslation);
               g2.rotate(-Math.PI / 2);
               g2.drawString(tickValue + "", 0, 0);
               g2.setTransform(origTransform);
               lastXTranslation = xTranslation;

            }
*/
         }
         else if (tickValue%mMinorTickStep == 0)
         {
            yOffset = 2;
         }
         else
         {
            continue;
         }

         int xOffset = mDescriptionWidthInPixels
                       + (int) ((tickValue - mXAxisStartValue) * getXScalingFactor());

         SvgLine tickLine = group.addLine(new Point(xOffset, 1),
                                          new Point(xOffset, yOffset));
//         tickLine.setFill(mAxisColor);
      }

      return svg;

   }

   //--------------------------------------------------------------------------
   public void draw(Graphics2D g2, Strand inStrand)
   {
      if (inStrand == Strand.FORWARD) mForwardImageMap = new ImageMap();
      else                            mReverseImageMap = new ImageMap();
      // TODO: Split mJavascript into forward and reverse?

      // Save settings
      Paint origPaint = g2.getPaint();
      Font  origFont  = g2.getFont();

      // Save inital location.
      AffineTransform origTransform = g2.getTransform();

      // Enable anti-aliasing
//      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
//                          RenderingHints.VALUE_ANTIALIAS_ON);



      int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
                                                              mReverseImageHeightInPixels);

      // Paint the background
      setSize(mImageWidthInPixels, imageHeightInPixels);
      g2.setPaint(getBackgroundColor());
      g2.fill(this);

      // Draw the line separating the description from the alignment
      g2.setPaint(mAxisColor);
//      g2.drawLine(mDescriptionWidthInPixels - mMinimumBufferInPixels, 0,
//                  mDescriptionWidthInPixels - mMinimumBufferInPixels, imageHeightInPixels - 2);
      g2.fillRect(mDescriptionWidthInPixels - (2 * mMinimumBufferInPixels), 0,
                  mMinimumBufferInPixels, imageHeightInPixels - 2);


      // Write the Track name
      g2.setPaint(mLabelColor);
      g2.setFont(mLabelFont);
      FontRenderContext frc = g2.getFontRenderContext();
      TextLayout layout = new TextLayout(mName, mLabelFont, frc);
      Rectangle textBounds = (g2 instanceof SvgGraphics2D ? TextUtil.getStringRect(mName, mLabelFont) : layout.getBounds().getBounds());
      int xOffset = (int) (mDescriptionWidthInPixels - textBounds.getWidth()) -  2 * mLinePaddingInPixels - mMinimumBufferInPixels;
      int yOffset = (int) (imageHeightInPixels/2 + textBounds.getHeight()/2);

      g2.drawString(mName, xOffset, yOffset);


      if (mURL != null)
      {
         // Set image map data
         ImageMap imageMap = (inStrand == Strand.FORWARD ? mForwardImageMap : mReverseImageMap);
         Area area = imageMap.addArea();
         area.setHref(mURL);
         area.setShape(Shape.RECT);

         area.setCoords(xOffset + "," + (yOffset - textBounds.getHeight()) + ","
                        + (xOffset + textBounds.getWidth()) + "," + yOffset);
      }



      drawHighlightRegions(g2, imageHeightInPixels);

      drawGenes(g2, inStrand);



      // Return to orig. location
      g2.setTransform(origTransform);

      // Restore settings
      g2.setPaint(origPaint);
      g2.setFont(origFont);
   }


   //--------------------------------------------------------------------------
   public Image getImage(Strand inStrand)
   {
      initialize(inStrand);

      int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
                                                              mReverseImageHeightInPixels);

      Frame frame = new Frame();
      frame.addNotify();
      Image image = frame.createImage(mImageWidthInPixels, imageHeightInPixels);

      Graphics2D g2 = (Graphics2D) image.getGraphics();
      draw(g2, inStrand);

      return image;
   }

   //--------------------------------------------------------------------------
   public BufferedImage getBufferedImage(Strand inStrand)
   {
      initialize(inStrand);

      int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels :
            mReverseImageHeightInPixels);

      Frame frame = new Frame();
      frame.addNotify();
      BufferedImage bufferedImage = new BufferedImage(mImageWidthInPixels,
                                                      imageHeightInPixels,
                                                      BufferedImage.TYPE_INT_RGB);

      Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics();
      draw(g2, inStrand);

      return bufferedImage;
   }

   //--------------------------------------------------------------------------
   public BufferedImage getXAxisBufferedImage()
   {
      Frame frame = new Frame();
      frame.addNotify();
      BufferedImage bufferedImage = new BufferedImage(mImageWidthInPixels,
                                                      mXAxisImageHeightInPixels,
                                                      BufferedImage.TYPE_INT_RGB);

      Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics();
      drawXAxis(g2);

      return bufferedImage;
   }

   //--------------------------------------------------------------------------
   public void writeImageAsJpeg(OutputStream inStream, Strand inStrand)
   throws IOException
   {
      BufferedImage bufferedImage = getBufferedImage(inStrand);
      ImageIO_Util.writeBufferedImageAsJpeg(bufferedImage, inStream);
   }

   //--------------------------------------------------------------------------
   public void writeXAxisImageAsJpeg(OutputStream inStream)
   throws IOException
   {
      mXAxisImageHeightInPixels = getLabelWidthInPixels(mXAxisEndValue + "") + 7;

      BufferedImage bufferedImage = getXAxisBufferedImage();
      ImageIO_Util.writeBufferedImageAsJpeg(bufferedImage, inStream);
   }

   //--------------------------------------------------------------------------
   public void writeImageAsPng(OutputStream inStream, Strand inStrand)
   throws IOException
   {
      BufferedImage bufferedImage = getBufferedImage(inStrand);
      ImageIO.write(bufferedImage, "png", inStream);
   }
 

   //##########################################################################
   // PRIVATE METHODS
   //##########################################################################

   //--------------------------------------------------------------------------
   private void initialize(Strand inStrand)
   {
      List genes = (inStrand == Strand.FORWARD ? mForwardGenes : mReverseGenes);

      if (genes != null)
      {
         for (Gene2D gene : genes)
         {
            initializeGene(gene);
         }
      }

      // Figure out how many lines high the image will be and set the image height.
      createLineMap(inStrand);
   }

   //--------------------------------------------------------------------------
   private void initializeGene(Gene2D inGene)
   {
      inGene.setDisplayRegion(mXAxisStartValue, mXAxisEndValue);
      inGene.setXScalingFactor(getXScalingFactor());
//      inGene.setForwardStrandColor(getForwardStrandColor());
//      inGene.setReverseStrandColor(getReverseStrandColor());

      if (mLabelFont != null)
      {
         inGene.setLabelFont(mLabelFont);
      }

      if (mShowId != null)
      {
         inGene.setShowId(mShowId);
      }
   }

   //--------------------------------------------------------------------------
   private boolean areAnyGenesInDisplayRegion(List inGenes)
   {
      boolean result = false;
      if (inGenes != null)
      {
         for (Gene2D gene : inGenes)
         {
            gene.setDisplayRegion(mXAxisStartValue, mXAxisEndValue);
            if (gene.inDisplayRegion())
            {
               result = true;
               break;
            }
         }
      }

      return result;
   }

   //--------------------------------------------------------------------------
   private double getXScalingFactor()
   {
      if (0 == mXScalingFactor)
      {
         mXScalingFactor = (double)(mImageWidthInPixels - mDescriptionWidthInPixels - mRightBufferInPixels)
                                    /(mXAxisEndValue - mXAxisStartValue + 1);
      }

      return mXScalingFactor;
   }

   //--------------------------------------------------------------------------
   private int getScaledPosition(int inPosition)
   {
      int xOffset = (int) ((inPosition - mXAxisStartValue) * getXScalingFactor());

      return xOffset;
   }

   //--------------------------------------------------------------------------
   private Gene2D getLastGeneForLevel(int inLevel, List> inLevelLists)
   {
      Gene2D outGene = null;

      if (inLevelLists.size() >= inLevel)
      {
         List levelList = inLevelLists.get(inLevel - 1);
         outGene = levelList.get(levelList.size() - 1);
      }

      return outGene;
   }

   //--------------------------------------------------------------------------
   private void addGeneToLevel(Gene2D inGene, int inLevel, List> inLevelLists)
   {
      List levelList = null;

      if (inLevelLists.size() < inLevel)
      {
         levelList = new ArrayList();
         inLevelLists.add(levelList);
      }
      else
      {
         levelList = inLevelLists.get(inLevel - 1);
      }

      levelList.add(inGene);
   }


   //--------------------------------------------------------------------------
   private boolean genesAreTooCloseTogether(Gene2D inGene1, Gene2D inGene2)
   {
      boolean resultValue = false;

      if (getScaledPosition(inGene1.getRight()) + (mShowId && inGene1.getShowId() ? inGene1.getLabelWidthInPixels() : 0)
          + mMinimumBufferInPixels > getScaledPosition(inGene2.getLeft()))
      {
         resultValue = true;
      }

      return resultValue;
   }

   //--------------------------------------------------------------------------
   private void createLineMap(Strand inStrand)
   {
      Map lineMap = null;
      List> levelLists = new ArrayList>();

      List genes = (inStrand == Strand.FORWARD ? mForwardGenes : mReverseGenes);

      if (genes != null)
      {
         Collections.sort(genes);

         lineMap = new HashMap();

         for (Gene2D gene : genes)
         {
            if (gene.inDisplayRegion())
            {
               int level = 1;
               boolean assigningLevel = true;
               while (assigningLevel)
               {
                  Gene2D lastGene = getLastGeneForLevel(level, levelLists);
                  if (lastGene != null
                        && genesAreTooCloseTogether(lastGene, gene))
                  {
                     level++;
                  }
                  else
                  {
                     addGeneToLevel(gene, level, levelLists);
                     lineMap.put(gene, level + "");
                     assigningLevel = false;
                  }
               }
            }

         }
      }

      if (inStrand == Strand.FORWARD)
      {
         mForwardLineMap = lineMap;
         mNumForwardLines = levelLists.size();
         mForwardLineHeights = determineLineHeights(levelLists);
//         mForwardImageHeightInPixels = mNumForwardLines * (mLineHeightInPixels + mLinePaddingInPixels);
         mForwardImageHeightInPixels = sum(mForwardLineHeights) + mForwardLineHeights.size() * mLinePaddingInPixels;
      }
      else
      {
         mReverseLineMap = lineMap;
         mNumReverseLines = levelLists.size();
         mReverseLineHeights = determineLineHeights(levelLists);
//         mReverseImageHeightInPixels = mNumReverseLines * (mLineHeightInPixels + mLinePaddingInPixels);
         mReverseImageHeightInPixels = sum(mReverseLineHeights) + mReverseLineHeights.size() * mLinePaddingInPixels;
      }

   }


   //--------------------------------------------------------------------------
   private int sum(List inValues)
   {
      int sum = 0;

      if (CollectionUtil.hasValues(inValues))
      {
         for (Integer value : inValues)
         {
            sum += value;
         }
      }

      return sum;
   }

   //--------------------------------------------------------------------------
   private List determineLineHeights(List> inLevelLists)
   {
      List lineHeights = new ArrayList(inLevelLists.size());

      for (List level : inLevelLists)
      {
         double maxLineHeight = 0;
         for (Gene2D gene : level)
         {
            if (gene.getHeightInPixels() > maxLineHeight) maxLineHeight = gene.getHeightInPixels();
         }

         lineHeights.add((int)maxLineHeight);
      }


      return lineHeights;
   }

   //--------------------------------------------------------------------------
   private int getYOffsetForLine(int inLine, Strand inStrand)
   {
      int yOffset = 0;

      List lineHeights = (inStrand == Strand.FORWARD ? mForwardLineHeights : mReverseLineHeights);
      for (int i = 0; i < inLine; i++)
      {
         yOffset -= lineHeights.get(i);
      }

      yOffset -= inLine * mLinePaddingInPixels;

      return yOffset;
   }

   //--------------------------------------------------------------------------
   private void drawHighlightRegions(Graphics2D g2, int inImageHeight)
   {
      if (mHighlightRegions != null)
      {
         for (HighlightRegion region : mHighlightRegions)
         {
            int xOffset = mDescriptionWidthInPixels + (int)((region.getStart() - mXAxisStartValue) * getXScalingFactor());
            int width = (int) ((region.getEnd() - region.getStart() - 1) * getXScalingFactor());
            if (width < 1) width = 1;
            g2.setPaint(mHighlightColor);

            g2.fillRect(xOffset, 0, width, inImageHeight);
         }

      }
   }

   //--------------------------------------------------------------------------
   private SvgGroup generateHighlightRegions(int inImageHeight)
   {
      SvgGroup highlightGroup = null;
      if (mHighlightRegions != null)
      {
         highlightGroup = new SvgGroup();
         for (HighlightRegion region : mHighlightRegions)
         {
            int xOffset = mDescriptionWidthInPixels + (int)((region.getStart() - mXAxisStartValue) * getXScalingFactor());
            int width = (int) ((region.getEnd() - region.getStart() - 1) * getXScalingFactor());
            if (width < 1) width = 1;
            highlightGroup.addRect(new Rectangle(new Point(xOffset, 0),
                                                 new Dimension(width, inImageHeight))).setFill(mHighlightColor);
         }
      }

      return highlightGroup;
   }

   //--------------------------------------------------------------------------
   private SvgGroup generateGenes(Strand inStrand)
   {
      // Move to the bottom left of the genomic display
      int imageHeightInPixels = 0;
      List genes = null;
      Map lineMap = null;
      if (inStrand == Strand.FORWARD)
      {
         imageHeightInPixels = mForwardImageHeightInPixels;
         genes = mForwardGenes;
         lineMap = mForwardLineMap;
      }
      else
      {
         imageHeightInPixels = mReverseImageHeightInPixels;
         genes = mReverseGenes;
         lineMap = mReverseLineMap;
      }

      SvgGroup group = new SvgGroup().setTransform("translate(" + mDescriptionWidthInPixels + ", " + imageHeightInPixels + ")");


      if (genes != null)
      {
         for (Gene2D gene : genes)
         {
            if (gene.inDisplayRegion())
            {

               SvgLink svgLink = null;
               List urls = gene.getURLs();
               if (CollectionUtil.hasValues(urls))
               {
                  if (1 == urls.size())
                  {
                     svgLink = new SvgLink(urls.get(0).getURL());
                  }
                  else
                  {
                     PopupMenuJS menu = new PopupMenuJS();
                     menu.setMenuTitle(gene.getId());
                     for (Link link : urls)
                     {
                        menu.addLink(link);
                     }

                     // Add the menu to the track's javascript
                     mJavascript.addContent(menu.generateMenuJavascript());
                     svgLink = new SvgLink(menu.getDisplayMenuJavascript());
                  }
               }

               SvgNode svgNode = null;
               if (svgLink != null)
               {
                  svgNode = svgLink;
               }
               else
               {
                  svgNode = new SvgGroup();
               }
               group.addSubtag(svgNode);
               
               // Move to gene location

               // What line is the gene on? Move to upper left of gene.
               int line = Integer.parseInt(lineMap.get(gene));
               if (inStrand == Strand.REVERSE)
               {
                  line = mNumReverseLines - line + 1;
               }

               //XXXX           int xOffset = (int) ((gene.getStartLocation() - mXAxisStartValue) * getXScalingFactor());
               int geneLeft = (int) (gene.getLeft() * mXScalingFactor);
               int bound = (int) (mXAxisStartValue * mXScalingFactor);
               int xOffset = geneLeft - bound;
//               int xOffset = (int) ((gene.getLeft() - mXAxisStartValue) * getXScalingFactor());
               // Don't back up into the description region.
               if (xOffset < 0) xOffset = 0;
        /*       // Don't go off the right side of the display region.
               if (xOffset > mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels)
               {
                  xOffset = mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels;
               }
        */

//               int yOffset = -line * (mLineHeightInPixels + mLinePaddingInPixels);
               int yOffset = getYOffsetForLine(line, inStrand);

//               SvgGroup geneGroup = group.addGroup().setTransform();

               svgNode.setTransform("translate(" + xOffset + ", " + yOffset + ")");

               svgNode.addSubtag(gene.toSVG());
            }
         }
      }

      return group;
   }

   //--------------------------------------------------------------------------
   private void drawGenes(Graphics2D g2, Strand inStrand)
   {
      TooltipJS tooltip = new TooltipJS();

      int imageHeightInPixels = 0;
      List genes = null;
      Map lineMap = null;
      ImageMap imageMap = null;
      if (inStrand == Strand.FORWARD)
      {
         imageHeightInPixels = mForwardImageHeightInPixels;
         genes = mForwardGenes;
         lineMap = mForwardLineMap;
         imageMap = mForwardImageMap;
      }
      else
      {
         imageHeightInPixels = mReverseImageHeightInPixels;
         genes = mReverseGenes;
         lineMap = mReverseLineMap;
         imageMap = mReverseImageMap;
      }

      // Move to the bottom left of the genomic display
      g2.translate(mDescriptionWidthInPixels, imageHeightInPixels);

      if (genes != null)
      {
         // Save initial location.
         AffineTransform origTransform = g2.getTransform();

         for (Gene2D gene : genes)
         {
            if (gene.inDisplayRegion())
            {
               // Dynamically set the gene model color based on the mapped %id?  
               if (mPctIdColorScale != null
                   && null == gene.getColor()
                   && gene.getSeqMappingCoverage() != null
                   && gene.getSeqMappingCoverage().getPercentIdentity() != null)
               {
                  gene.setColor(mPctIdColorScale.getColor(gene.getSeqMappingCoverage().getPercentIdentity() / 100));   
               }

               // Move to gene location

               // What line is the gene on? Move to upper left of gene.
               int line = Integer.parseInt(lineMap.get(gene));
               if (inStrand == Strand.REVERSE)
               {
                  line = mNumReverseLines - line + 1;
               }

               //XXXX           int xOffset = (int) ((gene.getStartLocation() - mXAxisStartValue) * getXScalingFactor());
               int geneLeft = (int) (gene.getLeft() * mXScalingFactor);
               int bound = (int) (mXAxisStartValue * mXScalingFactor);
               int xOffset = geneLeft - bound;
//               int xOffset = (int) ((gene.getLeft() - mXAxisStartValue) * getXScalingFactor());
               // Don't back up into the description region.
               if (xOffset < 0) xOffset = 0;
        /*       // Don't go off the right side of the display region.
               if (xOffset > mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels)
               {
                  xOffset = mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels;
               }
        */

//               int yOffset = -line * (mLineHeightInPixels + mLinePaddingInPixels);
               int yOffset = getYOffsetForLine(line, inStrand);

               g2.translate(xOffset, yOffset);

               gene.draw(g2);

               // Return to orig. location
               g2.setTransform(origTransform);

               // Set image map data
               Area area = imageMap.addArea();
               setAreaURL(area, gene);

               tooltip.addTooltip(area, gene.getTooltipContent());
               area.setShape(Shape.RECT);

               int x1 = mDescriptionWidthInPixels + xOffset;
               if (x1 < mDescriptionWidthInPixels) x1 = mDescriptionWidthInPixels;
               int x2 = mDescriptionWidthInPixels + xOffset + gene.getScaledWidthInPixels()
                        + (mShowId ? gene.getLabelWidthInPixels() : 0);
               if (x2 > mImageWidthInPixels)
               {
                  x2 = mImageWidthInPixels;
               }
               area.setCoords(x1 + "," + (imageHeightInPixels + yOffset) + ","
                              + x2 + "," + (imageHeightInPixels + yOffset + gene.getHeightInPixels() - 1));
            }
         }
      }
   }

   //--------------------------------------------------------------------------
   private void setAreaURL(Area inArea, Gene2D inGene)
   {
      List urls = inGene.getURLs();
      if (CollectionUtil.hasValues(urls))
      {
         if (1 == urls.size())
         {
            Link link = urls.get(0);
            inArea.setHref(link.getURL());
         }
         else
         {
            PopupMenuJS menu = new PopupMenuJS();
            menu.setMenuTitle(inGene.getId());
            for (Link link : urls)
            {
               menu.addLink(link);
            }

             // Add the menu to the track's javascript
            mJavascript.addContent(menu.generateMenuJavascript());

            inArea.setHref(menu.getDisplayMenuJavascript());
         }
      }
   }

   //--------------------------------------------------------------------------
   public void drawXAxis(Graphics2D g2)
   {
      // Save settings
      Paint origPaint = g2.getPaint();
      Font  origFont  = g2.getFont();

      // Save inital location.
      AffineTransform origTransform = g2.getTransform();

      // Enable anti-aliasing
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                          RenderingHints.VALUE_ANTIALIAS_ON);

      // Paint the background
      g2.setPaint(Color.white);
      g2.fillRect(0, 0, mImageWidthInPixels, mXAxisImageHeightInPixels);


      drawHighlightRegions(g2, mXAxisImageHeightInPixels);

      // Draw the x-axis line
      g2.setPaint(mAxisColor);
      g2.drawLine(mDescriptionWidthInPixels, 0,
                  mImageWidthInPixels - mRightBufferInPixels, 0);

      determineTickSize();
      double lastXTranslation = 0;
      int yOffset = 2;

      for (Integer tickValue : getTickValues())
      {
         if (tickValue%mMajorTickStep == 0
             || tickValue == mXAxisStartValue
             || tickValue == mXAxisEndValue)
         {
            yOffset = 4;

            g2.setFont(mLabelFont);
            FontRenderContext frc = g2.getFontRenderContext();
            TextLayout layout = new TextLayout(tickValue + "", mLabelFont, frc);
            double xTranslation = mDescriptionWidthInPixels
                               + ((tickValue - mXAxisStartValue) * getXScalingFactor())
                               + layout.getBounds().getHeight() / 2;

            if (xTranslation - lastXTranslation > 8
                && (tickValue == mXAxisEndValue || xTranslation < mImageWidthInPixels - mRightBufferInPixels - 10))
            {
               double yTranslation = yOffset + layout.getAdvance() + 1;
               g2.translate(xTranslation, yTranslation);
               g2.rotate(-Math.PI / 2);
               g2.drawString(tickValue + "", 0, 0);
               g2.setTransform(origTransform);
               lastXTranslation = xTranslation;
            }
         }
         else if (tickValue%mMinorTickStep == 0)
         {
            yOffset = 2;
         }
         else
         {
            continue;
         }

         int xOffset = mDescriptionWidthInPixels
                       + (int) ((tickValue - mXAxisStartValue) * getXScalingFactor());
         g2.drawLine(xOffset, 0,
                     xOffset, yOffset);
      }


      // Return to orig. location
      g2.setTransform(origTransform);

      // Restore settings
      g2.setPaint(origPaint);
      g2.setFont(origFont);
   }

   //--------------------------------------------------------------------------
   private void determineTickSize()
   {
      int xAxisWidth = mXAxisEndValue - mXAxisStartValue + 1;
      String widthString = xAxisWidth + "";
      mMajorTickStep = (int) Math.pow(10, widthString.length() - 1);
      mMinorTickStep = mMajorTickStep / 10;
   }

   //--------------------------------------------------------------------------
   private List getTickValues()
   {
      List tickValues = new ArrayList();

      // First add the major ticks
      tickValues.add(mXAxisStartValue);

      for (int tickValue = (int) (Math.ceil(mXAxisStartValue / mMajorTickStep) * mMajorTickStep);
           tickValue < mXAxisEndValue;
           tickValue += mMajorTickStep)
      {
         if (tickValue > mXAxisStartValue)
         {
            tickValues.add(tickValue);
         }
      }

      tickValues.add(mXAxisEndValue);

      // Now add the minor ticks
      for (int tickValue = (int) (Math.ceil(mXAxisStartValue / mMinorTickStep) * mMinorTickStep);
           tickValue < mXAxisEndValue;
           tickValue += mMinorTickStep)
      {
         if (tickValue%mMajorTickStep != 0
             && tickValue != mXAxisStartValue)
         {
            tickValues.add(tickValue);
         }
      }

      return tickValues;
   }

   //--------------------------------------------------------------------------
   public int getLabelWidthInPixels(String inLabel)
   {
      int widthInPixels = 0;

      if (inLabel != null
            && inLabel.length() > 0)
      {
         TextLayout layout = new TextLayout(inLabel, mLabelFont, sFRC);
         widthInPixels = (int) layout.getBounds().getWidth() + 1;
      }

      return widthInPixels;
   }




   //--------------------------------------------------------------------------
   private class HighlightRegion
   {
      private int mStart;
      private int mEnd;

      //-----------------------------------------------------------------------
      public HighlightRegion(int inStart, int inEnd)
      {
         if (inEnd < inStart)
         {
            mStart = inEnd;
            mEnd = inStart;
         }
         else
         {
            mStart = inStart;
            mEnd = inEnd;
         }
      }

      //-----------------------------------------------------------------------
      public int getStart()
      {
         return mStart;
      }

      //-----------------------------------------------------------------------
      public int getEnd()
      {
         return mEnd;
      }
   }

}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy