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