Ellipsoid creation in Python - python

I have ran into a problem relating to the drawing of the Ellipsoid.
The ellipsoid that I am drawing to draw is the following:
x**2/16 + y**2/16 + z**2/16 = 1.
So I saw a lot of references relating to calculating and plotting of an Ellipse void and in multiple questions a cartesian to spherical or vice versa calculation was mentioned.
Ran into a website that had a calculator for it, but I had no idea on how to successfully perform this calculation. Also I am not sure as to what the linspaces should be set to. Have seen the ones that I have there as defaults, but as I got no previous experience with these libraries, I really don't know what to expect from it.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=plt.figaspect(1)) # Square figure
ax = fig.add_subplot(111, projection='3d')
multip = (1, 1, 1)
# Radii corresponding to the coefficients:
rx, ry, rz = 1/np.sqrt(multip)
# Spherical Angles
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
# Cartesian coordinates
#Lots of uncertainty.
#x =
#y =
#z =
# Plot:
ax.plot_surface(x, y, z, rstride=4, cstride=4, color='b')
# Axis modifications
max_radius = max(rx, ry, rz)
for axis in 'xyz':
getattr(ax, 'set_{}lim'.format(axis))((-max_radius, max_radius))
plt.show()

Your ellipsoid is not just an ellipsoid, it's a sphere.
Notice that if you use the substitution formulas written below for x, y and z, you'll get an identity. It is in general easier to plot such a surface of revolution in a different coordinate system (spherical in this case), rather than attempting to solve an implicit equation (which in most plotting programs ends up jagged, unless you take some countermeasures).
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
phi = np.linspace(0,2*np.pi, 256).reshape(256, 1) # the angle of the projection in the xy-plane
theta = np.linspace(0, np.pi, 256).reshape(-1, 256) # the angle from the polar axis, ie the polar angle
radius = 4
# Transformation formulae for a spherical coordinate system.
x = radius*np.sin(theta)*np.cos(phi)
y = radius*np.sin(theta)*np.sin(phi)
z = radius*np.cos(theta)
fig = plt.figure(figsize=plt.figaspect(1)) # Square figure
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z, color='b')

Related

Make a Rotating Sphere in Python

