I want to plot a self-specified grid using Matplotlib in Python.
I know of the np.meshgrid function and can use it to obtain the array of different points I want to connect, but am unsure of how to then plot the grid.
Code example:
x = np.linspace(0,100,100)
y = np.linspace(0,10,20)
xv, yv = np.meshgrid(x, y)
Now, how can I plot a grid of this xv array?
You can turn a grid on/off with grid(), but it's only possible to have the grid lines on axis ticks, so if you want it hand-made, what about this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
xs = np.linspace(0, 100, 51)
ys = np.linspace(0, 10, 21)
ax = plt.gca()
# grid "shades" (boxes)
w, h = xs[1] - xs[0], ys[1] - ys[0]
for i, x in enumerate(xs[:-1]):
for j, y in enumerate(ys[:-1]):
if i % 2 == j % 2: # racing flag style
ax.add_patch(Rectangle((x, y), w, h, fill=True, color='#008610', alpha=.1))
# grid lines
for x in xs:
plt.plot([x, x], [ys[0], ys[-1]], color='black', alpha=.33, linestyle=':')
for y in ys:
plt.plot([xs[0], xs[-1]], [y, y], color='black', alpha=.33, linestyle=':')
plt.show()
It's much faster by using LineCollection:
import pylab as pl
from matplotlib.collections import LineCollection
x = np.linspace(0,100,100)
y = np.linspace(0,10,20)
pl.figure(figsize=(12, 7))
hlines = np.column_stack(np.broadcast_arrays(x[0], y, x[-1], y))
vlines = np.column_stack(np.broadcast_arrays(x, y[0], x, y[-1]))
lines = np.concatenate([hlines, vlines]).reshape(-1, 2, 2)
line_collection = LineCollection(lines, color="red", linewidths=1)
ax = pl.gca()
ax.add_collection(line_collection)
ax.set_xlim(x[0], x[-1])
ax.set_ylim(y[0], y[-1])
Related
I did a test code brigging something I saw on stack on different topic, and try to assemble it to make what I need : a filled curve with gradient.
After validate this test code I will make a subplot (4 plots for 4 weeks) with the same min/max for all plot (it's a power consumption).
My code :
from matplotlib import pyplot as plt
import numpy as np
# random x
x = range(100)
# smooth random y
y = 0
result = []
for _ in x:
result.append(y)
y += np.random.normal(loc=0, scale=1)#, size=len(x))
y = result
y = list(map(abs, y))
# creation of z for contour
z1 = min(y)
z3 = max(y)/(len(x)+1)
z2 = max(y)-z3
z = [[z] * len(x) for z in np.arange(z1,z2,z3)]
num_bars = len(x) # more bars = smoother gradient
# plt.contourf(x, y, z, num_bars, cmap='greys')
plt.contourf(x, y, z, num_bars, cmap='cool', levels=101)
background_color = 'w'
plt.fill_between(
x,
y,
y2=max(y),
color=background_color
)
But everytime I make the code run, the result display a different gradient scale, that is not smooth neither even straight right.
AND sometime the code is in error : TypeError: Length of y (100) must match number of rows in z (101)
I'm on it since too many time, turning around, and can't figure where I'm wrong...
I finally find something particularly cool, how to :
have both filled gradient curves in a different color (thanks to JohanC in this topic)
use x axis with datetime (thanks to Ffisegydd in this topic)
Here the code :
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.dates as mdates
np.random.seed(2022)
st_date = '2022-11-01 00:00:00'
st_date = pd.to_datetime(st_date)
en_date = st_date + pd.DateOffset(days=7)
x = pd.date_range(start=st_date,end=en_date,freq='30min')
x = mdates.date2num(x)
y = np.random.normal(0.01, 1, len(x)).cumsum()
fig, ax = plt.subplots(figsize=(18, 5))
ax.plot(x, y, color='grey')
########################
# positives fill
#######################
grad1 = ax.imshow(
np.linspace(0, 1, 256).reshape(-1, 1),
cmap='Blues',
vmin=-0.5,
aspect='auto',
extent=[x.min(), x.max(), 0, y.max()],
# extent=[x[0], x[1], 0, y.max()],
origin='lower'
)
poly_pos = ax.fill_between(x, y.min(), y, alpha=0.1)
grad1.set_clip_path(
poly_pos.get_paths()[0],
transform=ax.transData
)
poly_pos.remove()
########################
# negatives fill
#######################
grad2 = ax.imshow(
np.linspace(0, 1, 256).reshape(-1, 1),
cmap='Reds',
vmin=-0.5,
aspect='auto',
extent=[x.min(), x.max(), y.min(), 0],
origin='upper'
)
poly_neg = ax.fill_between(x, y, y.max(), alpha=0.1)
grad2.set_clip_path(
poly_neg.get_paths()[0],
transform=ax.transData
)
poly_neg.remove()
########################
# decorations and formatting plot
########################
ax.xaxis_date()
date_format = mdates.DateFormatter('%d-%b %H:%M')
ax.xaxis.set_major_formatter(date_format)
fig.autofmt_xdate()
ax.grid(True)
I have the following script that plots me a 2D field of some quantity in the domain:
def field(x, y, z):
fig, (ax) = plt.subplots()
ax.tricontour(x, y, z)
cntr = ax.tricontourf(x, y, z)
fig.colorbar(cntr, ax=ax)
plt.show()
Which gives me something like:
I want to add profiles on top of that figure, i.e.:
How do I do this?
Just plot on ax, setting linewidth (short lw) and color (short c) as needed:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(1)
x = np.random.uniform(-5, 40, 50)
y = np.random.uniform(-.25, .25, 50)
z = y * 100
fig, ax = plt.subplots(figsize=(10,2))
cntr = ax.tricontourf(x, y, z)
fig.colorbar(cntr, ax=ax)
ax.set(xlim=(0, 35), ylim=(-.2, .2))
for i in range(5, 35, 5):
ax.plot([i-1, i+1], [0.2, -0.2], lw=5, c='k')
The sample data is generated as follows,
import matplotlib as mpl
print(mpl.__version__) # 3.3.3
import matplotlib.pyplot as plt
import numpy as np
def f(x, y=0):
return np.piecewise(x, [x < 1, np.logical_and(1 <= x, x < 10), x >= 10], [lambda x: 0, lambda x: (x - 1) / 9 * 1000, lambda x: 1000])
x = np.logspace(-5, 5, 100)
y = np.logspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
I try to plot using the following code, but some contours disappear after calling clabel.
fig, ax = plt.subplots(figsize=(5, 3), dpi=120)
cr = ax.contour(X, Y, Z, levels=3, colors='black')
ax.clabel(cr, inline=True, fontsize=8, fmt='%d')
ax.set_xscale('log')
ax.set_yscale('log')
plt.show()
This issue still appears even when contour linewidth and label font size are decreased.
fig, ax = plt.subplots(figsize=(5, 3), dpi=120)
cr = ax.contour(X, Y, Z, levels=3, colors='black', linewidths=0.6)
ax.clabel(cr, inline=True, fontsize=3, fmt='%d')
ax.set_xscale('log')
ax.set_yscale('log')
plt.show()
I cannot figure out how to fix the weird behaviours of contour and clabel, and I suspect it is due to their incompatibility with log scale.
It is indeed a problem of the log axes, especially around the asymptote zero. However, why not defining the log axes before plotting, so matplotlib can take this into consideration when plotting?
import matplotlib as mpl
print(mpl.__version__) # 3.3.3
import matplotlib.pyplot as plt
import numpy as np
def f(x, y=0):
return np.piecewise(x, [x < 1, np.logical_and(1 <= x, x < 10), x >= 10], [lambda x: 0, lambda x: (x - 1) / 9 * 1000, lambda x: 1000])
x = np.logspace(-5, 5, 100)
y = np.logspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig, ax = plt.subplots(figsize=(5, 3), dpi=120)
ax.set_xscale('log')
ax.set_yscale('log')
cr = ax.contour(X, Y, Z, levels=3, colors='black')
ax.clabel(cr, inline=True, fontsize=8, fmt='%d')
plt.show()
Sample output:
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.
I am trying to 'paint' the faces of a cube with a contourf function using Python Matplotlib. Is this possible?
This is similar idea to what was done here but obviously I cannot use patches. Similarly, I don't think I can use add_collection3d like this as it only supports PolyCollection, LineColleciton and PatchCollection.
I have been trying to use contourf on a fig.gca(projection='3d'). Toy example below.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
plt.close('all')
fig = plt.figure()
ax = fig.gca(projection='3d')
############################################
# plotting the 'top' layer works okay... #
############################################
X = np.linspace(-5, 5, 43)
Y = np.linspace(-5, 5, 28)
X, Y = np.meshgrid(X, Y)
varone=np.random.rand(75,28,43)
Z=varone[0,:,:]
cset = ax.contourf(X, Y, Z, zdir='z', offset=1,
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
#see [1]
plt.show()
#################################################
# but now trying to plot a vertical slice.... #
#################################################
plt.close('all')
fig = plt.figure()
ax = fig.gca(projection='3d')
Z=varone[::-1,:,-1]
X = np.linspace(-5, 5, 28)
Y = np.linspace(-5, 5, 75)
X, Y = np.meshgrid(X, Y)
#this 'projection' doesn't result in what I want, I really just want to rotate it
cset = ax.contourf(X, Y, Z, offset=5,zdir='x',
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
#here's what it should look like....
ax=fig.add_subplot(1, 2,1)
cs1=ax.contourf(X,Y,Z,levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
#see [2]
plt.show()
1 From the example, the top surface comes easily:
2 But I'm not sure how to do the sides. Left side of this plot is what the section should look like (but rotated)...
Open to other python approaches. The data I'm actually plotting are geophysical netcdf files.
You have to assign the data to the right axis. The zig-zag results from the fact that now you are at x = const and have your oscillation in the z-direction (from the random data, which is generated between 0 and 1).
If you you assign the matrixes differently in your example, you end up with the desired result:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
plt.close('all')
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.linspace(-5, 5, 43)
Y = np.linspace(-5, 5, 28)
X, Y = np.meshgrid(X, Y)
varone=np.random.rand(75,28,43) * 5.0 - 10.0
Z=varone[0,:,:]
cset = [[],[],[]]
# this is the example that worked for you:
cset[0] = ax.contourf(X, Y, Z, zdir='z', offset=5,
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
# now, for the x-constant face, assign the contour to the x-plot-variable:
cset[1] = ax.contourf(Z, Y, X, zdir='x', offset=5,
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
# likewise, for the y-constant face, assign the contour to the y-plot-variable:
cset[2] = ax.contourf(X, Z, Y, zdir='y', offset=-5,
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
# setting 3D-axis-limits:
ax.set_xlim3d(-5,5)
ax.set_ylim3d(-5,5)
ax.set_zlim3d(-5,5)
plt.show()
The result looks like this:
The answer given below is not fully satisfying. Indeed, planes in x, y and z direction reproduce the same field.
Hereafter, a function that allows to represent the correct field in each of the planes.
import numpy as np
import matplotlib.pyplot as plt
def plot_cube_faces(arr, ax):
"""
External faces representation of a 3D array with matplotlib
Parameters
----------
arr: numpy.ndarray()
3D array to handle
ax: Axes3D object
Axis to work with
"""
x0 = np.arange(arr.shape[0])
y0 = np.arange(arr.shape[1])
z0 = np.arange(arr.shape[2])
x, y, z = np.meshgrid(x0, y0, z0)
xmax, ymax, zmax = max(x0), max(y0), max(z0)
vmin, vmax = np.min(arr), np.max(arr)
ax.contourf(x[:, :, 0], y[:, :, 0], arr[:, :, -1].T,
zdir='z', offset=zmax, vmin=vmin, vmax=vmax)
ax.contourf(x[0, :, :].T, arr[:, 0, :].T, z[0, :, :].T,
zdir='y', offset=0, vmin=vmin, vmax=vmax)
ax.contourf(arr[-1, :, :].T, y[:, 0, :].T, z[:, 0, :].T,
zdir='x', offset=xmax, vmin=vmin, vmax=vmax)
x0 = np.arange(30)
y0 = np.arange(20)
z0 = np.arange(10)
x, y, z = np.meshgrid(x0, y0, z0)
arr = (x + y + z) // 10
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
plot_cube_faces(arr, ax)
plt.show()