pythonmatplotlibplot

Matplotlib not generating plot when using log in the saved figure


I am trying to generate a plot and using log to show relative comparison, the plot is correctly shown however, the saved figure doesn't have any plots, only axis.

import matplotlib.pyplot as plt

# Data
y_values = [4.385236951214598, 4.385249349505862, 4.38524255048674, 4.385237751115038, 4.385241350648787]
x_labels = ["Dota", "Youtube", "Exchange", "Fifa", "Uber"]

# Create the plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.bar(x_labels, y_values, color=plt.cm.viridis(range(len(x_labels))))

# Set y-axis to log scale
ax.set_yscale('log')

# Add title and labels
ax.set_title("Contracts vs. Values (Log Scale)")
ax.set_xlabel("Contracts")
ax.set_ylabel("Values")

# Save the plot as a PDF file
fig.savefig('gas-burnt-per-contract.pdf', dpi=300, bbox_inches='tight', transparent=True, pad_inches=0)

# Display the plot
plt.show()

The culprit line is ax.set_yscale('log') , if i remove this, the plot is shown correctly in the saved figure, but empty when i use it.

Any help would be appreciated.


Solution

  • I believe you've uncovered a matplotlib bug.

    The bug is possibly localised to the savefig method. Here's what I've tried:

    Code as-is

    Running your code as-is, I get exactly the behaviour you describe. The following image is shown...

    SHOWn image when running OP's code as-is

    BUT, as you report, the saved PDF shows no bars within the axes:

    Saved image

    Using plt.yscale() instead of ax.set_yscale()

    Using plt.yscale() (as suggested by this answer instead of ax.set_yscale() as you have done, makes no difference.

    Changing input values

    If I change y_values from:

    y_values = [
        4.385236951214598,
        4.385249349505862,
        4.38524255048674,
        4.385237751115038,
        4.385241350648787,
    

    to...

    y_values = [
        4.385236951214598,
        5.385249349505862,
        6.38524255048674,
        7.385237751115038,
        8.385241350648787,
    

    Both the PDF and the "shown" image match up perfectly:

    Changing numbers to differ more

    If I change just one of the numbers, by adding 1 to it, I still get matching - and valid - images:

    With just one value changed

    The same is true if I add 0.01 to one of the numbers, but if I reduce the addition to just 0.001, the results go back to being screwy.

    Using decimal.Decimal()

    I tried converting all the y_values to Decimal, and this made no difference.

    Checking if problem arises with SVG as with PDF

    The fact that PNGs use a raster instead of a vector like PDFs, made me wonder whether it was a vector/raster thing.

    But saving to an SVG works perfectly, with shown and saved images matching.

    As SVGs are vectors like PDFs, this leads me to think it's not a vector/raster thing after all.

    Setting transparent=False

    Setting the transparent kwarg to False in fig.savefig() makes no difference.

    Increasing or decreasing dpi

    Increasing or decreasing dpi when calling fig.savefig() makes no difference.

    Changing the figure's size

    Making the figure bigger/smaller and changing its aspect ratio makes no difference.

    Changing the bar colour

    Changing the colours invoked when calling ax.bar() makes no difference.

    Conclusion

    This very inconsistent behaviour leads me to think this is a matplotlib bug of some kind.

    The fact that the problem only seems to arise when the difference between the values shrinks to about 0.001 leads me to think that this may have to do with rounding, but I can't be sure...

    My setup

    I'm running your code in VSCode, using Python 3.11, matplotlib v3.9.0, on Windows 10.