(Matplotlib) I want to draw a graph with a size specified in pixels

Introduction

For the graph created by matplotlib, it is explained as follows ** How to specify the size (width / height) of the plot area in pixels **.

top.png

Execution environment (Google Colab.)

Caution

In the case of Jupyter environment (Google Colab. Environment), ** by plt.show (...) ** PNG image displayed in the execution result cell ** and with plt.savefig (...) ** ** The size of the PNG image ** that is output as a file is different ** </ font>.

Specifically, the PNG image ** displayed in the execution result cell will be ** the surrounding margins are automatically cropped </ font> **. I will.

For example, if the following code is automated in the Google Colab. Environment, the image displayed in the output cell is $ 348 \ times 270 $ (px), while the image saved as test.png is $ 400 \ times 300. It will be $ (px).

python


import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0,20,100) #Sample data for drawing
y = x ** 0.5              #Sample data for drawing

fig = plt.figure(dpi=100, figsize=(4,3))
plt.plot(x,y)
plt.savefig('test.png') #Output to file 400 x 300 px
plt.show()              #348 x 270 px displayed in output cell

--figsize = (4,3) specifies the "width" and "height" of the image in ** inch increments **. --In dpi = 100," Dot per Inch (= number of dots per inch (number of pixels) "is specified. --The output image size is ** width ** is $ 4 , \ mathrm {inch} \ times 100 , \ mathrm {dpi} = 400 , \ mathrm {pixel} $, ** height ** is $ 3 \ , \ mathrm {inch} \ times 100 , \ mathrm {dpi} = 300 , \ mathrm {pixel} $ ... It should be, but it is displayed in the output cell The image will be a little smaller </ font> with the margins trimmed.

From now on, we will proceed to ** target images that are output as files ** with plt.savefig (...).

I want to find the size of the (net) plot area instead of the image size

The image size (in pixels) could be calculated from the "size" and "DPI" of the figure specified in inches, as described above. This can be calculated from an instance of figure (fig) as follows:

Calculate image size (in pixels)


fig_w_px = int(fig.get_figwidth()  * fig.get_dpi())  
fig_h_px = int(fig.get_figheight() * fig.get_dpi())
print(f'■ Fig size  {fig_w_px} x {fig_h_px} [px]')

First, here, instead of the overall size of this image, the size of the (net) plot area surrounded by ** red frame ** </ font> ** as follows: ** Find out what the (pixel unit) looks like.

top2.png

The size of the plot area (in pixels) can be calculated by getting the value from the Axes object (ʻax) as follows: In addition, since Japanese is used in the figure, in the Google Colab. Environment etc., please execute ! Pip install japanize-matplotlib` first to install the library.

Calculate plot area size (in pixels)


import numpy as np
import japanize_matplotlib 
import matplotlib.pyplot as plt

x = np.linspace(0,20,100) #Sample data for drawing
y = x ** 0.5              #Sample data for drawing

fig = plt.figure(dpi=100, figsize=(4,3))
ax = plt.gca()
ax.plot(x,y)
ax.set_xlabel('input',fontsize=12)
ax.set_ylabel('output',fontsize=12)
plt.savefig('foo.png')

#Figure (whole image) size
fig_w_px = int(fig.get_figwidth()  * fig.get_dpi())  
fig_h_px = int(fig.get_figheight() * fig.get_dpi())
print(f'■ Fig size   {fig_w_px} x {fig_h_px} [px]')

#Axis (plot area) size
ax_size_inch = ax.figure.get_size_inches()
ax_w_inch = ax_size_inch[0] * (ax.figure.subplotpars.right - ax.figure.subplotpars.left)
ax_h_inch = ax_size_inch[1] * (ax.figure.subplotpars.top - ax.figure.subplotpars.bottom)
ax_w_px = int( ax_w_inch * fig.get_dpi() )  
ax_h_px = int( ax_h_inch * fig.get_dpi() )
print(f'■ Axis size  {ax_w_px } x {ax_h_px } [px]')

The execution result is as follows. Also, if you open the file output foo.png with image editing software etc. and check the size of the plot area, it surely matches the following result.

Execution result


■ Fig size   400 x 300 [px]
■ Axis size  310 x 226 [px]

Program description

--ʻAx.figure.get_size_inches () `, the size of the area including the overall size of the Axes object (= ** scales and labels for each axis (characters such as" input "and" output ") ** ) Is obtained in inches.

