Is it possible to edit the inline labels of a contour plot after the inline label values are generated? - python-3.x

Below is an example code to generate a contour plot with an inline label. I would like to know how I can edit the inline label.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 100)
y = np.linspace(-10, 10, 100)
def z_func(x, y):
""" z = z(x,y) ==> Z = Z(X, Y) """
X, Y = np.meshgrid(x, y)
Z = np.sqrt(X**2 + Y**2)
return X, Y, Z
def get_xyz_contour_plot(X, Y, Z, cmap='plasma', ncontours=6, linecolor='white'):
""" Generates a filled contour plot with inline labels """
plt.contourf(X, Y, Z, cmap=cmap)
contours = plt.contour(X, Y, Z, ncontours, colors=linecolor)
plt.clabel(contours, inline=True, fontsize=8)
plt.show()
X, Y, Z = z_func(x, y)
get_xyz_contour_plot(X, Y, Z)
The code above generates a plot that looks like this. If I wanted to add a negative sign to the inline label, I could just apply a negative sign in the example above. But for my actual purpose, I am making a contour plot of the pvalue that is associated with a chi square value. The code is too long to post here (hence the alternative example above), but I minimize the negative pvalue associated with chi square rather than chi square itself (via scipy). As such, my function produces a negative output and the inline label shows a negative sign.
Is it possible to edit the inline label by removing the negative sign after the inline label values have been generated? As an example, how could I add a negative sign to the inline labels in the code above without changing z_func?

You may specify a fmt to the clabel, which may be a matplotlib formatter instance. You could use a FuncFormatter with a function the just reverses the sign of the value before formatting it.
fmt_func = lambda x,pos: "{:1.3f}".format(-x)
fmt = matplotlib.ticker.FuncFormatter(fmt_func)
plt.clabel(contours, inline=True, fontsize=8, fmt=fmt)
Complete example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker
x = np.linspace(-10, 10, 100)
y = np.linspace(-10, 10, 100)
def z_func(x, y):
""" z = z(x,y) ==> Z = Z(X, Y) """
X, Y = np.meshgrid(x, y)
Z = np.sqrt(X**2 + Y**2)
return X, Y, Z
def get_xyz_contour_plot(X, Y, Z, cmap='plasma', ncontours=6, linecolor='white'):
""" Generates a filled contour plot with inline labels """
plt.contourf(X, Y, Z, cmap=cmap)
contours = plt.contour(X, Y, Z, ncontours, colors=linecolor)
fmt_func = lambda x,pos: "{:1.3f}".format(-x)
fmt = matplotlib.ticker.FuncFormatter(fmt_func)
plt.clabel(contours, inline=True, fontsize=8, fmt=fmt)
plt.show()
X, Y, Z = z_func(x, y)
get_xyz_contour_plot(X, Y, Z)

Related

Plotting 3-D Solid of Revolution in Python Using Matplotlib

