Draw more than one helix using matplotlib - python

I am trying to draw helix (shape of spring). I was able to draw a single helix using axes3D and matplotlib.
Below is my code:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import rcParams
import matplotlib.pyplot as plt
import numpy as np
theta = np.linspace(-9 * np.pi, 9 * np.pi, 300)
radius = 5.0
x = radius*np.cos(theta)
x=[]
for i in theta:
if (i < 4.5* np.pi):
x.append(radius*np.cos(i))
else:
x.append((radius+2.0) * np.cos(i))
y=[]
for j in theta:
if (j < 4.5* np.pi):
y.append(radius*np.sin(j))
else:
y.append((radius+2.0) * np.sin(j))
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(x, y, theta,
label = 'Parametric Curve', # label of the curve
color = 'DarkMagenta', # colour of the curve
linewidth = 1, # thickness of the line
linestyle = '-' # available styles - -- -. :
)
rcParams['legend.fontsize'] = 11 # legend font size
ax.legend() # adds the legend
ax.set_xlabel('X axis')
ax.set_xlim(-5, 5)
ax.set_ylabel('Y axis')
ax.set_ylim(-10, 10)
ax.set_zlabel('Z axis')
ax.set_zlim(-9*np.pi, 9*np.pi)
ax.set_title('3D line plot,\n parametric curve', va='bottom')
plt.show() # display the plot
I have two questions:
1) I was able to adjust the radius of my spiral but was not able to adjust the number of pitch. What changes should i make so I can have 19 circular rings, instead of 9.
2) After certain point(ie. end point of helix), I want to increase my radius and create a right-handed helix that goes all the way to bottom to the starting point of my first helix ( my first helix was left-handed helix). I was able to increase my radius but was not able to change the orientation of my helix and was not able to move it downwards.
After reading the documentation of matplotlib I could find:
The example below illustrates a plotting several lines with different format styles in one command using arrays.
import numpy as np
import matplotlib.pyplot as plt
# evenly sampled time at 200ms intervals
t = np.arange(0., 5., 0.2)
# red dashes, blue squares and green triangles
plt.plot(t, t, 'r--', t, t**2, 'bs', t, t**3, 'g^')
plt.show()
Why cannot I do the same when there are three axes?

Related

Plotting surface and curve in 3D and a curve in xy-plane, all in the same plot

To illustrate an optimization problem, I want all of this in the same 3D plot:
A surface.
A curve in the xy-plane.
A curve/path on the surface which marks out the points on the surface that lies directly above the curve in the xy-plane.
This is my code so far:
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator
import numpy as np
from mpl_toolkits import mplot3d
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
X = np.linspace(-5,5,100)
Y = X
X, Y = np.meshgrid(X, Y)
Z = 50 - X**2 - Y**2
#Plotting curve on the surface
ax = plt.axes(projection='3d')
yline = np.linspace(-5,5,100)
xline = -np.sqrt(4/(2+yline**2)) #the x-values of the curve in the xy-plane
zline = 50 - xline**2 - yline**2
ax.plot3D(xline, yline, zline, "black")
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm)
ax.set_zlim(0, 50)
#Plotting curve in xy-plane
a = 5
g = 1 - 2*X - X*Y**2
plt.contour(X,Y,g, [a], offset=0)
plt.show()
Here is the plot from two different angles:
Some problems:
First of all, it seems like the axes have been numbered twice. Is that because I make a meshgrid, and later on use ax.plot3D? That I use two different ways of plotting something, and as a consequence make the 3D space twice?
The path on the surface appears weakly. Is there a way to make the path more visible?
From the picture in bird perspective, we see that the path does not lie directly above the curve in the xy-plane. What would be easier, was if Python had a built-in function who could project the curve in the xy-plane directly onto the surface. Am I missing something here? Does anyone know of such a function?
These questions might be dummy questions, but answers and tips are highly appreciated!
The code creates two axes objects (both assigned to the ax variable) in the same figure. This is not needed and results in double ticks marks.
To make the path on the surface more visible, plot it with a higher zorder.
The curve on the surface does not overlap with the curve on the xy plane because these are different curves. To plot the projection of the surface curve on the xy plane, set all z-coordinates of the curve to 0.
Below is the code with these changes.
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator
import numpy as np
from mpl_toolkits import mplot3d
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
X = np.linspace(-5, 5, 100)
Y = X
X, Y = np.meshgrid(X, Y)
Z = 50 - X**2 - Y**2
yline = np.linspace(-5, 5, 100)
xline = -np.sqrt(4 / (2 + yline**2))
zline = 50 - xline**2 - yline**2
ax.plot3D(xline, yline, zline, "b", zorder=10)
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm, alpha=0.7)
ax.set_zlim(0, 50)
#Plotting curve in xy-plane
ax.plot3D(xline, yline, 0, "k")
plt.show()

