以数据坐标中给定的恒定宽度绘制任意路径

时间:2019-04-04 13:15:58

标签: python matplotlib

总体目标

我正在尝试编写一些绘图功能(其核心) 以数据坐标中给定的恒定宽度绘制任意路径 (即与matplotlib中的线条不同,这些线条的宽度在显示坐标中给出)。

以前的解决方案

answer实现 基本目标。但是,此答案会在显示和数据之间转换 坐标,然后使用已调整的matplotlib行 坐标。我想要的代码中的现有功能 替换/扩展从matplotlib.patches.Polygon继承。以来 其余代码库大量使用 matplotlib.patches.Polygon属性和方法,我想 继续从该类继承。

问题

我当前的实现(下面的代码)似乎已经接近。然而, 由simple_test创建的补丁似乎向 中心比起点和终点要大,我没有 解释为什么可能会这样。

我怀疑问题出在正交向量的计算上。 作为支持证据,我想指出由complicated_test创建的图中补丁的起点和终点,它们似乎与路径不完全正交。但是,正交向量和切向量的点积始终为零,因此我不确定这里发生了什么。

simple_test的输出: enter image description here

complicated_test的输出: enter image description here

代码

#!/usr/bin/env python
import numpy as np
import matplotlib.patches
import matplotlib.pyplot as plt

class CurvedPatch(matplotlib.patches.Polygon):

    def __init__(self, path, width, *args, **kwargs):
        vertices = self.get_vertices(path, width)
        matplotlib.patches.Polygon.__init__(self, list(map(tuple, vertices)),
                                            closed=True,
                                            *args, **kwargs)

    def get_vertices(self, path, width):
        left = _get_parallel_path(path, -width/2)
        right = _get_parallel_path(path, width/2)
        full = np.concatenate([left, right[::-1]])
        return full


def _get_parallel_path(path, delta):
    # initialise output
    offset = np.zeros_like(path)

    # use the previous and the following point to
    # determine the tangent at each point in the path;
    for ii in range(1, len(path)-1):
        offset[ii] += _get_shift(path[ii-1], path[ii+1], delta)

    # handle start and end points
    offset[0] = _get_shift(path[0], path[1], delta)
    offset[-1] = _get_shift(path[-2], path[-1], delta)

    return path + offset


def _get_shift(p1, p2, delta):
    # unpack coordinates
    x1, y1 = p1
    x2, y2 = p2

    # get orthogonal unit vector;
    # adapted from https://stackoverflow.com/a/16890776/2912349
    v = np.r_[x2-x1, y2-y1]   # vector between points
    v = v / np.linalg.norm(v) # unit vector

    w = np.r_[-v[1], v[0]]    # orthogonal vector
    w = w / np.linalg.norm(w) # orthogonal unit vector

    # check that vectors are indeed orthogonal
    assert np.isclose(np.dot(v, w), 0.)

    # rescale unit vector
    dx, dy = delta * w

    return dx, dy


def simple_test():

    x = np.linspace(-1, 1, 1000)
    y = np.sqrt(1. - x**2)
    path = np.c_[x, y]

    curve = CurvedPatch(path, 0.1, facecolor='red', alpha=0.5)

    fig, ax = plt.subplots(1,1)
    ax.add_artist(curve)
    ax.plot(x, y) # plot path for reference
    plt.show()


def complicated_test():

    random_points = np.random.rand(10, 2)

    # Adapted from https://stackoverflow.com/a/35007804/2912349
    import scipy.interpolate as si

    def scipy_bspline(cv, n=100, degree=3, periodic=False):
        """ Calculate n samples on a bspline

            cv :      Array ov control vertices
            n  :      Number of samples to return
            degree:   Curve degree
            periodic: True - Curve is closed
        """
        cv = np.asarray(cv)
        count = cv.shape[0]

        # Closed curve
        if periodic:
            kv = np.arange(-degree,count+degree+1)
            factor, fraction = divmod(count+degree+1, count)
            cv = np.roll(np.concatenate((cv,) * factor + (cv[:fraction],)),-1,axis=0)
            degree = np.clip(degree,1,degree)

        # Opened curve
        else:
            degree = np.clip(degree,1,count-1)
            kv = np.clip(np.arange(count+degree+1)-degree,0,count-degree)

        # Return samples
        max_param = count - (degree * (1-periodic))
        spl = si.BSpline(kv, cv, degree)
        return spl(np.linspace(0,max_param,n))

    x, y = scipy_bspline(random_points, n=1000).T
    path = np.c_[x, y]

    curve = CurvedPatch(path, 0.1, facecolor='red', alpha=0.5)

    fig, ax = plt.subplots(1,1)
    ax.add_artist(curve)
    ax.plot(x, y) # plot path for reference
    plt.show()


if __name__ == '__main__':
    plt.ion()
    simple_test()
    complicated_test()

0 个答案:

没有答案