As the title suggests, I'm trying to plot a Basemap map on the z=0 surface of a matplotlib.mplot3d lineplot. I know the Axes3D object is capable of plotting on the z=0 surface (via Axes3D.plot, Axes3D.scatter, etc.), but I can't figure out how to do so with a Basemap object. Hopefully the code below shows what I need clearly enough. Any ideas would be much appreciated!
import matplotlib.pyplot as pp
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.basemap import Basemap
# make sample data for 3D lineplot
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
# make the 3D line plot
FIG = ct.pp.figure()
AX = Axes3D(FIG)
AX.plot(x, y, z, '-b')
# make the 2D basemap
### NEEDS TO SOMEHOW BE AT z=0 IN FIG
M = ct.Basemap(projection='stere', width=3700e3, height=2440e3,
lon_0=-5.0, lat_0=71.0, lat_ts=71.0,
area_thresh=100, resolution='c')
PATCHES = M.fillcontinents(lake_color='#888888', color='#282828')
Just add your map as a 3d collection to the Axes3D instance:
import numpy as np
import matplotlib.pyplot as pp
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.basemap import Basemap
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-500, 500, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
FIG = pp.figure()
AX = Axes3D(FIG)
AX.plot(x, y, z, '-b')
M = Basemap(projection='stere', width=3700e3, height=2440e3,
lon_0=-5.0, lat_0=71.0, lat_ts=71.0,
area_thresh=100, resolution='c')
AX.add_collection3d(M.drawcoastlines())
AX.grid(True)
pp.draw()
pp.show()
AX.add_collection3d(M.drawcoastlines())
works but
PATCHES = M.fillcontinents(lake_color='#888888', color='#282828')
does not work.
As soon as you add color fill you get an error similar to: "AttributeError: 'Polygon' object has no attribute 'do_3d_projection'"
M.fillcontinents(lake_color='#888888', color='#282828')`
returns an array of Polygons, not one of the inputs required by add_collection(). collect.PatchCollection() does not seem to work either.
So what do you use to add `M.fillcontinents(lake_color='#888888', color='#282828') to a 3D plot?
Related
I have a 3d plot made using matplotlib. I now want to fill the vertical space between the drawn line and the x,y axis to highlight the height of the line on the z axis. On a 2d plot this would be done with fill_between but there does not seem to be anything similar for a 3d plot. Can anyone help?
here is my current code
from stravalib import Client
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
... code to get the data ....
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
zi = alt
x = df['x'].tolist()
y = df['y'].tolist()
ax.plot(x, y, zi, label='line')
ax.legend()
plt.show()
and the current plot
just to be clear I want a vertical fill to the x,y axis intersection NOT this...
You're right. It seems that there is no equivalent in 3D plot for the 2D plot function fill_between. The solution I propose is to convert your data in 3D polygons. Here is the corresponding code:
import math as mt
import matplotlib.pyplot as pl
import numpy as np
import random as rd
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
# Parameter (reference height)
h = 0.0
# Code to generate the data
n = 200
alpha = 0.75 * mt.pi
theta = [alpha + 2.0 * mt.pi * (float(k) / float(n)) for k in range(0, n + 1)]
xs = [1.0 * mt.cos(k) for k in theta]
ys = [1.0 * mt.sin(k) for k in theta]
zs = [abs(k - alpha - mt.pi) * rd.random() for k in theta]
# Code to convert data in 3D polygons
v = []
for k in range(0, len(xs) - 1):
x = [xs[k], xs[k+1], xs[k+1], xs[k]]
y = [ys[k], ys[k+1], ys[k+1], ys[k]]
z = [zs[k], zs[k+1], h, h]
#list is necessary in python 3/remove for python 2
v.append(list(zip(x, y, z)))
poly3dCollection = Poly3DCollection(v)
# Code to plot the 3D polygons
fig = pl.figure()
ax = Axes3D(fig)
ax.add_collection3d(poly3dCollection)
ax.set_xlim([min(xs), max(xs)])
ax.set_ylim([min(ys), max(ys)])
ax.set_zlim([min(zs), max(zs)])
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
pl.show()
It produces the following figure:
I hope this will help you.
I am using matplotlib and I am struggling with style attributes.
How to add a marker only to the start point or end point of a 3D line and not on both sides?
Use the markevery parameter when plotting.
Example from the Parametric Curve example in the Gallery (version 2.2.5).
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
# Prepare arrays x, y, z
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)
l = ax.plot(x, y, z, marker='o', label='parametric curve both ends', markevery=[0,-1])
l = ax.plot(x+1, y+1, z, 'r', marker='o', label='parametric curve one end', markevery=[0])
ax.legend()
plt.show()
plt.close()
I used the example from version 2.2.5 because I don't have 3.2 installed. Making a 3d axis changed in 3.something - 3.2 example link.
Axes.plot markevery parameter
I've generated a 1D sine wave and then repeated it every row to have a 2D sine wave. I can show this in 2d space, but I need to produce a 3D plot that shows the peaks and valleys as well as the oscillatory patterns between them.
import numpy as np
import matplotlib.pyplot as plt
N = 256
x = np.linspace(-np.pi,np.pi, N)
sine1D = 128.0 + (127.0 * np.sin(x))
sine1D = np.uint8(sine1D)
sine2D = np.tile(sine1D, (N,1))
plt.imshow(sine2D, cmap='gray')
how about, as #Warren Weckesser said, use the mplot3d toolkit examples gallery, and for instance surface plot of the magnitude of a sinewave as function of time and phase:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
fig = plt.figure()
ax3d = fig.add_subplot(111, projection='3d')
# Make the X, Y meshgrid instead of np.tile
xs = np.linspace(-2*np.pi, 2*np.pi, 200)
ys = np.linspace(-2*np.pi, 2*np.pi, 200)
tau, phi = np.meshgrid(xs, ys)
# Z evaluation
amp = np.sin(tau+phi)
ax3d.set_xlabel(r'$\tau$') # tau = omega*t -> adimensional time
ax3d.set_ylabel(r'$\phi$') # phi -> phase
ax3d.set_zlabel(r'$amp$') # signal amplitude
surf = ax3d.plot_surface(tau, phi, amp,cmap=cm.inferno)
fig.colorbar(surf)
that gives :
The idea is to plot the curve: C(t) = (1 + cos(t))i + (1 + sin(t))j + (1 -sin(t)-cos(t))k. Following the instructions on the Plot Module at https://docs.sympy.org/latest/modules/plotting.html one can get it using plot3d_parametric_line:
Method 1:
%matplotlib notebook
from sympy import cos, sin
from sympy.plotting import plot3d_parametric_line
t = sp.symbols('t',real=True)
plot3d_parametric_line(1 + cos(t), 1 + sin(t), 1-sin(t)-cos(t), (t, 0, 2*sp.pi))
Though this is a valid method there is another way to plot it without using plot3d_parametric_line but ax.plot. What I have tried:
Method 2:
fig = plt.figure(figsize=(8, 6))
ax = fig.gca(projection='3d')
ax.set_xlim([-0.15, 2.25])
ax.set_ylim([-0.15, 2.25])
ax.set_zlim([-0.75, 2.50])
ax.plot(1+sp.cos(t),1+sp.sin(t),1-sp.sin(t)-sp.cos(t))
plt.show()
But TypeError: object of type 'Add' has no len() comes up...
How can I fix it so that I get the same curve than with method 1?
Thanks
You can use the 3d plotting from matplotlib after defining a linear NumPy mesh and computing your x, y, z variables
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection='3d')
t = np.linspace(0, 2*np.pi, 100)
x = 1 + np.cos(t)
y = 1 + np.sin(t)
z = 1 - np.sin(t) - np.cos(t)
ax.plot(x, y, z)
plt.show()
here is the code im using and I've also attached the output. I'd like to plot a two dimensional lognorm function as a 3d surface, the above code is supposed to do this however the output results in the entire plane being skewed rather than just the z values. any help or suggestions would be greatly appreciated.
dx = 90 - (-90)
dy = 90 - (-90)
c = [dx + dx/2.0, dy+dy/2.0]
z = np.zeros((400, 400))
x = np.linspace(-90, 90, 400)
y = x.copy()
for i in range(len(x)):
for j in range(len(y)):
p =[x[i], y[j]]
d = math.sqrt((p[0]-c[0])**2 + (p[1]-c[1])**2)
t = d
z[i][j] = lognorm.pdf(t, 1.2)
fig = plt.figure()
ax = fig.add_subplot(111, projection = '3d')
ax.plot_surface(x,y, z, cmap = 'viridis')
plt.show()
output of the provided code
ideally I'd like for it to look something like this.
this is the image here
I think you wanted to plot a 3D surface and here is an example:
#!/usr/bin/python3
# 2018/10/25 14:44 (+0800)
# Plot a 3D surface
from scipy.stats import norm, lognorm
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
xy = np.linspace(-5, 5, 400)
xx, yy = np.meshgrid(xy)
t = np.sqrt(xx**2 + yy**2)
zz = lognorm.pdf(t, 1.2)
fig = plt.figure()
ax = fig.add_subplot(111, projection = '3d')
ax.plot_surface(xx,yy, zz, cmap = 'viridis')
plt.show()