为什么我的线裁剪在matplotlib中?

时间:2023-02-04 15:57:20

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.

我想绘制一系列线条。这些线的长度都相同,随机切换颜色为随机长度(蓝色到橙色)。我用蓝色绘制线条,然后在顶部覆盖橙色。你可以从我的照片中看到它是灰色的线条的剪裁部分。我无法弄清楚为什么会这样。另外我相信我的标签并没有像他们应该那样向左移动。任何帮助是极大的赞赏。

为什么我的线裁剪在matplotlib中?

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

为什么我的线裁剪在matplotlib中?

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

为什么我的线裁剪在matplotlib中?

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刻度标签上。