Python matplotlib 3d surface plot - python

I am trying to convert an equation for a surface plot written for mathematica (image and script below) to a python script using matplotlib. There are only a handful of examples of surface plots on the web.
Help would be appreciated for my non functioning one of many attempts
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(2,-2)
y = np.linspace(2,-2)
z = np.linspace(2,-2)
surfx = -1 * (pow(y, 10) + pow(z, 10) - 100)
surfy = -1 * (pow(x, 10) + pow(z, 10) - 100)
surfz = -1 * (pow(x, 10) + pow(y, 10) - 100)
ax.plot_surface(surfx,surfy,surfz, rstride=4, cstride=4, color='b')
plt.show()

I don't think that matplotlib has an equivalent function at this point. If you are not restricted to using matplotlib, you might want to take a look at mayavi and its contour3d()function.
The following code produces a similar plot to your example using mayavi. I am not sure if it's possible to add the wireframe outline however.
import numpy as np
from mayavi import mlab
x, y, z = np.ogrid[-2:2:25j, -2:2:25j, -2:2:25j]
s = np.power(x, 10) + np.power(y, 10) + np.power(z, 10) - 100
mlab.figure(bgcolor=(1,1,1))
mlab.contour3d(s, contours=[2], color=(.5,.5,.5), transparent=True, opacity=.5)
ax = mlab.axes(nb_labels=5, ranges=(-2,2,-2,2,-2,2))
ax.axes.property.color = (0,0,0)
ax.axes.axis_title_text_property.color = (0,0,0)
ax.axes.axis_label_text_property.color = (0,0,0)
ax.axes.label_format='%.0f'
mlab.show()

Related

Make 3d Python plot as beautiful as Matlab