I have made this code that applies the spherical harmonics in a spherical manner as I am trying to model stellar pulsation modes. Ideally, I'd like to be able to have an image that rotates that can be saved as a gif image. I have found a few examples of code for doing this but none of it seems to apply to my code or uses python packages that aren't available to me. I'm not sure if this is too far out of my range of skills in python as I'm very much a beginner.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
from scipy.special import sph_harm #import package to calculate spherical harmonics
theta = np.linspace(0, 2*np.pi, 100) #setting range for theta
phi = np.linspace(0, np.pi, 100) #setting range for phi
phi, theta = np.meshgrid(phi, theta) #setting the grid for phi and theta
#Setting the cartesian coordinates of the unit sphere
#Converting phi, theta, z to cartesian coordinates
x = np.sin(phi)*np.cos(theta)
y = np.sin(phi)*np.sin(theta)
z = np.cos(phi)
m, l = 4, 4 #m and l control the mode of pulsation and overall appearance of the figure
#Calculating the spherical harmonic Y(l,m) and normalizing it
figcolors = sph_harm(m, l, theta, phi).real
figmax, figmin = figcolors.max(), figcolors.min()
figcolors = (figcolors-figmin)/(figmax-figmin)
#Setting the aspect ratio to 1 which makes the sphere look spherical and not elongated
fig = plt.figure(figsize=plt.figaspect(1.)) #aspect ratio
axes = fig.add_subplot(111, projection='3d') #sets figure to 3d
#Sets the plot surface and colors of the figure where seismic is the color scheme
axes.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=cm.autumn(figcolors))
#yellow zones are cooler and compressed, red zones are warmer and expanded
#Turn off the axis planes so only the sphere is visible
axes.set_axis_off()
fig.suptitle('m=4 l=4', fontsize=18, x=0.52, y=.85)
plt.savefig('m4_l4.png') #saves a .png file of my figure
plt.show() #Plots the figure
#figure saved for m=1, 2, 3, 4 and l=2, 3, 5, 6 respectively then all 6 were put together to form a single figure
I've also got an image showing what my code outputs currently. It's just a still sphere, of course. Thank you in advance! sphere4_4
Change the last part of your code to generate a set of figures (see below). In this case I create num = 10 frames, you can change this number if you want. Then open a terminal and type
convert m4_l4*.png m4_l4.gif
And this is the result
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
from scipy.special import sph_harm #import package to calculate spherical harmonics
theta = np.linspace(0, 2*np.pi, 100) #setting range for theta
phi = np.linspace(0, np.pi, 100) #setting range for phi
phi, theta = np.meshgrid(phi, theta) #setting the grid for phi and theta
#Setting the cartesian coordinates of the unit sphere
#Converting phi, theta, z to cartesian coordinates
x = np.sin(phi)*np.cos(theta)
y = np.sin(phi)*np.sin(theta)
z = np.cos(phi)
m, l = 4, 4 #m and l control the mode of pulsation and overall appearance of the figure
#Calculating the spherical harmonic Y(l,m) and normalizing it
figcolors = sph_harm(m, l, theta, phi).real
figmax, figmin = figcolors.max(), figcolors.min()
figcolors = (figcolors-figmin)/(figmax-figmin)
#Setting the aspect ratio to 1 which makes the sphere look spherical and not elongated
fig = plt.figure(figsize=plt.figaspect(1.)) #aspect ratio
axes = fig.add_subplot(111, projection='3d') #sets figure to 3d
#Sets the plot surface and colors of the figure where seismic is the color scheme
axes.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=cm.autumn(figcolors))
#yellow zones are cooler and compressed, red zones are warmer and expanded
axes.set_axis_off()
fig.suptitle('m=4 l=4', fontsize=18, x=0.52, y=.85)
for idx, angle in enumerate(np.linspace(0, 360, 10)):
axes.view_init(30, angle)
plt.draw()
#Turn off the axis planes so only the sphere is visible
plt.savefig('m4_l4-%04d.png' % idx) #saves a .png file of my figure
plt.show()

How to change the length of axes for 3D plots in matplotlib [duplicate]

