I need to make multiple contours plots of several variables on the same page. I can do this with MATLAB (see below for MATLAB code). I cannot get matplotlib to show multiple legends. Any help would be much appreciated.
Python code:
import numpy as np
from matplotlib import cm as cm
from matplotlib import pyplot as plt
delta = 0.25
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = X*np.exp(-X**2-Y**2)
Z2 = Y*np.exp(-X**2-Y**2)
plt.figure()
CS = plt.contour(X, Y, Z1, colors='k')
plt.clabel(CS, inline=1, fontsize=10)
CS = plt.contour(X, Y, Z2, colors='r')
plt.clabel(CS, inline=1, fontsize=10)
plt.legend(['case 1', 'case 2'])
plt.show()
MATLAB code:
[X,Y] = meshgrid(-2:.2:2,-2:.2:3);
Z1 = X.*exp(-X.^2-Y.^2);
Z2 = Y.*exp(-X.^2-Y.^2);
[C,h] = contour(X,Y,Z1, 'color', 'k');
set(h,'ShowText','on','TextStep',get(h,'LevelStep')*2);
hold on
[C,h] = contour(X,Y,Z2, 'color', 'r');
set(h,'ShowText','on','TextStep',get(h,'LevelStep')*2);
fn = {'case 1', 'case 2'};
legend(fn,'Location','NorthWest');
It would help if you showed your desired output from Matlab. For example, do you really want multiple legends? Or do you actually mean 1 legend with muliple items?
Since contour plots (can) have a different style for each level, its not obvious how you would want to plot that in a legend. But to get you started, you can access each individual line by examining the CS.collections array.
So for example:
delta = 0.25
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = X*np.exp(-X**2-Y**2)
Z2 = Y*np.exp(-X**2-Y**2)
fig, ax = plt.subplots()
CS1 = ax.contour(X, Y, Z1, colors='k')
ax.clabel(CS1, inline=1, fontsize=10)
CS2 = ax.contour(X, Y, Z2, colors='r')
ax.clabel(CS2, inline=1, fontsize=10)
lines = [ CS1.collections[0], CS1.collections[-1], CS2.collections[0], CS2.collections[-1]]
labels = ['CS1_neg','CS1_pos','CS2_neg','CS2_pos']
plt.legend(lines, labels)
Results in:
Perhaps something like plt.legend(CS2.legend_elements()[0], CS2.legend_elements()[1]), can also be useful for you.
Related
I want to shade the surface and the contours of a specific function based on some constraint in the domain of the function. So far I have the following and I want to improve it.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import LinearLocator, FormatStrFormatter
plt.figure(figsize=(8, 6))
plt.axes(projection="3d")
xdata = np.linspace(-3, 3, 20000)
ydata = np.linspace(-3, 3, 20000)
X, Y = np.meshgrid(xdata, ydata)
Z1 = X ** 2 + Y ** 2
Z2 = Z1.copy()
Z3 = Z1.copy()
Z1[np.multiply(X, Y) > 3] = np.nan
Z2[np.multiply(X, Y) <= 3] = np.nan
Z3[np.multiply(X, Y) == 3] = np.nan
ax3d = plt.axes(projection='3d')
ax3d.plot_surface(X, Y, Z1, cmap='Greys', antialiased=True, vmin=-np.nanmin(Z1), vmax=np.nanmax(Z1))
ax3d.plot_surface(X, Y, Z2, cmap='YlGnBu', antialiased=True, vmin=-np.nanmin(Z2), vmax=np.nanmax(Z2))
ax3d.contourf(X, Y, Z1, zdir='z', offset=0, cmap='Greys')
ax3d.contourf(X, Y, Z2, zdir='z', offset=0, cmap='Greys')
ax3d.set_title('Surface Plot in Matplotlib')
ax3d.set_xlabel('X')
ax3d.set_ylabel('Y')
ax3d.set_zlabel('Z')
plt.show()
Could you please someone help to solve the following problems:
The surface is over-imposed by the contour surface.
There are some gaps in the surface.
The contours of the two constraints are not continuous.
Is it possible to plot a line in the border of the two surfaces and contours?
Any help is highly appreciated.
The code below makes the following changes:
creating a custom colormap combining the two existing colormaps
using a TwoSlopeNorm to have the separation at z=3
setting antialiased=False (otherwise matplotlib creates a plot of antialiased lines instead of polygons)
xdata and ydata with 300 steps
using rstride=1, cstride=1 so every x and every y will be considered; this makes the surface smoother (but takes more time)
calling plt.axes(...) only once to prevent a dummy subplot
calling contourf before plot_surface; due to the painter's algorithm, matplotlib only minimally supports 3D overlaps
import matplotlib.pyplot as plt
from matplotlib.colors import TwoSlopeNorm, ListedColormap
import numpy as np
xdata = np.linspace(-3, 3, 300)
ydata = np.linspace(-3, 3, 300)
X, Y = np.meshgrid(xdata, ydata)
Z1 = X ** 2 + Y ** 2
cmap1 = plt.get_cmap('Greys')
cmap2 = plt.get_cmap('YlGnBu')
cmap = ListedColormap(np.r_[cmap1(np.linspace(0, 1, 128)), cmap2(np.linspace(0, 1, 128))])
norm = TwoSlopeNorm(vmin=np.nanmin(Z1), vmax=np.nanmax(Z1), vcenter=3)
plt.figure(figsize=(8, 6))
ax3d = plt.axes(projection='3d')
ax3d.contourf(X, Y, Z1, zdir='z', offset=0, cmap=cmap, norm=norm)
ax3d.plot_surface(X, Y, Z1, cmap=cmap, antialiased=False, norm=norm, rstride=1, cstride=1)
ax3d.set_title('Surface Plot in Matplotlib')
ax3d.set_xlabel('X')
ax3d.set_ylabel('Y')
ax3d.set_zlabel('Z')
plt.show()
xdata = np.linspace(-3, 3, 1000)
ydata = np.linspace(-3, 3, 1000)
X, Y = np.meshgrid(xdata, ydata)
Z1 = X ** 2 + Y ** 2
Z2 = Z1.copy()
Z3 = Z1.copy()
Z2[np.multiply(X, Y) <= 3] = np.nan
Z3[np.multiply(X, Y) == 3] = np.nan
plt.figure(figsize=(8, 6))
ax3d = plt.axes(projection='3d')
ax3d.contourf(X, Y, Z1, zdir='z', offset=0, cmap='Greys')
ax3d.contourf(X, Y, Z2, zdir='z', offset=0, cmap='YlGnBu')
ax3d.plot_surface(X, Y, Z1, cmap='Greys', antialiased=True, vmin=-np.nanmin(Z1), vmax=np.nanmax(Z1), alpha=.5)
ax3d.plot_surface(X, Y, Z2, cmap='YlGnBu', antialiased=True, vmin=-np.nanmin(Z2), vmax=np.nanmax(Z2), alpha=.5)
ax3d.set_title('Surface Plot in Matplotlib')
ax3d.set_xlabel('X')
ax3d.set_ylabel('Y')
ax3d.set_zlabel('Z')
plt.show()
I have plotted a contourf (and contour to plot lines on top) graph with colorbar and equispaced data levels and it is ok. However, when I add an arbitrary level value(1.1 in the example), it is not representd correctly in the colorbar.
In the reproducible example below, besides the levels with steps of 0.5, a new value with a step of 0.1 is added. In the colorbar, the distances are the same.
How can I correct this?
Thanks in advance
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(-2.0, 3.0, 0.025)
y = np.arange(-2.0, 3.0, 0.025)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) * 2
levels = np.arange(-2,3,1)
levels=np.append(levels,1.1)
levels=np.sort(levels)
fig, ax = plt.subplots()
c = plt.contourf(X, Y, Z, levels=levels,alpha=0.15)
cs = plt.contour(X, Y, Z, levels=levels)
ax.clabel(cs, inline=1, fontsize=10)
cbar=plt.colorbar(c)
plt.show()
You should pass spacing parameter to plt.colorbar:
spacing: uniform spacing gives each discrete color the same space; proportional makes the space proportional to the data interval.
cbar=plt.colorbar(c, spacing = 'proportional')
With these lines, currently I'm having this kind of figure.
fig, ax = plt.subplots()
X, Y = np.meshgrid(x, y)
cs = ax.contourf(X, Y, Z, 50, cmap=cm.get_cmap('jet')) # linear mapping
#cs = ax.contourf(X, Y, Z, 50, locator=ticker.LogLocator(), cmap=cm.get_cmap('jet')) # log mapping
cbar = fig.colorbar(cs)
plt.show()
Now I want to plot this in Log scale, so if I activate the commented line, I get this kind of result which seems to ignore 'levels' argument which is set to '50'.
I've reached this post (Python matplotlib contour plot logarithmic color scale), but I am pretty sure that there is a way in which I do not have to set all the values of levels manually.
Does anyone has a comment, or any other handy python functions for logarithmatic contour plot with many levels?
Setting the number of levels as a integer doesn't work for logscale but you can easily set the values with np.logspace(np.log10(z.min()),np.log10(z.max()), 50). Matplotlib 3.3.3 seems to have some difficulties in correctly formatting the colorbar ticks in this case, so you need to manually adjust them a bit.
import matplotlib.pyplot as plt
from matplotlib import ticker, cm
import numpy as np
x = np.linspace(-3.0, 3.0, 100)
y = np.linspace(-2.0, 2.0, 100)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-(X)**2 - (Y)**2)
Z2 = np.exp(-(X * 10)**2 - (Y * 10)**2)
z = Z1 + 50 * Z2
fig, ax = plt.subplots()
n_levels = 50
cs = ax.contourf(X, Y, z,
np.logspace(np.log10(z.min()),np.log10(z.max()), n_levels),
locator=ticker.LogLocator(),
cmap=cm.jet
)
cbar = fig.colorbar(cs)
cbar.locator = ticker.LogLocator(10)
cbar.set_ticks(cbar.locator.tick_values(z.min(), z.max()))
cbar.minorticks_off()
I have a contour plot that I color using the Yellow-Green color map named YlGn
The labels at the darker fields do not appear well, as they are black.
Is there a method to color the labels in the inverse of the used colormap? i.e., to color the 0.39 label in white, and the 0.15 label in dark green, and the labels in-between accordingly.
I used CS3 = plt.contourf(X, Z, M, levels, cmap=plt.cm.YlGn, extend='both') for the filled contour and CS4 = plt.contour(CS3, colors=('k',), linewidths=(1,)) for the line contour, and finally plt.clabel(CS4, linewidths=2, fmt='%2.2f', colors='k', fontsize=14) for the labels.
However when I tried to add cmap=plt.cm.YlGn_r and removed the colors='k' to the labels (to reverse the colors) it did nothing.
Note: The codes used here are partially taken from this documentation page, but with some modifications to fit my data.
Here are some data to try at a Jupyter notebook:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'
delta = 0.025
x = np.arange(1.0, 3.0, delta)
y = np.arange(1.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
plt.figure()
CS = plt.contour(X, Y, Z, cmap=plt.cm.YlGn_r)
CS2 = plt.contourf(X, Y, Z, color='k')
plt.clabel(CS, fontsize=10,color='k')
plt.title('Simplest default with labels')
I guess you mixed up the arguments to contour and contourf. Applying the reverse colormap to contour works fine.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import mlab
delta = 0.025
x = np.arange(1.0, 3.0, delta)
y = np.arange(1.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
plt.figure()
CS2 = plt.contourf(X, Y, Z, cmap=plt.cm.YlGn_r)
CS = plt.contour(X, Y, Z, cmap=plt.cm.YlGn)
plt.clabel(CS, fontsize=10)
plt.title('Simplest default with labels')
plt.show()
To use the same colormap for the lines as for the fills, but then use a different colormap for the labels, you need to define the colors manually. But the use of the existing levels helps you do that quite efficiently.
CS2 = plt.contourf(X, Y, Z, cmap=plt.cm.YlGn_r)
CS = plt.contour(CS2, cmap=plt.cm.YlGn_r)
plt.clabel(CS, fontsize=10, colors=plt.cm.Reds(CS.norm(CS.levels)))
I'm looking to plot a 2D probability distribution with one of its marginals in a single plot using Python and matplotlib. I'm almost there, but the line in the plot is always drawn in front of the surface, instead of being occluded properly. How do I fix this?
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
fig = plt.figure()
ax = fig.gca(projection='3d')
delta = 0.05
f = 0.5
X, Y = np.meshgrid(np.arange(-3.0, 3.0, delta),
np.arange(-3.0, 3.0, delta))
xy = np.hstack((X.flatten()[:, None], Y.flatten()[:, None]))
p1 = stats.multivariate_normal.pdf(xy, mean=[1, -1], cov=(np.eye(2) * 0.28 * f))
p2 = stats.multivariate_normal.pdf(xy, mean=[-1, 1], cov=(np.eye(2) * 0.5 * f))
p = 0.3 * p1 + 0.7 * p2
Z = p.reshape(len(X), len(X))
plt.plot(X[0, :], np.zeros(len(X)) + 3, np.sum(Z, 0) * 0.05) # , color='red')
ax.plot_surface(X, Y, Z, alpha=1.0, cmap='jet', linewidth=0.1, rstride=2, cstride=2)
ax.set_xlabel('Object colour')
ax.set_ylabel('Illumination colour')
ax.set_zlabel('Probability density')
ax.set_zlim(min(cont_offset, np.min(Z)), max(np.max(Z), cont_offset))
plt.show()
The built-in contour function at least gets the z-order right; if you don't want the full thing, you could cheat with a calculated Z. To start, replacing your call to plt.plot with this:
from matplotlib import cm
cset = ax.contour(X, Y, Z, zdir='y', offset=3, cmap='binary')
cset = ax.contour(X, Y, Z, zdir='x', offset=-3, cmap='Blues')
Faking up Z's for the contours, one way:
from matplotlib import cm
Zys = np.zeros_like(Z)
Zys[60,:] = Z.max(0)
cset = ax.contour(X, Y, Zys, zdir='y', offset=3, cmap='binary')
Zys = np.zeros_like(Z)
Zys[:,60] = Z.max(1)
cset = ax.contour(X, Y, Zys, zdir='x', offset=-3, cmap='Blues')
More ambitiously, somewhere in the contour code they're calculating the z-order...