如何在Python的Matplotlib线上绘制外边缘的轮廓? [英] How to plot the outline of the outer edges on a Matplotlib line in Python?

查看:125
本文介绍了如何在Python的Matplotlib线上绘制外边缘的轮廓?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在networkx边缘上绘制轮廓(linestyle=":").我似乎无法弄清楚如何对matplotlib patch对象执行此操作? 现在有人可以如何操纵这些patch对象以将轮廓绘制到这些边缘"上吗??如果不可能,是否有人知道如何获取线数据以分别使用ax.plot(x,y,linestyle=":")来进行操作这?

I am trying to plot an outline (linestyle=":") on the networkx edges. I can't seem to figure out how to do this to the matplotlib patch objects? Does anyone now how to manipulate these patch object to plot outlines to these "edges"? If this is not possible, does anyone know how to get the line data to use ax.plot(x,y,linestyle=":") separately to do this?

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=":")

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

After a couple of thought experiments, I realized I need to calculate the angle and then adjust the pads accordingly:

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

For example, if the line was completely vertical (at 90 or -90) then the y coords would not be shifted at all by the x coords would be shifted. The opposite would happen for a line with an angle 0 or 180.

但是,它仍然有点偏离.

However, it's still off a bit.

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

I suspect that this is relevant: matplotlib - Expand the line with specified width in data unit?

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

I don't think the linewidth directly translates to data space

或者,如果这些线集合可以转换为矩形对象,那么也有可能.

Alternatively, if these line collections could be converted into rectangle objects then it would also be possible.

推荐答案

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

The problem of surrounding a line with a certain width by another line is that the line is defined in data coordinates, while the linewidth is in a physical unit, namely points. This is in general desireable, because it allows to have the linewidth to be independent of the data range, zooming level etc. It also ensures that the end of the line is always perpendicular to the line, independent of the axes aspect.

因此,线条的轮廓线始终处于混合坐标系中,并且在使用渲染器绘制实际线条之前,无法确定最终外观.因此,对于一种考虑了(可能会改变的)坐标的解决方案,需要确定图形当前状态的轮廓.

So the outline of the line is always in a mixed coordinate system and the final appearance is not determined before drawing the actual line with the renderer. So for a solution that takes the (possibly changing) coordinates into account, one would need to determine the outline for the current state of the figure.

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

One option is to use a new artist, which takes the existing LineCollection as input and creates new transforms depending on the current position of the lines in pixel space.

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

In the following I chose a PatchCollection. Starting off with a rectangle, we can scale and rotate it and then translate it to the position of the original line.

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)

请注意如何仅在draw时间计算实际变换.这样可以确保他们考虑像素空间中的实际位置.

Note how the actual transforms are only calculated at draw time. This ensures that they take the actual positions in pixel space into account.

用法看起来像

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

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

Now of course the idea is to use the linecollection object from the question instead of the lc1 object from the above. This should be easy enough to replace in the code.

这篇关于如何在Python的Matplotlib线上绘制外边缘的轮廓?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