from typing import Optional, Sequence, Union
import pandas as pd
from matplotlib.axes import Axes
from trackc.pa import trackcl_11
from trackc.tl import GenomeRegion
def _scale_ticks(ticks, scale="bp", tick_fl="%0.2f"):
if scale == "Mb":
ticks = ticks / 1000000
ticks = ["{0}".format(tick_fl % i) for i in ticks]
elif scale == "kb":
ticks = ticks / 1000
ticks = ["{0}".format(tick_fl % i) for i in ticks]
else:
return ticks
return ticks
[docs]def scale_track(
ax: Optional[Axes] = None,
region: Union[str, None] = None,
tick_pos: str = "bottom",
ratio2ax: float = 0.5,
label_fontsize: Union[int, None] = 10,
scale_adjust: Union[str, None] = "kb",
tick_fl: Union[str, None] = "%0.2f",
tick_fontsize: Union[int, None] = 8,
tick_rotation: Union[int, None] = 0,
space: float = 0.1,
):
"""
Plot one region scale bar track
Parameters
----------
ax: :class:`matplotlib.axes.Axes` object
region: `str`
one genome region, format: `chrom:start-end`.
e.g. ``"chr18:45000000-78077248"``
if the start is bigger than end, the genome region will be reversed
tick_pos: `str`
ticks position, can be one of ['top', 'bottom']
ratio2ax: `float`
the height ratio refer to the given `ax`'s height
label_fontsize: `int`
the region text fontsize
scale_adjust: `str`
adjust the scale unit to make it pretty, can be one of ['kb', 'Mb']
tick_fl: `str`
ticks retains a few decimal places
tick_fontsize: `int`
ticks text fontsize
tick_rotation: `int`
ticks text rotation
space: `float`
space relative to the ax
Example
-------
>>> import trackc as tc
>>> region = 'chr7:153000000-151000000'
>>> ten = tc.tenon(figsize=(8,1))
>>> ten.add(pos='bottom', height=0.5)
>>> ten.axs(0).axis('off')
>>> tc.pl.scale_track(ax=ten.axs(0), region=region, scale_adjust='Mb', tick_pos='bottom', ratio2ax=1.2)
>>> tc.pl.scale_track(ax=ten.axs(0), region=region, scale_adjust='Mb', tick_pos='top', ratio2ax=1.2)
>>> tc.savefig('trackc_scalebar_track.pdf')
"""
line_GenomeRegions = None
if isinstance(region, list):
print("scale_track is only for one region")
return
else:
line_GenomeRegions = GenomeRegion(region).GenomeRegion2df()
line_GenomeRegions["raw_region"] = line_GenomeRegions.index
line_GenomeRegions = line_GenomeRegions.reset_index()
chrom = line_GenomeRegions.loc[0, "chrom"]
start = line_GenomeRegions.loc[0, "start"]
end = line_GenomeRegions.loc[0, "end"]
raw_region = line_GenomeRegions.loc[0, "raw_region"]
pos_dic = {
"left": [-ratio2ax, 0, ratio2ax, 1],
"right": [1, 0, ratio2ax, 1],
"top": [0, 1 + space, 1, ratio2ax],
"bottom": [0, -ratio2ax - space, 1, ratio2ax],
}
ax2 = ax.inset_axes(pos_dic[tick_pos], facecolor="none")
ax = ax2
if tick_pos in ["top", "bottom"]:
ax.set_xlim([start, end])
xticks = ax.get_xticks()
xtick_labels = xticks
last_label_ix = -1
if start > end:
last_label_ix = 0
if scale_adjust == "Mb":
if end % 1000000 > 0:
last_label_ix = -2
if start > end:
last_label_ix = 1
xtick_labels = xtick_labels / 1000000
xtick_labels = ["{0}".format(tick_fl % i) for i in xtick_labels]
ax.set_xticks(
xticks, xtick_labels, fontsize=tick_fontsize, rotation=tick_rotation
)
ax.spines["bottom"].set_position(("data", 0))
# ax.text(end-(abs(end-start)*0.05), -1, 'Mb', fontsize=chrom_fontsize)
elif scale_adjust == "kb":
if end % 1000 > 0:
last_label_ix = -2
if start > end:
last_label_ix = 1
xtick_labels = xtick_labels / 1000
xtick_labels = ["{0}".format(tick_fl % i) for i in xtick_labels]
ax.set_xticks(xticks, xtick_labels)
# ax.text(end-(abs(end-start)*0.05), -1, 'kb', fontsize=chrom_fontsize)
ax.spines["bottom"].set_position(("data", 0))
else:
pass
spines = ["bottom", "top", "right", "left"]
# chrom_x = start + (end-start)/2
chrom_x = start
chrom_y = 1
va = "top"
ha2 = "right"
if tick_pos == "bottom":
del spines[1]
ax.tick_params(top=True, labeltop=True, bottom=False, labelbottom=False)
chrom_y = 0
va = "bottom"
if tick_pos == "top":
ha2 = "left"
del spines[0]
ax.tick_params(top=False, labeltop=False, bottom=True, labelbottom=True)
for i in spines:
ax.spines[i].set_visible(False)
if tick_rotation == 0:
ha2 = "center"
ax.text(chrom_x, chrom_y, raw_region, fontsize=label_fontsize, ha="left", va=va)
labels = [label.get_text() for label in ax.get_xticklabels()]
if tick_rotation != 0:
labels[last_label_ix] = "{0}\n({1})".format(
labels[last_label_ix], scale_adjust
)
else:
labels[last_label_ix] = labels[last_label_ix] + "({0})".format(scale_adjust)
ax.set_xticklabels(
labels, fontsize=tick_fontsize, rotation=tick_rotation, va="center", ha=ha2
)
ax.set_xlim([start, end])
ax.tick_params(which="major", direction="in", pad=-16)
ax.set_yticks([])
ax.set_yticklabels("")
[docs]def multi_scale_track(
ax: Optional[Axes] = None,
regions: Union[Sequence[str], str, None] = None,
colors: Union[Sequence[str], None] = None,
alpha: Union[float, None] = 1,
intervals: Union[int, None] = 1,
scale_adjust: Union[str, None] = "kb",
tick_fl: Union[str, None] = "%0.2f",
tick_fontsize: Union[int, None] = 8,
tick_rotation: Union[int, None] = 0,
):
"""
Plot region scale bar, support for multiple or reverse genome regions.
Parameters
----------
ax: :class:`matplotlib.axes.Axes` object
regions: `str list` | `str`
e.g. ``"chr6:1000000-2000000"`` or ``["chr6:1000000-2000000", "chr3:5000000-4000000"]``
The start can be larger than the end (eg. ``"chr6:2000000-1000000"``),
which means you want to get the reverse region
colors: `str list`
scale bar colors
alpha: `float`
scale bar alpha
intervals: `int`
rows of the scale bar by region
scale_adjust: `str`
options in ['kb', 'Mb']
tick_fl: `str`
ticks retains a few decimal places
tick_fontsize: `int`
ticks text fontsize
tick_rotation: `int`
ticks text rotation
Example
-------
>>> import trackc as tc
>>> regions = ['7:153000000-151000000', '11:118500000-116500000']
>>> ten = tc.tenon(figsize=(8,1))
>>> ten.add(pos='bottom', height=1)
>>> tc.pl.multi_scale_track(ten.axs(0), regions=regions, scale_adjust='Mb', intervals=2)
"""
if colors != None:
track_colors = colors
else:
track_colors = trackcl_11
if isinstance(regions, list):
line_GenomeRegions = pd.concat(
[GenomeRegion(i).GenomeRegion2df() for i in regions]
)
else:
line_GenomeRegions = GenomeRegion(regions).GenomeRegion2df()
line_GenomeRegions = line_GenomeRegions.reset_index()
if len(track_colors) < line_GenomeRegions.shape[0]:
repeat_times = (line_GenomeRegions.shape[0] + len(track_colors) - 1) // len(
track_colors
)
track_colors = (track_colors * repeat_times)[: line_GenomeRegions.shape[0]]
else:
track_colors = track_colors[: line_GenomeRegions.shape[0]]
line_GenomeRegions["color"] = track_colors
line_GenomeRegions["len"] = (
line_GenomeRegions["fetch_end"] - line_GenomeRegions["fetch_start"]
)
line_GenomeRegions["acum"] = line_GenomeRegions["len"].cumsum()
sum_len = line_GenomeRegions["len"].sum()
bottom = list(range(intervals))
# broadcast
if intervals < line_GenomeRegions.shape[0]:
repeat_times = (line_GenomeRegions.shape[0] + intervals - 1) // intervals
bottom = (bottom * repeat_times)[: line_GenomeRegions.shape[0]]
else:
bottom = bottom[: line_GenomeRegions.shape[0]]
line_GenomeRegions.loc[:, "bottom"] = bottom
line_GenomeRegions["tick_s"] = _scale_ticks(
line_GenomeRegions["start"], scale=scale_adjust, tick_fl=tick_fl
)
line_GenomeRegions["tick_e"] = _scale_ticks(
line_GenomeRegions["end"], scale=scale_adjust, tick_fl=tick_fl
)
if scale_adjust in ["Mb", "kb"]:
if intervals > 1:
line_GenomeRegions.loc[line_GenomeRegions.index[-1], "tick_e"] = (
line_GenomeRegions.loc[line_GenomeRegions.index[-1], "tick_e"]
+ "("
+ scale_adjust
+ ")"
)
else:
line_GenomeRegions["tick_e"] = (
line_GenomeRegions["tick_e"] + "(" + scale_adjust + ")"
)
for i, row in line_GenomeRegions.iterrows():
arrow_s = row["acum"] - row["len"]
dx = row["len"]
if row["isReverse"] == True:
arrow_s = row["acum"]
dx = -1 * row["len"]
ax.arrow(
arrow_s,
row["bottom"],
dx,
0,
overhang=1,
width=0.02,
head_width=0.3,
head_length=sum_len / 100,
length_includes_head=True,
color=row["color"],
linewidth=1,
alpha=alpha,
)
tick_ha = "center"
if tick_rotation != 0:
tick_ha = "right"
if intervals > 1:
ax.text(
row["acum"] - row["len"] / 2,
row["bottom"] + 0.1,
row["chrom"],
ha="center",
va="bottom",
fontsize=tick_fontsize,
color=row["color"],
)
ax.text(
row["acum"] - row["len"],
row["bottom"] - 0.1,
row["tick_s"],
ha=tick_ha,
va="top",
fontsize=tick_fontsize,
rotation=tick_rotation,
color=row["color"],
)
ax.text(
row["acum"],
row["bottom"] - 0.1,
row["tick_e"],
ha=tick_ha,
va="top",
fontsize=tick_fontsize,
rotation=tick_rotation,
color=row["color"],
)
else:
ax.text(
row["acum"] - row["len"] / 2,
row["bottom"] - 0.2,
"{0}:{1}-{2}".format(row["chrom"], row["tick_s"], row["tick_e"]),
ha="center",
va="top",
fontsize=tick_fontsize,
color=row["color"],
)
ax.set_xlim([0, sum_len])
ax.set_ylim([-0.7, intervals - 0.3])
for i in ["bottom", "top", "right", "left"]:
ax.spines[i].set_color("none")
ax.spines[i].set_linewidth(0)
ax.tick_params(bottom=False, top=False, left=False, right=False)
ax.set_xticklabels("")
ax.set_yticklabels("")
# ax.spines["bottom"].set_color('black')
# ax.spines["bottom"].set_linewidth(1)