如何在Python的Matplotlib线上绘制外边缘的轮廓?

O.r*_*rka 10 python plot matplotlib line networkx

我正在尝试linestyle=":"networkx边缘绘制轮廓()。我似乎无法弄清楚如何对matplotlib patch对象执行此操作? 现在有没有人如何操纵这些patch物体以在这些“边缘”上绘制轮廓?如果无法做到这一点,是否有人知道如何获取行数据以ax.plot(x,y,linestyle=":")单独使用?

import networkx as nx
import numpy as np
from collections import *

# Graph data
G = {'y1': OrderedDict([('y2', OrderedDict([('weight', 0.8688325076457851)])), (1, OrderedDict([('weight', 0.13116749235421485)]))]), 'y2': OrderedDict([('y3', OrderedDict([('weight', 0.29660515972204304)])), ('y4', OrderedDict([('weight', 0.703394840277957)]))]), 'y3': OrderedDict([(4, OrderedDict([('weight', 0.2858185316736193)])), ('y5', OrderedDict([('weight', 0.7141814683263807)]))]), 4: OrderedDict(), 'input': OrderedDict([('y1', OrderedDict([('weight', 1.0)]))]), 'y4': OrderedDict([(3, OrderedDict([('weight', 0.27847763084646443)])), (5, OrderedDict([('weight', 0.7215223691535356)]))]), 3: OrderedDict(), 5: OrderedDict(), 'y5': OrderedDict([(6, OrderedDict([('weight', 0.5733512797415756)])), (2, OrderedDict([('weight', 0.4266487202584244)]))]), 6: OrderedDict(), 1: OrderedDict(), 2: OrderedDict()}
G = nx.from_dict_of_dicts(G)
G_scaffold = {'input': OrderedDict([('y1', OrderedDict())]), 'y1': OrderedDict([('y2', OrderedDict()), (1, OrderedDict())]), 'y2': OrderedDict([('y3', OrderedDict()), ('y4', OrderedDict())]), 1: OrderedDict(), 'y3': OrderedDict([(4, OrderedDict()), ('y5', OrderedDict())]), 'y4': OrderedDict([(3, OrderedDict()), (5, OrderedDict())]), 4: OrderedDict(), 'y5': OrderedDict([(6, OrderedDict()), (2, OrderedDict())]), 3: OrderedDict(), 5: OrderedDict(), 6: OrderedDict(), 2: OrderedDict()}
G_scaffold = nx.from_dict_of_dicts(G_scaffold)
G_sem = {'y1': OrderedDict([('y2', OrderedDict([('weight', 0.046032370518141796)])), (1, OrderedDict([('weight', 0.046032370518141796)]))]), 'y2': OrderedDict([('y3', OrderedDict([('weight', 0.08764771571290508)])), ('y4', OrderedDict([('weight', 0.08764771571290508)]))]), 'y3': OrderedDict([(4, OrderedDict([('weight', 0.06045928834718992)])), ('y5', OrderedDict([('weight', 0.06045928834718992)]))]), 4: OrderedDict(), 'input': OrderedDict([('y1', OrderedDict([('weight', 0.0)]))]), 'y4': OrderedDict([(3, OrderedDict([('weight', 0.12254141747735424)])), (5, OrderedDict([('weight', 0.12254141747735425)]))]), 3: OrderedDict(), 5: OrderedDict(), 'y5': OrderedDict([(6, OrderedDict([('weight', 0.11700701511079069)])), (2, OrderedDict([('weight', 0.11700701511079069)]))]), 6: OrderedDict(), 1: OrderedDict(), 2: OrderedDict()}
G_sem = nx.from_dict_of_dicts(G_sem)

# Edge info
edge_input = ('input', 'y1')
weights_sem = np.array([G_sem[u][v]['weight']for u,v in G_sem.edges()]) * 256

# Layout
pos = nx.nx_agraph.graphviz_layout(G_scaffold, prog="dot", root="input")

# Plotting graph
pad = 10
with plt.style.context("ggplot"):
    fig, ax = plt.subplots(figsize=(8,8))
    linecollection = nx.draw_networkx_edges(G_sem, pos, alpha=0.5, edges=G_sem.edges(), arrowstyle="-", edge_color="#000000", width=weights_sem)
    x = np.stack(pos.values())[:,0]
    y =  np.stack(pos.values())[:,1]
    ax.set(xlim=(x.min()-pad,x.max()+pad), ylim=(y.min()-pad, y.max()+pad))

    for path, lw in zip(linecollection.get_paths(), linecollection.get_linewidths()):
        x = path.vertices[:,0]
        y = path.vertices[:,1]
        w = lw/4
        theta = np.arctan2(y[-1] - y[0], x[-1] - x[0])
    #     ax.plot(x, y, color="blue", linestyle=":")
        ax.plot((x-np.sin(theta)*w), y+np.cos(theta)*w, color="blue", linestyle=":")
        ax.plot((x+np.sin(theta)*w), y-np.cos(theta)*w, color="blue", linestyle=":")
