Home > Net >  Setting the same x-scale but different x-limits for adjacent subplots matplotlib
Setting the same x-scale but different x-limits for adjacent subplots matplotlib

Time:08-05

I am trying to create a figure with three bar plots side by side. These bar plots have different yscales, but the data is fundamentally similar so I'd like all the bars to have the same width. The only way I was able to get the bars to have the exact same width was by using sharex when creating the subplots, in order to keep the same x scale.

import matplotlib.pyplot as plt

BigData = [[100,300],[400,200]]
MediumData = [[40, 30],[50,20],[60,50],[30,30]]
SmallData = [[3,2],[11,3],[7,5]]
data = [BigData, MediumData, SmallData]
colors = ['#FC766A','#5B84B1']

fig, axs = plt.subplots(1, 3, figsize=(30,5), sharex=True)
subplot = 0
for scale in data:
    for type in range(2):
        bar_x = [x   type*0.2 for x in range(len(scale))]
        bar_y = [d[type] for d in scale]
        axs[subplot].bar(bar_x,bar_y, width = 0.2, color = colors[type])
        
    subplot  = 1
plt.show()

This creates this figure:

figure

The problem with this is that the x-limits of the plot are also shared, leading to unwanted whitespace. I've tried setting the x-bounds after the fact, but it doesn't seem to override sharex. Is there a way to make the bars have the same width, without each subplot also being the same width?

Additionally, is there a way to create such a plot (one with different y scales to depending on the size of the data) without having to sort the data manually beforehand, like shown in my code?

Thanks!

CodePudding user response:

Thanks to Jody Klymak for help finding this solution! I thought I should document it for future users.

We can make use of the 'width_ratios' GridSpec parameter. Unfortunately there's no way to specify these ratios after we've already drawn a graph, so the best way I found to implement this is to write a function that creates a dummy graph, and measures the x-limits from that graph:

def getXRatios(data, size):
    phig, aks = plt.subplots(1, 3, figsize=size)
    subplot = 0
    for scale in data:
        for type in range(2):
            bar_x = [x   type*0.2 for x in range(len(scale))]
            bar_y = [d[type] for d in scale]
            aks[subplot].bar(bar_x,bar_y, width = 0.2)
        subplot  = 1
    ratios = [aks[i].get_xlim()[1] for i in range(3)]
    plt.close(phig)
    return ratios

This is essentially identical to the code that creates the actual figure, with the cosmetic aspects removed, as all we want from this dummy figure is the x-limits of the graph (something we can't get from our actual figure as we need to define those limits before we start in order to solve the problem).

Now all you need to do is call this function when you're creating your subplots:

fig, axs = plt.subplots(1, 3, figsize=(40,5), gridspec_kw = {'width_ratios':getXRatios(data,(40,5))})

As long as your XRatio function creates your graph in the same way your actual graph does, everything should work! Here's my output using this solution.

To save space you could re-purpose the getXRatios function to also construct your final graph, by calling itself in the arguments and giving an option to return either the ratios or the final figure. I couldn't be bothered.

  • Related