Given the region bounded by the curves y=x^2, y=(x-2)^2 and the axis.
I want to plot the 3-D solid rotated about the x-axis.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Define the function to rotate
def f(x):
return x**2
def g(x):
return (x-2)**2
# Define the range of x values to plot
x = np.linspace(0, 1, 100)
x2=np.linspace(1, 2, 100)
# Define the range of angles to rotate over
theta = np.linspace(0, 2*np.pi, 100)
# Create a meshgrid of x and theta values
X, Theta = np.meshgrid(x, theta)
X2, Theta = np.meshgrid(x2, theta)
# Calculate the corresponding cylindrical coordinates
R = X
Y = R*np.sin(Theta)
Z = R*np.cos(Theta)*f(X)
R2 = X2
Y2 = R2*np.sin(Theta)
Z2 = R2*np.cos(Theta)*g(X2)
# Create the 3D plot
fig = plt.figure(figsize = (11,8))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z)
ax.plot_surface(X2, Y2, Z2)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
Output:
As you can see, it works fine for the first curve y = x^2 (blue) but it's not rendering correctly for y=(x-2)^2 (orange). Why is it doing that?
The code and output attached above.
I used a trick to make the plotting process easier.
Instead of rotating around the x-axis, it is much easier rotating around the z-axis using spherical coordinates. matplotlib has intuitive example of utilizing spherical coordinates to draw a ball. Hence, we can swap the axis (e.g. treat the x-axis in the 2D plot as the z-axis in the 3D plot), compute the required spherical coordinates from the given two functions, and then convert back to Cartesian for plotting.
Since we swap the coordinates, eventually we have to rotate the plot and manually assign the axis label.
import matplotlib.pyplot as plt
import numpy as np
from typing import Tuple, Callable
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(projection='3d')
# Define the function to rotate
def f(x):
return x**2
def g(x):
return (x-2)**2
def get_p(phi: np.ndarray, f: Callable, x0: float = 0) -> np.ndarray:
"""Get the distance p
Let the origin be O and a line starting from O with its angle relative to
x-axis being phi intersect with the curve y = f(x) at point Q, the distance
p is the length of the line segment OQ.
:param phi: the angle relative to x-axis
:type phi: np.ndarray
:param f: the curve to be rotated around its x-axis
:type f: Callable
:param x0: starting estimate of x-coord of intersection Q. Use this to
control which intersection is desired. default to 0
:type x0: optional, float
:return: an array of distance, corresponding to each given phi
:rtype: np.ndarray
"""
ks = np.tan(phi)
x = []
for k in ks:
func = lambda x : f(x) - k * x
# we only look for one root
x.append(scipy.optimize.fsolve(func, x0)[0])
x = np.array(x)
y = x * ks
return np.sqrt(x**2 + y**2)
def get_xyz(
theta: np.ndarray, phi: np.ndarray, p: np.ndarray,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Produce the Cartesian coordinates from the given spherical coordinates.
For reference, see: https://mathinsight.org/spherical_coordinates#:~:text=In%20summary%2C%20the%20formulas%20for,%CE%B8z%3D%CF%81cos%CF%95.
:param theta: in the 3D coordinate, given its origin O, a point P and its
projection Q on the XY plane, theta is the angle between line segment
OQ and positive x-axis.
:type theta: np.ndarray
:param phi: using the same setup as described above, phi is the angle
between line segment OP and positive z-axis
:type phi: np.ndarray
:param p: using the same setup as described above, p is the length of line
segment OP.
:type p: np.ndarray
:return: the Cartesian coordinates converted from the spherical coordinates
in the form of (x, y, z)
:rtype: Tuple[np.ndarray, np.ndarray, np.ndarray]
"""
return (
np.outer(np.cos(theta), np.sin(phi) * p),
np.outer(np.sin(theta), np.sin(phi) * p),
np.outer(np.ones(np.size(theta)), np.cos(phi) * p),
)
# Make data
theta = np.linspace(0, 2 * np.pi, 100)
phi_intercept = np.pi / 4 # the angle relative to x-axis when the two curves meet
# Plot y = x^2 half
phi2 = np.linspace(0, phi_intercept, 50)
p2 = get_p(phi2, f, x0=1)
ax.plot_surface(*get_xyz(theta, phi2, p2))
# Plot y = (x - 2)^2 half
phi1 = np.linspace(0, phi_intercept, 50)
p1 = get_p(phi1, g, x0=1)
ax.plot_surface(*get_xyz(theta, phi1, p1))
# Set plot properties
ax.set_box_aspect([1,1,1])
# x axis in the 2D plot becomes z here
ax.set_zlim(0, 2)
ax.set_zlabel('X')
# y axis in the 2D plot is still y here
ax.set_ylim(-1, 1)
ax.set_ylabel('Y')
# the new z axis after rotation becomes x here
ax.set_xlim(-1, 1)
ax.set_xlabel('Z')
# rotate the plot
ax.view_init(10, 0, -90)
plt.savefig('demo.png', dpi=100)

How to get the plot of 3D geometry with equal axes in python using matplotlib?

I am facing a problem to plot the geometry in the python using matplotlib. I would like to have a plot which can have the equal lenth in all three axes (X, Y, Z). I have written below code but it does not show any equal axes in the obtained geometry.
How can I get the plot with equal axes?
def plotting(x, y, z, figname):
fig = plt.figure(figsize = (50,50))
ax = plt.axes(projection='3d')
ax.grid()
ax.scatter(x, y, z, c = 'r', s = 50)
ax.set_title(figname)
ax.set_xlabel('x', labelpad=20)
ax.set_ylabel('y', labelpad=20)
ax.set_zlabel('z', labelpad=20)
Matplotlib makes this very difficult. One way you could "achieve" that is by setting the same limits to xlim, ylim, zlim:
import numpy as np
import matplotlib.pyplot as plt
n = 1000
t = np.random.uniform(0, 2*np.pi, n)
p = np.random.uniform(0, 2*np.pi, n)
x = (4 + np.cos(t)) * np.cos(p)
y = (1.5 + np.cos(t)) * np.sin(p)
z = np.sin(t)
fig = plt.figure()
ax = fig.add_subplot(projection="3d")
ax.scatter(x, y, z)
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.set_zlim(-4, 4)
plt.show()
Otherwise, your best bet is to use a different plotting library for 3D plots. Plotly allows to easily set equal aspect ratio. K3D-Jupyter and Mayavi uses equal aspect ratio by default.

Draw curves with triple colors and width by using matplotlib and LineCollection [duplicate]

The figure above is a great artwork showing the wind speed, wind direction and temperature simultaneously. detailedly:
The X axes represent the date
The Y axes shows the wind direction(Southern, western, etc)
The variant widths of the line were stand for the wind speed through timeseries
The variant colors of the line were stand for the atmospheric temperature
This simple figure visualized 3 different attribute without redundancy.
So, I really want to reproduce similar plot in matplotlib.
My attempt now
## Reference 1 http://stackoverflow.com/questions/19390895/matplotlib-plot-with-variable-line-width
## Reference 2 http://stackoverflow.com/questions/17240694/python-how-to-plot-one-line-in-different-colors
def plot_colourline(x,y,c):
c = plt.cm.jet((c-np.min(c))/(np.max(c)-np.min(c)))
lwidths=1+x[:-1]
ax = plt.gca()
for i in np.arange(len(x)-1):
ax.plot([x[i],x[i+1]], [y[i],y[i+1]], c=c[i],linewidth = lwidths[i])# = lwidths[i])
return
x=np.linspace(0,4*math.pi,100)
y=np.cos(x)
lwidths=1+x[:-1]
fig = plt.figure(1, figsize=(5,5))
ax = fig.add_subplot(111)
plot_colourline(x,y,prop)
ax.set_xlim(0,4*math.pi)
ax.set_ylim(-1.1,1.1)
Does someone has a more interested way to achieve this? Any advice would be appreciate!
Using as inspiration another question.
One option would be to use fill_between. But perhaps not in the way it was intended. Instead of using it to create your line, use it to mask everything that is not the line. Under it you can have a pcolormesh or contourf (for example) to map color any way you want.
Look, for instance, at this example:
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d
def windline(x,y,deviation,color):
y1 = y-deviation/2
y2 = y+deviation/2
tol = (y2.max()-y1.min())*0.05
X, Y = np.meshgrid(np.linspace(x.min(), x.max(), 100), np.linspace(y1.min()-tol, y2.max()+tol, 100))
Z = X.copy()
for i in range(Z.shape[0]):
Z[i,:] = c
#plt.pcolormesh(X, Y, Z)
plt.contourf(X, Y, Z, cmap='seismic')
plt.fill_between(x, y2, y2=np.ones(x.shape)*(y2.max()+tol), color='w')
plt.fill_between(x, np.ones(x.shape) * (y1.min() - tol), y2=y1, color='w')
plt.xlim(x.min(), x.max())
plt.ylim(y1.min()-tol, y2.max()+tol)
plt.show()
x = np.arange(100)
yo = np.random.randint(20, 60, 21)
y = interp1d(np.arange(0, 101, 5), yo, kind='cubic')(x)
dv = np.random.randint(2, 10, 21)
d = interp1d(np.arange(0, 101, 5), dv, kind='cubic')(x)
co = np.random.randint(20, 60, 21)
c = interp1d(np.arange(0, 101, 5), co, kind='cubic')(x)
windline(x, y, d, c)
, which results in this:
The function windline accepts as arguments numpy arrays with x, y , a deviation (like a thickness value per x value), and color array for color mapping. I think it can be greatly improved by messing around with other details but the principle, although not perfect, should be solid.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(0,4*np.pi,10000) # x data
y = np.cos(x) # y data
r = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: 1-x/(2*np.pi), 0]) # red
g = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: x/(2*np.pi), lambda x: -x/(2*np.pi)+2]) # green
b = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [0, lambda x: x/(2*np.pi)-1]) # blue
a = np.ones(10000) # alpha
w = x # width
fig, ax = plt.subplots(2)
ax[0].plot(x, r, color='r')
ax[0].plot(x, g, color='g')
ax[0].plot(x, b, color='b')
# mysterious parts
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
# mysterious parts
rgba = list(zip(r,g,b,a))
lc = LineCollection(segments, linewidths=w, colors=rgba)
ax[1].add_collection(lc)
ax[1].set_xlim(0,4*np.pi)
ax[1].set_ylim(-1.1,1.1)
fig.show()
I notice this is what I suffered.

Colormaps with a colorscale AND one color for unwanted values

I would like to plot a grid of 3 variables (same min, same max, same spacing) in 3D and I would like each point on the grid to have a specific color according to a function f which is a function of these 3 variables except for when the values of the function are superior to a specific threshold for which I assign another color.
The code below as what I have tried so far:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.colors
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
import math
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
%matplotlib notebook
x = np.arange(0.001, 0.175, 0.01)
y = np.arange(0.001, 0.175, 0.01)
z = np.arange(0.001, 0.175, 0.01)
X, Y, Z = np.meshgrid(x, y, z)
def function(X,Y,Z):
'''function of (X,Y,Z) going from 0 to high values'''
return(f)
f=function(X,Y,Z)
#flatten the f array (I think there is a function to flatten an array but I have seen it to late)
fflat=[]
for l in f:
for p in l:
for t in p:
fflat.append(t)
#masking high values with the highest interesting value: maxV
mfflat = ma.masked_greater(fflat, maxV)
mfflat = mfflat.filled(maxV)
#normalizing values and mapping to veridis cmap:
cmap = matplotlib.cm.get_cmap('viridis')
norm = matplotlib.colors.Normalize(vmin=min(mfflat), vmax=maxV) #(vmax=maxV=max(mfflat))
colors = [cmap(norm(value)) for value in mfflat]
#plot
ax.scatter(X, Y, Z, color=colors, s=10, alpha=1)
cax, _ = matplotlib.colorbar.make_axes(ax)
cbar = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm)
The problem is that now all the "unwanted high values", i.e. values > maxV have the same colors as my "maximal wanted values", i.e. maxV ...
I would like all my "unwanted values" outside of my veridis colorscale and giving them another unique color.
Thanks for your help !
Cheers
Thanks to ImportanceOfBeingErnest for the answer, I just had to use: cmap.set_over, here is the corrected code:
x = np.arange(0.001, 0.175, 0.01)
y = np.arange(0.001, 0.175, 0.01)
z = np.arange(0.001, 0.175, 0.01)
X, Y, Z = np.meshgrid(x, y, z)
def function(X,Y,Z):
'''function of (X,Y,Z) going from 0 to high values'''
return(f)
f=function(X,Y,Z)
#flatten the f array (I think there is a function to flatten an array but I have seen it to late)
fflat=[]
for l in f:
for p in l:
for t in p:
fflat.append(t)
cmap = plt.cm.get_cmap('viridis')
cmap.set_over(color=(0,0,0), alpha=0.5)
norm = matplotlib.colors.Normalize(vmin=minV, vmax=maxV)
colors = [cmap(norm(value)) for value in fflat]
#plot
ax.scatter(X, Y, Z, color=colors, s=10, alpha=1)
cax, _ = matplotlib.colorbar.make_axes(ax)
cbar = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm)
In the meantime I have found a workaround by creating the "colors" list selectively which is of course not as clean as using cmap.set_over:
colors=[]
for value in fflat:
if minV <= value <= maxV:
colors.append(cmap(norm(value)))
else:
colors.append((255/258,255/258,255/258,0))

How to return string composed of superscript inside of fraction for matplotlib legend label? [duplicate]

This question already has answers here:
use format with tex fraction expression in matplotlib python
(2 answers)
Closed 4 years ago.
I have coded a routine that plots x and y data. This code also calculates the indices at which the nth derivative d/dx^n (y) changes sign, where n is specified as a function argument. I would like to include this derivative as a legend label in a matplotlib figure.
I can create a label to include this if it is predetermined. As an example, if n was pre-determined to be 2, then:
label = r'$\frac{d^2y}{dx^2}$'
But since n is a function argument, I do not know how to assign it to the fraction. As an example (containing failed attempts), see below:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1, 10, 10)
y = x
def f(x, y, n):
""" """
fig, ax = plt.subplots()
if n == 1:
label = r'$\frac{dy}{dx} = 0$'
else:
numerator = 'd^{}y'.format(n)
denominator = 'dx^{}'.format(n)
# label = r'$\frac{}{}$'.format(numerator, denominator)
# label = '$\frac{}{}$'.format(numerator, denominator)
# label = '$\frac{numerator}{denominator}$'
label = r'$\frac{numerator}{denominator}$'
ax.scatter(x, y, c='r', marker='.', s=5, label=label)
ax.legend(loc='upper left')
plt.show()
plt.close(fig)
f(x, y, n=1)
f(x, y, n=2)
I am only concerned with the legend label. How can I make it such that I get the desired output of a string fraction, the numerator of which appears as r'$d^ny$' and the denominator of which appears as r'$dx^n$' (where n is a number)?
You can do it using the string formatting syntax %s as following:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1, 10, 10)
y = x
def f(x, y, n):
""" """
fig, ax = plt.subplots()
if n == 1:
label = r'$\frac{dy}{dx} = 0$'
else:
numerator = 'd^{}y'.format(n)
denominator = 'dx^{}'.format(n)
label = r'$\frac{%s}{%s}$' %(numerator, denominator)
ax.scatter(x, y, c='r', marker='.', s=5, label=label)
ax.legend(loc='upper left', fontsize=18)
plt.show()
plt.close(fig)

Resources