Scatter plot - how to do it

I would like to reproduce this plot in Python: (https://i.stack.imgur.com/6CRfn.png)
Any idea how to do this?
I tried to do a normal plt.scatter() but I can't draw this axes on the zero, for example.
That's a very general question... Using plt.scatter() is certainly a good option. Then just add the two lines to the plot (e.g. using axhline and axvline).
Slightly adapting this example:
import numpy as np
import matplotlib.pyplot as plt
# don't show right and top axis[![enter image description here][1]][1]
import matplotlib as mpl
mpl.rcParams['axes.spines.right'] = False
mpl.rcParams['axes.spines.top'] = False
# some random data
N = 50
x = np.random.randint(-10, high=11, size=N, dtype=int)
y = np.random.randint(-10, high=11, size=N, dtype=int)
colors = np.random.rand(N)
area = (30 * np.random.rand(N))**2 # 0 to 15 point radii
# creating a vertical and a horizontal line
plt.axvline(x=0, color='grey', alpha=0.75, linestyle='-')
plt.axhline(y=0, color='grey', alpha=0.75, linestyle='-')
# scatter plot
plt.scatter(x, y, s=area, c=colors, alpha=0.5)
plt.show()

3D wireframe plot with 2D projections: Spatial organiszation & frequency of projection

I'm working on a 3D plot displayed by a wireframe, where 2D plots are projected on the x, y, and z surface, respectively. Below you can find a minimum example.
I have 2 questions:
With contourf, the 2D plots for every x=10, x=20,... or y=10, y=20,... are displayed on the plot walls. Is there a possibility to define for which x or y, respectively, the contour plots are displayed? For example, in case I only want to have the xz contour plot for y = 0.5 mirrored on the wall?
ADDITION: To display what I mean with "2D plots", I changed "contourf" in the code to "contour" and added the resulting plot to this question. Here you can see now the xz lines for different y values, all offset to y=90. What if I do not want to have all the lines, but only two of them for defined y values?
3D_plot_with_2D_contours
As you can see in the minimum example, the 2D contour plot optically covers the wireframe 3D plot. With increasing the transparency with alpha=0.5 I can increase the transparency of the 2D contours to at least see the wireframe, but it is still optically wrong. Is it possible to sort the objects correctly?
import matplotlib.pyplot as plt,numpy as np
import pylab as pl
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np
plt.clf()
fig = plt.figure(1,figsize=(35,17),dpi=600,facecolor='w',edgecolor='k')
fig.set_size_inches(10.5,8)
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
Xnew = X + 50
Ynew = Y + 50
cset = ax.contourf(Xnew, Ynew, Z, zdir='z', offset=-100, cmap=plt.cm.coolwarm, alpha=0.5)
cset = ax.contourf(Xnew, Ynew, Z, zdir='x', offset=10, cmap=plt.cm.coolwarm, alpha=0.5)
cset = ax.contourf(Xnew, Ynew, Z, zdir='y', offset=90, cmap=plt.cm.coolwarm, alpha = 0.5)
ax.plot_wireframe(Xnew, Ynew, Z, rstride=5, cstride=5, color='black')
Z=Z-Z.min()
Z=Z/Z.max()
from scipy.ndimage.interpolation import zoom
Xall=zoom(Xnew,5)
Yall=zoom(Ynew,5)
Z=zoom(Z,5)
ax.set_xlim(10, 90)
ax.set_ylim(10, 90)
ax.set_zlim(-100, 100)
ax.tick_params(axis='z', which='major', pad=10)
ax.set_xlabel('X',labelpad=10)
ax.set_ylabel('Y',labelpad=10)
ax.set_zlabel('Z',labelpad=17)
ax.view_init(elev=35., azim=-70)
fig.tight_layout()
plt.show()
ADDITION 2: Here is the actual code I'm working with. However, the original data are hidden in the csv files which are too big to be included in the minimal example. That's why was initially replacing them by the test data. However, maybe the actual code helps nevertheless.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np
import pylab as pl
from matplotlib.markers import MarkerStyle
import csv
with open("X.csv", 'r') as f:
X = list(csv.reader(f, delimiter=";"))
import numpy as np
X = np.array(X[1:], dtype=np.float)
import csv
with open("Z.csv", 'r') as f:
Z = list(csv.reader(f, delimiter=";"))
import numpy as np
Z = np.array(Z[1:], dtype=np.float)
Y = [[7,7.1,7.2,7.3,7.4,7.5,7.6,7.7,7.8,7.9,8,8.1,8.2,8.3,8.4,8.5,8.6,8.7,8.8,8.9,9]]
Xall = np.repeat(X[:],21,axis=1)
Yall = np.repeat(Y[:],30,axis=0)
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np
plt.clf()
fig = plt.figure(1,figsize=(35,17),dpi=600,facecolor='w',edgecolor='k')
fig.set_size_inches(10.5,8)
ax = fig.gca(projection='3d')
cset = ax.contourf(Xall, Yall, Z, 2, zdir='x', offset=0, cmap=plt.cm.coolwarm, shade = False, edgecolor='none', alpha=0.5)
cset = ax.contourf(Xall, Yall, Z, 2, zdir='y', offset=9, cmap=plt.cm.coolwarm, shade = False, edgecolor='none', alpha=0.5)
ax.plot_wireframe(Xall, Yall, Z, rstride=1, cstride=1, color='black')
Z=Z-Z.min()
Z=Z/Z.max()
from scipy.ndimage.interpolation import zoom
Xall=zoom(Xall,5)
Yall=zoom(Yall,5)
Z=zoom(Z,5)
cset = ax.plot_surface(Xall, Yall, np.zeros_like(Z)-0,facecolors=plt.cm.coolwarm(Z),shade=False,alpha=0.5,linewidth=False)
ax.set_xlim(-0.5, 31)
ax.set_ylim(6.9, 9.1)
ax.set_zlim(0, 500)
labelsx = [item.get_text() for item in ax.get_xticklabels()]
empty_string_labelsx = ['']*len(labelsx)
ax.set_xticklabels(empty_string_labelsx)
labelsy = [item.get_text() for item in ax.get_yticklabels()]
empty_string_labelsy = ['']*len(labelsy)
ax.set_yticklabels(empty_string_labelsy)
labelsz = [item.get_text() for item in ax.get_zticklabels()]
empty_string_labelsz = ['']*len(labelsz)
ax.set_zticklabels(empty_string_labelsz)
import matplotlib.ticker as ticker
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5))
ax.yaxis.set_minor_locator(ticker.MultipleLocator(0.25))
ax.zaxis.set_major_locator(ticker.MultipleLocator(100))
ax.zaxis.set_minor_locator(ticker.MultipleLocator(50))
ax.tick_params(axis='z', which='major', pad=10)
ax.set_xlabel('X',labelpad=5,fontsize=15)
ax.set_ylabel('Y',labelpad=5,fontsize=15)
ax.set_zlabel('Z',labelpad=5,fontsize=15)
ax.view_init(elev=35., azim=-70)
fig.tight_layout()
plt.show()
Alternate possible answer.
This code demonstrates
A plot of a surface and its correponding wireframe
The creation of data and its plot of 3d lines (draped on the surface in 1) at specified values of x and y
Projections of the 3d lines (in 2) on to the frame walls
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from scipy import interpolate
import numpy as np
# use the test data for plotting
fig = plt.figure(1, figsize=(6,6), facecolor='w', edgecolor='gray')
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.1) #get 3d data at appropriate density
# create an interpolating function
# can take a long time if data is too large
f1 = interpolate.interp2d(X, Y, Z, kind='linear')
# in general, one can use a set of other X,Y,Z that cover a surface
# preferably, (X,Y) are in grid arrangement
# make up a new set of 3d data to plot
# ranges of x1, and y1 will be inside (X,Y) of the data obtained above
# related grid, x1g,y1g,z1g will be obtained from meshgrid and the interpolated function
x1 = np.linspace(-15,15,10)
y1 = np.linspace(-15,15,10)
x1g, y1g = np.meshgrid(x1, y1)
z1g = f1(x1, y1) #dont use (x1g, y1g)
# prep data for 3d line on the surface (X,Y,Z) at x=7.5
n = 12
x_pf = 7.5
x5 = x_pf*np.ones(n)
y5 = np.linspace(-15, 15, n)
z5 = f1(x_pf, y5)
# x5,y5,z5 can be used to plot 3d line on the surface (X,Y,Z)
# prep data for 3d line on the surface (X,Y,Z) at y=6
y_pf = 6
x6 = np.linspace(-15, 15, n)
y6 = x_pf*np.ones(n)
z6 = f1(x6, y_pf)
# x6,y6,z6 can be used to plot 3d line on the surface (X,Y,Z)
ax = fig.gca(projection='3d')
ax.plot_surface(x1g, y1g, z1g, alpha=0.25)
ax.plot_wireframe(x1g, y1g, z1g, rstride=2, cstride=2, color='black', zorder=10, alpha=1, lw=0.8)
# 3D lines that follow the surface
ax.plot(x5,y5,z5.flatten(), color='red', lw=4)
ax.plot(x6,y6,z6.flatten(), color='green', lw=4)
# projections of 3d curves
# project red and green lines to the walls
ax.plot(-15*np.ones(len(y5)), y5, z5.flatten(), color='red', lw=4, linestyle=':', alpha=0.6)
ax.plot(x6, 15*np.ones(len(x6)), z6.flatten(), color='green', lw=4, linestyle=':', alpha=0.6)
# projections on other sides (become vertical lines)
# change to if True, to plot these
if False:
ax.plot(x5, 15*np.ones(len(x5)), z5.flatten(), color='red', lw=4, alpha=0.3)
ax.plot(-15*np.ones(len(x6)), y6, z6.flatten(), color='green', lw=4, alpha=0.3)
ax.set_title("Projections of 3D lines")
# set limits
ax.set_xlim(-15, 15.5)
ax.set_ylim(-15.5, 15)
plt.show();
(Answer to question 1) To plot the intersections between the surface and the specified planes (y=-20, and y=20), one need to find what Y[?]=-20 and 20. By inspection, I found that Y[100]=20, Y[20]=-20.
The relevant code to plot the lines of intersection:
# By inspection, Y[100]=20, Y[20]=-20
ax.plot3D(X[100], Y[100], Z[100], color='red', lw=6) # line-1 at y=20
ax.plot3D(X[20], Y[20], Z[20], color='green', lw=6) # line-2 at y=-20
# Project them on Z=-100 plane
ax.plot3D(X[100], Y[100], -100, color='red', lw=3) # projection of Line-1
ax.plot3D(X[20], Y[20], -100, color='green', lw=3) # projection of Line-2
The output plot:
(Answer to question 2) To get better plot with the wireframe standout from the surface plot. The surface plot must be partially transparent, which is achieved by setting option alpha=0.6. The relevant code follows.
Z1 = Z-Z.min()
Z1 = Z1/Z.max()
Xall = zoom(X,3)
Yall = zoom(Y,3)
Zz = zoom(Z1, 3)
surf = ax.plot_surface(Xall, Yall, Zz, rstride=10, cstride=10,
facecolors = cm.jet(Zz/np.amax(Zz)),
linewidth=0, antialiased=True,
alpha= 0.6)
# Wireframe
ax.plot_wireframe(X, Y, Z, rstride=5, cstride=5, color='black', alpha=1, lw=0.8)
The plot is:

