1

Background: I'm developing a python package for business charts based on matplotlib (see: https://pypi.org/project/Clean-Business-Chart/). I want to add a function for the horizontal barchart, inspired by IBCS.

Problem: The barheight of 0.65 is relative to the space between 2 categories in the example below. The linewidth seems to me like it is an absolute value and is added to around the bar (and makes the bar a bit higher. When you make a big figure, you still see white space between the different bars. When you make a small figure with exact the same barheight and linewidth, you'll see that the white space between the bars are gone. Also you can't see the "first bar" which will be partly behind the second bar.

import matplotlib.pyplot as plt

from random import *

linewidth = 2
barheight = 0.65

# create 10 y-coordinates, with budget slightly above actual
y_budget = [y+0.1*barheight for y in range(10)]
y_actual = [y-0.1*barheight for y in range(10)]

# create 10 random width-values for the length of the horizontal bars
width_budget = [randint(1,100) for w in range(10)]
width_actual = [randint(1,100) for w in range(10)]


# Make the "big" figure and the ax to plot on
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 6))
# first bar (white inside, dark grey border) will be mostly behind the second bar
ax.barh(y=y_budget, width=width_budget, color='#FFFFFF', height=barheight, linewidth=linewidth, edgecolor='#404040')
# second bar (dark grey inside, dark grey border) will be mostly behind the second bar
ax.barh(y=y_actual, width=width_actual, color='#404040', height=barheight, linewidth=linewidth, edgecolor='#404040')
# make category labels
ax.set_yticks(y_actual, ["category "+str(i+1) for i in range(10)]);

# Make the "small" figure and the ax to plot on
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 2))
# first bar (white inside, dark grey border) will be mostly behind the second bar
ax.barh(y=y_budget, width=width_budget, color='#FFFFFF', height=barheight, linewidth=linewidth, edgecolor='#404040')
# second bar (dark grey inside, dark grey border) will be mostly behind the second bar
ax.barh(y=y_actual, width=width_actual, color='#404040', height=barheight, linewidth=linewidth, edgecolor='#404040')

ax.set_yticks(y_actual, ["category "+str(i+1) for i in range(10)]);

What can I do so that the linewidth will be visible 'inside' the horizontal bar? Or how can I make the linewidth relative to the horizontal bar based on te size of the figure? What are your suggestions?

Big figure with small figure below

1

I've searched if I could find information how I can make the linewidth relative to the figure size, but I couldn't find that. I've searched if I could find a parameter so that the linewidth will be "inside" the bar, but I couldn't find that.

Nick ODell
  • 15,465
  • 3
  • 32
  • 66
  • Line width is measured in "points" (similar to font size, e.g. a standard 12 point font). A point is 1/72 of an inch (the inches used in figsize). Bar height is measured in data units. Thick lines are (more or less) centered around the position they are supposed to be. One idea to draw the line thickness inside, is drawing with the double thickness and then clipping away everything outside the rectangle of the bar. See e.g. [Draw linewidth inside rectangle matplotlib](https://stackoverflow.com/questions/60574840/draw-linewidth-inside-rectangle-matplotlib) – JohanC Mar 16 '23 at 22:48
  • If you really want to control line thickness in data coordinates, you could draw 4 thin black rectangles instead of lines. – JohanC Mar 16 '23 at 22:56
  • Nice viewingpoint. I've thought about that, but I was also thinking that maybe there is some parameter or option that I couldn't find because I was searching with the wrong words/terminology. – MarcelW1323 Mar 17 '23 at 09:28

1 Answers1

0

I would suggest calculating a line width based on the figure height.

Assuming you have access to the Figure object you're plotting on, you can call fig.get_size_inches() to work out how big it is.

Once you have that, you can divide by the number of categories to find the vertical size in inches of each bar. (This isn't exact - it doesn't include the space used by the legend.)

Since linewidth is expressed in points, you need to multiply inches by 72 to get points.

Next, this is the height of one bar, which is too big to use as a linewidth. I multiplied this by 0.04629. I picked this number by finding a number which gave me a final linewidth of about 2 for your first example.

Final code:

width, height = fig.get_size_inches()
linewidth = height / max(len(y_budget), 1) * 72 * 0.04629

Note: max(..., 1) is used to avoid a division by zero when there are zero categories plotted.

Plot this produces:

plot with thin lines

Nick ODell
  • 15,465
  • 3
  • 32
  • 66