How do you change the size of figures drawn with matplotlib?

后端 未结 19 3047
忘掉有多难
忘掉有多难 2020-11-21 06:01

How do you change the size of figure drawn with matplotlib?

19条回答
  •  难免孤独
    2020-11-21 06:38

    Comparison of different approaches to set exact image sizes in pixels

    This answer will focus on:

    • savefig
    • setting the size in pixels

    Here is a quick comparison of some of the approaches I've tried with images showing what the give.

    Baseline example without trying to set the image dimensions

    Just to have a comparison point:

    base.py

    #!/usr/bin/env python3
    
    import sys
    
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    
    fig, ax = plt.subplots()
    print('fig.dpi = {}'.format(fig.dpi))
    print('fig.get_size_inches() = ' + str(fig.get_size_inches())
    t = np.arange(-10., 10., 1.)
    plt.plot(t, t, '.')
    plt.plot(t, t**2, '.')
    ax.text(0., 60., 'Hello', fontdict=dict(size=25))
    plt.savefig('base.png', format='png')
    

    run:

    ./base.py
    identify base.png
    

    outputs:

    fig.dpi = 100.0
    fig.get_size_inches() = [6.4 4.8]
    base.png PNG 640x480 640x480+0+0 8-bit sRGB 13064B 0.000u 0:00.000
    

    My best approach so far: plt.savefig(dpi=h/fig.get_size_inches()[1] height-only control

    I think this is what I'll go with most of the time, as it is simple and scales:

    get_size.py

    #!/usr/bin/env python3
    
    import sys
    
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    
    height = int(sys.argv[1])
    fig, ax = plt.subplots()
    t = np.arange(-10., 10., 1.)
    plt.plot(t, t, '.')
    plt.plot(t, t**2, '.')
    ax.text(0., 60., 'Hello', fontdict=dict(size=25))
    plt.savefig(
        'get_size.png',
        format='png',
        dpi=height/fig.get_size_inches()[1]
    )
    

    run:

    ./get_size.py 431
    

    outputs:

    get_size.png PNG 574x431 574x431+0+0 8-bit sRGB 10058B 0.000u 0:00.000
    

    and

    ./get_size.py 1293
    

    outputs:

    main.png PNG 1724x1293 1724x1293+0+0 8-bit sRGB 46709B 0.000u 0:00.000
    

    I tend to set just the height because I'm usually most concerned about how much vertical space the image is going to take up in the middle of my text.

    plt.savefig(bbox_inches='tight' changes image size

    I always feel that there is too much white space around images, and tended to add bbox_inches='tight' from: Removing white space around a saved image in matplotlib

    However, that works by cropping the image, and you won't get the desired sizes with it.

    Instead, this other approach proposed in the same question seems to work well:

    plt.tight_layout(pad=1)
    plt.savefig(...
    

    which gives the exact desired height for height equals 431:

    Fixed height, set_aspect, automatically sized width and small margins

    Ermmm, set_aspect messes things up again and prevents plt.tight_layout from actually removing the margins...

    Asked at: How to obtain a fixed height in pixels, fixed data x/y aspect ratio and automatically remove remove horizontal whitespace margin in Matplotlib?

    plt.savefig(dpi=h/fig.get_size_inches()[1] + width control

    If you really need a specific width in addition to height, this seems to work OK:

    width.py

    #!/usr/bin/env python3
    
    import sys
    
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    
    h = int(sys.argv[1])
    w = int(sys.argv[2])
    fig, ax = plt.subplots()
    wi, hi = fig.get_size_inches()
    fig.set_size_inches(hi*(w/h), hi)
    t = np.arange(-10., 10., 1.)
    plt.plot(t, t, '.')
    plt.plot(t, t**2, '.')
    ax.text(0., 60., 'Hello', fontdict=dict(size=25))
    plt.savefig(
        'width.png',
        format='png',
        dpi=h/hi
    )
    

    run:

    ./width.py 431 869
    

    output:

    width.png PNG 869x431 869x431+0+0 8-bit sRGB 10965B 0.000u 0:00.000
    

    and for a small width:

    ./width.py 431 869
    

    output:

    width.png PNG 211x431 211x431+0+0 8-bit sRGB 6949B 0.000u 0:00.000
    

    So it does seem that fonts are scaling correctly, we just get some trouble for very small widths with labels getting cut off, e.g. the 100 on the top left.

    I managed to work around those with Removing white space around a saved image in matplotlib

    plt.tight_layout(pad=1)
    

    which gives:

    width.png PNG 211x431 211x431+0+0 8-bit sRGB 7134B 0.000u 0:00.000
    

    From this, we also see that tight_layout removes a lot of the empty space at the top of the image, so I just generally always use it.

    Fixed magic base height, dpi on fig.set_size_inches and plt.savefig(dpi= scaling

    I believe that this is equivalent to the approach mentioned at: https://stackoverflow.com/a/13714720/895245

    magic.py

    #!/usr/bin/env python3
    
    import sys
    
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    
    magic_height = 300
    w = int(sys.argv[1])
    h = int(sys.argv[2])
    dpi = 80
    fig, ax = plt.subplots(dpi=dpi)
    fig.set_size_inches(magic_height*w/(h*dpi), magic_height/dpi)
    t = np.arange(-10., 10., 1.)
    plt.plot(t, t, '.')
    plt.plot(t, t**2, '.')
    ax.text(0., 60., 'Hello', fontdict=dict(size=25))
    plt.savefig(
        'magic.png',
        format='png',
        dpi=h/magic_height*dpi,
    )
    

    run:

    ./magic.py 431 231
    

    outputs:

    magic.png PNG 431x231 431x231+0+0 8-bit sRGB 7923B 0.000u 0:00.000
    

    And to see if it scales nicely:

    ./magic.py 1291 693
    

    outputs:

    magic.png PNG 1291x693 1291x693+0+0 8-bit sRGB 25013B 0.000u 0:00.000
    

    So we see that this approach also does work well. The only problem I have with it is that you have to set that magic_height parameter or equivalent.

    Fixed DPI + set_size_inches

    This approach gave a slightly wrong pixel size, and it makes it is hard to scale everything seamlessly.

    set_size_inches.py

    #!/usr/bin/env python3
    
    import sys
    
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    
    w = int(sys.argv[1])
    h = int(sys.argv[2])
    fig, ax = plt.subplots()
    fig.set_size_inches(w/fig.dpi, h/fig.dpi)
    t = np.arange(-10., 10., 1.)
    plt.plot(t, t, '.')
    plt.plot(t, t**2, '.')
    ax.text(
        0,
        60.,
        'Hello',
        # Keep font size fixed independently of DPI.
        # https://stackoverflow.com/questions/39395616/matplotlib-change-figsize-but-keep-fontsize-constant
        fontdict=dict(size=10*h/fig.dpi),
    )
    plt.savefig(
        'set_size_inches.png',
        format='png',
    )
    

    run:

    ./set_size_inches.py 431 231
    

    outputs:

    set_size_inches.png PNG 430x231 430x231+0+0 8-bit sRGB 8078B 0.000u 0:00.000
    

    so the height is slightly off, and the image:

    The pixel sizes are also correct if I make it 3 times larger:

    ./set_size_inches.py 1291 693
    

    outputs:

    set_size_inches.png PNG 1291x693 1291x693+0+0 8-bit sRGB 19798B 0.000u 0:00.000
    

    We understand from this however that for this approach to scale nicely, you need to make every DPI-dependant setting proportional to the size in inches.

    In the previous example, we only made the "Hello" text proportional, and it did retain its height between 60 and 80 as we'd expect. But everything for which we didn't do that, looks tiny, including:

    • line width of axes
    • tick labels
    • point markers

    SVG

    I could not find how to set it for SVG images, my approaches only worked for PNG e.g.:

    get_size_svg.py

    #!/usr/bin/env python3
    
    import sys
    
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    
    height = int(sys.argv[1])
    fig, ax = plt.subplots()
    t = np.arange(-10., 10., 1.)
    plt.plot(t, t, '.')
    plt.plot(t, t**2, '.')
    ax.text(0., 60., 'Hello', fontdict=dict(size=25))
    plt.savefig(
        'get_size_svg.svg',
        format='svg',
        dpi=height/fig.get_size_inches()[1]
    )
    

    run:

    ./get_size_svg.py 431
    

    and the generated output contains:

    and identify says:

    get_size_svg.svg SVG 614x461 614x461+0+0 8-bit sRGB 17094B 0.000u 0:00.000
    

    and if I open it in Chromium 86 the browser debug tools mouse image hover confirm that height as 460.79.

    But of course, since SVG is a vector format, everything should in theory scale, so you can just convert to any fixed sized format without loss of resolution, e.g.:

    inkscape -h 431 get_size_svg.svg -b FFF -e get_size_svg.png
    

    gives the exact height:

    I use Inkscape instead of Imagemagick's convert here because you need to mess with -density as well to get sharp SVG resizes with ImageMagick:

    • https://superuser.com/questions/598849/imagemagick-convert-how-to-produce-sharp-resized-png-files-from-svg-files/1602059#1602059
    • How to convert a SVG to a PNG with ImageMagick?

    And setting on the HTML should also just work for the browser.

    Tested on matplotlib==3.2.2.

提交回复
热议问题