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.util.*; 011import java.util.List; 012import java.io.*; 013 014import com.hfg.bio.Strand; 015import com.hfg.html.*; 016import com.hfg.html.attribute.Shape; 017import com.hfg.javascript.PopupMenuJS; 018import com.hfg.javascript.TooltipJS; 019import com.hfg.util.collection.CollectionUtil; 020import com.hfg.image.ImageIO_Util; 021import com.hfg.util.mime.MimeType; 022import com.hfg.svg.*; 023import com.hfg.xml.XMLNode; 024 025import javax.imageio.ImageIO; 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 Track2D extends Rectangle implements Comparable<Track2D> 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 private ColorScale mPctIdColorScale; 073 074 private Color mBackgroundColor = Color.white; 075// private Color mForwardStrandColor = Color.blue; 076// private Color mReverseStrandColor = new Color(150, 150, 255); 077 private Color mAxisColor = Color.gray; 078 private Color mLabelColor = Color.black; 079 private Color mHighlightColor = new Color(150, 250, 150); 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// private Font mFont = Font.decode("Arial-PLAIN-10"); 104 105 106 // Calculated 107 private int mMajorTickStep; 108 private int mMinorTickStep; 109 110 private ImageMap mForwardImageMap = new ImageMap(); 111 private ImageMap mReverseImageMap = new ImageMap(); 112 private Script mJavascript = new Script(MimeType.TEXT_JAVASCRIPT); 113 114 private static FontRenderContext sFRC; 115// private static Font sDefaultLabelFont = Font.decode("Courier-PLAIN-9"); 116 private static Font sDefaultLabelFont = new Font("Monospaced", Font.PLAIN, 8); 117 static 118 { 119 Frame frame = new Frame(); 120 frame.addNotify(); 121 Image image =frame.createImage(1, 1); 122 sFRC = ((Graphics2D) image.getGraphics()).getFontRenderContext(); 123 } 124 125 126 //########################################################################## 127 // CONSTRUCTORS 128 //########################################################################## 129 130 //-------------------------------------------------------------------------- 131 public Track2D() 132 { 133 } 134 135 136 //-------------------------------------------------------------------------- 137 public Track2D(String inName) 138 { 139 this(); 140 mName = inName; 141 } 142 143 144 //########################################################################## 145 // PUBLIC METHODS 146 //########################################################################## 147 148 149 //-------------------------------------------------------------------------- 150 @Override 151 public boolean equals(Object o2) 152 { 153 return (o2 instanceof Track2D && compareTo((Track2D)o2) == 0); 154 } 155 156 //-------------------------------------------------------------------------- 157 public int compareTo(Track2D inTrack2) 158 { 159 int returnValue = 0; 160 161 if (this != inTrack2) 162 { 163 returnValue = this.getName().compareTo(inTrack2.getName()); 164 } 165 166 return returnValue; 167 } 168 169 170 //-------------------------------------------------------------------------- 171 public boolean hasGenesOnStrand(Strand inStrand) 172 { 173 return areAnyGenesInDisplayRegion(Strand.FORWARD == inStrand ? mForwardGenes : mReverseGenes); 174 } 175 176 177 //-------------------------------------------------------------------------- 178 public void addHighlightRegion(int start, int end) 179 { 180 if (null == mHighlightRegions) 181 { 182 mHighlightRegions = new ArrayList<HighlightRegion>(); 183 } 184 185 mHighlightRegions.add(new HighlightRegion(start, end)); 186 } 187 188 //-------------------------------------------------------------------------- 189 public void setGenes(Collection<Gene2D> inValues) 190 { 191 if (mForwardGenes != null) mForwardGenes.clear(); 192 if (mReverseGenes != null) mReverseGenes.clear(); 193 if (CollectionUtil.hasValues(inValues)) 194 { 195 for (Gene2D gene : inValues) 196 { 197 addGene(gene); 198 } 199 } 200 } 201 202 //-------------------------------------------------------------------------- 203 public void addGene(Gene2D inValue) 204 { 205 if (inValue.getStrand() == Strand.FORWARD 206 || (null == inValue.getStrand() 207 && inValue.getEndLocation() > inValue.getStartLocation())) 208 { 209 if (null == mForwardGenes) 210 { 211 mForwardGenes = new ArrayList<Gene2D>(); 212 } 213 214 mForwardGenes.add(inValue); 215 } 216 else 217 { 218 if (null == mReverseGenes) 219 { 220 mReverseGenes = new ArrayList<Gene2D>(); 221 } 222 223 mReverseGenes.add(inValue); 224 } 225 } 226 227 //-------------------------------------------------------------------------- 228 public Collection<Gene2D> getGenes() 229 { 230 Collection<Gene2D> genes = new ArrayList<Gene2D>(); 231 if (CollectionUtil.hasValues(mForwardGenes)) genes.addAll(mForwardGenes); 232 if (CollectionUtil.hasValues(mReverseGenes)) genes.addAll(mReverseGenes); 233 234 return genes; 235 } 236 237 //-------------------------------------------------------------------------- 238 public void setName(String inValue) 239 { 240 mName = inValue; 241 } 242 243 //-------------------------------------------------------------------------- 244 public String getName() 245 { 246 return mName; 247 } 248 249 250 //-------------------------------------------------------------------------- 251 public void setURL(String inValue) 252 { 253 mURL = inValue; 254 } 255 256 //-------------------------------------------------------------------------- 257 public void setDescription(String inValue) 258 { 259 mDescription = inValue; 260 } 261 262 //-------------------------------------------------------------------------- 263 public String getDescription() 264 { 265 return mDescription; 266 } 267 268 //-------------------------------------------------------------------------- 269 public void setBackgroundColor(Color inValue) 270 { 271 mBackgroundColor = inValue; 272 } 273 274 //-------------------------------------------------------------------------- 275 public Color getBackgroundColor() 276 { 277 return mBackgroundColor; 278 } 279 280 //-------------------------------------------------------------------------- 281 public void setPctIdColorScale(ColorScale inValue) 282 { 283 mPctIdColorScale = inValue; 284 } 285 286/* 287 //-------------------------------------------------------------------------- 288 public void setForwardStrandColor(Color inValue) 289 { 290 mForwardStrandColor = inValue; 291 } 292 293 //-------------------------------------------------------------------------- 294 public Color getForwardStrandColor() 295 { 296 return mForwardStrandColor; 297 } 298 299 //-------------------------------------------------------------------------- 300 public void setReverseStrandColor(Color inValue) 301 { 302 mReverseStrandColor = inValue; 303 } 304 305 //-------------------------------------------------------------------------- 306 public Color getReverseStrandColor() 307 { 308 return mReverseStrandColor; 309 } 310*/ 311 //-------------------------------------------------------------------------- 312 public void setLabelColor(Color inValue) 313 { 314 mLabelColor = inValue; 315 } 316 317 //-------------------------------------------------------------------------- 318 public Color getLabelColor() 319 { 320 return mLabelColor; 321 } 322 323 //-------------------------------------------------------------------------- 324 public void setAxisColor(Color inValue) 325 { 326 mAxisColor = inValue; 327 } 328 329 //-------------------------------------------------------------------------- 330 public Color getAxisColor() 331 { 332 return mLabelColor; 333 } 334 335 //-------------------------------------------------------------------------- 336 public void setHighlightColor(Color inValue) 337 { 338 mHighlightColor = inValue; 339 } 340 341 //-------------------------------------------------------------------------- 342 public Color getHighlightColor() 343 { 344 return mHighlightColor; 345 } 346 347 //-------------------------------------------------------------------------- 348 public void setLabelFont(Font inValue) 349 { 350 mLabelFont = inValue; 351 } 352 353 //-------------------------------------------------------------------------- 354 public void setXAxisStartValue(int inValue) 355 { 356 mXAxisStartValue = inValue; 357 } 358 359 //-------------------------------------------------------------------------- 360 public void setXAxisEndValue(int inValue) 361 { 362 mXAxisEndValue = inValue; 363 } 364 365 //-------------------------------------------------------------------------- 366 public int getForwardStrandImageHeight() 367 { 368 return mForwardImageHeightInPixels; 369 } 370 371 //-------------------------------------------------------------------------- 372 public int getReverseStrandImageHeight() 373 { 374 return mReverseImageHeightInPixels; 375 } 376 377 //-------------------------------------------------------------------------- 378 public void setImageWidth(int inValue) 379 { 380 mImageWidthInPixels = inValue; 381 } 382 383 //-------------------------------------------------------------------------- 384 public int getImageWidth() 385 { 386 return mImageWidthInPixels; 387 } 388 389 //-------------------------------------------------------------------------- 390 public Script getJavascript() 391 { 392 return mJavascript; 393 } 394 395 //-------------------------------------------------------------------------- 396 public ImageMap getImageMap(Strand inStrand) 397 { 398 return (inStrand == Strand.FORWARD ? mForwardImageMap : mReverseImageMap); 399 } 400 401 //-------------------------------------------------------------------------- 402 public void setShowId(boolean inValue) 403 { 404 mShowId = inValue; 405 } 406 407 //-------------------------------------------------------------------------- 408 public XMLNode toSVG(Strand inStrand) 409 { 410 initialize(inStrand); 411 412 SVG svg = new SVG(); 413 svg.setFont(mLabelFont); 414 svg.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing 415 416 SvgGroup group = svg.addGroup(); 417 group.setAttribute("transform", "scale(1)"); 418 419 420 int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels : 421 mReverseImageHeightInPixels); 422 // Paint the background 423 SvgRect background = group.addRect(new Rectangle(new Point(0, 0), 424 new Dimension(mImageWidthInPixels, imageHeightInPixels))); 425 background.setFill(getBackgroundColor()); 426 // background.setStyle("stroke-width: 0.5; stroke: red;"); // For debugging 427 428 // Draw the line separating the description from the alignment 429 SvgRect separator = group.addRect(new Rectangle(new Point(mDescriptionWidthInPixels - (2 * mMinimumBufferInPixels), 0), 430 new Dimension(mMinimumBufferInPixels, imageHeightInPixels - 2))); 431 separator.setFill(mAxisColor); 432 433 // Write the Track name 434 Rectangle textBoundBox = TextUtil.getStringRect(mName, mLabelFont); 435 int xOffset = (int) (mDescriptionWidthInPixels - textBoundBox.getWidth() - (2 * mLinePaddingInPixels) - mMinimumBufferInPixels); 436 int yOffset = (int) (imageHeightInPixels/2 + textBoundBox.getHeight()/2); 437 group.addText(mName, mLabelFont, new Point(xOffset, yOffset)); 438 439 // Add highlights 440 SvgGroup highlightGroup = generateHighlightRegions(imageHeightInPixels); 441 if (highlightGroup != null) group.addSubtag(highlightGroup); 442 443 // Add genes 444 group.addSubtag(generateGenes(inStrand)); 445 446 return svg; 447 } 448/* 449 //-------------------------------------------------------------------------- 450 public SVG writeImageAsSvg(Strand inStrand) 451 { 452 if (mLabelFont == sDefaultLabelFont) 453 { 454 setLabelFont(Font.decode("Courier-PLAIN-8")); 455 } 456 457 initialize(inStrand); 458 459 int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels : 460 mReverseImageHeightInPixels); 461 462 SvgGraphics2D g2 = new SvgGraphics2D(); 463 draw(g2, inStrand); 464 SVG svgTag = g2.getRootTag().setWidth(mImageWidthInPixels).setHeight(imageHeightInPixels); 465 svgTag.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing 466 return svgTag; 467 } 468*/ 469 470 //-------------------------------------------------------------------------- 471 public SVG getXAxisAsSVG() 472 { 473 mXAxisImageHeightInPixels = (int) TextUtil.getStringRect(mXAxisEndValue + "", mLabelFont).getWidth() + 7; 474 475 SVG svg = new SVG(); 476 svg.setFont(mLabelFont); 477 svg.addStyle("shape-rendering:crispEdges"); // Turn off anti-aliasing 478 479 SvgGroup group = svg.addGroup(); 480 group.setAttribute("transform", "scale(1)"); 481 482 483 // Paint the background 484 SvgRect background = group.addRect(new Rectangle(new Point(0, 0), 485 new Dimension(mImageWidthInPixels, mXAxisImageHeightInPixels))); 486 background.setFill(getBackgroundColor()); 487 // background.setStyle("stroke-width: 0.5; stroke: red;"); // For debugging 488 489 // Draw the x-axis line 490 SvgLine axisLine = group.addLine(new Point(mDescriptionWidthInPixels, 1), 491 new Point(mImageWidthInPixels - mRightBufferInPixels, 1)); 492// axisLine.setFill(mAxisColor); 493 494 // Add highlights 495 SvgGroup highlightGroup = generateHighlightRegions(mXAxisImageHeightInPixels); 496 if (highlightGroup != null) group.addSubtag(highlightGroup); 497/* 498 // Write the Track name 499 Rectangle textBoundBox = TextUtil.getStringRect(mName, mLabelFont); 500 int xOffset = (int) (mDescriptionWidthInPixels - textBoundBox.getWidth() - (2 * mLinePaddingInPixels) - mMinimumBufferInPixels); 501 int yOffset = (int) (imageHeightInPixels/2 + textBoundBox.getHeight()/2); 502 group.addText(mName, mLabelFont, new Point(xOffset, yOffset)); 503*/ 504 505 determineTickSize(); 506 double lastXTranslation = 0; 507 int yOffset = 2; 508 509 for (Integer tickValue : getTickValues()) 510 { 511 if (tickValue%mMajorTickStep == 0 512 || tickValue == mXAxisStartValue 513 || tickValue == mXAxisEndValue) 514 { 515 yOffset = 6; 516 517 Rectangle textBoundBox = TextUtil.getStringRect(tickValue + "", mLabelFont); 518 double xTranslation = mDescriptionWidthInPixels 519 + ((tickValue - mXAxisStartValue) * getXScalingFactor()) 520 - textBoundBox.getHeight() / 4; 521 522 if (xTranslation - lastXTranslation > 8) 523 { 524 SvgText label = group.addText(tickValue + "", mLabelFont, new Point((int)xTranslation, yOffset + 5)); 525 label.setTransform("rotate(45 " + (int) (xTranslation - 3) + " " + (yOffset + 5) + ")"); 526 lastXTranslation = xTranslation; 527 } 528/* 529 if (xTranslation - lastXTranslation > 8 530 && (tickValue == mXAxisEndValue || xTranslation < mImageWidthInPixels - mRightBufferInPixels - 10)) 531 { 532 double yTranslation = yOffset + layout.getAdvance() + 1; 533 g2.translate(xTranslation, yTranslation); 534 g2.rotate(-Math.PI / 2); 535 g2.drawString(tickValue + "", 0, 0); 536 g2.setTransform(origTransform); 537 lastXTranslation = xTranslation; 538 539 } 540*/ 541 } 542 else if (tickValue%mMinorTickStep == 0) 543 { 544 yOffset = 2; 545 } 546 else 547 { 548 continue; 549 } 550 551 int xOffset = mDescriptionWidthInPixels 552 + (int) ((tickValue - mXAxisStartValue) * getXScalingFactor()); 553 554 SvgLine tickLine = group.addLine(new Point(xOffset, 1), 555 new Point(xOffset, yOffset)); 556// tickLine.setFill(mAxisColor); 557 } 558 559 return svg; 560 561 } 562 563 //-------------------------------------------------------------------------- 564 public void draw(Graphics2D g2, Strand inStrand) 565 { 566 if (inStrand == Strand.FORWARD) mForwardImageMap = new ImageMap(); 567 else mReverseImageMap = new ImageMap(); 568 // TODO: Split mJavascript into forward and reverse? 569 570 // Save settings 571 Paint origPaint = g2.getPaint(); 572 Font origFont = g2.getFont(); 573 574 // Save inital location. 575 AffineTransform origTransform = g2.getTransform(); 576 577 // Enable anti-aliasing 578// g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 579// RenderingHints.VALUE_ANTIALIAS_ON); 580 581 582 583 int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels : 584 mReverseImageHeightInPixels); 585 586 // Paint the background 587 setSize(mImageWidthInPixels, imageHeightInPixels); 588 g2.setPaint(getBackgroundColor()); 589 g2.fill(this); 590 591 // Draw the line separating the description from the alignment 592 g2.setPaint(mAxisColor); 593// g2.drawLine(mDescriptionWidthInPixels - mMinimumBufferInPixels, 0, 594// mDescriptionWidthInPixels - mMinimumBufferInPixels, imageHeightInPixels - 2); 595 g2.fillRect(mDescriptionWidthInPixels - (2 * mMinimumBufferInPixels), 0, 596 mMinimumBufferInPixels, imageHeightInPixels - 2); 597 598 599 // Write the Track name 600 g2.setPaint(mLabelColor); 601 g2.setFont(mLabelFont); 602 FontRenderContext frc = g2.getFontRenderContext(); 603 TextLayout layout = new TextLayout(mName, mLabelFont, frc); 604 Rectangle textBounds = (g2 instanceof SvgGraphics2D ? TextUtil.getStringRect(mName, mLabelFont) : layout.getBounds().getBounds()); 605 int xOffset = (int) (mDescriptionWidthInPixels - textBounds.getWidth()) - 2 * mLinePaddingInPixels - mMinimumBufferInPixels; 606 int yOffset = (int) (imageHeightInPixels/2 + textBounds.getHeight()/2); 607 608 g2.drawString(mName, xOffset, yOffset); 609 610 611 if (mURL != null) 612 { 613 // Set image map data 614 ImageMap imageMap = (inStrand == Strand.FORWARD ? mForwardImageMap : mReverseImageMap); 615 Area area = imageMap.addArea(); 616 area.setHref(mURL); 617 area.setShape(Shape.RECT); 618 619 area.setCoords(xOffset + "," + (yOffset - textBounds.getHeight()) + "," 620 + (xOffset + textBounds.getWidth()) + "," + yOffset); 621 } 622 623 624 625 drawHighlightRegions(g2, imageHeightInPixels); 626 627 drawGenes(g2, inStrand); 628 629 630 631 // Return to orig. location 632 g2.setTransform(origTransform); 633 634 // Restore settings 635 g2.setPaint(origPaint); 636 g2.setFont(origFont); 637 } 638 639 640 //-------------------------------------------------------------------------- 641 public Image getImage(Strand inStrand) 642 { 643 initialize(inStrand); 644 645 int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels : 646 mReverseImageHeightInPixels); 647 648 Frame frame = new Frame(); 649 frame.addNotify(); 650 Image image = frame.createImage(mImageWidthInPixels, imageHeightInPixels); 651 652 Graphics2D g2 = (Graphics2D) image.getGraphics(); 653 draw(g2, inStrand); 654 655 return image; 656 } 657 658 //-------------------------------------------------------------------------- 659 public BufferedImage getBufferedImage(Strand inStrand) 660 { 661 initialize(inStrand); 662 663 int imageHeightInPixels = (inStrand == Strand.FORWARD ? mForwardImageHeightInPixels : 664 mReverseImageHeightInPixels); 665 666 Frame frame = new Frame(); 667 frame.addNotify(); 668 BufferedImage bufferedImage = new BufferedImage(mImageWidthInPixels, 669 imageHeightInPixels, 670 BufferedImage.TYPE_INT_RGB); 671 672 Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics(); 673 draw(g2, inStrand); 674 675 return bufferedImage; 676 } 677 678 //-------------------------------------------------------------------------- 679 public BufferedImage getXAxisBufferedImage() 680 { 681 Frame frame = new Frame(); 682 frame.addNotify(); 683 BufferedImage bufferedImage = new BufferedImage(mImageWidthInPixels, 684 mXAxisImageHeightInPixels, 685 BufferedImage.TYPE_INT_RGB); 686 687 Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics(); 688 drawXAxis(g2); 689 690 return bufferedImage; 691 } 692 693 //-------------------------------------------------------------------------- 694 public void writeImageAsJpeg(OutputStream inStream, Strand inStrand) 695 throws IOException 696 { 697 BufferedImage bufferedImage = getBufferedImage(inStrand); 698 ImageIO_Util.writeBufferedImageAsJpeg(bufferedImage, inStream); 699 } 700 701 //-------------------------------------------------------------------------- 702 public void writeXAxisImageAsJpeg(OutputStream inStream) 703 throws IOException 704 { 705 mXAxisImageHeightInPixels = getLabelWidthInPixels(mXAxisEndValue + "") + 7; 706 707 BufferedImage bufferedImage = getXAxisBufferedImage(); 708 ImageIO_Util.writeBufferedImageAsJpeg(bufferedImage, inStream); 709 } 710 711 //-------------------------------------------------------------------------- 712 public void writeImageAsPng(OutputStream inStream, Strand inStrand) 713 throws IOException 714 { 715 BufferedImage bufferedImage = getBufferedImage(inStrand); 716 ImageIO.write(bufferedImage, "png", inStream); 717 } 718 719 720 //########################################################################## 721 // PRIVATE METHODS 722 //########################################################################## 723 724 //-------------------------------------------------------------------------- 725 private void initialize(Strand inStrand) 726 { 727 List<Gene2D> genes = (inStrand == Strand.FORWARD ? mForwardGenes : mReverseGenes); 728 729 if (genes != null) 730 { 731 for (Gene2D gene : genes) 732 { 733 initializeGene(gene); 734 } 735 } 736 737 // Figure out how many lines high the image will be and set the image height. 738 createLineMap(inStrand); 739 } 740 741 //-------------------------------------------------------------------------- 742 private void initializeGene(Gene2D inGene) 743 { 744 inGene.setDisplayRegion(mXAxisStartValue, mXAxisEndValue); 745 inGene.setXScalingFactor(getXScalingFactor()); 746// inGene.setForwardStrandColor(getForwardStrandColor()); 747// inGene.setReverseStrandColor(getReverseStrandColor()); 748 749 if (mLabelFont != null) 750 { 751 inGene.setLabelFont(mLabelFont); 752 } 753 754 if (mShowId != null) 755 { 756 inGene.setShowId(mShowId); 757 } 758 } 759 760 //-------------------------------------------------------------------------- 761 private boolean areAnyGenesInDisplayRegion(List<Gene2D> inGenes) 762 { 763 boolean result = false; 764 if (inGenes != null) 765 { 766 for (Gene2D gene : inGenes) 767 { 768 gene.setDisplayRegion(mXAxisStartValue, mXAxisEndValue); 769 if (gene.inDisplayRegion()) 770 { 771 result = true; 772 break; 773 } 774 } 775 } 776 777 return result; 778 } 779 780 //-------------------------------------------------------------------------- 781 private double getXScalingFactor() 782 { 783 if (0 == mXScalingFactor) 784 { 785 mXScalingFactor = (double)(mImageWidthInPixels - mDescriptionWidthInPixels - mRightBufferInPixels) 786 /(mXAxisEndValue - mXAxisStartValue + 1); 787 } 788 789 return mXScalingFactor; 790 } 791 792 //-------------------------------------------------------------------------- 793 private int getScaledPosition(int inPosition) 794 { 795 int xOffset = (int) ((inPosition - mXAxisStartValue) * getXScalingFactor()); 796 797 return xOffset; 798 } 799 800 //-------------------------------------------------------------------------- 801 private Gene2D getLastGeneForLevel(int inLevel, List<List<Gene2D>> inLevelLists) 802 { 803 Gene2D outGene = null; 804 805 if (inLevelLists.size() >= inLevel) 806 { 807 List<Gene2D> levelList = inLevelLists.get(inLevel - 1); 808 outGene = levelList.get(levelList.size() - 1); 809 } 810 811 return outGene; 812 } 813 814 //-------------------------------------------------------------------------- 815 private void addGeneToLevel(Gene2D inGene, int inLevel, List<List<Gene2D>> inLevelLists) 816 { 817 List<Gene2D> levelList = null; 818 819 if (inLevelLists.size() < inLevel) 820 { 821 levelList = new ArrayList<Gene2D>(); 822 inLevelLists.add(levelList); 823 } 824 else 825 { 826 levelList = inLevelLists.get(inLevel - 1); 827 } 828 829 levelList.add(inGene); 830 } 831 832 833 //-------------------------------------------------------------------------- 834 private boolean genesAreTooCloseTogether(Gene2D inGene1, Gene2D inGene2) 835 { 836 boolean resultValue = false; 837 838 if (getScaledPosition(inGene1.getRight()) + (mShowId && inGene1.getShowId() ? inGene1.getLabelWidthInPixels() : 0) 839 + mMinimumBufferInPixels > getScaledPosition(inGene2.getLeft())) 840 { 841 resultValue = true; 842 } 843 844 return resultValue; 845 } 846 847 //-------------------------------------------------------------------------- 848 private void createLineMap(Strand inStrand) 849 { 850 Map<Gene2D, String> lineMap = null; 851 List<List<Gene2D>> levelLists = new ArrayList<List<Gene2D>>(); 852 853 List<Gene2D> genes = (inStrand == Strand.FORWARD ? mForwardGenes : mReverseGenes); 854 855 if (genes != null) 856 { 857 Collections.sort(genes); 858 859 lineMap = new HashMap<Gene2D, String>(); 860 861 for (Gene2D gene : genes) 862 { 863 if (gene.inDisplayRegion()) 864 { 865 int level = 1; 866 boolean assigningLevel = true; 867 while (assigningLevel) 868 { 869 Gene2D lastGene = getLastGeneForLevel(level, levelLists); 870 if (lastGene != null 871 && genesAreTooCloseTogether(lastGene, gene)) 872 { 873 level++; 874 } 875 else 876 { 877 addGeneToLevel(gene, level, levelLists); 878 lineMap.put(gene, level + ""); 879 assigningLevel = false; 880 } 881 } 882 } 883 884 } 885 } 886 887 if (inStrand == Strand.FORWARD) 888 { 889 mForwardLineMap = lineMap; 890 mNumForwardLines = levelLists.size(); 891 mForwardLineHeights = determineLineHeights(levelLists); 892// mForwardImageHeightInPixels = mNumForwardLines * (mLineHeightInPixels + mLinePaddingInPixels); 893 mForwardImageHeightInPixels = sum(mForwardLineHeights) + mForwardLineHeights.size() * mLinePaddingInPixels; 894 } 895 else 896 { 897 mReverseLineMap = lineMap; 898 mNumReverseLines = levelLists.size(); 899 mReverseLineHeights = determineLineHeights(levelLists); 900// mReverseImageHeightInPixels = mNumReverseLines * (mLineHeightInPixels + mLinePaddingInPixels); 901 mReverseImageHeightInPixels = sum(mReverseLineHeights) + mReverseLineHeights.size() * mLinePaddingInPixels; 902 } 903 904 } 905 906 907 //-------------------------------------------------------------------------- 908 private int sum(List<Integer> inValues) 909 { 910 int sum = 0; 911 912 if (CollectionUtil.hasValues(inValues)) 913 { 914 for (Integer value : inValues) 915 { 916 sum += value; 917 } 918 } 919 920 return sum; 921 } 922 923 //-------------------------------------------------------------------------- 924 private List<Integer> determineLineHeights(List<List<Gene2D>> inLevelLists) 925 { 926 List<Integer> lineHeights = new ArrayList<Integer>(inLevelLists.size()); 927 928 for (List<Gene2D> level : inLevelLists) 929 { 930 double maxLineHeight = 0; 931 for (Gene2D gene : level) 932 { 933 if (gene.getHeightInPixels() > maxLineHeight) maxLineHeight = gene.getHeightInPixels(); 934 } 935 936 lineHeights.add((int)maxLineHeight); 937 } 938 939 940 return lineHeights; 941 } 942 943 //-------------------------------------------------------------------------- 944 private int getYOffsetForLine(int inLine, Strand inStrand) 945 { 946 int yOffset = 0; 947 948 List<Integer> lineHeights = (inStrand == Strand.FORWARD ? mForwardLineHeights : mReverseLineHeights); 949 for (int i = 0; i < inLine; i++) 950 { 951 yOffset -= lineHeights.get(i); 952 } 953 954 yOffset -= inLine * mLinePaddingInPixels; 955 956 return yOffset; 957 } 958 959 //-------------------------------------------------------------------------- 960 private void drawHighlightRegions(Graphics2D g2, int inImageHeight) 961 { 962 if (mHighlightRegions != null) 963 { 964 for (HighlightRegion region : mHighlightRegions) 965 { 966 int xOffset = mDescriptionWidthInPixels + (int)((region.getStart() - mXAxisStartValue) * getXScalingFactor()); 967 int width = (int) ((region.getEnd() - region.getStart() - 1) * getXScalingFactor()); 968 if (width < 1) width = 1; 969 g2.setPaint(mHighlightColor); 970 971 g2.fillRect(xOffset, 0, width, inImageHeight); 972 } 973 974 } 975 } 976 977 //-------------------------------------------------------------------------- 978 private SvgGroup generateHighlightRegions(int inImageHeight) 979 { 980 SvgGroup highlightGroup = null; 981 if (mHighlightRegions != null) 982 { 983 highlightGroup = new SvgGroup(); 984 for (HighlightRegion region : mHighlightRegions) 985 { 986 int xOffset = mDescriptionWidthInPixels + (int)((region.getStart() - mXAxisStartValue) * getXScalingFactor()); 987 int width = (int) ((region.getEnd() - region.getStart() - 1) * getXScalingFactor()); 988 if (width < 1) width = 1; 989 highlightGroup.addRect(new Rectangle(new Point(xOffset, 0), 990 new Dimension(width, inImageHeight))).setFill(mHighlightColor); 991 } 992 } 993 994 return highlightGroup; 995 } 996 997 //-------------------------------------------------------------------------- 998 private SvgGroup generateGenes(Strand inStrand) 999 { 1000 // Move to the bottom left of the genomic display 1001 int imageHeightInPixels = 0; 1002 List<Gene2D> genes = null; 1003 Map<Gene2D, String> lineMap = null; 1004 if (inStrand == Strand.FORWARD) 1005 { 1006 imageHeightInPixels = mForwardImageHeightInPixels; 1007 genes = mForwardGenes; 1008 lineMap = mForwardLineMap; 1009 } 1010 else 1011 { 1012 imageHeightInPixels = mReverseImageHeightInPixels; 1013 genes = mReverseGenes; 1014 lineMap = mReverseLineMap; 1015 } 1016 1017 SvgGroup group = new SvgGroup().setTransform("translate(" + mDescriptionWidthInPixels + ", " + imageHeightInPixels + ")"); 1018 1019 1020 if (genes != null) 1021 { 1022 for (Gene2D gene : genes) 1023 { 1024 if (gene.inDisplayRegion()) 1025 { 1026 1027 SvgLink svgLink = null; 1028 List<Link> urls = gene.getURLs(); 1029 if (CollectionUtil.hasValues(urls)) 1030 { 1031 if (1 == urls.size()) 1032 { 1033 svgLink = new SvgLink(urls.get(0).getURL()); 1034 } 1035 else 1036 { 1037 PopupMenuJS menu = new PopupMenuJS(); 1038 menu.setMenuTitle(gene.getId()); 1039 for (Link link : urls) 1040 { 1041 menu.addLink(link); 1042 } 1043 1044 // Add the menu to the track's javascript 1045 mJavascript.addContent(menu.generateMenuJavascript()); 1046 svgLink = new SvgLink(menu.getDisplayMenuJavascript()); 1047 } 1048 } 1049 1050 SvgNode svgNode = null; 1051 if (svgLink != null) 1052 { 1053 svgNode = svgLink; 1054 } 1055 else 1056 { 1057 svgNode = new SvgGroup(); 1058 } 1059 group.addSubtag(svgNode); 1060 1061 // Move to gene location 1062 1063 // What line is the gene on? Move to upper left of gene. 1064 int line = Integer.parseInt(lineMap.get(gene)); 1065 if (inStrand == Strand.REVERSE) 1066 { 1067 line = mNumReverseLines - line + 1; 1068 } 1069 1070 //XXXX int xOffset = (int) ((gene.getStartLocation() - mXAxisStartValue) * getXScalingFactor()); 1071 int geneLeft = (int) (gene.getLeft() * mXScalingFactor); 1072 int bound = (int) (mXAxisStartValue * mXScalingFactor); 1073 int xOffset = geneLeft - bound; 1074// int xOffset = (int) ((gene.getLeft() - mXAxisStartValue) * getXScalingFactor()); 1075 // Don't back up into the description region. 1076 if (xOffset < 0) xOffset = 0; 1077 /* // Don't go off the right side of the display region. 1078 if (xOffset > mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels) 1079 { 1080 xOffset = mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels; 1081 } 1082 */ 1083 1084// int yOffset = -line * (mLineHeightInPixels + mLinePaddingInPixels); 1085 int yOffset = getYOffsetForLine(line, inStrand); 1086 1087// SvgGroup geneGroup = group.addGroup().setTransform(); 1088 1089 svgNode.setTransform("translate(" + xOffset + ", " + yOffset + ")"); 1090 1091 svgNode.addSubtag(gene.toSVG()); 1092 } 1093 } 1094 } 1095 1096 return group; 1097 } 1098 1099 //-------------------------------------------------------------------------- 1100 private void drawGenes(Graphics2D g2, Strand inStrand) 1101 { 1102 TooltipJS tooltip = new TooltipJS(); 1103 1104 int imageHeightInPixels = 0; 1105 List<Gene2D> genes = null; 1106 Map<Gene2D, String> lineMap = null; 1107 ImageMap imageMap = null; 1108 if (inStrand == Strand.FORWARD) 1109 { 1110 imageHeightInPixels = mForwardImageHeightInPixels; 1111 genes = mForwardGenes; 1112 lineMap = mForwardLineMap; 1113 imageMap = mForwardImageMap; 1114 } 1115 else 1116 { 1117 imageHeightInPixels = mReverseImageHeightInPixels; 1118 genes = mReverseGenes; 1119 lineMap = mReverseLineMap; 1120 imageMap = mReverseImageMap; 1121 } 1122 1123 // Move to the bottom left of the genomic display 1124 g2.translate(mDescriptionWidthInPixels, imageHeightInPixels); 1125 1126 if (genes != null) 1127 { 1128 // Save initial location. 1129 AffineTransform origTransform = g2.getTransform(); 1130 1131 for (Gene2D gene : genes) 1132 { 1133 if (gene.inDisplayRegion()) 1134 { 1135 // Dynamically set the gene model color based on the mapped %id? 1136 if (mPctIdColorScale != null 1137 && null == gene.getColor() 1138 && gene.getSeqMappingCoverage() != null 1139 && gene.getSeqMappingCoverage().getPercentIdentity() != null) 1140 { 1141 gene.setColor(mPctIdColorScale.assignColorForValue(gene.getSeqMappingCoverage().getPercentIdentity() / 100)); 1142 } 1143 1144 // Move to gene location 1145 1146 // What line is the gene on? Move to upper left of gene. 1147 int line = Integer.parseInt(lineMap.get(gene)); 1148 if (inStrand == Strand.REVERSE) 1149 { 1150 line = mNumReverseLines - line + 1; 1151 } 1152 1153 //XXXX int xOffset = (int) ((gene.getStartLocation() - mXAxisStartValue) * getXScalingFactor()); 1154 int geneLeft = (int) (gene.getLeft() * mXScalingFactor); 1155 int bound = (int) (mXAxisStartValue * mXScalingFactor); 1156 int xOffset = geneLeft - bound; 1157// int xOffset = (int) ((gene.getLeft() - mXAxisStartValue) * getXScalingFactor()); 1158 // Don't back up into the description region. 1159 if (xOffset < 0) xOffset = 0; 1160 /* // Don't go off the right side of the display region. 1161 if (xOffset > mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels) 1162 { 1163 xOffset = mImageWidthInPixels - mDescriptionWidthInPixels - mMinimumBufferInPixels; 1164 } 1165 */ 1166 1167// int yOffset = -line * (mLineHeightInPixels + mLinePaddingInPixels); 1168 int yOffset = getYOffsetForLine(line, inStrand); 1169 1170 g2.translate(xOffset, yOffset); 1171 1172 gene.draw(g2); 1173 1174 // Return to orig. location 1175 g2.setTransform(origTransform); 1176 1177 // Set image map data 1178 Area area = imageMap.addArea(); 1179 setAreaURL(area, gene); 1180 1181 tooltip.addTooltip(area, gene.getTooltipContent()); 1182 area.setShape(Shape.RECT); 1183 1184 int x1 = mDescriptionWidthInPixels + xOffset; 1185 if (x1 < mDescriptionWidthInPixels) x1 = mDescriptionWidthInPixels; 1186 int x2 = mDescriptionWidthInPixels + xOffset + gene.getScaledWidthInPixels() 1187 + (mShowId ? gene.getLabelWidthInPixels() : 0); 1188 if (x2 > mImageWidthInPixels) 1189 { 1190 x2 = mImageWidthInPixels; 1191 } 1192 area.setCoords(x1 + "," + (imageHeightInPixels + yOffset) + "," 1193 + x2 + "," + (imageHeightInPixels + yOffset + gene.getHeightInPixels() - 1)); 1194 } 1195 } 1196 } 1197 } 1198 1199 //-------------------------------------------------------------------------- 1200 private void setAreaURL(Area inArea, Gene2D inGene) 1201 { 1202 List<Link> urls = inGene.getURLs(); 1203 if (CollectionUtil.hasValues(urls)) 1204 { 1205 if (1 == urls.size()) 1206 { 1207 Link link = urls.get(0); 1208 inArea.setHref(link.getURL()); 1209 } 1210 else 1211 { 1212 PopupMenuJS menu = new PopupMenuJS(); 1213 menu.setMenuTitle(inGene.getId()); 1214 for (Link link : urls) 1215 { 1216 menu.addLink(link); 1217 } 1218 1219 // Add the menu to the track's javascript 1220 mJavascript.addContent(menu.generateMenuJavascript()); 1221 1222 inArea.setHref(menu.getDisplayMenuJavascript()); 1223 } 1224 } 1225 } 1226 1227 //-------------------------------------------------------------------------- 1228 public void drawXAxis(Graphics2D g2) 1229 { 1230 // Save settings 1231 Paint origPaint = g2.getPaint(); 1232 Font origFont = g2.getFont(); 1233 1234 // Save inital location. 1235 AffineTransform origTransform = g2.getTransform(); 1236 1237 // Enable anti-aliasing 1238 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 1239 RenderingHints.VALUE_ANTIALIAS_ON); 1240 1241 // Paint the background 1242 g2.setPaint(Color.white); 1243 g2.fillRect(0, 0, mImageWidthInPixels, mXAxisImageHeightInPixels); 1244 1245 1246 drawHighlightRegions(g2, mXAxisImageHeightInPixels); 1247 1248 // Draw the x-axis line 1249 g2.setPaint(mAxisColor); 1250 g2.drawLine(mDescriptionWidthInPixels, 0, 1251 mImageWidthInPixels - mRightBufferInPixels, 0); 1252 1253 determineTickSize(); 1254 double lastXTranslation = 0; 1255 int yOffset = 2; 1256 1257 for (Integer tickValue : getTickValues()) 1258 { 1259 if (tickValue%mMajorTickStep == 0 1260 || tickValue == mXAxisStartValue 1261 || tickValue == mXAxisEndValue) 1262 { 1263 yOffset = 4; 1264 1265 g2.setFont(mLabelFont); 1266 FontRenderContext frc = g2.getFontRenderContext(); 1267 TextLayout layout = new TextLayout(tickValue + "", mLabelFont, frc); 1268 double xTranslation = mDescriptionWidthInPixels 1269 + ((tickValue - mXAxisStartValue) * getXScalingFactor()) 1270 + layout.getBounds().getHeight() / 2; 1271 1272 if (xTranslation - lastXTranslation > 8 1273 && (tickValue == mXAxisEndValue || xTranslation < mImageWidthInPixels - mRightBufferInPixels - 10)) 1274 { 1275 double yTranslation = yOffset + layout.getAdvance() + 1; 1276 g2.translate(xTranslation, yTranslation); 1277 g2.rotate(-Math.PI / 2); 1278 g2.drawString(tickValue + "", 0, 0); 1279 g2.setTransform(origTransform); 1280 lastXTranslation = xTranslation; 1281 } 1282 } 1283 else if (tickValue%mMinorTickStep == 0) 1284 { 1285 yOffset = 2; 1286 } 1287 else 1288 { 1289 continue; 1290 } 1291 1292 int xOffset = mDescriptionWidthInPixels 1293 + (int) ((tickValue - mXAxisStartValue) * getXScalingFactor()); 1294 g2.drawLine(xOffset, 0, 1295 xOffset, yOffset); 1296 } 1297 1298 1299 // Return to orig. location 1300 g2.setTransform(origTransform); 1301 1302 // Restore settings 1303 g2.setPaint(origPaint); 1304 g2.setFont(origFont); 1305 } 1306 1307 //-------------------------------------------------------------------------- 1308 private void determineTickSize() 1309 { 1310 int xAxisWidth = mXAxisEndValue - mXAxisStartValue + 1; 1311 String widthString = xAxisWidth + ""; 1312 mMajorTickStep = (int) Math.pow(10, widthString.length() - 1); 1313 mMinorTickStep = mMajorTickStep / 10; 1314 } 1315 1316 //-------------------------------------------------------------------------- 1317 private List<Integer> getTickValues() 1318 { 1319 List<Integer> tickValues = new ArrayList<Integer>(); 1320 1321 // First add the major ticks 1322 tickValues.add(mXAxisStartValue); 1323 1324 for (int tickValue = (int) (Math.ceil(mXAxisStartValue / mMajorTickStep) * mMajorTickStep); 1325 tickValue < mXAxisEndValue; 1326 tickValue += mMajorTickStep) 1327 { 1328 if (tickValue > mXAxisStartValue) 1329 { 1330 tickValues.add(tickValue); 1331 } 1332 } 1333 1334 tickValues.add(mXAxisEndValue); 1335 1336 // Now add the minor ticks 1337 for (int tickValue = (int) (Math.ceil(mXAxisStartValue / mMinorTickStep) * mMinorTickStep); 1338 tickValue < mXAxisEndValue; 1339 tickValue += mMinorTickStep) 1340 { 1341 if (tickValue%mMajorTickStep != 0 1342 && tickValue != mXAxisStartValue) 1343 { 1344 tickValues.add(tickValue); 1345 } 1346 } 1347 1348 return tickValues; 1349 } 1350 1351 //-------------------------------------------------------------------------- 1352 public int getLabelWidthInPixels(String inLabel) 1353 { 1354 int widthInPixels = 0; 1355 1356 if (inLabel != null 1357 && inLabel.length() > 0) 1358 { 1359 TextLayout layout = new TextLayout(inLabel, mLabelFont, sFRC); 1360 widthInPixels = (int) layout.getBounds().getWidth() + 1; 1361 } 1362 1363 return widthInPixels; 1364 } 1365 1366 1367 1368 1369 //-------------------------------------------------------------------------- 1370 private class HighlightRegion 1371 { 1372 private int mStart; 1373 private int mEnd; 1374 1375 //----------------------------------------------------------------------- 1376 public HighlightRegion(int inStart, int inEnd) 1377 { 1378 if (inEnd < inStart) 1379 { 1380 mStart = inEnd; 1381 mEnd = inStart; 1382 } 1383 else 1384 { 1385 mStart = inStart; 1386 mEnd = inEnd; 1387 } 1388 } 1389 1390 //----------------------------------------------------------------------- 1391 public int getStart() 1392 { 1393 return mStart; 1394 } 1395 1396 //----------------------------------------------------------------------- 1397 public int getEnd() 1398 { 1399 return mEnd; 1400 } 1401 } 1402 1403} 1404