46

I wrote a function that took a dataframe generated from Pandas and produce a heatmap:

def drawHeatMap(df, city, province, collector, classtype, color, titleposy):
    try:
        thePlot = pl.matshow(df.values, cmap='PuBuGn')
        pl.colorbar(thePlot, orientation='vertical')
        aTitle = (classtype + ' Composition Changes Over Time in ' + city + 
                ', ' + province + '\n' + collector + ' collector. ' + 'rs100')
        pl.title(aTitle, x=0.5, y=titleposy, style='oblique', weight='bold')
        pl.xlabel('Collection Time')
        pl.xticks(range(len(df.columns)), df.columns, rotation=90)
        pl.yticks(range(len(df.index)), df.index)
        fileName = (classtype + '-' + city + '-' 
                + province + '-' + collector + '.png')
        pl.savefig(fileName)
    except ZeroDivisionError:
        errorMessage = ('No Data Avaiable for ' + city + ', ' + province + 
                ' with ' + collector + ' collector.')
        print errorMessage

The problem I am having is, savefig() would save figures with the axis and graphics trimmed. I have to use show(), maximize the graph and manually save the figure with the GUI button myself.

How can I fix my function so savefig() would save the graphs properly? I tried to put a line like this before pl.savefig() to control my figure:

       pl.figure(figsize=....) 

but I end up producing some empty graphs. What is the proper way to write a matplotlib function that give me full control on saving the figure?

Updated with Example of a problem figure: enter image description here

hitzg
  • 12,133
  • 52
  • 54
WonderSteve
  • 837
  • 2
  • 9
  • 15

9 Answers9

31

From the documentation, you can add a dpi argument to set the resolution.

savefig('foo.png', dpi=199)
hitzg
  • 12,133
  • 52
  • 54
Xiaorong Liao
  • 1,201
  • 14
  • 14
29

I added plt.tight_layout() before savefig(), and it solved the trimming issue I had. Maybe it will help yours as well.

EDIT: I also set the figure size at the begining matplotlib.rcParams['figure.figsize'] = 40, 12(you can set your own width and height)

Intrastellar Explorer
  • 3,005
  • 9
  • 52
  • 119
WHZW
  • 445
  • 6
  • 10
  • I had tried using tight_layout() previously but it never worked for me. Including both figsize and tight_layout() is what did the trick for me. Thanks! – Sarthchoudhary Dec 09 '21 at 16:46
15

Short:
You just need to call pl.figure(figsize=...) before you call the pl.colorbar (and all the other stuff)

Explanation:
pl.figure creates a new figure (with given size), on which all pl.* methods will act in the following.
So pl.savefig just saves the last created figure, which is empty if you created a new one in the preceeding line.

sodd
  • 12,482
  • 3
  • 54
  • 62
Maximilian
  • 496
  • 5
  • 5
  • Calling matshow will create a figure, so he actually needs to call pl.figure() prior to matshow. – Mr. Squig Oct 25 '12 at 17:10
  • I tried putting pl.figure(...) before pl.colorbar(). It seems to mess up my graph. I think it messed up matshow() somehow – WonderSteve Oct 25 '12 at 17:47
  • I moved pl.figure() before matshow. What is interesting is...if I don't use show() and just savefig(). I will have my figures saved (still with stuff trimmed). If I called show() and savefig(). It will show my graphs and bunch of empty graphs – WonderSteve Oct 25 '12 at 17:52
  • I put pl.figure() before matshow() Changing the figsize and dpi doesn't do anything to do the saved figure. – WonderSteve Oct 25 '12 at 18:05
  • you could try to adjust the margins: with pl.rc('figure.subplot', right=0.9, left=0.15, top=0.95, bottom=0.1) (and then try to find "good" parameters) or maybe calling pl.tight_layout() just before pl.savefig() can do this automatically. – Maximilian Oct 27 '12 at 09:25
9

You need to use the set_size_inches function and the dpi parameter in savefig together to define the saved figure size in pixels.

  1. set the figure width and height in inches through plt.gcf().set_size_inches(10, 5).
  2. define the dpi while saving the figure plt.savefig('filename.png', dpi=100). dpi means "dot per inch".
  3. The saved figure width will be 1000(10x100) and the height will be 500(5x100). The saved figure size will be 1000x500 in pixels.

Sample code piece:

import matplotlib.pyplot as plt


def fig_size():
    plt.plot(range(0, 10), range(0, 10))
    plt.gcf().set_size_inches(10, 5)
    plt.savefig('charts/fig_size.png', dpi=200)
HongchaoZhang
  • 3,494
  • 1
  • 17
  • 8
6

before calling pl.savefig(fileName) do plt.tight_layout()

Kumail Haider
  • 97
  • 1
  • 2
4

The command pl.figure() makes a new matplotlib figure. The figure size is set at instantiation. You do want to set the figure size, but you already have a figure. So you were on the right track, but try this instead:

def drawHeatMap(df, city, province, collector, classtype, color, titleposy):
    try:
        fig = pl.figure(figsize=(....))
        ax = fig.add_subplot(111)
        ax.matshow(df.values, cmap='PuBuGn')
        pl.colorbar()
        aTitle = classtype + ' Composition Changes Over Time in ' + city + ', ' + province + '\n' + collector + ' collector. ' + 'rs100'
        ax.set_title(aTitle, x=0.5, y=titleposy, style='oblique', weight='bold')
        ax.set_xlabel('Collection Time')
        ax.set_xticks(range(len(df.columns)), df.columns, rotation=90)
        ax.set_yticks(range(len(df.index)), df.index)
        fileName = classtype + '-' + city + '-' + province + '-' + collector + '.png'
        fig.savefig(fileName)
    except ZeroDivisionError:
        errorMessage = 'No Data Available for ' + city + ', ' + province + ' with ' + collector + ' collector.'
        print errorMessage
G-Nugget
  • 8,666
  • 1
  • 24
  • 31
Mr. Squig
  • 2,755
  • 17
  • 10
  • I got AttributeError: 'NoneType' object has no attribute 'autoscale_None' – WonderSteve Oct 25 '12 at 17:49
  • I changed the code so thePlot=ax.matshow(df.values, cmap='PuBuGn') pl.colorbar(thePlot, orientation='vertical) I got his with ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() – WonderSteve Oct 25 '12 at 18:11
  • The attribute error should not have been caused by my changes to your code. The trace back should provide more information, but it looks like you're trying to access the autoscale_None attribute of some object. – Mr. Squig Oct 26 '12 at 00:41
3

you can use the dpi parameter to set The resolution in dots per inch in savefig method. so you can control the figure size. for example, if you this code :

import pandas as pd
import matplotlib.pyplot as plt


df = pd.DataFrame({ 
    'ser1' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] , 
    'ser2' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
    })

df.plot()
plt.savefig("100.png" , dpi = 100)
plt.savefig("300.png" , dpi = 300)

100.png will be :

100.png

300.png will be :

300.png

3

You can pass bbox_inches='tight' to the savefig

plt.savefig("image.png", bbox_inches='tight')

It will do the magic.

1

To save figure on desired size

figure = plt.gcf()
figure.set_size_inches(width/100, height/100)
plt.axis('off')
plt.plot(x, y, color='black', linewidth=1)
plt.gca().set_position([0, 0, 1, 1])  
plt.savefig("image.png", dpi=100)
Zorro
  • 1,085
  • 12
  • 19