如何在matplotlib中绘制半无限直线(射线)?

Bur*_*ito 6 python matplotlib

我们可以使用 plt.axline() 从 matplotlib 中给定点以给定斜率绘制无限直线(https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.axline.html

有没有一种干净的方法可以从给定点向给定方向绘制半无限直线或射线?最好不必计算轴限制。

对于 axhline 和 axvline,我们可以使用 xmin、xmax、ymin、ymax 参数之一来获取射线,但 axline 不接受这些。

相关问题:

小智 5

这是axline的修改,它允许指定一个semi_x参数。xy1它控制要绘制点周围的 x 半平面。

这适用于slopexy2参数。忽略semi_x保留默认axline行为。

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.transforms import Bbox, BboxTransformTo
from matplotlib.lines import Line2D

def axline(ax, xy1, xy2=None, *, slope=None, semi_x=None, **kwargs):
    if slope is not None and (ax.get_xscale() != 'linear' or
                                ax.get_yscale() != 'linear'):
        raise TypeError("'slope' cannot be used with non-linear scales")

    datalim = [xy1] if xy2 is None else [xy1, xy2]
    if "transform" in kwargs:
        # if a transform is passed (i.e. line points not in data space),
        # data limits should not be adjusted.
        datalim = []

    line = _AxLine(xy1, xy2, slope, semi_x, **kwargs)
    # Like add_line, but correctly handling data limits.
    ax._set_artist_props(line)
    if line.get_clip_path() is None:
        line.set_clip_path(ax.patch)
    if not line.get_label():
        line.set_label(f"_line{len(ax.lines)}")
    ax.lines.append(line)
    line._remove_method = ax.lines.remove
    ax.update_datalim(datalim)

    ax._request_autoscale_view()
    return line

class _AxLine(Line2D):
    def __init__(self, xy1, xy2, slope, semi_x, **kwargs):
        super().__init__([0, 1], [0, 1], **kwargs)

        if (xy2 is None and slope is None or
                xy2 is not None and slope is not None):
            raise TypeError(
                "Exactly one of 'xy2' and 'slope' must be given")

        self._slope = slope
        self._xy1 = xy1
        self._xy2 = xy2
        self._semi_x = semi_x

    def get_transform(self):
        ax = self.axes
        points_transform = self._transform - ax.transData + ax.transScale

        if self._xy2 is not None:
            # two points were given
            (x1, y1), (x2, y2) = \
                points_transform.transform([self._xy1, self._xy2])
            dx = x2 - x1
            dy = y2 - y1
            if np.allclose(x1, x2):
                if np.allclose(y1, y2):
                    raise ValueError(
                        f"Cannot draw a line through two identical points "
                        f"(x={(x1, x2)}, y={(y1, y2)})")
                slope = np.inf
            else:
                slope = dy / dx
        else:
            # one point and a slope were given
            x1, y1 = points_transform.transform(self._xy1)
            slope = self._slope
        (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim)
        # General case: find intersections with view limits in either
        # direction, and draw between the middle two points.
        if np.isclose(slope, 0):
            start = vxlo, y1
            stop = vxhi, y1
        elif np.isinf(slope):
            start = x1, vylo
            stop = x1, vyhi
        else:
            _, start, stop, _ = sorted([
                (vxlo, y1 + (vxlo - x1) * slope),
                (vxhi, y1 + (vxhi - x1) * slope),
                (x1 + (vylo - y1) / slope, vylo),
                (x1 + (vyhi - y1) / slope, vyhi),
            ])
        # Handle semi-plane
        if self._semi_x == True:
            start = (x1,y1)
        elif self._semi_x == False:
            stop = (x1,y1)
        return (BboxTransformTo(Bbox([start, stop]))
                + ax.transLimits + ax.transAxes)

    def draw(self, renderer):
        self._transformed_path = None  # Force regen.
        super().draw(renderer)


## Usage with slope
fig, ax = plt.subplots()
xy1 = (.5, .5)
slope = -1
ax.scatter(*xy1)
axline(ax, xy1, slope=slope, c='g', semi_x=True)
axline(ax, xy1, slope=slope, c='r', semi_x=False)
ax.set_xlim([0,1])
ax.set_ylim([0,1])
plt.show()


## Usage with xy2
fig, ax = plt.subplots()
xy1 = (.5, .5)
xy2 = (.75, .75)
ax.scatter(*xy1)
ax.scatter(*xy2)
axline(ax, xy1, xy2=xy2, c='g', semi_x=True)
axline(ax, xy1, xy2=xy2, c='r', semi_x=False)
ax.set_xlim([0,1])
ax.set_ylim([0,1])
plt.show()
Run Code Online (Sandbox Code Playgroud)

使用示例slope

使用示例xy2


Der*_*k O 2

我无法根据axline 文档linewidth找到一种干净的方法来执行此操作,因此我将发布我的 hacky 解决方法,即通过从 xmin绘制一条线段(具有比您的 axline 更大的线段)来模糊该线的部分起点的 x 值。

我承认这是一个丑陋的解决方案,如果我想到更好的解决方案,我会更新我的答案。

import matplotlib.pyplot as plt

## draw infinite line starting from (0.5,0.5) with slope=1
x0,y0,m= 0.5,0.5,1
plt.axline((x0, y0), slope=m, color='k', transform=plt.gca().transAxes, linewidth=0.5, alpha=0.5)

## obscure the line segment from xmin to x0
ymin,ymax = plt.gca().get_ylim()
xmin = x0 - (y0-ymin / m)

## plot twice so that a portion of the axline can't be seen
plt.plot([xmin,x0], [ymin,y0], '#ffffff', linewidth=1.0, alpha=1.0)
plt.plot([xmin,x0], [ymin,y0], '#ffffff', linewidth=1.0, alpha=1.0)

plt.ylim([0, 1])
plt.xlim([0, 1])
plt.show()
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

  • 哈哈,这很丑xD,但我很欣赏你的努力!绘制两次模糊线是相当荒谬的,但似乎是在解决透明度问题时派上用场的一种技巧。然而,除了这一切之外,我们不能只绘制从 x0, y0 到 xmax, ymax 的图吗? (2认同)