Note:
This is not a conversion question. It is meant to see if Python has the capability to produce 3D plot like Matlab.
I have created a Matlab plot as follows:
I tried to plot it using Python but I could not get it as good as Matlab. Is there any packages that can plot the above as good as the original one? If it is please convert my code to a Python version. Here is my Matlab code.
set(groot,'defaultAxesTickLabelInterpreter','latex');
set(groot,'defaulttextinterpreter','latex');
set(groot,'defaultLegendInterpreter','latex');
x0=0;
y0=0;
width=3000;
height=2000;
set(gcf,'position',[x0,y0,width,height])
[X,Y] = meshgrid(-1:.01:1);
a = 3;
b = 2;
Z = a*X.^2 + b*Y.^2;
subplot(1,3,1)
s = surf(X,Y,Z,'FaceColor','r', 'FaceAlpha',0.5, 'EdgeColor','none');
s.EdgeColor = 'none';
xlabel('$x_1$','Interpreter','latex','FontSize', 15)
ylabel('$x_2$','Interpreter','latex','FontSize', 15)
zlabel('$f(\mathbf{x};\mathbf{\theta})$','Interpreter','latex','FontSize', 15)
legend({'$f([x_1, x_2]^\top; [\theta_1=3,\theta_2=2]^\top)=3x_1^2+2x_2^2$'},'interpreter','latex','FontSize', 10)
subplot(1,3,2)
Z2 = a*X.^2 ;
s2 = surf(X,Y,Z2,'FaceColor','b', 'FaceAlpha',0.5, 'EdgeColor','none');
s2.EdgeColor = 'none';
xlabel('$x_1$','Interpreter','latex','FontSize', 15)
ylabel('$x_2$','Interpreter','latex','FontSize', 15)
zlabel('$f(\mathbf{x};\mathbf{\theta})$','Interpreter','latex','FontSize', 15)
legend({'$f([x_1, x_2]^\top; [\theta_1=3,\theta_2=0]^\top)=3x_1^2$'},'interpreter','latex','FontSize', 10)
subplot(1,3,3)
s3 = surf(X,Y,Z,'FaceColor','r', 'FaceAlpha',0.5, 'EdgeColor','none');
s3.EdgeColor = 'none';
hold
s4 = surf(X,Y,Z2,'FaceColor','b', 'FaceAlpha',0.5, 'EdgeColor','none');
s4.EdgeColor = 'none';
xlabel('$x_1$','Interpreter','latex','FontSize', 15)
ylabel('$x_2$','Interpreter','latex','FontSize', 15)
zlabel('$f(\mathbf{x};\mathbf{\theta})$','Interpreter','latex','FontSize', 15)
legend({'$f(\mathbf{x};\mathbf{\theta})=3x_1^2+2x_2^2$', '$f(\mathbf{x};\mathbf{\theta})=3x_1^2$'},'interpreter','latex','FontSize', 10)
Yes.
numpy + plotly is an effective Matlab replacement - you may recognize some of the code :). As a benefit, the plots render as html, which means they are highly portable, save as a single file, and can be embedded in a webpage. There may be small details that are different (I don't know the current status of latex axis labels), but, provided you have python, numpy and plotly installed, the following is a good replacement of your first plot:
import plotly.graph_objects as go
import numpy as np
x = np.arange(-1,1,.01)
y = np.arange(-1,1,.01)
X,Y = np.meshgrid(x,y)
a = 3
b = 2
Z = a*X**2 + b*Y**2
fig = go.Figure(
data=[go.Surface(z=Z, x=x, y=y, colorscale="Reds", opacity=0.5)])
fig.update_layout(
title='My title',
autosize=False,
width=500,
height=500,
margin=dict(l=65, r=50, b=65, t=90),
scene_aspectmode='cube'
)
fig.show()
Notice that the go-to plotting package in python is Matplotlib. IMO, it inherited all the worst parts of Matlab's plotting and none of the good (performant rendering). Plotly superior from a performance (esp 3D rendering), interactivity, and API standpoint.
For 3D charting in Python I've had the best results with matplotlib.pyplot.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D, get_test_data
from matplotlib import cm
import numpy as np
import random
X_k_list = range(1, 100, 10)
Y_p_list = [ float(x)/100.0 for x in range(1, 100, 10) ]
# set up a figure twice as wide as it is tall
fig = plt.figure(figsize=plt.figaspect(0.5))
# set up the axes for the first plot
ax = fig.add_subplot(1, 1, 1, projection='3d')
# plot a 3D surface like in the example mplot3d/surface3d_demo
X, Y = np.meshgrid(X_k_list, Y_p_list)
def critical_function(b, c):
num = random.uniform(0, 1) * 10.0
return num + (b * c)
Z_accuracy = X.copy()
Z_accuracy = Z_accuracy.astype(np.float32)
for i in range(len(X_k_list)):
for j in range(len(Y_p_list)):
Z_accuracy[j][i] = critical_function(Y_p_list[j], X_k_list[i])
surf = ax.plot_surface(X, Y, Z_accuracy,
rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
fig.colorbar(surf, shrink=0.5, aspect=10)
plt.show()
https://www.python-graph-gallery.com/371-surface-plot
You can increase the smoothness of the chart by adding more datapoints, rotate the graph along the x,y,z axis, with the mouse and you can add a title, legend and other eye candy.
matplotlib.mplot3d looks like it does euclidian continuous surfaces
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
ax = plt.figure().add_subplot(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
cset = ax.contour(X, Y, Z, extend3d=True, cmap=cm.coolwarm)
ax.clabel(cset, fontsize=9, inline=True)
plt.show()
https://matplotlib.org/stable/gallery/mplot3d/contour3d_2.html#sphx-glr-gallery-mplot3d-contour3d-2-py
You're using matlab's meshgrid(...) tool to generate x,y,z data. Python can achieve the same results with numpy.meshgrid fed into matplotlib.pyplot thustly.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
def f(x, y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
x = np.linspace(-6, 6, 30)
y = np.linspace(-6, 6, 30)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.contour3D(X, Y, Z, 50, cmap='binary')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
https://jakevdp.github.io/PythonDataScienceHandbook/04.12-three-dimensional-plotting.html

matplotlib: how to put picture to a specific point of data-coordinate system

I'm trying to add picture at the plot in specific place in it by a given coordinates in data-coordinate system. However it runs a little bit unpredicted. Here is a code snippet I wrote:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(5,5))
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.grid('on')
x = np.arange(0, 1, 0.005)
y = np.exp(-x/2.) * np.sin(2*np.pi*x)
ax.plot(x, y)
im_orange = plt.imread('./test100x100_orange.png')
im_blue = plt.imread('./test100x100_blue.png')
x0, y0 = ax.transData.transform((0,0))
print('transData(0,0) = {}'.format(ax.transData.transform((0,0))))
ax.figure.figimage(im_orange, alpha=0.5)
ax.figure.figimage(im_blue, x0, y0, alpha=0.5)
ax.transData.transform((0,0)) returns transData(0,0) = [45. 45.] which is unexpected since doesn't represent actual position of (0,0) on the plot. Here is result image as well:
My base question is how to put picture left bottom corner exactly at (0,0) point in data-coordinates? And if possible please explain such behaviour of matplotlib.
Update. A few experiments on top. I've run a slightly modified script (provided below by Iammuratc but with plt.savefig() instead) in 3 modes:
From python console (copy and paste):
Python script (something like python test.py:
Result is the same as before.
ipython from Jupiter Notebook:
Suboption A: plt.show()
Suboption B: plt.savefig()
Now it's even more confusing..
It works for me how you did it. You might check the image arrays in case they are shifted.
import numpy as np
import matplotlib.pyplot as plt
im_orange = np.zeros((128,128,3),'uint8')
im_orange[:,:,0] = 255
im_blue = np.zeros((128,128,3),'uint8')
im_blue[:,:,2] = 255
fig, ax = plt.subplots(figsize=(5,5))
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.grid('on')
x = np.arange(0, 1, 0.005)
y = np.exp(-x/2.) * np.sin(2*np.pi*x)
ax.plot(x, y)
# im_orange = plt.imread('./test100x100_orange.png')
# im_blue = plt.imread('./test100x100_blue.png')
x0, y0 = ax.transData.transform((0,0))
print('transData(0,0) = {}'.format(ax.transData.transform((0,0))))
ax.figure.figimage(im_orange,x0,y0, alpha=0.5)
ax.figure.figimage(im_blue, x0, y0, alpha=0.5)
plt.show()

Line plot that continuously varies transparency - Matplotlib

I wish to produce a single line plot in Matplotlib that has variable transparency, i.e. it starts from solid color to full transparent color.
I tried this but it didn't work.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 500)
y = np.sin(x)
alphas = np.linspace(1, 0, 500)
fig, ax = plt.subplots(1, 1)
ax.plot(x, y, alpha=alphas)
Matplotlib's "LineCollection" allows you to split the line to be plotted into individual line segments and you can assign a color to each segment. The code example below shows how each horizontal "x" value can be assigned an alpha (transparency) value that indexes into a sequential colormap that runs from transparent to a given color. A suitable colormap "myred" was created using Matplotlib's "colors" module.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.colors as colors
redfade = colors.to_rgb("red") + (0.0,)
myred = colors.LinearSegmentedColormap.from_list('my',[redfade, "red"])
x = np.linspace(0,1, 1000)
y = np.sin(x * 4 * np.pi)
alphas = x * 4 % 1
points = np.vstack((x, y)).T.reshape(-1, 1, 2)
segments = np.hstack((points[:-1], points[1:]))
fig, ax = plt.subplots()
lc = LineCollection(segments, array=alphas, cmap=myred, lw=3)
line = ax.add_collection(lc)
ax.autoscale()
plt.show()
If you are using the standard white background then you can save a few lines by using one of Matplotlib's builtin sequential colormaps that runs from white to a given color. If you remove the lines that created the colormap above and just put the agument cmap="Reds" in the LineCollection function, it creates a visually similar result.
The only solution I found was to plot each segment independently with varying transparency
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 500)
y = np.sin(x)
alphas = np.linspace(1, 0, 499)
fig, ax = plt.subplots(1, 1)
for i in range(499):
ax.plot(x[i:i+2], y[i:i+2], 'k', alpha=alphas[i])
But I don't like it... Maybe this is enough for someone
I don't know how to do this in matplotlib, but it's possible in Altair:
import numpy as np
import pandas as pd
import altair as alt
x = np.linspace(0, 2 * np.pi, 500)
y = np.sin(x)
alt.Chart(
pd.DataFrame({"x": x, "y": y, "o": np.linspace(0, 1, len(x))}),
).mark_point(
).encode(
alt.X("x"),
alt.Y("y"),
alt.Opacity(field="x", type="quantitative", scale=alt.Scale(range=[1, 0]), legend=None),
)
Result:

How to plot a parametric curve without using `plot3d_parametric_line`

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()

Basemap on the face of a matplotlib.mplot3d cube

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?

Categories

Resources