--In ʻax.figure.subplotpars.left, it is a value ($ 0.0 $ to $ 1.0 $) that represents the ** left edge position ** of the plot area when the entire width of the Axes object is ** $ 1.0 $. Also, ʻax.figure.subplotpars.right is a value that represents ** the rightmost position of the plot area **. --Here, the difference between the two ʻax.figure.subplotpars.right --ax.figure.subplotpars.left` is the width of the plot area where the ** width of the Axes object is ** $ 1.0 $ It represents the ratio of **.

--In order to obtain the width of the plot area in ** inches **, the total width of the Axes object ʻax_size_inch [0] (in inches) and the width (ratio) of the plot area ʻax.figure.subplotpars.right - Multiply by ax.figure.subplotpars.left and store in ʻax_w_inch`.

--In order to convert inches to pixels, ʻax_w_inch is multiplied by the resolution fig.get_dpi () and stored in ʻax_w_px.

I want to draw a graph by specifying the size of the plot area in pixels

I was able to ** find the size of the plot area in pixels **.

This time, on the contrary, ** specify the size of the plot area in pixels ** and draw a graph. The following code is a sample that draws a graph so that the size of the plot area is $ 400 \ times 300 $ (px).

--Reference: Demo Fixed Size Axes @ matplotlib.org

The cause is unknown, but at the end of the program, when the size of the plot area is recalculated and output, it becomes ʻAxis size 387 x 302 [px] `. However, when I measure the size of the plot area for the actually output image (foo.png), it is correctly $ 400 \ times 300 $ (px).

Graph output by specifying the size of the plot area in pixels


import numpy as np
import japanize_matplotlib 
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import Divider, Size #add to
from mpl_toolkits.axes_grid1.mpl_axes import Axes #add to

x = np.linspace(0,20,100) #Sample data for drawing
y = x ** 0.5              #Sample data for drawing

ax_w_px = 400  #Specify the width of the plot area in pixels
ax_h_px = 300  #Specify the height of the plot area in pixels

#Processing for size specification ↓ ↓ From here ↓ ↓
fig_dpi = 100
ax_w_inch = ax_w_px / fig_dpi
ax_h_inch = ax_h_px / fig_dpi
ax_margin_inch = (0.5, 0.5, 0.5, 0.5)  # Left,Top,Right,Bottom [inch]

fig_w_inch = ax_w_inch + ax_margin_inch[0] + ax_margin_inch[2] 
fig_h_inch = ax_h_inch + ax_margin_inch[1] + ax_margin_inch[3]

fig = plt.figure( dpi=fig_dpi, figsize=(fig_w_inch, fig_h_inch))
ax_p_w = [Size.Fixed(ax_margin_inch[0]),Size.Fixed(ax_w_inch)]
ax_p_h = [Size.Fixed(ax_margin_inch[1]),Size.Fixed(ax_h_inch)]
divider = Divider(fig, (0.0, 0.0, 1.0, 1.0), ax_p_w, ax_p_h, aspect=False)
ax = Axes(fig, divider.get_position())
ax.set_axes_locator(divider.new_locator(nx=1,ny=1))
fig.add_axes(ax)
#Processing for size specification ↑↑ Up to here ↑↑

ax.plot(x,y)
ax.set_xlabel('input',fontsize=12)
ax.set_ylabel('output',fontsize=12)
plt.savefig('foo.png')

#Figure (whole image) size
fig_w_px = int(fig.get_figwidth()  * fig.get_dpi())  
fig_h_px = int(fig.get_figheight() * fig.get_dpi())
print(f'■ Fig size   {fig_w_px} x {fig_h_px} [px]')

#Axis (plot area) size
ax_size_inch = ax.get_figure().get_size_inches()
ax_w_inch = ax_size_inch[0] * (ax.figure.subplotpars.right - ax.figure.subplotpars.left)
ax_h_inch = ax_size_inch[1] * (ax.figure.subplotpars.top - ax.figure.subplotpars.bottom)
ax_w_px = int( ax_w_inch * fig.get_dpi() )  
ax_h_px = int( ax_h_inch * fig.get_dpi() )
print(f'■ Axis size  {ax_w_px } x {ax_h_px } [px]') 
#The print output above does not result in 400 x 300,
#Actual image ('foo.png'), The size of the plot area is 400 x 300.

Recommended Posts