#!/usr/bin/env python
"""
Positions of subplots, used with add_axes
This module was written by Matthias Cuntz while at Department of Computational
Hydrosystems, Helmholtz Centre for Environmental Research - UFZ, Leipzig,
Germany, and continued while at Institut National de Recherche pour
l'Agriculture, l'Alimentation et l'Environnement (INRAE), Nancy, France.
:copyright: Copyright 2009-2022 Matthias Cuntz, see AUTHORS.rst for details.
:license: MIT License, see LICENSE for details.
.. moduleauthor:: Matthias Cuntz
The following functions are provided:
.. autosummary::
position
History
* Written Aug 2009 by Matthias Cuntz (mc (at) macu (dot) de)
* Ported to Python 3, Feb 2013, Matthias Cuntz
* Add vspace instead of wspace, Jul 2013, Matthias Cuntz
* Use assert instead of raise Error, Apr 2014, Matthias Cuntz
* Added height and width, Feb 2016, Stephan Thober
* Ported to pyjams, Nov 2021, Matthias Cuntz
* More consistent docstrings, Jan 2022, Matthias Cuntz
"""
__all__ = ['position']
[docs]def position(row=1, col=1, num=1,
left=0.125, right=0.9, bottom=0.1, top=0.9,
hspace=0.1, vspace=0.1,
width=None, height=None,
sortcol=False,
golden=False, inversegolden=False,
figsize=(1., 1.)):
"""
Gives positions of subplots, to be used with add_axes instead of subplot
All dimensions are fractions of the figure width or height.
Figure and subplot spaces are the same as for figure.subplot params
except for hspace and vspace, which are halved.
If the figsize keyword is given, a square section of the figure
will be used.
Parameters
----------
row : int, optional
Number of subplot rows (default: 1)
col : int, optional
Number of subplot columns (default: 1)
num : int, optional
Subplot number (default: 1)
left : float, optional
Left border of plot (default: 0.125)
right : float, optional
Right border of plot (default: 0.9)
bottom : float, optional
Bottom border of plot (default: 0.1)
top : float, optional
Top border of plot (default: 0.9)
hspace : float, optional
Horizontal space between columns (default: 0.1)
vspace : float, optional
Vertical space between rows (default: 0.1)
width : float, optional
Prescribe width of plots (default: calculated *col*, *left*, etc.)
height : float, optional
Prescribe height of plots (default: calculated *row*, *top*, etc.)
sortcol : bool, optional
Fill columns then rows if True (default: False)
golden : bool, optional
Ratio of width/height = (1+sqrt(5))/2 if True, i.e. the golden ratio
(default: False)
inversegolden : bool, optional
Ratio of height/width = (1+sqrt(5))/2 if True, i.e. the golden ratio
(default: False). The *golden* keyword takes precedence over
*inversegolden*.
figsize : tuple of 2 float, optional
(width, height) of figure as given by e.g.
matplotlib.rcParams['figure.figsize'].
Scales everything to a square section (default: (1, 1))
Returns
-------
list
[left, bottom, width, height] to be used with Matplotlib's
fig.add_axes.
Examples
--------
Use, for example, as follows
.. code-block:: python
fig1 = figure(1)
sub1 = fig1.add_axes(position(2, 2, 1))
sub2 = fig1.add_axes(position(2, 2, 2))
Give *figsize* and set same left and right margins if you want to have true
squares
.. code-block:: python
figsize = matplotlib.rcParams['figure.figsize']
sub = fig1.add_axes(position(1, 1, 1, figsize=figsize, left=0.1,
right=0.9))
If you want to have a true golden ratio
.. code-block:: python
sub = fig1.add_axes(position(1, 1, 1, golden=True))
>>> import numpy as np
>>> print(np.around(position(2, 2, 1), 3))
[0.125 0.55 0.338 0.35 ]
>>> print(np.around(position(2, 2, 1, sortcol=True), 3))
[0.125 0.55 0.338 0.35 ]
>>> print(np.around(position(2, 2, 1, golden=True), 3))
[0.125 0.409 0.338 0.209]
>>> print(np.around(position(2, 2, 1, inversegolden=True), 3))
[0.125 0.55 0.216 0.35 ]
>>> print(np.around(position(2, 2, 1, golden=True, sortcol=True), 3))
[0.125 0.409 0.338 0.209]
>>> print(np.around(position(2, 2, 1, top=1., bottom=0., left=0., right=1.,
... hspace=0., vspace=0.), 3))
[0. 0.5 0.5 0.5]
>>> print(np.around(position(2, 2, 2, top=1., bottom=0., left=0., right=1.,
... hspace=0., vspace=0.), 3))
[0.5 0.5 0.5 0.5]
>>> print(np.around(position(2, 2, 3, top=1., bottom=0., left=0., right=1.,
... hspace=0., vspace=0.), 3))
[0. 0. 0.5 0.5]
>>> print(np.around(position(2, 2, 4, top=1., bottom=0., left=0., right=1.,
... hspace=0., vspace=0.), 3))
[0.5 0. 0.5 0.5]
>>> print(np.around(position(2, 2, 1, top=1., bottom=0., left=0., right=1.,
... hspace=0., vspace=0., golden=True), 3))
[0. 0.309 0.5 0.309]
>>> print(np.around(position(2, 2, 2, top=1., bottom=0., left=0., right=1.,
... hspace=0., vspace=0., golden=True), 3))
[0.5 0.309 0.5 0.309]
>>> print(np.around(position(2, 2, 3, top=1., bottom=0., left=0., right=1.,
... hspace=0., vspace=0., golden=True), 3))
[0. 0. 0.5 0.309]
>>> print(np.around(position(2, 2, 4, top=1., bottom=0., left=0., right=1.,
... hspace=0., vspace=0., golden=True), 3))
[0.5 0. 0.5 0.309]
>>> figsize=[8, 11]
>>> print(np.around(position(2, 2, 1, golden=True, sortcol=True,
... figsize=figsize), 3))
[0.125 0.324 0.338 0.152]
>>> print(np.around(position(2, 2, 1, figsize=figsize, left=0.1), 3))
[0.1 0.427 0.35 0.255]
>>> print(np.around(position(2, 2, 1, figsize=figsize, left=0.1,
... golden=True), 3))
[0.1 0.33 0.35 0.157]
"""
# Check
nplots = row * col
assert num <= nplots, ('num > number of plots: ' + str(num) + ' > '
+ str(nplots))
assert right-left > 0., 'right > left: ' + str(right) + ' > ' + str(left)
assert top-bottom > 0., 'top < bottom: ' + str(top) + ' < ' + str(bottom)
# Scaling to figsize
scalex = figsize[1] / float(max(figsize))
scaley = figsize[0] / float(max(figsize))
# width, height
if width is None:
dx = (right - left - (col-1)*hspace) / col
else:
dx = width
if height is None:
dy = (top - bottom - (row-1)*vspace) / row
else:
dy = height
# golden ratio
ratio = (1. + 5**0.5) / 2.
if golden:
width = dx
height = dx / ratio
checkheight = (top - bottom - row*height) - (row-1)*vspace
if checkheight < 0.:
height = dy
width = dy * ratio
checkwidth = (right - left - col*width) - (col-1)*hspace
if checkwidth < 0.:
raise ValueError('golden ratio does not work. Have to recode.')
elif inversegolden:
height = dy
width = dy / ratio
checkwidth = (right - left - col*width) - (col-1)*hspace
if checkwidth < 0.:
width = dx
height = dx * ratio
checkheight = (top - bottom - row*height) - (row-1)*vspace
if checkheight < 0.:
raise ValueError('inverse golden ratio does not work.'
' Have to recode.')
else:
width = dx
height = dy
# order row/colmn, column/row
if sortcol:
irow = (num-1) % row
icol = (num-1) // row
else:
irow = (num-1) // col
icol = (num-1) % col
# position
pos = ['']*4
pos[0] = left + icol * (width+hspace) * scalex
pos[1] = bottom + (row-1-irow) * (height+vspace) * scaley
pos[2] = width * scalex
pos[3] = height * scaley
return pos
if __name__ == '__main__':
import doctest
doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)