如何用箭头填充pyplot条形图的条形图?

时间:2018-01-19 04:54:15

标签: python matplotlib bar-chart

我正在尝试创建一个条形图,其中条形图包含附加信息,方法是让条形图中的箭头指向基点。

现在我只设法用彩色条创建一个。由于需要8种方向的颜色加上一种不存在的颜色,它不如我希望的那么好。区分颜色也很困难。

Microsoft.Data.OData (>= 5.6.2)

因此,箭头指向由单独变量提供的方向,直观且易于查看。

我不知道该怎么做。我已经尝试过使用阴影线,但似乎只有一组有限的符号。

有没有办法在酒吧里拿到一些箭头?

编辑: 这是一个pciture,它看起来如何,我追求的是什么。箭头的形状可以不同。当然,预计会有更多的支柱。由于任务数据,甚至可能有一些没有任何箭头。 https://github.com/artoolkitx/jsartoolkit5

2 个答案:

答案 0 :(得分:3)

这是一个使用ax.annotate在每个条形图内绘制箭头的解决方案。由于OP不太清楚箭头应该是什么样子,我将每个条形分成矩形(我在代码中将它们称为squares,但如果你修正了绘图的宽高比,它们只是正方形)和每个矩形绘制一个居中的箭头,其方向由用户提供的角度给出(此处在一个名为wind_direction的向量中)。

在代码中,我将Axes的宽高比设置为x - 和y - 限制的宽高比,这使得Axes方形因此可以很容易地绘制相同长度的箭头,与其方向无关。如果不需要,可以注释掉相应的行。如果箭头必须具有相同的长度而没有该限制,则必须计算图形纵横比,例如参见here如何做到这一点。

我还用风向标注每个条形,只是为了便于检查箭头是否与给定的风向相对应。

from matplotlib import pyplot as plt
import numpy as np

fig,ax = plt.subplots()

x = np.arange(12)
wind_strength = np.random.random(12)
wind_direction = np.linspace(0,2*np.pi,12, endpoint = False)

colors = ['green', 'yellow', 'blue', 'pink', 'orange']
bar_cols = [colors[i%len(colors)] for i,s in enumerate(wind_strength)]

bars = ax.bar(x,wind_strength, color=bar_cols)

##computing the aspect ratio of the plot ranges:
xlim = ax.get_xlim()
ylim = ax.get_ylim()
aspect = (xlim[1]-xlim[0])/(ylim[1]-ylim[0])


##comment out this line if you don't care about the arrows being the
##same length
ax.set_aspect(aspect)


##dividing each bar into 'squares' and plotting an arrow into each square
##with orientation given by wind direction
for bar,angle in zip(bars,wind_direction):
    (x1,y1),(x2,y2) = bar.get_bbox().get_points()
    w = x2-x1
    h = w/aspect
    x_mid = (x1+x2)/2

    dx = np.sin(angle)*w
    dy = np.cos(angle)*h

    ##the top and bottom of the current square:
    y_bottom = y1
    y_top = y_bottom+h

    ##draw at least one arrow (for very small bars)
    first = True

    while y_top < y2 or first:
        y_mid = (y_top+y_bottom)/2

        ax.annotate(
            '',xytext=(x_mid-dx/2,y_mid-dy/2),
            xy=(x_mid+dx/2,y_mid+dy/2),
            arrowprops=dict(arrowstyle="->"),
        )

        ##next square
        y_bottom = y_top
        y_top += h

        ##first arrow drawn:
        first = False

    ##annotating the wind direction:
    ax.text(x_mid, y2+0.05, '{}'.format(int(180*angle/np.pi)), ha = 'center')

plt.show()

最终结果如下:

result of the above code

希望这有帮助。

答案 1 :(得分:2)

在栏内绘制箭头的一个选项确实是阴影线。然而,这有点牵扯。需要创建一些自定义填充,如下面的答案所示:How to fill a polygon with a custom hatch in matplotlib?这里我们可以使用箭头的路径。