I have this so far:
x,y,z = data.nonzero()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Which creates:
What I'd like to do is stretch this out to make the Z axis 9 times taller and keep X and Y the same. I'd like to keep the same coordinates though.
So far I tried this guy:
fig = plt.figure(figsize=(4.,35.))
But that just stretches out the plot.png image.
The code example below provides a way to scale each axis relative to the others. However, to do so you need to modify the Axes3D.get_proj function. Below is an example based on the example provided by matplot lib: http://matplotlib.org/1.4.0/mpl_toolkits/mplot3d/tutorial.html#line-plots
(There is a shorter version at the end of this answer)
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
#Make sure these are floating point values:
scale_x = 1.0
scale_y = 2.0
scale_z = 3.0
#Axes are scaled down to fit in scene
max_scale=max(scale_x, scale_y, scale_z)
scale_x=scale_x/max_scale
scale_y=scale_y/max_scale
scale_z=scale_z/max_scale
#Create scaling matrix
scale = np.array([[scale_x,0,0,0],
[0,scale_y,0,0],
[0,0,scale_z,0],
[0,0,0,1]])
print scale
def get_proj_scale(self):
"""
Create the projection matrix from the current viewing position.
elev stores the elevation angle in the z plane
azim stores the azimuth angle in the x,y plane
dist is the distance of the eye viewing point from the object
point.
"""
relev, razim = np.pi * self.elev/180, np.pi * self.azim/180
xmin, xmax = self.get_xlim3d()
ymin, ymax = self.get_ylim3d()
zmin, zmax = self.get_zlim3d()
# transform to uniform world coordinates 0-1.0,0-1.0,0-1.0
worldM = proj3d.world_transformation(
xmin, xmax,
ymin, ymax,
zmin, zmax)
# look into the middle of the new coordinates
R = np.array([0.5, 0.5, 0.5])
xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
zp = R[2] + np.sin(relev) * self.dist
E = np.array((xp, yp, zp))
self.eye = E
self.vvec = R - E
self.vvec = self.vvec / proj3d.mod(self.vvec)
if abs(relev) > np.pi/2:
# upside down
V = np.array((0, 0, -1))
else:
V = np.array((0, 0, 1))
zfront, zback = -self.dist, self.dist
viewM = proj3d.view_transformation(E, R, V)
perspM = proj3d.persp_transformation(zfront, zback)
M0 = np.dot(viewM, worldM)
M = np.dot(perspM, M0)
return np.dot(M, scale);
Axes3D.get_proj=get_proj_scale
"""
You need to include all the code above.
From here on you should be able to plot as usual.
"""
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve')
ax.legend()
plt.show()
Standard output:
Scaled by (1, 2, 3):
Scaled by (1, 1, 3):
The reason I particularly like this method,
Swap z and x, scale by (3, 1, 1):
Below is a shorter version of the code.
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
"""
Scaling is done from here...
"""
x_scale=1
y_scale=1
z_scale=2
scale=np.diag([x_scale, y_scale, z_scale, 1.0])
scale=scale*(1.0/scale.max())
scale[3,3]=1.0
def short_proj():
return np.dot(Axes3D.get_proj(ax), scale)
ax.get_proj=short_proj
"""
to here
"""
ax.plot(z, y, x, label='parametric curve')
ax.legend()
plt.show()
Please note that the answer below simplifies the patch, but uses the same underlying principle as the answer by #ChristianSarofeen.
Solution
As already indicated in other answers, it is not a feature that is currently implemented in matplotlib. However, since what you are requesting is simply a 3D transformation that can be applied to the existing projection matrix used by matplotlib, and thanks to the wonderful features of Python, this problem can be solved with a simple oneliner:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([scale_x, scale_y, scale_z, 1]))
where scale_x, scale_y and scale_z are values from 0 to 1 that will re-scale your plot along each of the axes accordingly. ax is simply the 3D axes which can be obtained with ax = fig.gca(projection='3d')
Explanation
To explain, the function get_proj of Axes3D generates the projection matrix from the current viewing position. Multiplying it by a scaling matrix:
scale_x, 0, 0
0, scale_y, 0
0, 0, scale_z
0, 0, 1
includes the scaling into the projection used by the renderer. So, what we are doing here is substituting the original get_proj function with an expression taking the result of the original get_proj and multiplying it by the scaling matrix.
Example
To illustrate the result with the standard parametric function example:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z ** 2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
# OUR ONE LINER ADDED HERE:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([0.5, 0.5, 1, 1]))
ax.plot(x, y, z)
plt.show()
for values 0.5, 0.5, 1, we get:
while for values 0.2, 1.0, 0.2, we get:
In my case I wanted to stretch z-axis 2 times for better point visibility
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# plt.rcParams["figure.figsize"] = (10,200)
# plt.rcParams["figure.autolayout"] = True
ax = plt.axes(projection='3d')
ax.set_box_aspect(aspect = (1,1,2))
ax.plot(dataX,dataY,dataZ)
I looks like by default, mplot3d will leave quite a bit of room at the top and bottom of a very tall plot. But, you can trick it into filling that space using fig.subplots_adjust, and extending the top and bottom out of the normal plotting area (i.e. top > 1 and bottom < 0). Some trial and error here is probably needed for your particular plot.
I've created some random arrays for x, y, and z with limits similar to your plot, and have found the parameters below (bottom=-0.15, top = 1.2) seem to work ok.
You might also want to change ax.view_init to set a nice viewing angle.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from numpy import random
# Make some random data with similar limits to the OP's example
x,y,z=random.rand(3,100)
z*=250
y*=800
y+=900
x*=350
x+=1200
fig=plt.figure(figsize=(4,35))
# Set the bottom and top outside the actual figure limits,
# to stretch the 3D axis
fig.subplots_adjust(bottom=-0.15,top=1.2)
ax = fig.add_subplot(111, projection='3d')
# Change the viewing angle to an agreeable one
ax.view_init(2,None)
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Sounds like you're trying to adjust the scale of the plot. I don't think there's a way to stretch a linear scale to user specifications, but you can use set_yscale(), set_xscale(), set_zscale() to alter the scales with respect to each other.
Intuitively, set_yscale(log), set_xscale(log), set_zscale(linear) might solve your problems.
A likely better option: specify a stretch, set them all to symlog with the same log base and then specify the Z-axis's symlog scale with the linscalex/linscaley kwargs to your specifications.
More here:
http://matplotlib.org/mpl_toolkits/mplot3d/api.html
I found this while searching on a similar problem. After experimenting a bit, perhaps I can share some of my prelim findings here..matplotlib library is VAST!! (am a newcomer). Note that quite akin to this question, all i wanted was to 'visually' stretch the chart without distorting it.
Background story (only key code snippets are shown to avoid unnecessary clutter for those who know the library, and if you want a run-able code please drop a comment):
I have three 1-d ndarrays representing the X,Y and Z data points respectively. Clearly I can't use plot_surface (as it requires 2d ndarrays for each dim) so I went for the extremely useful plot_trisurf:
fig = plt.figure()
ax = Axes3D(fig)
3d_surf_obj = ax.plot_trisurf(X, Y, Z_defl, cmap=cm.jet,linewidth=0,antialiased=True)
You can think of the plot like a floating barge deforming in waves...As you can see, the axes stretch make it pretty deceiving visually (note that x is supposed to be at x6 times longer than y and >>>>> z). While the plot points are correct, I wanted something more visually 'stretched' at the very least. Was looking for A QUICK FIX, if I may. Long story cut short, I found a bit of success with...'figure.figsize' general setting (see snippet below).
matplotlib.rcParams.update({'font.serif': 'Times New Roman',
'font.size': 10.0,
'axes.labelsize': 'Medium',
'axes.labelweight': 'normal',
'axes.linewidth': 0.8,
###########################################
# THIS IS THE IMPORTANT ONE FOR STRETCHING
# default is [6,4] but...i changed it to
'figure.figsize':[15,5] # THIS ONE #
})
For [15,5] I got something like...
Pretty neat!!
So I started to push it.... and got up to [20,6] before deciding to settle there..
If you want to try for visually stretching the vertical axis, try with ratios like... [7,10], which in this case gives me ...
Not too shabby !
Should do it for visual prowess.
Multiply all your z values by 9,
ax.scatter(x, y, 9*z, zdir='z', c= 'red')
And then give the z-axis custom plot labels and spacing.
ax.ZTick = [0,-9*50, -9*100, -9*150, -9*200];
ax.ZTickLabel = {'0','-50','-100','-150','-200'};

