1 /* 2 * Jalview - A Sequence Alignment Editor and Viewer (2.11.1.4) 3 * Copyright (C) 2021 The Jalview Authors 4 * 5 * This file is part of Jalview. 6 * 7 * Jalview is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation, either version 3 10 * of the License, or (at your option) any later version. 11 * 12 * Jalview is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty 14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 15 * PURPOSE. See the GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>. 19 * The Jalview Authors are detailed in the 'AUTHORS' file. 20 */ 21 package jalview.datamodel; 22 23 import jalview.analysis.AAFrequency; 24 import jalview.analysis.Conservation; 25 import jalview.renderer.ResidueShader; 26 import jalview.renderer.ResidueShaderI; 27 import jalview.schemes.ColourSchemeI; 28 29 import java.awt.Color; 30 import java.beans.PropertyChangeListener; 31 import java.beans.PropertyChangeSupport; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 import java.util.Map; 36 37 /** 38 * Collects a set contiguous ranges on a set of sequences 39 * 40 * @author $author$ 41 * @version $Revision$ 42 */ 43 public class SequenceGroup implements AnnotatedCollectionI 44 { 45 // TODO ideally this event notification functionality should be separated into 46 // a 47 // subclass of ViewportProperties similarly to ViewportRanges. Done here as 48 // quick fix for JAL-2665 49 public static final String SEQ_GROUP_CHANGED = "Sequence group changed"; 50 51 protected PropertyChangeSupport changeSupport = new PropertyChangeSupport( 52 this); 53 addPropertyChangeListener(PropertyChangeListener listener)54 public void addPropertyChangeListener(PropertyChangeListener listener) 55 { 56 changeSupport.addPropertyChangeListener(listener); 57 } 58 removePropertyChangeListener(PropertyChangeListener listener)59 public void removePropertyChangeListener(PropertyChangeListener listener) 60 { 61 changeSupport.removePropertyChangeListener(listener); 62 } 63 // end of event notification functionality initialisation 64 65 String groupName; 66 67 String description; 68 69 Conservation conserve; 70 71 boolean displayBoxes = true; 72 73 boolean displayText = true; 74 75 boolean colourText = false; 76 77 /** 78 * True if the group is defined as a group on the alignment, false if it is 79 * just a selection. 80 */ 81 boolean isDefined = false; 82 83 /** 84 * after Olivier's non-conserved only character display 85 */ 86 boolean showNonconserved = false; 87 88 /** 89 * group members 90 */ 91 private List<SequenceI> sequences; 92 93 /** 94 * representative sequence for this group (if any) 95 */ 96 private SequenceI seqrep = null; 97 98 int width = -1; 99 100 /** 101 * Colourscheme applied to group if any 102 */ 103 public ResidueShaderI cs; 104 105 /** 106 * start column (base 0) 107 */ 108 private int startRes = 0; 109 110 /** 111 * end column (base 0) 112 */ 113 private int endRes = 0; 114 115 public Color outlineColour = Color.black; 116 117 public Color idColour = null; 118 119 public int thresholdTextColour = 0; 120 121 public Color textColour = Color.black; 122 123 public Color textColour2 = Color.white; 124 125 /** 126 * consensus calculation property 127 */ 128 private boolean ignoreGapsInConsensus = true; 129 130 /** 131 * consensus calculation property 132 */ 133 private boolean showSequenceLogo = false; 134 135 /** 136 * flag indicating if logo should be rendered normalised 137 */ 138 private boolean normaliseSequenceLogo; 139 140 /* 141 * visibility of rows or represented rows covered by group 142 */ 143 private boolean hidereps = false; 144 145 /* 146 * visibility of columns intersecting this group 147 */ 148 private boolean hidecols = false; 149 150 AlignmentAnnotation consensus = null; 151 152 AlignmentAnnotation conservation = null; 153 154 private boolean showConsensusHistogram; 155 156 private AnnotatedCollectionI context; 157 158 /** 159 * Creates a new SequenceGroup object. 160 */ SequenceGroup()161 public SequenceGroup() 162 { 163 groupName = "JGroup:" + this.hashCode(); 164 cs = new ResidueShader(); 165 sequences = new ArrayList<>(); 166 } 167 168 /** 169 * Creates a new SequenceGroup object. 170 * 171 * @param sequences 172 * @param groupName 173 * @param scheme 174 * @param displayBoxes 175 * @param displayText 176 * @param colourText 177 * @param start 178 * first column of group 179 * @param end 180 * last column of group 181 */ SequenceGroup(List<SequenceI> sequences, String groupName, ColourSchemeI scheme, boolean displayBoxes, boolean displayText, boolean colourText, int start, int end)182 public SequenceGroup(List<SequenceI> sequences, String groupName, 183 ColourSchemeI scheme, boolean displayBoxes, boolean displayText, 184 boolean colourText, int start, int end) 185 { 186 this(); 187 this.sequences = sequences; 188 this.groupName = groupName; 189 this.displayBoxes = displayBoxes; 190 this.displayText = displayText; 191 this.colourText = colourText; 192 this.cs = new ResidueShader(scheme); 193 startRes = start; 194 endRes = end; 195 recalcConservation(); 196 } 197 198 /** 199 * copy constructor 200 * 201 * @param seqsel 202 */ SequenceGroup(SequenceGroup seqsel)203 public SequenceGroup(SequenceGroup seqsel) 204 { 205 this(); 206 if (seqsel != null) 207 { 208 sequences = new ArrayList<>(); 209 sequences.addAll(seqsel.sequences); 210 if (seqsel.groupName != null) 211 { 212 groupName = new String(seqsel.groupName); 213 } 214 displayBoxes = seqsel.displayBoxes; 215 displayText = seqsel.displayText; 216 colourText = seqsel.colourText; 217 218 startRes = seqsel.startRes; 219 endRes = seqsel.endRes; 220 cs = new ResidueShader((ResidueShader) seqsel.cs); 221 if (seqsel.description != null) 222 { 223 description = new String(seqsel.description); 224 } 225 hidecols = seqsel.hidecols; 226 hidereps = seqsel.hidereps; 227 showNonconserved = seqsel.showNonconserved; 228 showSequenceLogo = seqsel.showSequenceLogo; 229 normaliseSequenceLogo = seqsel.normaliseSequenceLogo; 230 showConsensusHistogram = seqsel.showConsensusHistogram; 231 idColour = seqsel.idColour; 232 outlineColour = seqsel.outlineColour; 233 seqrep = seqsel.seqrep; 234 textColour = seqsel.textColour; 235 textColour2 = seqsel.textColour2; 236 thresholdTextColour = seqsel.thresholdTextColour; 237 width = seqsel.width; 238 ignoreGapsInConsensus = seqsel.ignoreGapsInConsensus; 239 if (seqsel.conserve != null) 240 { 241 recalcConservation(); // safer than 242 // aaFrequency = (Vector) seqsel.aaFrequency.clone(); // ?? 243 } 244 } 245 } 246 247 /** 248 * Constructor that copies the given list of sequences 249 * 250 * @param seqs 251 */ SequenceGroup(List<SequenceI> seqs)252 public SequenceGroup(List<SequenceI> seqs) 253 { 254 this(); 255 this.sequences.addAll(seqs); 256 } 257 isShowSequenceLogo()258 public boolean isShowSequenceLogo() 259 { 260 return showSequenceLogo; 261 } 262 getSelectionAsNewSequences(AlignmentI align)263 public SequenceI[] getSelectionAsNewSequences(AlignmentI align) 264 { 265 int iSize = sequences.size(); 266 SequenceI[] seqs = new SequenceI[iSize]; 267 SequenceI[] inorder = getSequencesInOrder(align); 268 269 for (int i = 0, ipos = 0; i < inorder.length; i++) 270 { 271 SequenceI seq = inorder[i]; 272 273 seqs[ipos] = seq.getSubSequence(startRes, endRes + 1); 274 if (seqs[ipos] != null) 275 { 276 seqs[ipos].setDescription(seq.getDescription()); 277 seqs[ipos].setDBRefs(seq.getDBRefs()); 278 seqs[ipos].setSequenceFeatures(seq.getSequenceFeatures()); 279 if (seq.getDatasetSequence() != null) 280 { 281 seqs[ipos].setDatasetSequence(seq.getDatasetSequence()); 282 } 283 284 if (seq.getAnnotation() != null) 285 { 286 AlignmentAnnotation[] alann = align.getAlignmentAnnotation(); 287 // Only copy annotation that is either a score or referenced by the 288 // alignment's annotation vector 289 for (int a = 0; a < seq.getAnnotation().length; a++) 290 { 291 AlignmentAnnotation tocopy = seq.getAnnotation()[a]; 292 if (alann != null) 293 { 294 boolean found = false; 295 for (int pos = 0; pos < alann.length; pos++) 296 { 297 if (alann[pos] == tocopy) 298 { 299 found = true; 300 break; 301 } 302 } 303 if (!found) 304 { 305 continue; 306 } 307 } 308 AlignmentAnnotation newannot = new AlignmentAnnotation( 309 seq.getAnnotation()[a]); 310 newannot.restrict(startRes, endRes); 311 newannot.setSequenceRef(seqs[ipos]); 312 newannot.adjustForAlignment(); 313 seqs[ipos].addAlignmentAnnotation(newannot); 314 } 315 } 316 ipos++; 317 } 318 else 319 { 320 iSize--; 321 } 322 } 323 if (iSize != inorder.length) 324 { 325 SequenceI[] nseqs = new SequenceI[iSize]; 326 System.arraycopy(seqs, 0, nseqs, 0, iSize); 327 seqs = nseqs; 328 } 329 return seqs; 330 331 } 332 333 /** 334 * If sequence ends in gaps, the end residue can be correctly calculated here 335 * 336 * @param seq 337 * SequenceI 338 * @return int 339 */ findEndRes(SequenceI seq)340 public int findEndRes(SequenceI seq) 341 { 342 int eres = 0; 343 char ch; 344 345 for (int j = 0; j < endRes + 1 && j < seq.getLength(); j++) 346 { 347 ch = seq.getCharAt(j); 348 if (!jalview.util.Comparison.isGap((ch))) 349 { 350 eres++; 351 } 352 } 353 354 if (eres > 0) 355 { 356 eres += seq.getStart() - 1; 357 } 358 359 return eres; 360 } 361 362 @Override getSequences()363 public List<SequenceI> getSequences() 364 { 365 return sequences; 366 } 367 368 @Override getSequences( Map<SequenceI, SequenceCollectionI> hiddenReps)369 public List<SequenceI> getSequences( 370 Map<SequenceI, SequenceCollectionI> hiddenReps) 371 { 372 if (hiddenReps == null) 373 { 374 // TODO: need a synchronizedCollection here ? 375 return sequences; 376 } 377 else 378 { 379 List<SequenceI> allSequences = new ArrayList<>(); 380 for (SequenceI seq : sequences) 381 { 382 allSequences.add(seq); 383 if (hiddenReps.containsKey(seq)) 384 { 385 SequenceCollectionI hsg = hiddenReps.get(seq); 386 for (SequenceI seq2 : hsg.getSequences()) 387 { 388 if (seq2 != seq && !allSequences.contains(seq2)) 389 { 390 allSequences.add(seq2); 391 } 392 } 393 } 394 } 395 396 return allSequences; 397 } 398 } 399 getSequencesAsArray( Map<SequenceI, SequenceCollectionI> map)400 public SequenceI[] getSequencesAsArray( 401 Map<SequenceI, SequenceCollectionI> map) 402 { 403 List<SequenceI> tmp = getSequences(map); 404 if (tmp == null) 405 { 406 return null; 407 } 408 return tmp.toArray(new SequenceI[tmp.size()]); 409 } 410 411 /** 412 * DOCUMENT ME! 413 * 414 * @param col 415 * DOCUMENT ME! 416 * 417 * @return DOCUMENT ME! 418 */ adjustForRemoveLeft(int col)419 public boolean adjustForRemoveLeft(int col) 420 { 421 // return value is true if the group still exists 422 if (startRes >= col) 423 { 424 startRes = startRes - col; 425 } 426 427 if (endRes >= col) 428 { 429 endRes = endRes - col; 430 431 if (startRes > endRes) 432 { 433 startRes = 0; 434 } 435 } 436 else 437 { 438 // must delete this group!! 439 return false; 440 } 441 442 return true; 443 } 444 445 /** 446 * DOCUMENT ME! 447 * 448 * @param col 449 * DOCUMENT ME! 450 * 451 * @return DOCUMENT ME! 452 */ adjustForRemoveRight(int col)453 public boolean adjustForRemoveRight(int col) 454 { 455 if (startRes > col) 456 { 457 // delete this group 458 return false; 459 } 460 461 if (endRes >= col) 462 { 463 endRes = col; 464 } 465 466 return true; 467 } 468 469 /** 470 * DOCUMENT ME! 471 * 472 * @return DOCUMENT ME! 473 */ getName()474 public String getName() 475 { 476 return groupName; 477 } 478 getDescription()479 public String getDescription() 480 { 481 return description; 482 } 483 484 /** 485 * DOCUMENT ME! 486 * 487 * @param name 488 * DOCUMENT ME! 489 */ setName(String name)490 public void setName(String name) 491 { 492 groupName = name; 493 // TODO: URGENT: update dependent objects (annotation row) 494 } 495 setDescription(String desc)496 public void setDescription(String desc) 497 { 498 description = desc; 499 } 500 501 /** 502 * DOCUMENT ME! 503 * 504 * @return DOCUMENT ME! 505 */ getConservation()506 public Conservation getConservation() 507 { 508 return conserve; 509 } 510 511 /** 512 * DOCUMENT ME! 513 * 514 * @param c 515 * DOCUMENT ME! 516 */ setConservation(Conservation c)517 public void setConservation(Conservation c) 518 { 519 conserve = c; 520 } 521 522 /** 523 * Add s to this sequence group. If aligment sequence is already contained in 524 * group, it will not be added again, but recalculation may happen if the flag 525 * is set. 526 * 527 * @param s 528 * alignment sequence to be added 529 * @param recalc 530 * true means Group's conservation should be recalculated 531 */ addSequence(SequenceI s, boolean recalc)532 public void addSequence(SequenceI s, boolean recalc) 533 { 534 synchronized (sequences) 535 { 536 if (s != null && !sequences.contains(s)) 537 { 538 sequences.add(s); 539 changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, 540 sequences.size() - 1, sequences.size()); 541 } 542 543 if (recalc) 544 { 545 recalcConservation(); 546 } 547 } 548 } 549 550 /** 551 * Max Gaps Threshold (percent) for performing a conservation calculation 552 */ 553 private int consPercGaps = 25; 554 555 /** 556 * @return Max Gaps Threshold for performing a conservation calculation 557 */ getConsPercGaps()558 public int getConsPercGaps() 559 { 560 return consPercGaps; 561 } 562 563 /** 564 * set Max Gaps Threshold (percent) for performing a conservation calculation 565 * 566 * @param consPercGaps 567 */ setConsPercGaps(int consPercGaps)568 public void setConsPercGaps(int consPercGaps) 569 { 570 this.consPercGaps = consPercGaps; 571 } 572 573 /** 574 * calculate residue conservation and colourschemes for group - but only if 575 * necessary. returns true if the calculation resulted in a visible change to 576 * group 577 */ recalcConservation()578 public boolean recalcConservation() 579 { 580 return recalcConservation(false); 581 } 582 583 /** 584 * calculate residue conservation for group - but only if necessary. returns 585 * true if the calculation resulted in a visible change to group 586 * 587 * @param defer 588 * when set, colourschemes for this group are not refreshed after 589 * recalculation 590 */ recalcConservation(boolean defer)591 public boolean recalcConservation(boolean defer) 592 { 593 if (cs == null && consensus == null && conservation == null) 594 { 595 return false; 596 } 597 // TODO: try harder to detect changes in state in order to minimise 598 // recalculation effort 599 boolean upd = false; 600 try 601 { 602 ProfilesI cnsns = AAFrequency.calculate(sequences, startRes, 603 endRes + 1, showSequenceLogo); 604 if (consensus != null) 605 { 606 _updateConsensusRow(cnsns, sequences.size()); 607 upd = true; 608 } 609 if (cs != null) 610 { 611 cs.setConsensus(cnsns); 612 upd = true; 613 } 614 615 if ((conservation != null) 616 || (cs != null && cs.conservationApplied())) 617 { 618 Conservation c = new Conservation(groupName, sequences, startRes, 619 endRes + 1); 620 c.calculate(); 621 c.verdict(false, consPercGaps); 622 if (conservation != null) 623 { 624 _updateConservationRow(c); 625 } 626 if (cs != null) 627 { 628 if (cs.conservationApplied()) 629 { 630 cs.setConservation(c); 631 } 632 } 633 // eager update - will cause a refresh of overview regardless 634 upd = true; 635 } 636 if (cs != null && !defer) 637 { 638 // TODO: JAL-2034 should cs.alignmentChanged modify return state 639 cs.alignmentChanged(context != null ? context : this, null); 640 return true; 641 } 642 else 643 { 644 return upd; 645 } 646 } catch (java.lang.OutOfMemoryError err) 647 { 648 // TODO: catch OOM 649 System.out.println("Out of memory loading groups: " + err); 650 } 651 return upd; 652 } 653 _updateConservationRow(Conservation c)654 private void _updateConservationRow(Conservation c) 655 { 656 if (conservation == null) 657 { 658 getConservation(); 659 } 660 // update Labels 661 conservation.label = "Conservation for " + getName(); 662 conservation.description = "Conservation for group " + getName() 663 + " less than " + consPercGaps + "% gaps"; 664 // preserve width if already set 665 int aWidth = (conservation.annotations != null) 666 ? (endRes < conservation.annotations.length 667 ? conservation.annotations.length 668 : endRes + 1) 669 : endRes + 1; 670 conservation.annotations = null; 671 conservation.annotations = new Annotation[aWidth]; // should be alignment 672 // width 673 c.completeAnnotations(conservation, null, startRes, endRes + 1); 674 } 675 676 public ProfilesI consensusData = null; 677 _updateConsensusRow(ProfilesI cnsns, long nseq)678 private void _updateConsensusRow(ProfilesI cnsns, long nseq) 679 { 680 if (consensus == null) 681 { 682 getConsensus(); 683 } 684 consensus.label = "Consensus for " + getName(); 685 consensus.description = "Percent Identity"; 686 consensusData = cnsns; 687 // preserve width if already set 688 int aWidth = (consensus.annotations != null) 689 ? (endRes < consensus.annotations.length 690 ? consensus.annotations.length 691 : endRes + 1) 692 : endRes + 1; 693 consensus.annotations = null; 694 consensus.annotations = new Annotation[aWidth]; // should be alignment width 695 696 AAFrequency.completeConsensus(consensus, cnsns, startRes, endRes + 1, 697 ignoreGapsInConsensus, showSequenceLogo, nseq); // TODO: setting 698 // container 699 // for 700 // ignoreGapsInConsensusCalculation); 701 } 702 703 /** 704 * @param s 705 * sequence to either add or remove from group 706 * @param recalc 707 * flag passed to delete/addSequence to indicate if group properties 708 * should be recalculated 709 */ addOrRemove(SequenceI s, boolean recalc)710 public void addOrRemove(SequenceI s, boolean recalc) 711 { 712 synchronized (sequences) 713 { 714 if (sequences.contains(s)) 715 { 716 deleteSequence(s, recalc); 717 } 718 else 719 { 720 addSequence(s, recalc); 721 } 722 } 723 } 724 725 /** 726 * remove 727 * 728 * @param s 729 * to be removed 730 * @param recalc 731 * true means recalculate conservation 732 */ deleteSequence(SequenceI s, boolean recalc)733 public void deleteSequence(SequenceI s, boolean recalc) 734 { 735 synchronized (sequences) 736 { 737 sequences.remove(s); 738 changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, 739 sequences.size() + 1, sequences.size()); 740 741 if (recalc) 742 { 743 recalcConservation(); 744 } 745 } 746 } 747 748 /** 749 * 750 * 751 * @return the first column selected by this group. Runs from 0<=i<N_cols 752 */ 753 @Override getStartRes()754 public int getStartRes() 755 { 756 return startRes; 757 } 758 759 /** 760 * 761 * @return the groups last selected column. Runs from 0<=i<N_cols 762 */ 763 @Override getEndRes()764 public int getEndRes() 765 { 766 return endRes; 767 } 768 769 /** 770 * Set the first column selected by this group. Runs from 0<=i<N_cols 771 * 772 * @param newStart 773 */ setStartRes(int newStart)774 public void setStartRes(int newStart) 775 { 776 int before = startRes; 777 startRes= Math.max(0,newStart); // sanity check for negative start column positions 778 changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, startRes); 779 780 781 782 } 783 784 /** 785 * Set the groups last selected column. Runs from 0<=i<N_cols 786 * 787 * @param i 788 */ setEndRes(int i)789 public void setEndRes(int i) 790 { 791 int before = endRes; 792 endRes = i; 793 changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, endRes); 794 } 795 796 /** 797 * @return number of sequences in group 798 */ getSize()799 public int getSize() 800 { 801 return sequences.size(); 802 } 803 804 /** 805 * @param i 806 * @return the ith sequence 807 */ getSequenceAt(int i)808 public SequenceI getSequenceAt(int i) 809 { 810 return sequences.get(i); 811 } 812 813 /** 814 * @param state 815 * colourText 816 */ setColourText(boolean state)817 public void setColourText(boolean state) 818 { 819 colourText = state; 820 } 821 822 /** 823 * DOCUMENT ME! 824 * 825 * @return DOCUMENT ME! 826 */ getColourText()827 public boolean getColourText() 828 { 829 return colourText; 830 } 831 832 /** 833 * DOCUMENT ME! 834 * 835 * @param state 836 * DOCUMENT ME! 837 */ setDisplayText(boolean state)838 public void setDisplayText(boolean state) 839 { 840 displayText = state; 841 } 842 843 /** 844 * DOCUMENT ME! 845 * 846 * @return DOCUMENT ME! 847 */ getDisplayText()848 public boolean getDisplayText() 849 { 850 return displayText; 851 } 852 853 /** 854 * DOCUMENT ME! 855 * 856 * @param state 857 * DOCUMENT ME! 858 */ setDisplayBoxes(boolean state)859 public void setDisplayBoxes(boolean state) 860 { 861 displayBoxes = state; 862 } 863 864 /** 865 * DOCUMENT ME! 866 * 867 * @return DOCUMENT ME! 868 */ getDisplayBoxes()869 public boolean getDisplayBoxes() 870 { 871 return displayBoxes; 872 } 873 874 /** 875 * computes the width of current set of sequences and returns it 876 * 877 * @return DOCUMENT ME! 878 */ 879 @Override getWidth()880 public int getWidth() 881 { 882 synchronized (sequences) 883 { 884 // MC This needs to get reset when characters are inserted and deleted 885 boolean first = true; 886 for (SequenceI seq : sequences) 887 { 888 if (first || seq.getLength() > width) 889 { 890 width = seq.getLength(); 891 first = false; 892 } 893 } 894 return width; 895 } 896 } 897 898 /** 899 * DOCUMENT ME! 900 * 901 * @param c 902 * DOCUMENT ME! 903 */ setOutlineColour(Color c)904 public void setOutlineColour(Color c) 905 { 906 outlineColour = c; 907 } 908 909 /** 910 * DOCUMENT ME! 911 * 912 * @return DOCUMENT ME! 913 */ getOutlineColour()914 public Color getOutlineColour() 915 { 916 return outlineColour; 917 } 918 919 /** 920 * 921 * returns the sequences in the group ordered by the ordering given by al. 922 * this used to return an array with null entries regardless, new behaviour is 923 * below. TODO: verify that this does not affect use in applet or application 924 * 925 * @param al 926 * Alignment 927 * @return SequenceI[] intersection of sequences in group with al, ordered by 928 * al, or null if group does not intersect with al 929 */ getSequencesInOrder(AlignmentI al)930 public SequenceI[] getSequencesInOrder(AlignmentI al) 931 { 932 return getSequencesInOrder(al, true); 933 } 934 935 /** 936 * return an array representing the intersection of the group with al, 937 * optionally returning an array the size of al.getHeight() where nulls mark 938 * the non-intersected sequences 939 * 940 * @param al 941 * @param trim 942 * @return null or array 943 */ getSequencesInOrder(AlignmentI al, boolean trim)944 public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim) 945 { 946 synchronized (sequences) 947 { 948 int sSize = sequences.size(); 949 int alHeight = al.getHeight(); 950 951 SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight]; 952 953 int index = 0; 954 for (int i = 0; i < alHeight && index < sSize; i++) 955 { 956 if (sequences.contains(al.getSequenceAt(i))) 957 { 958 seqs[(trim) ? index : i] = al.getSequenceAt(i); 959 index++; 960 } 961 } 962 if (index == 0) 963 { 964 return null; 965 } 966 if (!trim) 967 { 968 return seqs; 969 } 970 if (index < seqs.length) 971 { 972 SequenceI[] dummy = seqs; 973 seqs = new SequenceI[index]; 974 while (--index >= 0) 975 { 976 seqs[index] = dummy[index]; 977 dummy[index] = null; 978 } 979 } 980 return seqs; 981 } 982 } 983 984 /** 985 * @return the idColour 986 */ getIdColour()987 public Color getIdColour() 988 { 989 return idColour; 990 } 991 992 /** 993 * @param idColour 994 * the idColour to set 995 */ setIdColour(Color idColour)996 public void setIdColour(Color idColour) 997 { 998 this.idColour = idColour; 999 } 1000 1001 /** 1002 * @return the representative sequence for this group 1003 */ 1004 @Override getSeqrep()1005 public SequenceI getSeqrep() 1006 { 1007 return seqrep; 1008 } 1009 1010 /** 1011 * set the representative sequence for this group. Note - this affects the 1012 * interpretation of the Hidereps attribute. 1013 * 1014 * @param seqrep 1015 * the seqrep to set (null means no sequence representative) 1016 */ 1017 @Override setSeqrep(SequenceI seqrep)1018 public void setSeqrep(SequenceI seqrep) 1019 { 1020 this.seqrep = seqrep; 1021 } 1022 1023 /** 1024 * 1025 * @return true if group has a sequence representative 1026 */ 1027 @Override hasSeqrep()1028 public boolean hasSeqrep() 1029 { 1030 return seqrep != null; 1031 } 1032 1033 /** 1034 * set visibility of sequences covered by (if no sequence representative is 1035 * defined) or represented by this group. 1036 * 1037 * @param visibility 1038 */ setHidereps(boolean visibility)1039 public void setHidereps(boolean visibility) 1040 { 1041 hidereps = visibility; 1042 } 1043 1044 /** 1045 * 1046 * @return true if sequences represented (or covered) by this group should be 1047 * hidden 1048 */ isHidereps()1049 public boolean isHidereps() 1050 { 1051 return hidereps; 1052 } 1053 1054 /** 1055 * set intended visibility of columns covered by this group 1056 * 1057 * @param visibility 1058 */ setHideCols(boolean visibility)1059 public void setHideCols(boolean visibility) 1060 { 1061 hidecols = visibility; 1062 } 1063 1064 /** 1065 * 1066 * @return true if columns covered by group should be hidden 1067 */ isHideCols()1068 public boolean isHideCols() 1069 { 1070 return hidecols; 1071 } 1072 1073 /** 1074 * create a new sequence group from the intersection of this group with an 1075 * alignment Hashtable of hidden representatives 1076 * 1077 * @param alignment 1078 * (may not be null) 1079 * @param map 1080 * (may be null) 1081 * @return new group containing sequences common to this group and alignment 1082 */ intersect(AlignmentI alignment, Map<SequenceI, SequenceCollectionI> map)1083 public SequenceGroup intersect(AlignmentI alignment, 1084 Map<SequenceI, SequenceCollectionI> map) 1085 { 1086 SequenceGroup sgroup = new SequenceGroup(this); 1087 SequenceI[] insect = getSequencesInOrder(alignment); 1088 sgroup.sequences = new ArrayList<>(); 1089 for (int s = 0; insect != null && s < insect.length; s++) 1090 { 1091 if (map == null || map.containsKey(insect[s])) 1092 { 1093 sgroup.sequences.add(insect[s]); 1094 } 1095 } 1096 return sgroup; 1097 } 1098 1099 /** 1100 * @return the showUnconserved 1101 */ getShowNonconserved()1102 public boolean getShowNonconserved() 1103 { 1104 return showNonconserved; 1105 } 1106 1107 /** 1108 * @param showNonconserved 1109 * the showUnconserved to set 1110 */ setShowNonconserved(boolean displayNonconserved)1111 public void setShowNonconserved(boolean displayNonconserved) 1112 { 1113 this.showNonconserved = displayNonconserved; 1114 } 1115 1116 /** 1117 * set this alignmentAnnotation object as the one used to render consensus 1118 * annotation 1119 * 1120 * @param aan 1121 */ setConsensus(AlignmentAnnotation aan)1122 public void setConsensus(AlignmentAnnotation aan) 1123 { 1124 if (consensus == null) 1125 { 1126 consensus = aan; 1127 } 1128 } 1129 1130 /** 1131 * 1132 * @return automatically calculated consensus row note: the row is a stub if a 1133 * consensus calculation has not yet been performed on the group 1134 */ getConsensus()1135 public AlignmentAnnotation getConsensus() 1136 { 1137 // TODO get or calculate and get consensus annotation row for this group 1138 int aWidth = this.getWidth(); 1139 // pointer 1140 // possibility 1141 // here. 1142 if (aWidth < 0) 1143 { 1144 return null; 1145 } 1146 if (consensus == null) 1147 { 1148 consensus = new AlignmentAnnotation("", "", new Annotation[1], 0f, 1149 100f, AlignmentAnnotation.BAR_GRAPH); 1150 consensus.hasText = true; 1151 consensus.autoCalculated = true; 1152 consensus.groupRef = this; 1153 consensus.label = "Consensus for " + getName(); 1154 consensus.description = "Percent Identity"; 1155 } 1156 return consensus; 1157 } 1158 1159 /** 1160 * set this alignmentAnnotation object as the one used to render consensus 1161 * annotation 1162 * 1163 * @param aan 1164 */ setConservationRow(AlignmentAnnotation aan)1165 public void setConservationRow(AlignmentAnnotation aan) 1166 { 1167 if (conservation == null) 1168 { 1169 conservation = aan; 1170 } 1171 } 1172 1173 /** 1174 * get the conservation annotation row for this group 1175 * 1176 * @return autoCalculated annotation row 1177 */ getConservationRow()1178 public AlignmentAnnotation getConservationRow() 1179 { 1180 if (conservation == null) 1181 { 1182 conservation = new AlignmentAnnotation("", "", new Annotation[1], 0f, 1183 11f, AlignmentAnnotation.BAR_GRAPH); 1184 } 1185 1186 conservation.hasText = true; 1187 conservation.autoCalculated = true; 1188 conservation.groupRef = this; 1189 conservation.label = "Conservation for " + getName(); 1190 conservation.description = "Conservation for group " + getName() 1191 + " less than " + consPercGaps + "% gaps"; 1192 return conservation; 1193 } 1194 1195 /** 1196 * 1197 * @return true if annotation rows have been instantiated for this group 1198 */ hasAnnotationRows()1199 public boolean hasAnnotationRows() 1200 { 1201 return consensus != null || conservation != null; 1202 } 1203 getConsensusSeq()1204 public SequenceI getConsensusSeq() 1205 { 1206 getConsensus(); 1207 StringBuffer seqs = new StringBuffer(); 1208 for (int i = 0; i < consensus.annotations.length; i++) 1209 { 1210 if (consensus.annotations[i] != null) 1211 { 1212 String desc = consensus.annotations[i].description; 1213 if (desc.length() > 1 && desc.charAt(0) == '[') 1214 { 1215 seqs.append(desc.charAt(1)); 1216 } 1217 else 1218 { 1219 seqs.append(consensus.annotations[i].displayCharacter); 1220 } 1221 } 1222 } 1223 1224 SequenceI sq = new Sequence("Group" + getName() + " Consensus", 1225 seqs.toString()); 1226 sq.setDescription("Percentage Identity Consensus " 1227 + ((ignoreGapsInConsensus) ? " without gaps" : "")); 1228 return sq; 1229 } 1230 setIgnoreGapsConsensus(boolean state)1231 public void setIgnoreGapsConsensus(boolean state) 1232 { 1233 if (this.ignoreGapsInConsensus != state && consensus != null) 1234 { 1235 ignoreGapsInConsensus = state; 1236 recalcConservation(); 1237 } 1238 ignoreGapsInConsensus = state; 1239 } 1240 getIgnoreGapsConsensus()1241 public boolean getIgnoreGapsConsensus() 1242 { 1243 return ignoreGapsInConsensus; 1244 } 1245 1246 /** 1247 * @param showSequenceLogo 1248 * indicates if a sequence logo is shown for consensus annotation 1249 */ setshowSequenceLogo(boolean showSequenceLogo)1250 public void setshowSequenceLogo(boolean showSequenceLogo) 1251 { 1252 // TODO: decouple calculation from settings update 1253 if (this.showSequenceLogo != showSequenceLogo && consensus != null) 1254 { 1255 this.showSequenceLogo = showSequenceLogo; 1256 recalcConservation(); 1257 } 1258 this.showSequenceLogo = showSequenceLogo; 1259 } 1260 1261 /** 1262 * 1263 * @param showConsHist 1264 * flag indicating if the consensus histogram for this group should 1265 * be rendered 1266 */ setShowConsensusHistogram(boolean showConsHist)1267 public void setShowConsensusHistogram(boolean showConsHist) 1268 { 1269 1270 if (showConsensusHistogram != showConsHist && consensus != null) 1271 { 1272 this.showConsensusHistogram = showConsHist; 1273 recalcConservation(); 1274 } 1275 this.showConsensusHistogram = showConsHist; 1276 } 1277 1278 /** 1279 * @return the showConsensusHistogram 1280 */ isShowConsensusHistogram()1281 public boolean isShowConsensusHistogram() 1282 { 1283 return showConsensusHistogram; 1284 } 1285 1286 /** 1287 * set flag indicating if logo should be normalised when rendered 1288 * 1289 * @param norm 1290 */ setNormaliseSequenceLogo(boolean norm)1291 public void setNormaliseSequenceLogo(boolean norm) 1292 { 1293 normaliseSequenceLogo = norm; 1294 } 1295 isNormaliseSequenceLogo()1296 public boolean isNormaliseSequenceLogo() 1297 { 1298 return normaliseSequenceLogo; 1299 } 1300 1301 @Override 1302 /** 1303 * returns a new array with all annotation involving this group 1304 */ getAlignmentAnnotation()1305 public AlignmentAnnotation[] getAlignmentAnnotation() 1306 { 1307 // TODO add in other methods like 'getAlignmentAnnotation(String label), 1308 // etc' 1309 ArrayList<AlignmentAnnotation> annot = new ArrayList<>(); 1310 synchronized (sequences) 1311 { 1312 for (SequenceI seq : sequences) 1313 { 1314 AlignmentAnnotation[] aa = seq.getAnnotation(); 1315 if (aa != null) 1316 { 1317 for (AlignmentAnnotation al : aa) 1318 { 1319 if (al.groupRef == this) 1320 { 1321 annot.add(al); 1322 } 1323 } 1324 } 1325 } 1326 if (consensus != null) 1327 { 1328 annot.add(consensus); 1329 } 1330 if (conservation != null) 1331 { 1332 annot.add(conservation); 1333 } 1334 } 1335 return annot.toArray(new AlignmentAnnotation[0]); 1336 } 1337 1338 @Override findAnnotation(String calcId)1339 public Iterable<AlignmentAnnotation> findAnnotation(String calcId) 1340 { 1341 return AlignmentAnnotation.findAnnotation( 1342 Arrays.asList(getAlignmentAnnotation()), calcId); 1343 } 1344 1345 @Override findAnnotations(SequenceI seq, String calcId, String label)1346 public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq, 1347 String calcId, String label) 1348 { 1349 return AlignmentAnnotation.findAnnotations( 1350 Arrays.asList(getAlignmentAnnotation()), seq, calcId, label); 1351 } 1352 1353 /** 1354 * Answer true if any annotation matches the calcId passed in (if not null). 1355 * 1356 * @param calcId 1357 * @return 1358 */ hasAnnotation(String calcId)1359 public boolean hasAnnotation(String calcId) 1360 { 1361 return AlignmentAnnotation 1362 .hasAnnotation(Arrays.asList(getAlignmentAnnotation()), calcId); 1363 } 1364 1365 /** 1366 * Remove all sequences from the group (leaving other properties unchanged). 1367 */ clear()1368 public void clear() 1369 { 1370 synchronized (sequences) 1371 { 1372 int before = sequences.size(); 1373 sequences.clear(); 1374 changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, 1375 sequences.size()); 1376 } 1377 } 1378 1379 /** 1380 * Sets the alignment or group context for this group, and whether it is 1381 * defined as a group 1382 * 1383 * @param ctx 1384 * the context for the group 1385 * @param defined 1386 * whether the group is defined on the alignment or is just a 1387 * selection 1388 * @throws IllegalArgumentException 1389 * if setting the context would result in a circular reference chain 1390 */ setContext(AnnotatedCollectionI ctx, boolean defined)1391 public void setContext(AnnotatedCollectionI ctx, boolean defined) 1392 { 1393 setContext(ctx); 1394 this.isDefined = defined; 1395 } 1396 1397 /** 1398 * Sets the alignment or group context for this group 1399 * 1400 * @param ctx 1401 * the context for the group 1402 * @throws IllegalArgumentException 1403 * if setting the context would result in a circular reference chain 1404 */ setContext(AnnotatedCollectionI ctx)1405 public void setContext(AnnotatedCollectionI ctx) 1406 { 1407 AnnotatedCollectionI ref = ctx; 1408 while (ref != null) 1409 { 1410 if (ref == this || ref.getContext() == ctx) 1411 { 1412 throw new IllegalArgumentException( 1413 "Circular reference in SequenceGroup.context"); 1414 } 1415 ref = ref.getContext(); 1416 } 1417 this.context = ctx; 1418 } 1419 1420 /* 1421 * (non-Javadoc) 1422 * 1423 * @see jalview.datamodel.AnnotatedCollectionI#getContext() 1424 */ 1425 @Override getContext()1426 public AnnotatedCollectionI getContext() 1427 { 1428 return context; 1429 } 1430 isDefined()1431 public boolean isDefined() 1432 { 1433 return isDefined; 1434 } 1435 setColourScheme(ColourSchemeI scheme)1436 public void setColourScheme(ColourSchemeI scheme) 1437 { 1438 if (cs == null) 1439 { 1440 cs = new ResidueShader(); 1441 } 1442 cs.setColourScheme(scheme); 1443 } 1444 setGroupColourScheme(ResidueShaderI scheme)1445 public void setGroupColourScheme(ResidueShaderI scheme) 1446 { 1447 cs = scheme; 1448 } 1449 getColourScheme()1450 public ColourSchemeI getColourScheme() 1451 { 1452 return cs == null ? null : cs.getColourScheme(); 1453 } 1454 getGroupColourScheme()1455 public ResidueShaderI getGroupColourScheme() 1456 { 1457 return cs; 1458 } 1459 1460 @Override isNucleotide()1461 public boolean isNucleotide() 1462 { 1463 if (context != null) 1464 { 1465 return context.isNucleotide(); 1466 } 1467 return false; 1468 } 1469 1470 /** 1471 * @param seq 1472 * @return true if seq is a member of the group 1473 */ 1474 contains(SequenceI seq1)1475 public boolean contains(SequenceI seq1) 1476 { 1477 return sequences.contains(seq1); 1478 } 1479 1480 /** 1481 * @param seq 1482 * @param apos 1483 * @return true if startRes<=apos and endRes>=apos and seq is in the group 1484 */ contains(SequenceI seq, int apos)1485 public boolean contains(SequenceI seq, int apos) 1486 { 1487 return (startRes <= apos && endRes >= apos) && sequences.contains(seq); 1488 } 1489 } 1490