5

In the definition of the function barh of matplotlib:

matplotlib.pyplot.barh(bottom, width, height=0.8, left=None, hold=None, **kwargs)

The default "height" is 0.8, but when I draw some figures with different Figure's height for example (30, 40,..) and dpi=100. I see the Bar's height is changed. It's not fixed. So I wonder what is the unit of the height in barh and how to make it fixed (not depend on figure's height).

hminle
  • 501
  • 1
  • 8
  • 19

1 Answers1

5

I'll split this into two parts:

I wonder what is the unit of the height in barh

(Apparently people have been wondering this since 2009... so I guess you're in good company!)

This question is the easier part - it's a percentage of the height allotted to the bar in the figure. For example, the default height=0.8 means the height of the bar will be 0.8 * (plot_height / n_bars). You can see this by setting height=1.0 (or even a value > 1, the bars will overlap).

If you really want to be sure, here's the source of axes.barh. This just calls axes.bar - take a look at these lines:

nbars = len(bottom)
if len(left) == 1:
    left *= nbars
if len(height) == 1:
    height *= nbars

And later on...

args = zip(left, bottom, width, height, color, edgecolor, linewidth)
for l, b, w, h, c, e, lw in args:
    if h < 0:
        b += h
        h = abs(h)
    if w < 0:
        l += w
        w = abs(w)
    r = mpatches.Rectangle(
        xy=(l, b), width=w, height=h,
        facecolor=c,
        edgecolor=e,
        linewidth=lw,
        label='_nolegend_',
        margins=margins
        )
    r.update(kwargs)
    r.get_path()._interpolation_steps = 100
    #print r.get_label(), label, 'label' in kwargs
    self.add_patch(r)
    patches.append(r)

So you see the height is scaled by nbars, and when you draw the rectangle they are spaced out by this height.

how to make it fixed

This is harder, you will have to manually set it. The bars on the chart are ultimately matplotlib.patches.Rectangle objects, which have a width and height... which is also a percentage. I think the best solution is to compute the appropriate percentage manually.

Here's a short example, based off a barh demo:

import matplotlib.pyplot as plt
plt.rcdefaults()
import numpy as np
import matplotlib.pyplot as plt

# Example data
people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
error = np.random.rand(len(people))

plt.figure(figsize=(5,5), dpi=80)
myplot = plt.barh(y_pos, performance, height=0.8, xerr=error, align='center', alpha=0.4)
plt.yticks(y_pos, people)
plt.xlabel('Performance')
plt.title('How fast do you want to go today?')

for obj in myplot:
    # Let's say we want to set height of bars to always 5px..
    desired_h = 5
    current_h = obj.get_height()
    current_y = obj.get_y()
    pixel_h = obj.get_verts()[2][1] - obj.get_verts()[0][1]
    print("current position = ", current_y)
    print("current pixel height = ", pixel_h)

    # (A) Use ratio of pixels to height-units to calculate desired height
    h = desired_h / (pixel_h/current_h)
    obj.set_height(h)
    pixel_h = obj.get_verts()[2][1] - obj.get_verts()[0][1]
    print("now pixel height = ", pixel_h)

    # (B) Move the rectangle so it still aligns with labels and error bars
    y_diff = current_h - h # height is same units as y
    new_y = current_y + y_diff/2
    obj.set_y(new_y)
    print("now position = ", obj.get_y())
plt.show()

Part A calculates pixel_h/current_h to get a conversion between pixels and height-units. Then we can divide desired_h (pixels) by that ratio to obtain desired_h in height-units. This sets the bar width to 5 px, but the bottom of the bar stays in the same place, so it's no longer aligned with the labels and error bars.

Part B calculates the new y position. Since y and height are in the same units, we can just add half the height difference (y_diff) to get the new position. This keeps the bar centered around whatever original y-position it had.

Note that this only sets the initial size. If you resize the plot, for example, the bars will still scale - you'd have to override that event to resize the bars appropriately.

user812786
  • 4,302
  • 5
  • 38
  • 50
  • Hi @whrrgarbi, when I run your program, I see that: `current pixel height = 42.67` and `now pixel height = 5.0`. But if I use your formula to calculate the bar height, I get: 0.8 * (5 * 80)/ 5 = 64 different from 42.67. Could you explain this one further? The plot height is: 5*80 pixel right? – hminle Aug 08 '16 at 05:52
  • @hminle Oh good catch, I get the same thing but I wasn't paying that much attention since I was only interested in the "after" measurement. It looks like there's a default padding/margin around the bars when the `Rectangle` is created. Fortunately the formula here to scale is only based on the final height of the bar, so it does end up 5px as expected! – user812786 Aug 08 '16 at 12:20
  • And one more thing, when you change to desired height, the bar now is not aligned horizontally with its y-label. And of course we need to calculate y labels positions as well. But how can we do that? because the desired_h is pixel, it's not the same unit with y-label positions. – hminle Aug 09 '16 at 01:52
  • @hminle updated the example to show that. It's easier to move the bar than move labels and error bars so I just did that, but it would be similar if you wanted to move the other parts instead. – user812786 Aug 09 '16 at 12:13
  • Hi @whrrgarbi, could you please explain me this line? `pixel_h = obj.get_verts()[2][1] - obj.get_verts()[0][1]` – hminle Aug 15 '16 at 12:41
  • @hminle that line calculates the height of the bar in pixels. `get_verts()` gives the vertices of the bar, which have pixel coordinates. (`0` is at the lower left corner, coordinates are listed in CCW order, and pixel coordinates appear to be relative to the lower left corner of the plot.) Points `0` and `2` are vertically separated, so subtracting the y-coordinates from each other gives the difference. – user812786 Aug 15 '16 at 13:29
  • Hi @whrrgarbi, and how do you know the values of coordinates is pixel unit? I have no clue for that. I've read the source code, but there is nothing specified pixel unit. Could you please explain me? – hminle Aug 16 '16 at 02:32
  • The reason why I ask this: I try a different approach. After I finish plotted one bar, I fixed it height immediately. The image I generated is quite strange. For your code, after you plotted all the bars, you looped over all the bars again and fixed one by one. Here is my image: [![Capture.png](https://s4.postimg.org/5txte5n25/Capture.png)](https://postimg.org/image/kpwclqygp/) – hminle Aug 16 '16 at 03:31
  • @hminle don't post things that aren't answers in the answer box, if it's a new question post a new question. – user812786 Aug 16 '16 at 11:17
  • Hi @whrrgarbl, I think your method has some problems. I prove it is not right by my code. What do you think after run my code? – hminle Aug 16 '16 at 13:07