Plot 2D image in 3D axes

Using matplotlib is it possible to take a 2D image of something and place it in a 3D figure? I'd like to take a 2D image and place it at z position of 0. I want to then move the other pixels in the image along the z-axis separately based on a calculation I am making.
Look for example: https://matplotlib.org/gallery/mplot3d/2dcollections3d.html
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
# Plot a sin curve using the x and y axes.
x = np.linspace(0, 1, 100)
y = np.sin(x * 2 * np.pi) / 2 + 0.5
ax.plot(x, y, zs=0, zdir='z', label='curve in (x,y)')
# Plot scatterplot data (20 2D points per colour) on the x and z axes.
colors = ('r', 'g', 'b', 'k')
# Fixing random state for reproducibility
np.random.seed(19680801)
x = np.random.sample(20 * len(colors))
y = np.random.sample(20 * len(colors))
c_list = []
for c in colors:
c_list.extend([c] * 20)
# By using zdir='y', the y value of these points is fixed to the zs value 0
# and the (x,y) points are plotted on the x and z axes.
ax.scatter(x, y, zs=0, zdir='y', c=c_list, label='points in (x,z)')
# Make legend, set axes limits and labels
ax.legend()
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.set_zlim(0, 1)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
# Customize the view angle so it's easier to see that the scatter points lie
# on the plane y=0
ax.view_init(elev=20., azim=-35)
plt.show()
If your image is a coloured image you must first ensure that it is an indexed image. This means that you can only have 2d matrix (and not 3 matricies for the RGB components). Command rgb2ind can help.
Then you can directly show you image in a 3D way. Use the mesh or surf command.
You can also adjust perspective with angle and azimuth.

