I am trying to draw a series of lines. The lines are all the same length, and randomly switch colors for a random length (blue to orange). I am drawing the lines in blue and then overlaying orange on top. You can see from my picture there are clipped parts of the lines where it is grey. I cannot figure out why this is happening. Also related I believe is that my labels are not moving to a left alignment like they should. Any help is greatly appreciated.
我想绘制一系列线条。这些线的长度都相同,随机切换颜色为随机长度(蓝色到橙色)。我用蓝色绘制线条,然后在顶部覆盖橙色。你可以从我的照片中看到它是灰色的线条的剪裁部分。我无法弄清楚为什么会这样。另外我相信我的标签并没有像他们应该那样向左移动。任何帮助是极大的赞赏。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import random
plt.close('all')
fig, ax = plt.subplots(figsize=(15,11))
def label(xy, text):
y = xy[1] - 2
ax.text(xy[0], y, text, ha="left", family='sans-serif', size=14)
def draw_chromosome(start, stop, y, color):
x = np.array([start, stop])
y = np.array([y, y])
line = mlines.Line2D(x , y, lw=10., color=color)
ax.add_line(line)
x = 50
y = 100
chr = 1
for i in range(22):
draw_chromosome(x, 120, y, "#1C2F4D")
j = 0
while j < 120:
print j
length = 1
if random.randint(1, 100) > 90:
length = random.randint(1, 120-j)
draw_chromosome(j, j+length, y, "#FA9B00")
j = j+length+1
label([x, y], "Chromosome%i" % chr)
y -= 3
chr += 1
plt.axis('equal')
plt.axis('off')
plt.tight_layout()
plt.show()
2 个解决方案
#1
You're only drawing the blue background from x = 50 to x = 120.
您只绘制从x = 50到x = 120的蓝色背景。
Replace this line:
替换此行:
draw_chromosome(x, 120, y, "#1C2F4D")
with this:
draw_chromosome(0, 120, y, "#1C2F4D")
To draw the blue line all the way across.
一直画蓝线。
Alternately, if you also want to move your labels to the left, you can just set x=0
instead of setting it to 50.
或者,如果您还想将标签移到左侧,则可以设置x = 0而不是将其设置为50。
#2
I suggest using LineCollection
for this. Below is a little helper function I wrote based on the example at http://matplotlib.org/examples/pylab_examples/multicolored_line.html (it looks long, but there is a lot of comments + docstrings)
我建议使用LineCollection。下面是我根据http://matplotlib.org/examples/pylab_examples/multicolored_line.html上的示例编写的一个小助手函数(看起来很长,但有很多评论+文档字符串)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
from matplotlib.ticker import NullLocator
from collections import OrderedDict
def binary_state_lines(ax, chrom_data, xmin=0, xmax=120,
delta_y=3,
off_color = "#1C2F4D",
on_color = "#FA9B00"):
"""
Draw a whole bunch of chromosomes
Parameters
----------
ax : Axes
The axes to draw stuff to
chrom_data : OrderedDict
The chromosome data as a dict, key on the label with a list of pairs
of where the data is 'on'. Data is plotted top-down
xmin, xmax : float, optional
The minimum and maximum limits for the x values
delta_y : float, optional
The spacing between lines
off_color, on_color : color, optional
The colors to use for the the on/off state
Returns
-------
collections : dict
dictionary of the collections added keyed on the label
"""
# base offset
y_val = 0
# make the color map and norm
cmap = ListedColormap([off_color, on_color])
norm = BoundaryNorm([0, 0.5, 1], cmap.N)
# sort out where the text should be
txt_x = (xmax + xmin) / 2
# dictionary to hold the returned artists
ret = dict()
# loop over the input data draw each collection
for label, data in chrom_data.items():
# increment the y offset
y_val += delta_y
# turn the high windows on to alternating
# high/low regions
x = np.asarray(data).ravel()
# assign the high/low state to each one
state = np.mod(1 + np.arange(len(x)), 2)
# deal with boundary conditions to be off
# at start/end
if x[0] > xmin:
x = np.r_[xmin, x]
state = np.r_[0, state]
if x[-1] < xmax:
x = np.r_[x, xmax]
state = np.r_[state, 0]
# make the matching y values
y = np.ones(len(x)) * y_val
# call helper function to create the collection
coll = draw_segments(ax, x, y, state,
cmap, norm)
ret[label] = coll
# set up the axes limits
ax.set_xlim(xmin, xmax)
ax.set_ylim(0, y_val + delta_y)
# turn off x-ticks
ax.xaxis.set_major_locator(NullLocator())
# make the y-ticks be labeled as per the input
ax.yaxis.set_ticks((1 + np.arange(len(chrom_data))) * delta_y)
ax.yaxis.set_ticklabels(list(chrom_data.keys()))
# invert so that the first data is at the top
ax.invert_yaxis()
# turn off the frame and patch
ax.set_frame_on(False)
# return the added artists
return ret
def draw_segments(ax, x, y, state, cmap, norm, lw=10):
"""
helper function to turn boundary edges into the input LineCollection
expects.
Parameters
----------
ax : Axes
The axes to draw to
x, y, state : array
The x edges, the y values and the state of each region
cmap : matplotlib.colors.Colormap
The color map to use
norm : matplotlib.ticker.Norm
The norm to use with the color map
lw : float, optional
The width of the lines
"""
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, cmap=cmap, norm=norm)
lc.set_array(state)
lc.set_linewidth(lw)
ax.add_collection(lc)
return lc
An example:
synthetic_data = OrderedDict()
for j in range(21):
key = 'data {:02d}'.format(j)
synthetic_data[key] = np.cumsum(np.random.randint(1, 10, 20)).reshape(-1, 2)
fig, ax = plt.subplots(tight_layout=True)
binary_state_lines(ax, synthetic_data, xmax=120)
plt.show()
Separating the plotting logic from everything else will make your code easier to maintain and more reusable.
将绘图逻辑与其他所有内容分开将使您的代码更易于维护和更可重用。
I also took the liberty of moving your labels from between the lines (where they can be ambiguous) to the yaxis tick labels.
我还冒昧地将标签从线条之间(它们可以模棱两可)移动到yaxis刻度标签上。
#1
You're only drawing the blue background from x = 50 to x = 120.
您只绘制从x = 50到x = 120的蓝色背景。
Replace this line:
替换此行:
draw_chromosome(x, 120, y, "#1C2F4D")
with this:
draw_chromosome(0, 120, y, "#1C2F4D")
To draw the blue line all the way across.
一直画蓝线。
Alternately, if you also want to move your labels to the left, you can just set x=0
instead of setting it to 50.
或者,如果您还想将标签移到左侧,则可以设置x = 0而不是将其设置为50。
#2
I suggest using LineCollection
for this. Below is a little helper function I wrote based on the example at http://matplotlib.org/examples/pylab_examples/multicolored_line.html (it looks long, but there is a lot of comments + docstrings)
我建议使用LineCollection。下面是我根据http://matplotlib.org/examples/pylab_examples/multicolored_line.html上的示例编写的一个小助手函数(看起来很长,但有很多评论+文档字符串)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
from matplotlib.ticker import NullLocator
from collections import OrderedDict
def binary_state_lines(ax, chrom_data, xmin=0, xmax=120,
delta_y=3,
off_color = "#1C2F4D",
on_color = "#FA9B00"):
"""
Draw a whole bunch of chromosomes
Parameters
----------
ax : Axes
The axes to draw stuff to
chrom_data : OrderedDict
The chromosome data as a dict, key on the label with a list of pairs
of where the data is 'on'. Data is plotted top-down
xmin, xmax : float, optional
The minimum and maximum limits for the x values
delta_y : float, optional
The spacing between lines
off_color, on_color : color, optional
The colors to use for the the on/off state
Returns
-------
collections : dict
dictionary of the collections added keyed on the label
"""
# base offset
y_val = 0
# make the color map and norm
cmap = ListedColormap([off_color, on_color])
norm = BoundaryNorm([0, 0.5, 1], cmap.N)
# sort out where the text should be
txt_x = (xmax + xmin) / 2
# dictionary to hold the returned artists
ret = dict()
# loop over the input data draw each collection
for label, data in chrom_data.items():
# increment the y offset
y_val += delta_y
# turn the high windows on to alternating
# high/low regions
x = np.asarray(data).ravel()
# assign the high/low state to each one
state = np.mod(1 + np.arange(len(x)), 2)
# deal with boundary conditions to be off
# at start/end
if x[0] > xmin:
x = np.r_[xmin, x]
state = np.r_[0, state]
if x[-1] < xmax:
x = np.r_[x, xmax]
state = np.r_[state, 0]
# make the matching y values
y = np.ones(len(x)) * y_val
# call helper function to create the collection
coll = draw_segments(ax, x, y, state,
cmap, norm)
ret[label] = coll
# set up the axes limits
ax.set_xlim(xmin, xmax)
ax.set_ylim(0, y_val + delta_y)
# turn off x-ticks
ax.xaxis.set_major_locator(NullLocator())
# make the y-ticks be labeled as per the input
ax.yaxis.set_ticks((1 + np.arange(len(chrom_data))) * delta_y)
ax.yaxis.set_ticklabels(list(chrom_data.keys()))
# invert so that the first data is at the top
ax.invert_yaxis()
# turn off the frame and patch
ax.set_frame_on(False)
# return the added artists
return ret
def draw_segments(ax, x, y, state, cmap, norm, lw=10):
"""
helper function to turn boundary edges into the input LineCollection
expects.
Parameters
----------
ax : Axes
The axes to draw to
x, y, state : array
The x edges, the y values and the state of each region
cmap : matplotlib.colors.Colormap
The color map to use
norm : matplotlib.ticker.Norm
The norm to use with the color map
lw : float, optional
The width of the lines
"""
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, cmap=cmap, norm=norm)
lc.set_array(state)
lc.set_linewidth(lw)
ax.add_collection(lc)
return lc
An example:
synthetic_data = OrderedDict()
for j in range(21):
key = 'data {:02d}'.format(j)
synthetic_data[key] = np.cumsum(np.random.randint(1, 10, 20)).reshape(-1, 2)
fig, ax = plt.subplots(tight_layout=True)
binary_state_lines(ax, synthetic_data, xmax=120)
plt.show()
Separating the plotting logic from everything else will make your code easier to maintain and more reusable.
将绘图逻辑与其他所有内容分开将使您的代码更易于维护和更可重用。
I also took the liberty of moving your labels from between the lines (where they can be ambiguous) to the yaxis tick labels.
我还冒昧地将标签从线条之间(它们可以模棱两可)移动到yaxis刻度标签上。