how to rotate a 3D surface in matplotlib

I have written code to plot a 3D surface of a parabaloid in matplotlib.
How would I rotate the figure so that the figure remains in place (i.e. no vertical or horizontal shifts) however it rotates around the line y = 0 and z = 0 through an angle of theta ( I have highlighted the line about which the figure should rotate in green). Here is an illustration to help visualize what I am describing:
For example, If the figure were rotated about the line through an angle of 180 degrees then this would result in the figure being flipped 'upside down' so that the point at the origin would be now be the maximum point.
I would also like to rotate the axis so that the colormap is maintained.
Here is the code for drawing the figure:
#parabaloid
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#creating grid
y = np.linspace(-1,1,1000)
x = np.linspace(-1,1,1000)
x,y = np.meshgrid(x,y)
#set z values
z = x**2+y**2
#label axes
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
#plot figure
ax.plot_surface(x,y,z,linewidth=0, antialiased=False, shade = True, alpha = 0.5)
plt.show()
Something like this?
ax.view_init(-140, 30)
Insert it just before your plt.show() command.
Following my comment:
import mayavi.mlab as mlab
import numpy as np
x,y = np.mgrid[-1:1:0.001, -1:1:0.001]
z = x**2+y**2
s = mlab.mesh(x, y, z)
alpha = 30 # degrees
mlab.view(azimuth=0, elevation=90, roll=-90+alpha)
mlab.show()
or following #Tamas answer:
#parabaloid
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from math import sin, cos, pi
import matplotlib.cm as cm
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#creating grid
y = np.linspace(-1,1,200)
x = np.linspace(-1,1,200)
x,y = np.meshgrid(x,y)
#set z values
z0 = x**2+y**2
# rotate the samples by pi / 4 radians around y
a = pi / 4
t = np.transpose(np.array([x,y,z0]), (1,2,0))
m = [[cos(a), 0, sin(a)],[0,1,0],[-sin(a), 0, cos(a)]]
x,y,z = np.transpose(np.dot(t, m), (2,0,1))
# or `np.dot(t, m)` instead `t # m`
#label axes
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
#plot figure
ax.plot_surface(x,y,z,linewidth=0, antialiased=False, shade = True, alpha = 0.5, facecolors=cm.viridis(z0))
plt.show()
The best I could come up with is to rotate the data itself.
#parabaloid
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from math import sin, cos, pi
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#creating grid
y = np.linspace(-1,1,200)
x = np.linspace(-1,1,200)
x,y = np.meshgrid(x,y)
#set z values
z = x**2+y**2
# rotate the samples by pi / 4 radians around y
a = pi / 4
t = np.transpose(np.array([x,y,z]), (1,2,0))
m = [[cos(a), 0, sin(a)],[0,1,0],[-sin(a), 0, cos(a)]]
x,y,z = np.transpose(t # m, (2,0,1))
# or `np.dot(t, m)` instead `t # m`
#label axes
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
#plot figure
ax.plot_surface(x,y,z,linewidth=0, antialiased=False, shade = True, alpha = 0.5)
plt.show()
I can't seem to add a comment just yet but I wanted to make an amendment to Tamas' implementation. There is an issue where the surface is not rotated counter-clockwise to the axis (the y-axis in this case) where the y-axis is coming out of the page. Rather, it's rotated clockwise.
In order to rectify this, and to make it more straightforward, I construct the x, y and z grids and reshape them into straightforward lists on which we perform the rotation. Then I reshape them into grids in order to use the plot_surface() function:
import numpy as np
from matplotlib import pyplot as plt
from math import sin, cos, pi
import matplotlib.cm as cm
num_steps = 50
# Creating grid
y = np.linspace(-1,1,num_steps)
x = np.linspace(-1,1,num_steps)
x,y = np.meshgrid(x,y)
# Set z values
z = x**2+y**2
# Work with lists
x = x.reshape((-1))
y = y.reshape((-1))
z = z.reshape((-1))
# Rotate the samples by pi / 4 radians around y
a = pi / 4
t = np.array([x, y, z])
m = [[cos(a), 0, sin(a)],[0,1,0],[-sin(a), 0, cos(a)]]
x, y, z = np.dot(m, t)
ax = plt.axes(projection='3d')
# Label axes
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
# Plot the surface view it with y-axis coming out of the page.
ax.view_init(30, 90)
# Plot the surface.
ax.plot_surface(x.reshape(num_steps,num_steps), y.reshape(num_steps,num_steps), z.reshape(num_steps,num_steps));
here is the best solution:
- First, you have to perform your python script in the Spyder environment which is easy to get by downloading Anaconda. Once you perform your script in Spyder, all you have to do is to follow the next instructions:
Click on “Tools”.
Click on “Preferences”.
Click on “IPython console”.
Click on “Graphics”.
Here you’ll find an option called “Backend”, you have to change it from “Inline” to “Automaticlly”.
Finally, apply the performed changes, then Click on “OK”, and reset spyder!!!!.
Once you perform the prior steps, in theory, if you run your script, then the graphics created will appear in a different windows and you could interact with them through zooming and panning. In the case of 3d plots (3d surface) you will be able to orbit it.

Python: How to revolve a surface around z axis and make a 3d plot?

I want to get 2d and 3d plots as shown below.
The equation of the curve is given.
How can we do so in python?
I know there may be duplicates but at the time of posting
I could not fine any useful posts.
My initial attempt is like this:
# Imports
import numpy as np
import matplotlib.pyplot as plt
# to plot the surface rho = b*cosh(z/b) with rho^2 = r^2 + b^2
z = np.arange(-3, 3, 0.01)
rho = np.cosh(z) # take constant b = 1
plt.plot(rho,z)
plt.show()
Some related links are following:
Rotate around z-axis only in plotly
The 3d-plot should look like this:
Ok so I think you are really asking to revolve a 2d curve around an axis to create a surface. I come from a CAD background so that is how i explain things.
and I am not the greatest at math so forgive any clunky terminology. Unfortunately you have to do the rest of the math to get all the points for the mesh.
Heres your code:
#import for 3d
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
change arange to linspace which captures the endpoint otherwise arange will be missing the 3.0 at the end of the array:
z = np.linspace(-3, 3, 600)
rho = np.cosh(z) # take constant b = 1
since rho is your radius at every z height we need to calculate x,y points around that radius. and before that we have to figure out at what positions on that radius to get x,y co-ordinates:
#steps around circle from 0 to 2*pi(360degrees)
#reshape at the end is to be able to use np.dot properly
revolve_steps = np.linspace(0, np.pi*2, 600).reshape(1,600)
the Trig way of getting points around a circle is:
x = r*cos(theta)
y = r*sin(theta)
for you r is your rho, and theta is revolve_steps
by using np.dot to do matrix multiplication you get a 2d array back where the rows of x's and y's will correspond to the z's
theta = revolve_steps
#convert rho to a column vector
rho_column = rho.reshape(600,1)
x = rho_column.dot(np.cos(theta))
y = rho_column.dot(np.sin(theta))
# expand z into a 2d array that matches dimensions of x and y arrays..
# i used np.meshgrid
zs, rs = np.meshgrid(z, rho)
#plotting
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
fig.tight_layout(pad = 0.0)
#transpose zs or you get a helix not a revolve.
# you could add rstride = int or cstride = int kwargs to control the mesh density
ax.plot_surface(x, y, zs.T, color = 'white', shade = False)
#view orientation
ax.elev = 30 #30 degrees for a typical isometric view
ax.azim = 30
#turn off the axes to closely mimic picture in original question
ax.set_axis_off()
plt.show()
#ps 600x600x600 pts takes a bit of time to render
I am not sure if it's been fixed in latest version of matplotlib but the setting the aspect ratio of 3d plots with:
ax.set_aspect('equal')
has not worked very well. you can find solutions at this stack overflow question
Only rotate the axis, in this case x
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
np.seterr(divide='ignore', invalid='ignore')
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-3, 3, 60)
rho = np.cosh(x)
v = np.linspace(0, 2*np.pi, 60)
X, V = np.meshgrid(x, v)
Y = np.cosh(X) * np.cos(V)
Z = np.cosh(X) * np.sin(V)
ax.set_xlabel('eje X')
ax.set_ylabel('eje Y')
ax.set_zlabel('eje Z')
ax.plot_surface(X, Y, Z, cmap='YlGnBu_r')
plt.plot(x, rho, 'or') #Muestra la curva que se va a rotar
plt.show()
The result:

