class Axis(object): def __init__(self, scale, variant, allele): self.scale = scale self.allele = allele self.variant = variant self.chromPartsCollection = variant.chromParts(allele) self.height = 75 def baseHeight(self): return 75 def render(self, scaleFactor=1.0, spacing=1.0, height=None, thickerLines=False): self.height = height if height == None: self.height = 75 * scaleFactor self.svg = SVG(self.scale.pixelWidth, self.height, yrelto="top", headerExtras="""preserveAspectRatio="none" """) # dividers! (multi-part only) if len(self.chromPartsCollection) > 1: for part in list(self.chromPartsCollection)[1:]: divWidth = self.scale.relpixels(self.scale.dividerSize) x = self.scale.partsToStartPixels[part.id] - divWidth self.svg.rect(x, 0, divWidth, self.height, fill="#B2B2B2") self.svg.line(x, 0, x, self.height, stroke="black", **{"stroke-width":1*scaleFactor}) self.svg.line(x+divWidth, 0, x+divWidth, self.height, stroke="black", **{"stroke-width":1*scaleFactor}) for i, part in enumerate(self.chromPartsCollection): if i == 0: x = self.scale.topixels(self.scale.partsToLengths[part.id], part.id) - 15 anchor = "end" else: x = self.scale.partsToStartPixels[part.id] + 15 anchor = "start" self.svg.text(x, 18*scaleFactor, part.id, anchor=anchor, size=18*scaleFactor) # tick marks and coordinates for regionID in self.scale.partsToStartPixels: for tick in self.getTicks(regionID): x = self.scale.topixels(tick, regionID) self.svg.rect(x, 35*scaleFactor, 1*scaleFactor, 15*scaleFactor, fill="black") label = tick if tick > 1e6: label = "{:.1f}MB".format(tick/1e6) elif tick > 1e3: label = "{:.1f}KB".format(tick/1e3) if x < 50: x = 50 elif x > self.scale.pixelWidth - 50: x = self.scale.pixelWidth - 50 extras = {} if thickerLines: extras["font-weight"] = "bold" self.svg.text(x, self.height-4*scaleFactor, label, size=18*scaleFactor, **extras) # segment arrows for part in self.chromPartsCollection: curOffset = self.scale.partsToStartPixels[part.id] for segment in part.segments: start = curOffset end = self.scale.relpixels(len(segment)) + curOffset curOffset = end arrowDirection = "right" if segment.strand == "-": start, end = end, start arrowDirection = "left" y = 35*scaleFactor self.svg.line(start, y, end, y, stroke=segment.color(), **{"stroke-width":8*scaleFactor}) self.svg.lineWithInternalArrows(start, y, end, y, stroke=segment.color(), direction=arrowDirection, arrowKwdArgs={"class":"scaleArrow"}, **{"stroke-width":3*scaleFactor}) # breakpoints previousPosition = None for part in self.chromPartsCollection: for vline in self.scale.getBreakpointPositions(part.id): thickness = 1*scaleFactor if thickerLines: thickness *= 2 x = self.scale.topixels(vline, part.id) self.svg.line(x, 20*scaleFactor, x, 55*scaleFactor, stroke="black", **{"stroke-width":thickness}) if previousPosition is None or x-previousPosition > 250*scaleFactor: self.svg.text(x-(scaleFactor/2.0), 18*scaleFactor, "breakpoint", size=18*scaleFactor, fill="black") previousPosition = x return str(self.svg) def getTicks(self, regionID): ticks = [] start = 0 width = self.scale.partsToLengths[regionID] end = start + width res = (10 ** round(math.log10(end - start))) / 10.0 if width / res > 15: res *= 2.5 elif width / res < 5: res /= 2.0 roundStart = start - (start%res) for i in range(int(roundStart), end, int(res)): ticks.append(i) return ticks
class AnnotationTrack(object): def __init__(self, annotationSet, scale, variant, allele): self.chromPartsCollection = variant.chromParts(allele) self.annotationSet = annotationSet self.scale = scale self.height = None self.variant = variant self.allele = allele self._annos = None self.rows = [None] self.svg = None self.rowheight = 20 def _topixels(self, gpos, segment, psegoffset): if segment.strand == "+": pos = self.scale.relpixels(gpos - segment.start) + psegoffset elif segment.strand == "-": pos = self.scale.relpixels(segment.end - gpos) + psegoffset return pos def findRow(self, start, end): for currow in range(len(self.rows)): if self.rows[currow] is None or (start - self.rows[currow]) >= 2: self.rows[currow] = end break else: self.rows.append(end) currow = len(self.rows)-1 return currow def baseHeight(self): if self._annos is not None and len(self._annos) == 0: return 0 return ((len(self.rows)+2) * self.rowheight) + 20 def dolayout(self, scaleFactor, spacing): # coordinates are in pixels not base pairs self.rows = [None] self._annos = [] for part in self.chromPartsCollection: segmentStart = self.scale.partsToStartPixels[part.id] for segment in variants.mergedSegments(part.segments): curWidth = len(segment) curAnnos = self.annotationSet.getAnnotations(segment.chrom, segment.start, segment.end, clip=True) if segment.strand == "-": curAnnos = sorted(curAnnos, key=lambda x:x.end, reverse=True) for anno in curAnnos: start = max(anno.start, segment.start) end = min(anno.end, segment.end) start = self._topixels(start, segment, segmentStart) end = self._topixels(end, segment, segmentStart) if end < start: start, end = end, start textLength = len(anno.name)*self.rowheight/1.0*scaleFactor*spacing rowNum = self.findRow(start, end+textLength) anno.coords = {} anno.coords["row"] = rowNum anno.coords["start"] = start anno.coords["end"] = end anno.coords["strand"] = anno.strand if segment.strand=="+" else utilities.switchStrand(anno.strand) anno.coords["segment"] = segment anno.coords["segmentStart"] = segmentStart self._annos.append(anno) segmentStart += self.scale.relpixels(curWidth) def drawBox(self, start, end, segment, segmentStart, y, height, scaleFactor, color, anno): start = self._topixels(start, segment, segmentStart) end = self._topixels(end, segment, segmentStart) if end < start: start, end = end, start width = end - start ystart = y-((self.rowheight-height)/2.0)*scaleFactor self.svg.rect(start, ystart, width, height*scaleFactor, fill=color) def _drawGenes(self, scaleFactor): for anno in self._annos: anno.txExons # check to see if this is a gff or a bed color = "blue" if anno.coords["strand"] == "+" else "darkorange" y = ((anno.coords["row"]+1) * self.rowheight + 20) * scaleFactor width = anno.coords["end"] - anno.coords["start"] self.svg.rect(anno.coords["start"], y-((self.rowheight-2.0)/2.0)*scaleFactor, width, 2.0*scaleFactor, fill=color) for txExon in anno.txExons: start, end = txExon self.drawBox(start, end, anno.coords["segment"], anno.coords["segmentStart"], y, 5, scaleFactor, color, anno) for cdExon in anno.cdExons: start, end = cdExon self.drawBox(start, end, anno.coords["segment"], anno.coords["segmentStart"], y, self.rowheight, scaleFactor, color, anno) textSize = (self.rowheight-2)*scaleFactor if anno.coords["end"] + len(anno.label)*textSize*0.70 > self.scale.pixelWidth: w = len(anno.label)*textSize*0.70 self.svg.rect(self.scale.pixelWidth - w, y, w, self.rowheight*scaleFactor, fill="white", **{"fill-opacity":0.40}) self.svg.text(self.scale.pixelWidth, y-((self.rowheight-1)*scaleFactor), anno.label, size=textSize, fill=color, anchor="end", **{"fill-opacity":0.70}) else: self.svg.text(anno.coords["end"]+(self.rowheight/2.0), y-((self.rowheight-1)*scaleFactor), anno.label, size=textSize, anchor="start", fill=color) def _drawBED(self, scaleFactor): for anno in self._annos: color = "blue" if anno.coords["strand"] == "+" else "darkorange" y = ((anno.coords["row"]+1) * self.rowheight + 20) * scaleFactor width = anno.coords["end"] - anno.coords["start"] self.svg.rect(anno.coords["start"], y, width, self.rowheight*scaleFactor, fill=color) self.svg.text(anno.coords["end"]+(self.rowheight/2.0), y-((self.rowheight-1)*scaleFactor), anno.label, size=(self.rowheight-2)*scaleFactor, anchor="start", fill=color) def render(self, scaleFactor=1.0, spacing=1, height=None, thickerLines=False): self.dolayout(scaleFactor, spacing) self.height = self.baseHeight()*scaleFactor self.svg = SVG(self.scale.pixelWidth, self.height) # dividers! (multi-part only) for part in list(self.chromPartsCollection)[1:]: divWidth = self.scale.relpixels(self.scale.dividerSize) x = self.scale.partsToStartPixels[part.id] - divWidth self.svg.rect(x, self.height, divWidth, self.height, fill="#B2B2B2") self.svg.line(x, 0, x, self.height, stroke="black", **{"stroke-width":1*scaleFactor}) self.svg.line(x+divWidth, 0, x+divWidth, self.height, stroke="black", **{"stroke-width":1*scaleFactor}) try: self._drawGenes(scaleFactor) except Exception, e: self._drawBED(scaleFactor) for part in self.chromPartsCollection: for vline in self.scale.getBreakpointPositions(part.id): x = self.scale.topixels(vline, part.id)-scaleFactor/2.0 y1 = 0 y2 = self.height thickness = 1*scaleFactor if thickerLines: thickness *= 2 self.svg.line(x, y1, x, y2, stroke="black", **{"stroke-width":thickness})
class Track(object): def __init__(self, chromPartsCollection, alignmentSets, height, width, variant, allele, thickerLines, colorCigar): self.chromPartsCollection = chromPartsCollection self.height = height self.width = width self.scale = Scale(chromPartsCollection, width) self.rowHeight = 5 self.rowMargin = 1 self.readRenderer = ReadRenderer(self.rowHeight, self.scale, self.chromPartsCollection, thickerLines, colorCigar) self.alignmentSets = alignmentSets self.svg = None self.rendered = None self.variant = variant self.allele = allele self.rows = [] self._axis = None self.xmin = None self.xmax = None self.thickerLines = thickerLines def findRow(self, start, end, regionID): for currow in range(len(self.rows)): if self.rows[currow] is None or (start - self.rows[currow]) >= 2: self.rows[currow] = end break else: self.rows.append(end) currow = len(self.rows)-1 return currow def getAlignments(self): # check which reads are overlapping (self.gstart, self.gend) # sorting by name makes the layout process deterministic regionIDsToPositions = dict((part.id, i) for i, part in enumerate(self.chromPartsCollection)) def sortKey(alnSet): return (regionIDsToPositions[alnSet.getAlignments()[0].regionID], alnSet.start, alnSet.end, alnSet.name()) return sorted(self.alignmentSets, key=sortKey) def dolayout(self): self.rows = [None]#*numRows self.xmin = 1e100 self.xmax = 0 for alignmentSet in self.getAlignments(): # if len(alignmentSet.getAlignments()) < 2: # continue regionIDs = set([x.regionID for x in alignmentSet.getAlignments()]) assert self.allele=="amb" or len(regionIDs)==1, alignmentSet.getAlignments() regionID = regionIDs.pop() start = self.scale.topixels(alignmentSet.start, regionID) end = self.scale.topixels(alignmentSet.end, regionID) currow = self.findRow(start, end, regionID) yoffset = (self.rowHeight+self.rowMargin) * currow alignmentSet.yoffset = yoffset self.xmin = min(self.xmin, self.scale.topixels(alignmentSet.start, regionID)) self.xmax = max(self.xmax, self.scale.topixels(alignmentSet.end, regionID)) self.height = (self.rowHeight+self.rowMargin) * len(self.rows) def render(self): if len(self.getAlignments()) == 0: xmiddle = self.scale.pixelWidth / 2.0 self.height = xmiddle/20.0 self.svg = SVG(self.width, self.height) self.svg.text(xmiddle, self.height*0.05, "No reads found", size=self.height*0.9, fill="#999999") self.rendered = self.svg.asString() return self.rendered self.dolayout() self.svg = SVG(self.width, self.height) self.readRenderer.svg = self.svg alnSets = self.getAlignments() flankingAlignments = [alnSet for alnSet in alnSets if alnSet.parentCollection.why == "flanking"] nonFlankingAlignments = [alnSet for alnSet in alnSets if alnSet.parentCollection.why != "flanking"] for alignmentSet in flankingAlignments+nonFlankingAlignments: self.readRenderer.render(alignmentSet) lineWidth = 1 if not self.thickerLines else 3 lineWidth = lineWidth * ((self.xmax-self.xmin)/1200.0) for part in self.chromPartsCollection: for vline in self.scale.getBreakpointPositions(part.id): x = self.scale.topixels(vline, part.id) y1 = -20 y2 = self.height+20 self.svg.line(x, y1, x, y2, stroke="black", **{"stroke-width":lineWidth}) # dividers! for part in list(self.chromPartsCollection)[1:]: divWidth = self.scale.relpixels(self.scale.dividerSize) x = self.scale.partsToStartPixels[part.id] - divWidth self.svg.rect(x, self.height+40, divWidth, self.height+40, fill="#B2B2B2") self.svg.line(x, 0, x, self.height+40, stroke="black", **{"stroke-width":4}) self.svg.line(x+divWidth, 0, x+divWidth, self.height+40, stroke="black", **{"stroke-width":4}) self.svg.rect(0, self.svg.height+20, self.scale.pixelWidth, self.height+40, opacity=0.0, zindex=0) self.rendered = str(self.svg) return self.rendered
class AnnotationTrack(object): def __init__(self, annotationSet, scale, variant, allele): self.chromPartsCollection = variant.chromParts(allele) self.annotationSet = annotationSet self.scale = scale self.height = None self.variant = variant self.allele = allele self._annos = None self.rows = [None] self.svg = None self.rowheight = 20 def _topixels(self, gpos, segment, psegoffset): if segment.strand == "+": pos = self.scale.relpixels(gpos - segment.start) + psegoffset elif segment.strand == "-": pos = self.scale.relpixels(segment.end - gpos) + psegoffset return pos def findRow(self, start, end): for currow in range(len(self.rows)): if self.rows[currow] is None or (start - self.rows[currow]) >= 2: self.rows[currow] = end break else: self.rows.append(end) currow = len(self.rows)-1 return currow def baseHeight(self): if self._annos is not None and len(self._annos) == 0: return 0 return ((len(self.rows)+2) * self.rowheight) + 20 def dolayout(self, scaleFactor, spacing): # coordinates are in pixels not base pairs self.rows = [None] self._annos = [] for part in self.chromPartsCollection: segmentStart = self.scale.partsToStartPixels[part.id] for segment in variants.mergedSegments(part.segments): curWidth = len(segment) curAnnos = self.annotationSet.getAnnotations(segment.chrom, segment.start, segment.end, clip=True) if segment.strand == "-": curAnnos = sorted(curAnnos, key=lambda x:x.end, reverse=True) for anno in curAnnos: start = max(anno.start, segment.start) end = min(anno.end, segment.end) start = self._topixels(start, segment, segmentStart) end = self._topixels(end, segment, segmentStart) if end < start: start, end = end, start textLength = len(anno.label)*self.rowheight/1.0*scaleFactor*spacing rowNum = self.findRow(start, end+textLength) anno.coords = {} anno.coords["row"] = rowNum anno.coords["start"] = start anno.coords["end"] = end anno.coords["strand"] = anno.strand if segment.strand=="+" else utilities.switchStrand(anno.strand) anno.coords["segment"] = segment anno.coords["segmentStart"] = segmentStart self._annos.append(anno) segmentStart += self.scale.relpixels(curWidth) def drawBox(self, start, end, segment, segmentStart, y, height, scaleFactor, color, anno): start = self._topixels(start, segment, segmentStart) end = self._topixels(end, segment, segmentStart) if end < start: start, end = end, start width = end - start ystart = y-((self.rowheight-height)/2.0)*scaleFactor self.svg.rect(start, ystart, width, height*scaleFactor, fill=color) def _drawGenes(self, scaleFactor): for anno in self._annos: anno.txExons # check to see if this is a gff or a bed color = "blue" if anno.coords["strand"] == "+" else "darkorange" y = ((anno.coords["row"]+1) * self.rowheight + 20) * scaleFactor width = anno.coords["end"] - anno.coords["start"] self.svg.rect(anno.coords["start"], y-((self.rowheight-2.0)/2.0)*scaleFactor, width, 2.0*scaleFactor, fill=color) for txExon in anno.txExons: start, end = txExon self.drawBox(start, end, anno.coords["segment"], anno.coords["segmentStart"], y, 5, scaleFactor, color, anno) for cdExon in anno.cdExons: start, end = cdExon self.drawBox(start, end, anno.coords["segment"], anno.coords["segmentStart"], y, self.rowheight, scaleFactor, color, anno) textSize = (self.rowheight-2)*scaleFactor if anno.coords["end"] + len(anno.label)*textSize*0.70 > self.scale.pixelWidth: w = len(anno.label)*textSize*0.70 self.svg.rect(self.scale.pixelWidth - w, y, w, self.rowheight*scaleFactor, fill="white", **{"fill-opacity":0.40}) self.svg.text(self.scale.pixelWidth, y-((self.rowheight-1)*scaleFactor), anno.label, size=textSize, fill=color, anchor="end", **{"fill-opacity":0.70}) else: self.svg.text(anno.coords["end"]+(self.rowheight/2.0), y-((self.rowheight-1)*scaleFactor), anno.label, size=textSize, anchor="start", fill=color) def _drawBED(self, scaleFactor): for anno in self._annos: color = "blue" if anno.coords["strand"] == "+" else "darkorange" y = ((anno.coords["row"]+1) * self.rowheight + 20) * scaleFactor width = anno.coords["end"] - anno.coords["start"] self.svg.rect(anno.coords["start"], y, width, self.rowheight*scaleFactor, fill=color) self.svg.text(anno.coords["end"]+(self.rowheight/2.0), y-((self.rowheight-1)*scaleFactor), anno.label, size=(self.rowheight-2)*scaleFactor, anchor="start", fill=color) def render(self, scaleFactor=1.0, spacing=1, height=None, thickerLines=False): self.dolayout(scaleFactor, spacing) self.height = self.baseHeight()*scaleFactor self.svg = SVG(self.scale.pixelWidth, self.height) # dividers! (multi-part only) for part in list(self.chromPartsCollection)[1:]: divWidth = self.scale.relpixels(self.scale.dividerSize) x = self.scale.partsToStartPixels[part.id] - divWidth self.svg.rect(x, self.height, divWidth, self.height, fill="#B2B2B2") self.svg.line(x, 0, x, self.height, stroke="black", **{"stroke-width":1*scaleFactor}) self.svg.line(x+divWidth, 0, x+divWidth, self.height, stroke="black", **{"stroke-width":1*scaleFactor}) try: self._drawGenes(scaleFactor) except Exception, e: self._drawBED(scaleFactor) for part in self.chromPartsCollection: for vline in self.scale.getBreakpointPositions(part.id): x = self.scale.topixels(vline, part.id)-scaleFactor/2.0 y1 = 0 y2 = self.height thickness = 1*scaleFactor if thickerLines: thickness *= 2 self.svg.line(x, y1, x, y2, stroke="black", **{"stroke-width":thickness})