I have 9 temperature points. 1 in the center and 8 on the circle. I need to create a heatmap in a circle. I set the points at which to perform calculations, and use the scipy.interpolate.griddata, but the full circle is not drawn, program draws an octagon. How can i fill in the missing parts?
import scipy.interpolate
import numpy
import matplotlib
import matplotlib.pyplot as plt
import math
# close old plots
plt.close("all")
# some parameters
xy_center = [2,2] # center of the plot
radius = 2 # radius
# mostly original code
meanR = [33.9, 34.2, 33.1, 33.5, 33., 32.7, 32.3, 31.8, 35.]
x = numpy.array([2, 2, 2+math.sqrt(2), 4, 2+math.sqrt(2), 2, 2+(-math.sqrt(2)), 0, 2+(-math.sqrt(2))])
y = numpy.array([2, 4, 2+math.sqrt(2), 2, 2+(-math.sqrt(2)), 0, 2+(-math.sqrt(2)), 2, 2+math.sqrt(2)])
z = meanR
xi, yi = numpy.mgrid[x.min():x.max():500j, y.min():y.max():500j]
zi = scipy.interpolate.griddata((x, y), z, (xi, yi), method='cubic')
# make figure
fig = plt.figure(figsize=(10, 10))
# set aspect = 1 to make it a circle
ax = fig.add_subplot(111, aspect = 1)
# use different number of levels for the fill and the lines
CS = ax.contourf(xi, yi, zi, 300, cmap=plt.cm.viridis, zorder=1)
# make a color bar
cbar = fig.colorbar(CS, ax=ax)
# add the data points
ax.scatter(x, y, marker = 'o', c = 'b', s = 15, zorder = 3)
for i in range(9):
ax.annotate(str(z[i]), (x[i],y[i]))
# draw a circle
circle = matplotlib.patches.Circle(xy = xy_center, radius = radius, edgecolor = "k", facecolor = "none")
ax.add_patch(circle)
# remove the ticks
ax.set_xticks([])
ax.set_yticks([])
# set axes limits
ax.set_xlim(-0.5, 4.5)
ax.set_ylim(-0.5, 4.5)
plt.show()
Radial basis functions (Rbf) can be used to interpolate/extrapolate your data.
scipy.interpolation Here is a modified code that produces the plot you need.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import math
from scipy.interpolate import Rbf
# some parameters
xy_center = [2,2] # center of the plot
radius = 2 # radius
# Data part
# ---------
# mostly original code
meanR = [33.9, 34.2, 33.1, 33.5, 33., 32.7, 32.3, 31.8, 35.] #9 points data
x = np.array([2, 2, 2+math.sqrt(2), 4, 2+math.sqrt(2), 2, 2+(-math.sqrt(2)), 0, 2+(-math.sqrt(2))])
y = np.array([2, 4, 2+math.sqrt(2), 2, 2+(-math.sqrt(2)), 0, 2+(-math.sqrt(2)), 2, 2+math.sqrt(2)])
z = meanR
# use RBF (Radial basis functions) that allows extrapolation
rbf = Rbf(x, y, z, epsilon=radius+1) #epsilon is based on some parameters of the data
# Interpolation/extrapolation
# ---------------------------
xi, yi = np.mgrid[x.min():x.max():500j, y.min():y.max():500j]
# applies and get inter/extra-polated values
zi = rbf(xi, yi)
# make zi outside circle --> np.none
midr,midc = zi.shape[0]/2, zi.shape[1]/2
for er in range(zi.shape[0]):
for ec in range(zi.shape[1]):
if np.abs(math.sqrt((er-midr)**2 + (ec-midc)**2))>zi.shape[0]/2:
# outside the circle, dont plot this pixel
zi[er][ec] = np.nan
pass
pass
# make figure
fig = plt.figure(figsize=(8, 8))
# set aspect = 1 to make it a circle
ax = fig.add_subplot(111, aspect = 1)
# add the data points
ax.scatter(x, y, marker = 'o', c = 'b', s = 15, zorder = 3)
for i in range(9):
ax.annotate(str(z[i]), (x[i],y[i]))
# draw a circle
circle = matplotlib.patches.Circle(xy = xy_center, radius = radius, edgecolor = "k", facecolor = "none")
ax.add_patch(circle)
CS = ax.contourf(xi, yi, zi, 300, cmap=plt.cm.viridis, zorder=1)
cbar = fig.colorbar(CS, ax=ax, shrink=0.7) # make a color bar
# remove the ticks
ax.set_xticks([])
ax.set_yticks([])
# set axes limits
ax.set_xlim(-0.5, 4.5)
ax.set_ylim(-0.5, 4.5)
plt.show()
The result:
Related
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 am trying to create a map showing an interpolated values over a scatter plot, here my code so far
# define map extent
lllon = dd
lllat = bb
urlon = cc
urlat = aa
# Set up Basemap instance
m = Basemap(
projection = 'merc',
llcrnrlon = lllon, llcrnrlat = lllat, urcrnrlon = urlon, urcrnrlat = urlat,
resolution='h')
# transform lon / lat coordinates to map projection
newdf['LONGITUDE'], newdf['LATITUDE'] = m(*(newdf['LONGITUDE'].values, newdf['LATITUDE'].values))
# grid data
#numcols, numrows = count_col, count_row
#xi = np.linspace(dd, cc, numcols)
#yi = np.linspace(bb, aa, numrows)
#xi, yi = np.meshgrid(xi, yi)
count_row = newdf.shape[0] # gives number of row count
count_col = newdf.shape[1] # gives number of col count
xi = np.linspace(newdf['LONGITUDE'].min(), newdf['LONGITUDE'].max(), count_col)
yi = np.linspace(newdf['LATITUDE'].min(), newdf['LATITUDE'].max(), count_row)
xi, yi = np.meshgrid(xi, yi)
x, y, z = newdf['LONGITUDE'].values, newdf['LATITUDE'].values, newdf['MUD_WGHT'].values
#zi = griddata(x, y, z, xi, yi)
zi = griddata((x,y),z,(xi,yi),method='linear')
# interpolate
#x, y, z = newdf['LONGITUDE'].values, newdf['LATITUDE'].values, newdf['MUD_WGHT'].values
#zi = griddata((x,y),z,(xi,yi),method='linear')
# draw map details
m.drawmapboundary(fill_color = 'white')
m.fillcontinents(color='#C0C0C0', lake_color='#7093DB')
m.drawcountries(
linewidth=.75, linestyle='solid', color='#000073',
antialiased=True,
ax=ax, zorder=3)
m.drawparallels(
np.arange(lllat, urlat, 2.),
color = 'black', linewidth = 0.5,
labels=[True, False, False, False])
m.drawmeridians(
np.arange(lllon, urlon, 2.),
color = '0.25', linewidth = 0.5,
labels=[False, False, False, True])
# contour plot
con = m.contourf(xi, yi, zi, zorder=4, alpha=0.6, cmap='RdPu')
# scatter plot
m.scatter(
newdf['LONGITUDE'],
newdf['LATITUDE'],
color='#545454',
edgecolor='#ffffff',
alpha=.75,
s=50 * norm(newdf['MUD_WGHT'].values),
cmap='RdPu',
ax=ax,
vmin=zi.min(), vmax=zi.max(), zorder=4)
# add colour bar and title
# add colour bar, title, and scale
cbar = plt.colorbar(orientation='horizontal', fraction=.057, pad=0.05)
cbar.set_label("Mud Weight - PPG")
#m.drawmapscale(
# 24., -9., 28., -13,
# 100,
# units='km', fontsize=10,
#yoffset=None,
#barstyle='fancy', labelstyle='simple',
#fillcolor1='w', fillcolor2='#000000',
#fontcolor='#000000',
#zorder=5)
plt.title("Regional Mud Weights in The Lateral")
plt.show()
The result is the following:
How can I get the interpolated contour region to extend to the full scatter plot? I have been focusing on if this is an issue with the meshgrid, so I am not sure if the meshgrid isn't interpolating all of the data or if it is an issue with the plotting.
You need to do triangulation and use tricontour and/or tricontourf. Here is a demonstration code and sample plot.
from mpl_toolkits.basemap import Basemap
from matplotlib.tri import Triangulation
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
# data coordinates must conform to the projection
x = np.random.random((30))
y = np.random.random((30))
z = x * y
yn = 30*x
xn = 80 + 30*y
tri = Triangulation(xn,yn) #create tri mesh
fig = plt.figure(figsize=(7, 7))
m = Basemap(projection = 'cyl',
llcrnrlat = 0,
urcrnrlat = 30,
llcrnrlon = 80,
urcrnrlon = 110,
resolution = 'l')
ctf = plt.tricontourf(tri, z, cmap=cm.coolwarm, zorder=10, alpha=0.75)
#plt.tricontour(tri, z, )
plt.scatter(xn, yn, c='g', zorder=13)
m.drawparallels(np.arange(-90, 90,10), labels=[1,0,0,0])
m.drawmeridians(np.arange(-180, 180, 10), labels = [0,0,0,1])
m.drawcoastlines(linewidth=0.8, color='blue', zorder=12)
m.fillcontinents(color='#C0C0C0', lake_color='#7093DB')
cbar = plt.colorbar(ctf , orientation='horizontal', fraction=.045, pad=0.08)
plt.show()
Are there libraries or methods in python that are capable of creating plots that look like this? (preferably based around MatPlotLib for the sake of embedding the plots in HTML pages)
My goal is to create 3D renderings of data that is read from a Neo4J database and model them as the cylinders above.
The code below attempts to create a similar 3D plot (not cylindrical but rectangular) with legends from a dataframe. The plot is interactive. Resources: 1, 2, 3, 4 (Jupyter Notebook 5.0.0, Python 3.6.6)
Import libraries
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from mpl_toolkits.mplot3d import axes3d
import matplotlib.patches as mpatches # for legends
%matplotlib notebook
Create a sample dataframe
# Create two sets of identical xpos and ypos
# So taht the z-values are plotted at same location for stacking
xtemp = np.random.randint(1, 10, size=5)
ytemp = np.random.randint(1, 10, size=5)
df = pd.DataFrame({
# category
'season': ['S1']*5 + ['S2']*5 + ['S3']*5,
#'wins': np.random.randint(1, 10, size=15),
# define pos
'xpos' : list(xtemp)+list(xtemp)+list(xtemp),
'ypos' : list(ytemp)+list(ytemp)+list(ytemp),
'zpos' : np.zeros(15),
# define delta
'dx': 0.8*np.ones(15),
'dy': 0.8*np.ones(15),
'dz': np.random.randint(1, 5, size=15), #np.ones(15)
})
df.head(5)
Plot the figure
Note: Figure are in two parts: (1) 2D plot for the N-S, E-W lines and (2) 3D bar plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# ..................
# Line-1 on x-y plane
x = [4, 4]
y = [-3, 12]
ax.plot(x, y, zs=0, zdir='z', color='orange', alpha=0.8)
# Line-2 on x-y plane
y = [4, 4]
x = [-3, 12]
ax.plot(x, y, zs=0, zdir='z', color='blue', alpha=0.5)
# Creat multiple overlap plots within a loop
color = ['#6495ED', '#6E8B3D', '#FFB90F']
slist = ['S1', 'S2', 'S3']
stack_zpos = pd.Series(np.zeros(5))
for i in range(0,3):
q = df[df['season']==slist[i]].reset_index(inplace=False)
ax.bar3d(q.xpos, q.ypos, stack_zpos, q.dx, q.dy, q.dz, color=color[i], alpha=1)
stack_zpos += q.dz # values added here for stacking
Annotate lines and remove z-axis panes and grid lines
# Remove the z-axis panes, grids and lines
alpha = 0
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, alpha))
ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, alpha))
#
ax.zaxis._axinfo["grid"]['color'] = (1.0, 1.0, 1.0, alpha)
ax.w_yaxis._axinfo["grid"]['linewidth'] = 0
ax.w_xaxis._axinfo["grid"]['linewidth'] = 0
#
ax.w_zaxis.line.set_lw(0.)
ax.set_zticks([])
#
ax.set_zlabel("") # remove z-axis label 'z'
# ..........
# Annotate the N, S, E, W lines on the x-y plane
zdirs = (None, 'x', 'y', 'z', (1, 1, 0), (1, 1, 1))
xs = (4, 4, -3, 12)
ys = (-3,12, 4, 4)
zs = (0, 0, 0, 0)
i=0 # Counter
nsew = ['N', 'S', 'E', 'W'] # list of labels
for zdir, x, y, z in zip(zdirs, xs, ys, zs):
label = '{0}'.format(nsew[i])
#label = 'N, S, E, W' #% (x, y, z, zdir)
ax.text(x, y, z, label, zdir)
i +=1
Create and add legends to the plot
# Add legend
patch1 = mpatches.Patch(color=color[0], label=slist[0])
patch2 = mpatches.Patch(color=color[1], label=slist[1])
patch3 = mpatches.Patch(color=color[2], label=slist[2])
plt.legend(handles=[patch1, patch2,patch3])
Visualize plot
plt.show()
I use matplotlib to generate an image in the following way:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.fill(border[0],border[1], color='g', linewidth=1, fill=True, alpha = 0.5)
patches = []
for x1,y1,r in zip(x, y, radii):
circle = Circle((x1,y1), r)
patches.append(circle)
p = PatchCollection(patches, cmap='cool', alpha=1.0)
p.set_array(c)
ax.add_collection(p)
plt.colorbar(p)
plt.savefig(fig_name)
What I want to have is a polygon (given by its border) and colored circles on the top of this polygon. However, I get the polygon on the top of the circles.
This is strange because I plot the polygon first and then I add circles to the plot.
Does anybody know why it happens and how this problem can be resolved?
ADDED
As requested, here is fully working example:
import pandas
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Circle, Polygon
import numpy as np
def plot_xyc(df, x_col, y_col, c_col, radius, fig_name, title, zrange):
resolution = 50
x = df[x_col]
y = df[y_col]
c = df[c_col]
x0 = (max(x) + min(x))/2.0
y0 = (max(y) + min(y))/2.0
dx = (max(x) - min(x))
dy = (max(y) - min(y))
delta = max(dx, dy)
radii = [delta*radius for i in range(len(x))]
fig = plt.figure()
plt.title(title)
ax = fig.add_subplot(111)
border = ([-3, 3, 3, -3], [-3, -3, 3, 3])
ax.fill(border[0],border[1], color='g', linewidth=1, fill=True, alpha = 1.0)
patches = []
for x1,y1,r in zip(x, y, radii):
circle = Circle((x1,y1), r)
patches.append(circle)
patches.append(Circle((-100,-100), r))
patches.append(Circle((-100,-100), r))
p = PatchCollection(patches, cmap='cool', alpha=1.0)
p.set_array(c)
max_ind = max(c.index)
c.set_value(max_ind + 1, min(zrange))
c.set_value(max_ind + 2, max(zrange))
plt.xlim([x0 - delta/2.0 - 0.05*delta, x0 + delta/2.0 + 0.05*delta])
plt.ylim([y0 - delta/2.0 - 0.05*delta, y0 + delta/2.0 + 0.05*delta])
ax.add_collection(p)
plt.colorbar(p)
plt.savefig(fig_name)
if __name__ == '__main__':
df = pandas.DataFrame({'x':[1,2,3,4], 'y':[4,3,2,1], 'z':[1,1,2,2]})
plot_xyc(df, 'x', 'y', 'z', 0.1, 'test2.png', 'My Titlle', (0.0, 3.0))
You're looking for zorder.
In matplotlib, all additional arguments are just passed up the class heirarchy. zorder is a kwarg of the Artist class, so you just need to make sure that at some point it gets zorder.
You can do it two ways in your example;
either add it in here:
ax.fill(border[0],border[1], color='g', linewidth=1, fill=True, alpha = 1.0, zorder=1)
or here:
p = PatchCollection(patches, cmap='cool', alpha=1.0, zorder=2)
or if you want, both. Objects with a higher zorder sit on top of those with lower values.
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")