matplotlib (mplot3d) - how to increase the size of an axis (stretch) in a 3D Plot?

I have this so far:
x,y,z = data.nonzero()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Which creates:
What I'd like to do is stretch this out to make the Z axis 9 times taller and keep X and Y the same. I'd like to keep the same coordinates though.
So far I tried this guy:
fig = plt.figure(figsize=(4.,35.))
But that just stretches out the plot.png image.
The code example below provides a way to scale each axis relative to the others. However, to do so you need to modify the Axes3D.get_proj function. Below is an example based on the example provided by matplot lib: http://matplotlib.org/1.4.0/mpl_toolkits/mplot3d/tutorial.html#line-plots
(There is a shorter version at the end of this answer)
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
#Make sure these are floating point values:
scale_x = 1.0
scale_y = 2.0
scale_z = 3.0
#Axes are scaled down to fit in scene
max_scale=max(scale_x, scale_y, scale_z)
scale_x=scale_x/max_scale
scale_y=scale_y/max_scale
scale_z=scale_z/max_scale
#Create scaling matrix
scale = np.array([[scale_x,0,0,0],
[0,scale_y,0,0],
[0,0,scale_z,0],
[0,0,0,1]])
print scale
def get_proj_scale(self):
"""
Create the projection matrix from the current viewing position.
elev stores the elevation angle in the z plane
azim stores the azimuth angle in the x,y plane
dist is the distance of the eye viewing point from the object
point.
"""
relev, razim = np.pi * self.elev/180, np.pi * self.azim/180
xmin, xmax = self.get_xlim3d()
ymin, ymax = self.get_ylim3d()
zmin, zmax = self.get_zlim3d()
# transform to uniform world coordinates 0-1.0,0-1.0,0-1.0
worldM = proj3d.world_transformation(
xmin, xmax,
ymin, ymax,
zmin, zmax)
# look into the middle of the new coordinates
R = np.array([0.5, 0.5, 0.5])
xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
zp = R[2] + np.sin(relev) * self.dist
E = np.array((xp, yp, zp))
self.eye = E
self.vvec = R - E
self.vvec = self.vvec / proj3d.mod(self.vvec)
if abs(relev) > np.pi/2:
# upside down
V = np.array((0, 0, -1))
else:
V = np.array((0, 0, 1))
zfront, zback = -self.dist, self.dist
viewM = proj3d.view_transformation(E, R, V)
perspM = proj3d.persp_transformation(zfront, zback)
M0 = np.dot(viewM, worldM)
M = np.dot(perspM, M0)
return np.dot(M, scale);
Axes3D.get_proj=get_proj_scale
"""
You need to include all the code above.
From here on you should be able to plot as usual.
"""
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve')
ax.legend()
plt.show()
Standard output:
Scaled by (1, 2, 3):
Scaled by (1, 1, 3):
The reason I particularly like this method,
Swap z and x, scale by (3, 1, 1):
Below is a shorter version of the code.
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
"""
Scaling is done from here...
"""
x_scale=1
y_scale=1
z_scale=2
scale=np.diag([x_scale, y_scale, z_scale, 1.0])
scale=scale*(1.0/scale.max())
scale[3,3]=1.0
def short_proj():
return np.dot(Axes3D.get_proj(ax), scale)
ax.get_proj=short_proj
"""
to here
"""
ax.plot(z, y, x, label='parametric curve')
ax.legend()
plt.show()
Please note that the answer below simplifies the patch, but uses the same underlying principle as the answer by #ChristianSarofeen.
Solution
As already indicated in other answers, it is not a feature that is currently implemented in matplotlib. However, since what you are requesting is simply a 3D transformation that can be applied to the existing projection matrix used by matplotlib, and thanks to the wonderful features of Python, this problem can be solved with a simple oneliner:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([scale_x, scale_y, scale_z, 1]))
where scale_x, scale_y and scale_z are values from 0 to 1 that will re-scale your plot along each of the axes accordingly. ax is simply the 3D axes which can be obtained with ax = fig.gca(projection='3d')
Explanation
To explain, the function get_proj of Axes3D generates the projection matrix from the current viewing position. Multiplying it by a scaling matrix:
scale_x, 0, 0
0, scale_y, 0
0, 0, scale_z
0, 0, 1
includes the scaling into the projection used by the renderer. So, what we are doing here is substituting the original get_proj function with an expression taking the result of the original get_proj and multiplying it by the scaling matrix.
Example
To illustrate the result with the standard parametric function example:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z ** 2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
# OUR ONE LINER ADDED HERE:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([0.5, 0.5, 1, 1]))
ax.plot(x, y, z)
plt.show()
for values 0.5, 0.5, 1, we get:
while for values 0.2, 1.0, 0.2, we get:
In my case I wanted to stretch z-axis 2 times for better point visibility
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# plt.rcParams["figure.figsize"] = (10,200)
# plt.rcParams["figure.autolayout"] = True
ax = plt.axes(projection='3d')
ax.set_box_aspect(aspect = (1,1,2))
ax.plot(dataX,dataY,dataZ)
I looks like by default, mplot3d will leave quite a bit of room at the top and bottom of a very tall plot. But, you can trick it into filling that space using fig.subplots_adjust, and extending the top and bottom out of the normal plotting area (i.e. top > 1 and bottom < 0). Some trial and error here is probably needed for your particular plot.
I've created some random arrays for x, y, and z with limits similar to your plot, and have found the parameters below (bottom=-0.15, top = 1.2) seem to work ok.
You might also want to change ax.view_init to set a nice viewing angle.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from numpy import random
# Make some random data with similar limits to the OP's example
x,y,z=random.rand(3,100)
z*=250
y*=800
y+=900
x*=350
x+=1200
fig=plt.figure(figsize=(4,35))
# Set the bottom and top outside the actual figure limits,
# to stretch the 3D axis
fig.subplots_adjust(bottom=-0.15,top=1.2)
ax = fig.add_subplot(111, projection='3d')
# Change the viewing angle to an agreeable one
ax.view_init(2,None)
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Sounds like you're trying to adjust the scale of the plot. I don't think there's a way to stretch a linear scale to user specifications, but you can use set_yscale(), set_xscale(), set_zscale() to alter the scales with respect to each other.
Intuitively, set_yscale(log), set_xscale(log), set_zscale(linear) might solve your problems.
A likely better option: specify a stretch, set them all to symlog with the same log base and then specify the Z-axis's symlog scale with the linscalex/linscaley kwargs to your specifications.
More here:
http://matplotlib.org/mpl_toolkits/mplot3d/api.html
I found this while searching on a similar problem. After experimenting a bit, perhaps I can share some of my prelim findings here..matplotlib library is VAST!! (am a newcomer). Note that quite akin to this question, all i wanted was to 'visually' stretch the chart without distorting it.
Background story (only key code snippets are shown to avoid unnecessary clutter for those who know the library, and if you want a run-able code please drop a comment):
I have three 1-d ndarrays representing the X,Y and Z data points respectively. Clearly I can't use plot_surface (as it requires 2d ndarrays for each dim) so I went for the extremely useful plot_trisurf:
fig = plt.figure()
ax = Axes3D(fig)
3d_surf_obj = ax.plot_trisurf(X, Y, Z_defl, cmap=cm.jet,linewidth=0,antialiased=True)
You can think of the plot like a floating barge deforming in waves...As you can see, the axes stretch make it pretty deceiving visually (note that x is supposed to be at x6 times longer than y and >>>>> z). While the plot points are correct, I wanted something more visually 'stretched' at the very least. Was looking for A QUICK FIX, if I may. Long story cut short, I found a bit of success with...'figure.figsize' general setting (see snippet below).
matplotlib.rcParams.update({'font.serif': 'Times New Roman',
'font.size': 10.0,
'axes.labelsize': 'Medium',
'axes.labelweight': 'normal',
'axes.linewidth': 0.8,
###########################################
# THIS IS THE IMPORTANT ONE FOR STRETCHING
# default is [6,4] but...i changed it to
'figure.figsize':[15,5] # THIS ONE #
})
For [15,5] I got something like...
Pretty neat!!
So I started to push it.... and got up to [20,6] before deciding to settle there..
If you want to try for visually stretching the vertical axis, try with ratios like... [7,10], which in this case gives me ...
Not too shabby !
Should do it for visual prowess.
Multiply all your z values by 9,
ax.scatter(x, y, 9*z, zdir='z', c= 'red')
And then give the z-axis custom plot labels and spacing.
ax.ZTick = [0,-9*50, -9*100, -9*150, -9*200];
ax.ZTickLabel = {'0','-50','-100','-150','-200'};

Categories