import collections
import numpy
from genomeview.track import Track
from genomeview.utilities import match_chrom_format
COLORS = ["blue", "red", "green", "black"]
class Series:
def __init__(self, x, y, color=None, label=None):
x, y = zip(*sorted(zip(x,y)))
self.x = x
self.y = y
self.color = color
self.label = label
[docs]class GraphTrack(Track):
"""
Visualizes quantitative data as a line across coordinates within the current genomic view.
One or more datasets can be visualized (with different colors) on the same track using the
``add_series()`` method.
"""
def __init__(self, name=None, x=None, y=None):
super().__init__(name)
self.series = collections.OrderedDict()
self.min_y = 0
self.max_y = 0
if x is not None:
self.add_series(x, y)
self.height = 100
self.ymargin = 5
[docs] def add_series(self, x, y, color=None, label=None):
"""
Add a dataset corresponding to a single line in the track (ie, a "series"). Note that
while a single GraphTrack can visualize multiple datasets, they are all plotted on
the same y-axis and so should share the same units.
Arguments:
x: a list of genomic coordinates
y: a list of data values; each y-value must correspond to a single x-value
color: an SVG color for the line being plotted
label: an optional text label for the graph being plotted (currently unused)
"""
if label is None:
label = "series_{}".format(len(self.series))
assert label not in self.series
x = numpy.asarray(x)
y = numpy.asarray(y)
if color is None:
color = COLORS[len(self.series) % len(COLORS)]
self.series[label] = Series(x, y, color, label)
self.min_y = min(self.min_y, y[numpy.isfinite(y)].min())
self.max_y = max(self.max_y, y[numpy.isfinite(y)].max())
def ytopixels(self, yval):
height = self.max_y - self.min_y
return self.height - ((yval - self.min_y) / height * (self.height-2*self.ymargin) + self.ymargin)
def render(self, renderer):
for label, series in self.series.items():
for i in range(len(series.x)-1):
if any(numpy.isnan(series.x[i:i+2])) or any(numpy.isnan(series.y[i:i+2])):
continue
x1 = self.scale.topixels(series.x[i])
x2 = self.scale.topixels(series.x[i+1])
y1 = self.ytopixels(series.y[i])
y2 = self.ytopixels(series.y[i+1])
yield from renderer.line(x1, y1, x2, y2,
**{"stroke-width":1, "stroke":series.color, "stroke-linecap":"square"})
# since the labels are drawn at the top of the ticks, let's make sure the top tick/label is
# more than 12 pixels from the top of the track so it doesn't get clipped
# TODO: this ignores the margin, as of now
axis_max_y = self.min_y + (self.max_y - self.min_y) * (1-7/self.height)
# ticks = get_ticks(self.min_y, axis_max_y, 4)
ticks = numpy.linspace(self.min_y, axis_max_y, 4)
yield from renderer.line(1, self.ytopixels(ticks[0]), 1, self.ytopixels(ticks[-1]),
**{"stroke-width":2, "stroke":"gray", "stroke-linecap":"square"})
for tick in ticks:
if self.max_y > 1_000:
label = "{:.1g}".format(tick)
elif self.max_y < 1:
label = "{:.1f}".format(tick)
else:
label = "{:,.0f}".format(tick)
y = self.ytopixels(tick)
yield from renderer.line(1, y, 10, y,
**{"stroke-width":2, "stroke":"gray", "stroke-linecap":"square"})
yield from renderer.text(14, y, label, anchor="start", fill="gray")
for x in self.render_label(renderer):
yield x
[docs]class BigWigTrack(GraphTrack):
"""
Visualizes continuous-valued data from a bigwig file. Requires pyBigWig
module to be installed. Supports using web URLs as well as file paths.
"""
def __init__(self, path, nbins=1000, name=None):
super().__init__(name)
import pyBigWig
self.bigwig = pyBigWig.open(path)
self.nbins = 1000
def layout(self, scale):
super().layout(scale)
x = []
y = []
binsize = max(1, int((scale.end-scale.start) / self.nbins))
chrom = match_chrom_format(scale.chrom, self.bigwig.chroms().keys())
for i in range(scale.start, scale.end, binsize):
value = self.bigwig.stats(chrom, i, i+binsize)[0]
value = 0.0 if value==None else value
x.append(i+binsize/2)
y.append(value)
self.series = {"vals":Series(x, y, color="black")}
self.min_y = min(y)
self.max_y = max(y)