首页 > 解决方案 > 使用 matplotlib 绘制固定长度的线段(切线)以保留纵横角

问题描述

背景:我试图在梯度噪声图上将梯度显示为固定长度的线。每个“梯度”都可以看作是给定点的切线。问题是,即使我确保线条具有相同的长度,纵横比也会拉伸它们:

梯度噪声

生成这个的完整代码:

from math import sqrt, floor, modf, sin
import matplotlib.pyplot as plt


mix = lambda a, b, x: a*(1-x) + b*x
interpolant = lambda t: ((6*t - 15)*t + 10)*t*t*t
rng01 = lambda x: modf(sin(x) * 43758.5453123)[0]


def _gradient_noise(t):
    i = floor(t)
    f = t - i
    s0 = rng01(i)     * 2 - 1
    s1 = rng01(i + 1) * 2 - 1
    v0 = s0 * f;
    v1 = s1 * (f - 1);
    return mix(v0, v1, interpolant(f))


def _plot_noise(n, interp_npoints=100):
    xdata = [i/interp_npoints for i in range(n * interp_npoints)]
    gnoise = [_gradient_noise(x) for x in xdata]

    plt.plot(xdata, gnoise, label='gradient noise')
    plt.xlabel('t')
    plt.ylabel('amplitude')
    plt.grid(linestyle=':')
    plt.legend()

    for i in range(n + 1):
        a = rng01(i) * 2 - 1  # gradient slope
        norm = sqrt(1 + a**2)
        norm *= 4  # 1/4 length
        vnx, vny = 1/norm, a/norm
        x = (i-vnx/2, i+vnx/2)
        y = (-vny/2, vny/2)
        plt.plot(x, y, 'r-')

    plt.show()


if __name__ == '__main__':
    _plot_noise(15)

红线图位于 for 循环中。

hypot(x[1]-x[0], y[1]-y[0])为每个向量提供一个常数.25,对应于我的目标长度 (¼)。这意味着我的段实际上在给定方面的正确长度。这也可以通过以下方式“验证” .set_aspect(1)

梯度噪声方面=1

我尝试了几件事,例如将坐标转换为显示坐标plt.gca().transData.transform(...)plt.gca().transData.inverted().transform(...)无论如何,这样做也可能实际上也会改变角度。

总结一下:我正在寻找一种方法来显示具有固定长度(在 x 数据坐标系中表示)并在 xy 数据坐标系中定向(旋转)的线条。

标签: matplotlib

解决方案


在此处输入图像描述

欢迎来到 SO。第一个问题问得真好。一旦我复制了情节并检查了数学,这让我怀疑自己的理智有多么强烈......

但是,您自己确定了核心问题:问题是在您的代码中,渐变线的长度是在数据坐标中确定的,而它应该取决于绘图的纵横比。

因此,如果您希望渐变线在显示空间中具有一致的长度,那么您需要在计算然后范数时通过绘图的纵横比(或它的倒数)重新缩放 thedx或组件:dy

import matplotlib.pyplot as plt

from math import sqrt, floor

mix = lambda a, b, x: a*(1-x) + b*x
interpolant = lambda t: ((6*t - 15)*t + 10)*t*t*t
rng01 = lambda x: ((1103515245*x + 12345) % 2**32) / 2**32


def _gradient_noise(t):
    i = floor(t)
    f = t - i
    s0 = rng01(i)     * 2 - 1
    s1 = rng01(i + 1) * 2 - 1
    v0 = s0 * f;
    v1 = s1 * (f - 1);
    return mix(v0, v1, interpolant(f))


def _plot_noise(n, interp_npoints=100):
    xdata = [i/interp_npoints for i in range(n * interp_npoints)]
    gnoise = [_gradient_noise(x) for x in xdata]

    fig, ax = plt.subplots()
    ax.plot(xdata, gnoise, label='gradient noise')
    ax.set_xlabel('t')
    ax.set_ylabel('amplitude')
    ax.grid(linestyle=':')
    ax.legend(loc=1)

    x0, x1, y0, y1 = ax.axis()
    aspect = (y1 - y0) / (x1 - x0)

    for i in range(n + 1):
        dy = rng01(i) * 2 - 1  # gradient slope
        dx = 1
        norm = sqrt(dx**2 + (dy / aspect)**2)
        # norm *= 4  # 1/4 length
        vnx, vny = dx/norm, dy/norm
        x = (i-vnx/2, i+vnx/2)
        y = (-vny/2, vny/2)
        ax.plot(x, y, 'r-')

    plt.show()


if __name__ == '__main__':
    _plot_noise(15)

推荐阅读