I'm trying to draw gradient text. My idea is to create a gradient using the code from the docs to create a gradient background and then clip that image using a text path. For whatever reason, I'm only getting the first letter and some of the second.
Changing the limits of the axes or the width of the figure don't seem to work. I can change the size of the TextPath, but then I can't seem to make the text take up the full size of the image. Making the text size .2 and setting the ylim from 0 to .2 only makes squished text. How can I make the text span the size of the axes and take the full range of the gradient?
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import matplotlib.textpath
import matplotlib.patches
import numpy as np
def gradient_image(ax, extent, direction=0.3, cmap_range=(0, 1), **kwargs):
"""
Draw a gradient image based on a colormap.
Parameters
----------
ax : Axes
The axes to draw on.
extent
The extent of the image as (xmin, xmax, ymin, ymax).
By default, this is in Axes coordinates but may be
changed using the *transform* keyword argument.
direction : float
The direction of the gradient. This is a number in
range 0 (=vertical) to 1 (=horizontal).
cmap_range : float, float
The fraction (cmin, cmax) of the colormap that should be
used for the gradient, where the complete colormap is (0, 1).
**kwargs
Other parameters are passed on to `.Axes.imshow()`.
In particular useful is *cmap*.
"""
phi = direction * np.pi / 2
v = np.array([np.cos(phi), np.sin(phi)])
X = np.array([[v # [1, 0], v # [1, 1]],
[v # [0, 0], v # [0, 1]]])
a, b = cmap_range
X = a + (b - a) / X.max() * X
im = ax.imshow(X, extent=extent, interpolation='bicubic',
vmin=0, vmax=1, **kwargs)
return im
fig, ax = plt.subplots(figsize=(1,1))
im = gradient_image(ax, direction=1, extent=(0, 1, 0, 1), transform=ax.transAxes,
cmap=plt.cm.winter, cmap_range=(0.2, 0.8), alpha=0.5)
fp = FontProperties(family='DejaVu Sans', weight='bold')
text = matplotlib.textpath.TextPath((0.0, 0.0), 'ABCDEFG',
size=1, prop=fp)
im.set_clip_path(text, transform=ax.transAxes)
# ax.set_xticks(())
# ax.set_yticks(())
# ax.tick_params(width=0, which='both')
# ax.set_xlim((0, 10))
# ax.set_ylim((0, 1))
for spine in ['top', 'bottom', 'left', 'right']:
ax.spines[spine].set_visible(False)
I am not fully fluent in matplotlib transformations, but from what I've read, ax.transData seems to be the better approach here (comments were I changed your code):
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import matplotlib.textpath
import matplotlib.patches
import numpy as np
def gradient_image(ax, extent, direction=0.3, cmap_range=(0, 1), **kwargs):
"""
Draw a gradient image based on a colormap.
Parameters
----------
ax : Axes
The axes to draw on.
extent
The extent of the image as (xmin, xmax, ymin, ymax).
By default, this is in Axes coordinates but may be
changed using the *transform* keyword argument.
direction : float
The direction of the gradient. This is a number in
range 0 (=vertical) to 1 (=horizontal).
cmap_range : float, float
The fraction (cmin, cmax) of the colormap that should be
used for the gradient, where the complete colormap is (0, 1).
**kwargs
Other parameters are passed on to `.Axes.imshow()`.
In particular useful is *cmap*.
"""
phi = direction * np.pi / 2
v = np.array([np.cos(phi), np.sin(phi)])
X = np.array([[v # [1, 0], v # [1, 1]],
[v # [0, 0], v # [0, 1]]])
a, b = cmap_range
X = a + (b - a) / X.max() * X
# added origin = lower, elsewise text is flipped upside down
im = ax.imshow(X, extent=extent, interpolation='bicubic',
vmin=0, vmax=1, origin='lower', **kwargs)
return im
fig, ax = plt.subplots(figsize=(1, 1))
# define text before gradient to get extent
fp = FontProperties(family='DejaVu Sans', weight='bold')
text = matplotlib.textpath.TextPath((0.0, 0.0), 'ABCDEFG',
size=1, prop=fp)
# use text to define imshow extent
extent = text.get_extents().extents[[0, 2, 1, 3]]
im = gradient_image(ax, direction=1, extent=extent,
cmap=plt.cm.winter, cmap_range=(0.2, 0.8), alpha=0.5)
# use transData instead of transAxes
im.set_clip_path(text, transform=ax.transData)
# © trenton
ax.spines[['top', 'bottom', 'left', 'right']].set_visible(False)
plt.show()
Related
I have a 1D distribution (x values vs probability, shown below) and I would like to convert that to a 2D plot like the one shown below in which the color gradient is based on the values probabilities.
Currently, my code just plot in a qualitative manner because I am manually defining the array v1 and the color list. I tried my best to crack this and understand how to do it, but I failed. Does anyone have a suggestion?
def gradient_image(ax, extent, direction=0.3, cmap_range=(0, 1), **kwargs):
"""
Draw a gradient image based on a colormap.
Parameters
----------
ax : Axes
The axes to draw on.
extent
The extent of the image as (xmin, xmax, ymin, ymax).
By default, this is in Axes coordinates but may be
changed using the *transform* keyword argument.
direction : float
The direction of the gradient. This is a number in
range 0 (=vertical) to 1 (=horizontal).
cmap_range : float, float
The fraction (cmin, cmax) of the colormap that should be
used for the gradient, where the complete colormap is (0, 1).
**kwargs
Other parameters are passed on to `.Axes.imshow()`.
In particular useful is *cmap*.
"""
phi = direction * np.pi / 2
v = np.array([np.cos(phi), np.sin(phi)])
X = np.array([[v # [1, 0], v # [1, 1]],
[v # [0, 0], v # [0, 1]]])
a, b = cmap_range
X = a + (b - a) / X.max() * X
im = ax.imshow(X, extent=extent, interpolation='bicubic',
vmin=0, vmax=1, **kwargs)
return im
v1 = [0, 0.15, 0.5, 0.85, 1.0] # | Those two lines here
b = ["white","lightblue", "dodgerblue","lightblue", "white"] # | were the best I could do
bl = list(zip(v1,b))
blue_grad=LinearSegmentedColormap.from_list('custom',bl, N=256)
xmin, xmax = xlim = 0, 4
ymin, ymax = ylim = -300, 300
fig, ax = plt.subplots()
ax.set(xlim=xlim, ylim=ylim, autoscale_on=False)
gradient_image(ax, direction=1, extent=(0 , 2, -300, 300), cmap=blue_grad, cmap_range=(0., 1), alpha=0.5)
Here is a minimal example with a gaussian distribution (code for generating the gaussian distribution was adapted from this):
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as stats
mu=0 #Create gaussian distribution
sigma=1
x = np.linspace(mu - 3*sigma, mu + 3*sigma, 100)
y=stats.norm.pdf(x, mu, sigma)
y_expand=np.expand_dims(y, axis=0) #expanding dimensions to use imshow
plt.plot(x, stats.norm.pdf(x, mu, sigma),color='k',lw=3,ls='--')# plot distribution
extent = np.min(x), np.max(x), np.min(y), np.max(y)
plt.imshow(y_expand,interpolation=None,aspect='auto',cmap='Blues',extent=extent) #plot imshow
plt.colorbar()
plt.show()
I wish to modify the 2D line in my legend to plot as line segments (or another method like patches) that will display the range of my colormap (here viridis_r) instead of a singular color. While the third variable (radius) is included in the colorbar, having it displayed in the legend as well will be informative when I add more complications to the plot. Thanks!
fig, ax = plt.subplots()
radii = [1,2,3,4,5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
m = plt.cm.ScalarMappable(cmap=cmap)
m.set_array(radii)
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
ax.plot(x, y, color=cmap(norm(radius)))
radius_2Dline = plt.Line2D((0, 1), (0, 0), color='k', linewidth=2)
ax.legend([radius_2Dline],['Radius'], loc='best')
ax.set_aspect( 1 )
fig.colorbar(m).set_label('Radius', size=15)
plt.show()
The following approach uses the "tuple legend handler". That handler puts a list of legend handles (in this case the circles drawn via ax.plot). Setting ndivide=None will draw one short line for each element in the list. The padding can be set to 0 to avoid gaps between these short lines. The default handlelength might be too small to properly see these special handles; therefore, the example code below increases it a bit.
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerTuple
import numpy as np
fig, ax = plt.subplots()
radii = [1, 2, 3, 4, 5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
lines = [] # list of lines to be used for the legend
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
line, = ax.plot(x, y, color=cmap(norm(radius)))
lines.append(line)
ax.legend(handles=[tuple(lines)], labels=['Radius'],
handlelength=3, handler_map={tuple: HandlerTuple(ndivide=None, pad=0)})
ax.set_aspect('equal')
plt.tight_layout()
plt.show()
I am not sure if this is your goal but here is a stab at it. Following this answer, you can make a 'fake' legend with a colormap.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, ax = plt.subplots()
radii = [1, 2, 3, 4, 5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
m = plt.cm.ScalarMappable(cmap=cmap)
m.set_array(radii)
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
ax.plot(x, y, color=cmap(norm(radius)))
# Set box that will act as a 'fake' legend, 25% width of the
# x-axis, 15% of y-axis
cbbox = inset_axes(ax, width="25%", height="15%", loc=2)
cbbox.tick_params(
axis = 'both',
left = False,
top = False,
right = False,
bottom = False,
labelleft = False,
labeltop = False,
labelright = False,
labelbottom = False
)
# Semi-transparent like the usual ax.legend()
cbbox.set_facecolor([1, 1, 1, 0.7])
# Colorbar inside the fake legend box, occupying 85% of the
# box width and %5 box height
cbaxes = inset_axes(cbbox, width="85%", height="5%", loc=2)
cbar = fig.colorbar(m, cax=cbaxes, orientation='horizontal',
ticks=[1, 3, 5])
cbar.set_label('Radius', size=9)
cbar.ax.tick_params(labelsize=9)
ax.set_aspect(1)
plt.show()
I was unsuccessful in creating an actual ax.legend() from a LineCollection or a multicolored line - it only plotted one color - so my solution was this 'fake' legend approach. Hope this helps, cheers.
I'm borrowing an example from the matplotlib custom cmap examples page:
https://matplotlib.org/examples/pylab_examples/custom_cmap.html
This produces the same image with different numbers of shading contours, as specified in the number of bins: n_bins:
https://matplotlib.org/_images/custom_cmap_00.png
However, I'm interested not only in the number of bins, but the specific break points between the color values. For example, when nbins=6 in the top right subplot, how can I specify the ranges of the bins to such that the shading is filled in these custom areas:
n_bins_ranges = ([-10,-5],[-5,-2],[-2,-0.5],[-0.5,2.5],[2.5,7.5],[7.5,10])
Is it also possible to specify the inclusivity of the break points? For example, I'd like to specify in the range between -2 and 0.5 whether it's -2 < x <= -0.5 or -2 <= x < -0.5.
EDIT WITH ANSWER BELOW:
Using the accepted answer below, here is code that plots each step including finally adding custom colorbar ticks at the midpoint. Note I can't post an image since I'm a new user.
Set up data and 6 color bins:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# Make some illustrative fake data:
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
# Create colormap with 6 discrete bins
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6
cmap_name = 'my_list'
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
Plot different options:
# Set up 4 subplots
fig, axs = plt.subplots(2, 2, figsize=(6, 9))
fig.subplots_adjust(left=0.02, bottom=0.06, right=0.95, top=0.94, wspace=0.05)
# Plot 6 bin figure
im = axs[0,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm)
axs[0,0].set_title("Original 6 Bin")
fig.colorbar(im, ax=axs[0,0])
# Change the break points
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
im = axs[0,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[0,1].set_title("Custom Break Points")
fig.colorbar(im, ax=axs[0,1])
# Arrange color labels by data interval (not colors)
im = axs[1,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[1,0].set_title("Linear Color Distribution")
fig.colorbar(im, ax=axs[1,0], spacing="proportional")
# Provide custom labels at color midpoints
# And change inclusive equality by adding arbitrary small value
n_bins_ranges_arr = np.asarray(n_bins_ranges)+1e-9
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
n_bins_ranges_midpoints = (n_bins_ranges_arr[1:] + n_bins_ranges_arr[:-1])/2.0
im = axs[1,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm ,norm=norm)
axs[1,1].set_title("Midpoint Labels\n Switched Equal Sign")
cbar=fig.colorbar(im, ax=axs[1,1], spacing="proportional",
ticks=n_bins_ranges_midpoints.tolist())
cbar.ax.set_yticklabels(['Red', 'Brown', 'Green 1','Green 2','Gray Blue','Blue'])
plt.show()
You can use a BoundaryNorm as follows:
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6 # Discretizes the interpolation into bins
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
cmap_name = 'my_list'
fig, ax = plt.subplots()
# Create the colormap
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
# Fewer bins will result in "coarser" colomap interpolation
im = ax.imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
ax.set_title("N bins: %s" % n_bin)
fig.colorbar(im, ax=ax)
plt.show()
Or, if you want proportional spacing, i.e. the distance between colors according to their values,
fig.colorbar(im, ax=ax, spacing="proportional")
As the boundary norm documentation states
If b[i] <= v < b[i+1]
then v is mapped to color j; as i varies from 0 to len(boundaries)-2, j goes from 0 to ncolors-1.
So the colors are always chosen as -2 <= x < -0.5, in order to obtain the equal sign on the other side you would need to supply
something like n_bins_ranges = np.array([-10,-5,-2,-0.5,2.5,7.5,10])-1e-9
I'm trying to plot a certain data with opacity values.
To plot it without opacity, I can do this:
plt.figure()
fig = plt.imshow(scalar_field, origin='lower', zorder=1, extent=(-4, 4, -4, 4))
plt.colorbar(fig)
plt.show()
plt.close()
Which gives me that image:
But I have some opacity values that should be included. Then, I do the following:
plt.figure()
data = plt.cm.jet(scalar_field)
data[..., 3] = 1.0 # just for tests purposes
fig = plt.imshow(data, origin='lower', extent=(-4, 4, -4, 4))
plt.colorbar(fig)
plt.show()
plt.close()
Which is correct, since vmin=0.0 and vmax=1.0 and my values are too small.
So, to properly see something, I do this:
plt.figure()
data = plt.cm.jet(scalar_field)
data[..., 3] = 1.0 # just for tests purposes
max_lim = numpy.max(field_property)
min_lim = numpy.min(field_property)
fig = plt.imshow(data, origin='lower', extent=(-4, 4, -4, 4), vmin=min_lim, vmax=max_lim, cmap=plt.cm.jet))
plt.colorbar(fig)
plt.show()
plt.close()
Which properly adjust the image limits as the ones showed in the first figure:
However, I can't see the image yet. In both cases, I can see something like a weird blue circle in the images.
What am I missing?
Thank you.
What you are missing is the fact that calling a cmap with some value assumes fixed values. If you pass a float, the range of the cmap is fixed between 0 and 1. If you pass an int the range is 0 to 255. You are, apparently, passing very small values to the cmap which then returns colors equal or close to the value 0 (so Blue for jet).
You then pass this 'color array' to imshow, this assumes and displays the array as an RGB(A) image. Any vmin, vmax and cmap stuff you add is ignored, because its already a RGB image, there is nothing to map.
What you want to do is create a ScalarMappable which you can create with a cmap AND a normalizer which sets the range.
Since you dont define your 'scalar_field' i'll use some sample data from the MPL Gallery:
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
# create the sample data
# after http://matplotlib.org/examples/images_contours_and_fields/pcolormesh_levels.html
dx, dy = 0.05, 0.05
y, x = np.mgrid[slice(1, 5 + dy, dy),
slice(1, 5 + dx, dx)]
z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
fig, ax = plt.subplots(figsize=(6,6))
ax.axis('off')
# with a normalizer you can set the range against which you want to map
norm = mpl.colors.Normalize(vmin=-1, vmax=1)
# the cmap of your choice
cmap = plt.cm.jet
# create a 'mapper'
mapper = mpl.cm.ScalarMappable(norm=norm, cmap=cmap)
# map the data to a rgba array
rgba = mapper.to_rgba(z)
# make some adjustments to the alpha
rgba[:,:,3] = 0.5
# and plot
# there is no need for vmin, cmap etc keywords, its already rgba
ax.imshow(rgba)
Four-way logarithmic plot is a very often used graph for vibration control and earthquake protection. I am quite interesting in how this plot can be plotted in Matplotlib instead of adding axes in Inkscape. A sample of Four-way logarithmic plot is here.
A quick and dirty Python code can generate main part of the figure, but I cannot add the two axes onto the figure. http://matplotlib.org/examples/axes_grid/demo_curvelinear_grid.html provides an example of adding axes, but I fails to make it working. Anyone has similar experience on adding axes to Matplotlib figure?
from pylab import *
from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
from mpl_toolkits.axisartist import Subplot
beta=logspace(-1,1,500)
Rd={}
for zeta in [0.01,0.1,0.2,0.7,1]:
Rd[zeta]=beta/sqrt((1-beta*beta)**2+(2*beta*zeta)**2)
loglog(beta,Rd[zeta])
ylim([0.1,10])
xlim([0.1,10])
grid('on',which='minor')
Update: Thank you all! I use Inkscape to modify the figure above. I think the result is just fine. However, I am still looking for methods to draw this figure in Matplotlib.
Here is a partial solution. I am still working on how to do all of this in a natural loglog() plot rather than scaling the data. (To complete this example you would have to define custom tick-lables so that they display 10**x rather than x.)
%matplotlib inline # I am doing this in an IPython notebook.
from matplotlib import pyplot as plt
import numpy as np
from numpy import log10
# Generate the data
beta = np.logspace(-1, 1, 500)[:, None]
zeta = np.array([0.01,0.1,0.2,0.7,1])[None, :]
Rd = beta/np.sqrt((1 - beta*beta)**2 + (2*beta*zeta)**2)
def draw(beta=beta, Rd=Rd):
plt.plot(log10(beta), log10(Rd))
plt.ylim([log10(0.1), log10(10)])
plt.xlim([log10(0.1), log10(10)])
plt.grid('on',which='minor')
ax = plt.gca()
ax.set_aspect(1)
from mpl_toolkits.axisartist import GridHelperCurveLinear
from matplotlib.transforms import Affine2D
from mpl_toolkits.axisartist import SubplotHost
from mpl_toolkits.axisartist import Subplot
#tr = Affine2D().rotate(-np.pi/2)
#inv_tr = Affine2D().rotate(np.pi/2)
class Transform(object):
"""Provides transforms to go to and from rotated grid.
Parameters
----------
ilim : (xmin, xmax, ymin, ymax)
The limits of the displayed axes (in physical units)
olim : (xmin, xmax, ymin, ymax)
The limits of the rotated axes (in physical units)
"""
def __init__(self, ilim, olim):
# Convert each to a 3x3 matrix and compute the transform
# [x1, y1, 1] = A*[x0, y0, 1]
x0, x1, y0, y1 = np.log10(ilim)
I = np.array([[x0, x0, x1],
[y0, y1, y1],
[ 1, 1, 1]])
x0, x1, y0, y1 = np.log10(olim)
x_mid = (x0 + x1)/2
y_mid = (y0 + y1)/2
O = np.array([[ x0, x_mid, x1],
[y_mid, y1, y_mid],
[ 1, 1, 1]])
self.A = np.dot(O, np.linalg.inv(I))
self.Ainv = np.linalg.inv(self.A)
def tr(self, x, y):
"""From "curved" (rotated) coords to rectlinear coords"""
x, y = map(np.asarray, (x, y))
return np.dot(self.A, np.asarray([x, y, 1]))[:2]
def inv_tr(self, x, y):
"""From rectlinear coords to "curved" (rotated) coords"""
x, y = map(np.asarray, (x, y))
return np.dot(self.Ainv, np.asarray([x, y, 1]))[:2]
ilim = (0.1, 10)
olim = (0.01, 100)
tr = Transform(ilim + ilim, olim + olim)
grid_helper = GridHelperCurveLinear((tr.tr, tr.inv_tr))
fig = plt.gcf()
ax0 = Subplot(fig, 1, 1, 1)
ax1 = Subplot(fig, 1, 1, 1, grid_helper=grid_helper, frameon=False)
ax1.set_xlim(*np.log10(olim))
ax1.set_ylim(*np.log10(olim))
ax1.axis["left"] = ax1.new_floating_axis(0, 0.)
ax1.axis["bottom"] = ax1.new_floating_axis(1, 0.0)
fig.add_subplot(ax0)
fig.add_subplot(ax1)
ax0.grid('on', which='both')
ax1.grid('on', which='both')
plt.plot(log10(beta), log10(Rd))
plt.ylim(np.log10(ilim))
plt.xlim(np.log10(ilim))
This seems to be a bit tricker than it should. There are ways to center the spines (axis lines), and ways to rotate them, but those do not work together. Adding a normal axis on a line (a la mpl demos) results in a curved axis (because it is logarithmic). Here is a [poor] example of how to draw -- as in, like you would with Inkscape something to look like an additional pair of axis spines with the example data.
import matplotlib.pyplot as plt
import numpy as np
#data
b = np.logspace(-1, 1, 500)
Rd = {}
for zeta in [0.01, 0.1, 0.2, 0.7, 1]:
Rd[zeta] = b / np.sqrt((1 - b * b) ** 2 + (2 * b * zeta) ** 2)
#plot
fig = plt.figure()
ax1 = fig.add_subplot(111)
for z in Rd:
ax1.loglog(b, Rd[z])
ax1.set_xlim([0.1, 10])
ax1.set_ylim([0.1, 10])
ax1.set_aspect(1.)
#draw lines to look like diagonal spines (axes)
xmin, xmax = ax1.get_xlim() # xlim == ylim
a = np.log10(xmin)
b = np.log10(xmax)
span = b - a
period_points = 3 # number of points/ticks per decade
npts = (span * period_points) + 1 # +1 for even powers of 10
x1 = np.logspace(a, b, num=npts)
x2 = np.logspace(b, a, num=npts)
ax1.plot(x1, x1, color='k', marker='x', ms='9')
ax1.plot(x1, x2, color='k', marker='x', ms='9')
#NOTE: v1.2.1 lacks 'TICKUP' and similar - these may be
# a better choice in v1.3x and beyond
ax1.text(0.97, 0.9,
"axis label: A",
size='large',
horizontalalignment='right',
verticalalignment='top',
rotation=45,
transform=ax1.transAxes,
#bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 10},
)
ax1.text(0.03, 0.9,
"axis label: B",
size='large',
horizontalalignment='left',
verticalalignment='top',
rotation=-45,
transform=ax1.transAxes,
#bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 10},
)
plt.savefig("example.pdf")