Search code examples
pythonmatplotliblogarithm

Drawing log-linear plot on a square plot area in matplotlib


I would like to draw a plot with a logarithmic y axis and a linear x axis on a square plot area in matplotlib. I can draw linear-linear as well as log-log plots on squares, but the method I use, Axes.set_aspect(...), is not implemented for log-linear plots. Is there a good workaround?


linear-linear plot on a square:

from pylab import *
x = linspace(1,10,1000)
y = sin(x)**2+0.5
plot (x,y)
ax = gca()
data_aspect = ax.get_data_ratio()
ax.set_aspect(1./data_aspect)
show()

Square linear-linear plot


log-log plot on a square:

from pylab import *
x = linspace(1,10,1000)
y = sin(x)**2+0.5
plot (x,y)
ax = gca()
ax.set_yscale("log")
ax.set_xscale("log")
xmin,xmax = ax.get_xbound()
ymin,ymax = ax.get_ybound()
data_aspect = (log(ymax)-log(ymin))/(log(xmax)-log(xmin))
ax.set_aspect(1./data_aspect)
show()

square log-log plot


But when I try this with a log-linear plot, I do not get the square area, but a warning

from pylab import *
x = linspace(1,10,1000)
y = sin(x)**2+0.5
plot (x,y)
ax = gca()
ax.set_yscale("log")
xmin,xmax = ax.get_xbound()
ymin,ymax = ax.get_ybound()
data_aspect = (log(ymax)-log(ymin))/(xmax-xmin)
ax.set_aspect(1./data_aspect)
show()

yielding the warning:

axes.py:1173: UserWarning: aspect is not supported for Axes with xscale=linear, yscale=log

not-square log-linear plot


Is there a good way of achieving square log-linear plots despite the lack support in Axes.set_aspect?


Solution

  • Well, there is a sort of a workaround. The actual axis area (the area where the plot is, not including external ticks &c) can be resized to any size you want it to have.

    You may use the ax.set_position to set the relative (to the figure) size and position of the plot. In order to use it in your case we need a bit of maths:

    from pylab import *
    
    x = linspace(1,10,1000)
    y = sin(x)**2+0.5
    plot (x,y)
    ax = gca()
    ax.set_yscale("log")
    
    # now get the figure size in real coordinates:
    fig  = gcf()
    fwidth = fig.get_figwidth()
    fheight = fig.get_figheight()
    
    # get the axis size and position in relative coordinates
    # this gives a BBox object
    bb = ax.get_position()
    
    # calculate them into real world coordinates
    axwidth = fwidth * (bb.x1 - bb.x0)
    axheight = fheight * (bb.y1 - bb.y0)
    
    # if the axis is wider than tall, then it has to be narrowe
    if axwidth > axheight:
        # calculate the narrowing relative to the figure
        narrow_by = (axwidth - axheight) / fwidth
        # move bounding box edges inwards the same amount to give the correct width
        bb.x0 += narrow_by / 2
        bb.x1 -= narrow_by / 2
    # else if the axis is taller than wide, make it vertically smaller
    # works the same as above
    elif axheight > axwidth:
        shrink_by = (axheight - axwidth) / fheight
        bb.y0 += shrink_by / 2
        bb.y1 -= shrink_by / 2
    
    ax.set_position(bb)
    
    show()
    

    A slight stylistic comment is that import pylab is not usually used. The lore goes:

    import matplotlib.pyplot as plt
    

    pylab as an odd mixture of numpy and matplotlib imports created to make interactive IPython use easier. (I use it, too.)