Plotting two different sized grids. Smaller grid centred on the centre of the bigger

I am trying to get something like this (image the image is fully populated with the red squares (I only drew a few)): . Expanding on what I want: I want the RED squares to be centred int the YELLOW squares as shown in the picture (but with RED squares in ALL the YELLOW squares).
What is happening there is the bigger windows (yellow grid) are overlapped between each other by half their size, where the smaller windows in this case, half the size of the big window, (red square) are centred on the centre of the big window. The furthest I could get was using this Multiple grids on matplotlib I am basically using their code, but to make things absolutely clear, I include the code:
EDIT: Thanks to Rutgers I got what I wanted. Here is a slightly edited and shortened version. This code gives the first centre of the four yellow grid intersection where I want.
import matplotlib.pyplot as plt
from matplotlib.pyplot import subplot
from scipy.misc import imread
import numpy as np
import matplotlib.cm as cmps
import matplotlib.collections as collections
i = 1
initial_frame = 1
ax = subplot(111)
bg = imread("./png/frame_" + str("%05d" % (i + initial_frame) ) + ".png").astype(np.float64)
# define the normal (yellow) grid
ytcks = np.arange(16,bg.shape[0],32)
xtcks = np.arange(16,bg.shape[1],32)
# plot the sample data
ax.imshow(bg, cmap=plt.cm.Greys_r, interpolation='none')
ax.set_xticks(xtcks)
ax.set_xticks(xtcks+16, minor=True)
ax.set_yticks(ytcks)
ax.set_yticks(ytcks+16, minor=True)
ax.xaxis.grid(True,'minor', linestyle='--', lw=1., color='y')
ax.yaxis.grid(True,'minor', linestyle='--', lw=1., color='y')
ax.xaxis.grid(True,'major', linestyle='--', lw=0.5, color='g')
ax.yaxis.grid(True,'major', linestyle='--', lw=0.5, color='g')
plt.show()
Given the sample data z from my other answer:
# define the normal (yellow) grid
tcks = np.arange(0,90,10)
fig, ax = plt.subplots(figsize=(8,8))
# plot the sample data
ax.imshow(z, cmap=plt.cm.Greys_r, interpolation='none', vmin=0.4, vmax=1.5, extent=[0,z.shape[0],0,z.shape[1]])
ax.set_xticks(tcks)
ax.set_xticks(tcks+5, minor=True)
ax.set_yticks(tcks)
ax.set_yticks(tcks+5, minor=True)
ax.xaxis.grid(True,'minor', linestyle='--', lw=1., color='y')
ax.yaxis.grid(True,'minor', linestyle='--', lw=1., color='y')
ax.xaxis.grid(True,'major', linestyle='-', lw=1., color='r')
ax.yaxis.grid(True,'major', linestyle='-', lw=1., color='r')
ax.set_xlim(0,80)
ax.set_ylim(0,80)
I expect this to be much faster than drawing with polygons.
Mentioning bigger and smaller grid is a little bit confusing, since to me they seem of equal size, but i assume you mean the 'major' and 'minor' grid.
Well, to sort of mimic your picture with what i had in mind, see if this makes any sense:
import matplotlib.collections as collections
import numpy as np
import matplotlib.pyplot as plt
# generate some fake 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), slice(1, 5, dx)]
z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
# define the normal (yellow) grid
tcks = np.arange(0,90,10)
fig, ax = plt.subplots(figsize=(8,8), subplot_kw={'xticks': tcks, 'yticks': tcks})
# plot the sample data
ax.imshow(z, cmap=plt.cm.Greys_r, interpolation='none', vmin=0.4, vmax=1.5, extent=[0,z.shape[0],0,z.shape[1]])
# plot the yellow grid
ax.grid(True, linestyle='--', color='y', lw=1.5, alpha=1.0)
# define some random 'red' grid cells
custom_grid = []
for i in range(10):
x = np.random.randint(0,7) * 10 + 5
y = np.random.randint(0,7) * 10 + 5
polygon = plt.Rectangle((x, y), 10, 10)
custom_grid.append(polygon)
p = collections.PatchCollection(custom_grid, facecolor='none', edgecolor='r', lw=1.5)
ax.add_collection(p)
Its stil a bit unclear for example when you want to show the 'red' grid cells and when not.

Categories