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 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