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