package org.broad.igv.sam;
import org.broad.igv.Globals;
import org.broad.igv.feature.Strand;
import org.broad.igv.track.WindowFunction;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* Class for experimenting with 10X linked reads.
*/
public class LinkedAlignment implements Alignment {
final String tag; // Tag used to link, or "READNAME"
final String name; // Tag value (usually barcode or readname)
String haplotype;
String sample;
String readGroup;
String library;
String chr;
Strand strand;
int alignmentStart;
int alignmentEnd;
List<Alignment> alignments;
Map<String, Object> attributes;
public LinkedAlignment(String tag, String bc) {
attributes = new HashMap<>();
this.tag = tag;
this.name = bc;
attributes.put(tag, name);
alignments = new ArrayList<>();
}
public void addAlignment(Alignment alignment) {
String sample = alignment.getSample();
String readGroup = alignment.getReadGroup();
String library = alignment.getLibrary();
if (alignments.isEmpty()) {
this.chr = alignment.getChr();
alignmentStart = alignment.getAlignmentStart();
alignmentEnd = alignment.getAlignmentEnd();
Object hp = alignment.getAttribute("HP");
haplotype = hp == null ? null : hp.toString();
strand = alignment.getReadStrand();
this.sample = sample == null ? "" : sample;
this.readGroup = readGroup == null ? "" : readGroup;
this.library = library == null ? "" : library;
} else {
if (!this.chr.equals(alignment.getChr())) {
throw new RuntimeException("Mixed chromosome linked alignments not supported");
}
alignmentStart = Math.min(alignment.getAlignmentStart(), this.alignmentStart);
alignmentEnd = Math.max(alignment.getAlignmentEnd(), this.alignmentEnd);
Object hp = alignment.getAttribute("HP");
if (hp != null) {
if (!hp.toString().equals(this.haplotype)) {
this.haplotype = "MIXED";
}
}
if (this.strand != alignment.getReadStrand()) {
this.strand = Strand.NONE; // i.e. mixed
}
if (!this.sample.equals(sample)) {
this.sample += ", " + sample;
}
if (!this.readGroup.equals(readGroup)) {
this.readGroup += ", " + readGroup;
}
if (!this.library.equals(library)) {
this.library += ", " + library;
}
}
alignments.add(alignment);
}
public Strand getStrand() {
return strand;
}
/**
* Return the strand of the linked alignment at the genomic position
* @param position
* @return
*/
public Strand getStrandAtPosition(double position) {
if(strand == Strand.NONE) {
for (Alignment a : alignments) {
if (a.contains(position)) {
return a.getReadStrand();
}
}
}
return strand;
}
@Override
public String getChr() {
return chr;
}
@Override
public int getAlignmentStart() {
return this.alignmentStart;
}
@Override
public int getAlignmentEnd() {
return this.alignmentEnd;
}
@Override
public int getStart() {
return this.alignmentStart;
}
@Override
public int getEnd() {
return this.alignmentEnd;
}
@Override
public boolean contains(double location) {
return location >= this.alignmentStart && location <= this.alignmentEnd;
}
@Override
public boolean isMapped() {
return true;
}
@Override
public String getValueString(double position, int mouseX, WindowFunction windowFunction) {
if (alignments.size() == 1) {
return alignments.get(0).getValueString(position, mouseX, windowFunction);
} else {
// First check to see if we are over an insertion. Insertions take precedence.
for(Alignment a : alignments) {
for(AlignmentBlock block : a.getInsertions()) {
if(block.containsPixel(mouseX)) {
return a.getValueString(position, mouseX, windowFunction);
}
}
}
StringBuffer buffer = new StringBuffer();
buffer.append("Linking id (" + tag + ") = " + this.name);
if (this.haplotype != null) buffer.append("<br>Haplotype = " + this.haplotype);
buffer.append("<br># alignments = " + alignments.size());
buffer.append("<br>Total span = " + Globals.DECIMAL_FORMAT.format(getAlignmentEnd() - getAlignmentStart()) + "bp");
// Link by readname == supplementary alignments. Crude "is not 10x?" test
if ("READNAME".equals(tag)) {
buffer.append("<br>Strands = ");
for (Alignment a : alignments) {
buffer.append(a.getReadStrand() == Strand.POSITIVE ? "+" : "-");
}
for (Alignment a : alignments) {
if (a instanceof SAMAlignment) {
buffer.append("<br>");
buffer.append(((SAMAlignment) a).getSynopsisString());
}
}
}
for (Alignment a : alignments) {
if (a.contains(position)) {
buffer.append("<hr>");
buffer.append(a.getValueString(position, mouseX, windowFunction));
}
}
return buffer.toString();
}
}
@Override
public Object getAttribute(String key) {
if ("HP".equals(key)) {
return haplotype;
} else {
return attributes.get(key);
}
}
@Override
public int getMappingQuality() {
return 30; // This is used for coloring. Not sure what to do here
}
/////////////////////////////////////////////////////////////
@Override
public String getReadName() {
return "READNAME".equals(tag) ? name : null;
}
@Override
public String getReadSequence() {
return null;
}
@Override
public AlignmentBlock[] getAlignmentBlocks() {
return null;
}
@Override
public AlignmentBlock[] getInsertions() {
int n = 0;
for (Alignment a : alignments) {
n += a.getInsertions().length;
}
AlignmentBlock[] insertions = new AlignmentBlock[n];
n = 0;
for (Alignment a : alignments) {
AlignmentBlock[] blocks = a.getInsertions();
System.arraycopy(blocks, 0, insertions, n, blocks.length);
n += blocks.length;
}
return insertions;
}
@Override
public String getCigarString() {
return null;
}
@Override
public List<Gap> getGaps() {
return null;
}
@Override
public int getInferredInsertSize() {
return 0;
}
@Override
public ReadMate getMate() {
return null;
}
@Override
public Strand getReadStrand() {
return null;
}
@Override
public boolean isProperPair() {
return false;
}
@Override
public boolean isPaired() {
return false;
}
@Override
public boolean isFirstOfPair() {
return false;
}
@Override
public boolean isSecondOfPair() {
return false;
}
@Override
public boolean isNegativeStrand() {
return strand == Strand.NEGATIVE;
}
@Override
public boolean isDuplicate() {
return false;
}
@Override
public boolean isPrimary() {
return false;
}
@Override
public boolean isSupplementary() {
return false;
}
@Override
public byte getBase(double position) {
byte base = 0;
for (Alignment al : alignments) {
if (al.contains(position)) {
byte b = al.getBase(position);
if (base == 0) {
base = b;
} else {
if (base != b) {
base = 0;
break;
}
}
}
}
return base;
}
@Override
public byte getPhred(double position) {
return 0;
}
@Override
public void setMateSequence(String sequence) {
}
@Override
public String getPairOrientation() {
return null;
}
@Override
public Strand getFirstOfPairStrand() {
return null;
}
@Override
public Strand getSecondOfPairStrand() {
return null;
}
@Override
public boolean isVendorFailedRead() {
return false;
}
@Override
public Color getColor() {
return null;
}
@Override
public String getSample() {
return sample;
}
@Override
public String getReadGroup() {
return null;
}
@Override
public String getLibrary() {
return null;
}
@Override
public String getClipboardString(double location, int mouseX) {
return null;
}
@Override
public void finish() {
alignments.sort(ALIGNMENT_START_COMPARATOR);
}
@Override
public void setStart(int start) {
}
@Override
public void setEnd(int end) {
}
@Override
public float getScore() {
return 0;
}
@Override
public String getContig() {
return null;
}
static final Comparator<Alignment> ALIGNMENT_START_COMPARATOR = new Comparator<Alignment>() {
public int compare(Alignment o1, Alignment o2) {
return o1.getAlignmentStart() - o2.getAlignmentStart();
}
};
}