在下文中,我们将子类matplotlib.hatch.Shapes子类化并创建一些箭头路径。现在的问题是我们需要一些参数来插入阴影线,以便能够定义角度。然后我们可以定义一个自定义的阴影图案,我选择看起来像这样

hatch="arr{angle}{size}{density}"

其中

  • angle:0到360之间的整数
  • size:2到20之间的某个整数
  • 密度:某个整数&gt; = 1

这与我之前关于this question的回答类似。 取决于角度,路径旋转,尺寸和密度基本上确定了显示多大尺寸的箭头。请注意,并非所有参数都看起来很好,有些参数会导致重叠。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.offsetbox
import matplotlib.hatch
from matplotlib.patches import Polygon


class ArrowHatch(matplotlib.hatch.Shapes):
    """
    Arrow hatch. Use with hatch="arr{angle}{size}{density}"
                 angle: integer number between 0 and 360
                 size: some integer between 2 and 20
                 density: some integer >= 1 
    """
    filled = True
    size = 1

    def __init__(self, hatch, density):
        v1 = [[.355,0], [.098, .1], [.151,.018], [-.355,.018]]
        v2 = np.copy(v1)[::-1]
        v2[:,1] *= -1 
        v = np.concatenate((v1,v2))
        self.path = Polygon(v, closed=True, fill=False).get_path()
        self.num_lines = 0
        if len(hatch) >= 5:
            if hatch[:3] == "arr":
                h = hatch[3:].strip("{}").split("}{")
                angle = np.deg2rad(float(h[0]))
                self.size = float(h[1])/10.
                d = int(h[2])
                self.num_rows = 2*(int(density)//6*d)
                self.num_vertices = (self.num_lines + 1) * 2

                R = np.array([[np.cos(angle), -np.sin(angle)],
                              [np.sin(angle), np.cos(angle)]])
                self.shape_vertices = np.dot(R,self.path.vertices.T).T
                self.shape_codes = self.path.codes
        matplotlib.hatch.Shapes.__init__(self, hatch, density)

matplotlib.hatch._hatch_types.append(ArrowHatch)


n = 7
a = 1017
x = np.arange(n)
y = np.linspace(0.2*a,a,len(x))

fig, ax = plt.subplots()
bar = ax.bar(x,y, ec="k", color="lightblue")

angles = [0,45,360-45,90,225,360-90,160]
for b, a in zip(bar, angles):
    f = 'arr{{{}}}{{9}}enter image description here'.format(a)
    b.set_hatch(f)


plt.show()

使用{angle}{9}{3}输出:

enter image description here

使用{angle}{11}{2}输出:

{{3}}

<小时/> 关于孵化模式及其管理方式的说明如下。或者换句话说,ArrowHatch如何知道它应该创建一个舱口? 将使用matplotlib.hatch._hatch_types内任何剖面线的顶点应用阴影线。这就是我们需要将ArrowHatch类附加到此列表的原因。根据此类是否具有大于零的属性num_vertices,它将有助于最终的阴影线。这就是我们在init函数中将其设置为self.num_lines = 0的原因。但是,如果hatch(提供给_hatch_types列表中每个类的字符串包含我们的匹配模式,则我们将self.num_rows设置为0以外的值(它应该是偶数形状,因为它们以2个移位的行产生),这样它可以有助于孵化 这个概念有点奇特,因为基本上每个类本身都决定是否参与孵化,具体取决于hatch字符串。一方面,这非常方便,因为它允许容易地组合不同的舱口类型,例如, "///++oo"。另一方面,它使得难以用于需要输入参数的舱口,在这种情况下作为角度。还需要注意不要在孵化中使用任何角色,其他舱口使用;例如,我最初想要使用类似"arrow45.8,9,2"的内容,因为o.,是其他有效的填充类型,因此效果不佳遍布整个地方的点。