Run Code Online (Sandbox Code Playgroud)

经过几次思想实验,我意识到我需要计算角度,然后相应地调整垫板:

例如,如果线是完全垂直的(在90或-90处),则y坐标将完全不被x坐标移动。角度为0或180的线则相反。

但是,它仍然有点偏离。

我怀疑这是相关的: matplotlib-以数据单位的指定宽度扩展行吗?

我不认为linewidth直接转换为数据空间

或者,如果这些线集合可以转换为矩形对象,那么它也是可能的。

在此处输入图片说明

Imp*_*est 5

用另一条线包围一定宽度的线的问题在于,该线是在数据坐标中定义的,而线宽是在一个物理单位中,即点。这通常是可取的,因为它允许线宽与数据范围、缩放级别等无关。它还确保线的末端始终垂直于线,与轴方面无关。

所以线条的轮廓总是在一个混合坐标系中,最终的外观在用渲染器绘制实际线条之前是不确定的。因此,对于考虑(可能发生变化的)坐标的解决方案,需要确定图形当前状态的轮廓。

一种选择是使用新艺术家,它将现有艺术家LineCollection作为输入,并根据像素空间中线条的当前位置创建新的变换。

在下面我选择了一个PatchCollection. 从一个矩形开始,我们可以缩放和旋转它,然后将其平移到原始线的位置。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection, PatchCollection
import matplotlib.transforms as mtrans


class OutlineCollection(PatchCollection):
    def __init__(self, linecollection, ax=None, **kwargs):
        self.ax = ax or plt.gca()
        self.lc = linecollection
        assert np.all(np.array(self.lc.get_segments()).shape[1:] == np.array((2,2)))
        rect = plt.Rectangle((-.5, -.5), width=1, height=1)
        super().__init__((rect,), **kwargs)
        self.set_transform(mtrans.IdentityTransform())
        self.set_offsets(np.zeros((len(self.lc.get_segments()),2)))
        self.ax.add_collection(self)

    def draw(self, renderer):
        segs = self.lc.get_segments()
        n = len(segs)
        factor = 72/self.ax.figure.dpi
        lws = self.lc.get_linewidth()
        if len(lws) <= 1:
            lws = lws*np.ones(n)
        transforms = []
        for i, (lw, seg) in enumerate(zip(lws, segs)):
            X = self.lc.get_transform().transform(seg)
            mean = X.mean(axis=0)
            angle = np.arctan2(*np.squeeze(np.diff(X, axis=0))[::-1])
            length = np.sqrt(np.sum(np.diff(X, axis=0)**2))
            trans = mtrans.Affine2D().scale(length,lw/factor).rotate(angle).translate(*mean)
            transforms.append(trans.get_matrix())
        self._transforms = transforms
        super().draw(renderer)
Run Code Online (Sandbox Code Playgroud)

注意实际的变换是如何只在draw时间计算的。这确保它们考虑到像素空间中的实际位置。

用法可能看起来像

verts = np.array([[[5,10],[5,5]], [[5,5],[8,2]], [[5,5],[1,4]], [[1,4],[2,0]]])

plt.rcParams["axes.xmargin"] = 0.1
fig, (ax1, ax2) = plt.subplots(ncols=2, sharex=True, sharey=True)

lc1 = LineCollection(verts, color="k", alpha=0.5, linewidth=20)
ax1.add_collection(lc1)

olc1 = OutlineCollection(lc1, ax=ax1, linewidth=2, 
                         linestyle=":", edgecolor="black", facecolor="none")


lc2 = LineCollection(verts, color="k", alpha=0.3, linewidth=(10,20,40,15))
ax2.add_collection(lc2)

olc2 = OutlineCollection(lc2, ax=ax2, linewidth=3, 
                         linestyle="--", edgecolors=["r", "b", "gold", "indigo"], 
                        facecolor="none")

for ax in (ax1,ax2):
    ax.autoscale()
plt.show()
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

现在当然的想法是使用linecollection问题中的lc1对象而不是上面的对象。这应该很容易在代码中替换。