Colormaps with a colorscale AND one color for unwanted values - python-3.x

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

Related

My cmap won't separate around 0, what exactly is wrong?

I'm trying to plot out a contour plot with a colour scheme that has one colour for positive values and one colour for negative values. I tried using the answer in Colorplot that distinguishes between positive and negative values but to no avail. I've attached a snippet of code below, with this code the cmap doesn't separate Red and Blue at 0 but at -40. What exactly is wrong?
import numpy as np
from matplotlib.colors import BoundaryNorm
from matplotlib.ticker import MaxNLocator
Vr=1438.7228 #some constants
Va=626.8932
dr=3.11
da=1.55
def func(x,y):
repulsive = Vr*np.log( ((y+x)**2 + dr**2)/((y-x)**2 + dr**2) )
attractive = Va*np.log( ((y+x)**2 + da**2)/((y-x)**2 + da**2) )
return (1.0/(4.0*x*y))*(repulsive-attractive)
x = np.linspace(1e-4,10,100)
y = np.linspace(1e-4,10,100)
X, Y = np.meshgrid(x, y)
Z = func(X,Y)
levels = MaxNLocator(nbins=15).tick_values(Z.min(), Z.max())
cmap = plt.get_cmap('RdBu')
norm = BoundaryNorm(levels, ncolors=cmap.N, clip=True)
sc = plt.contourf(X, Y, Z, norm=norm, cmap=cmap)
plt.colorbar(sc)
plt.show()

Buggy vectors in quiver plot (Gradient of of Voltage) with matplotlib

I edited some examples to make a simulation for the voltage superposition of 2 point charges and made a 3D surface plot, the code is the following:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
q1 = 2e-9
q2 = -2e-9
K = 9e9
#Charge1 position
x1 = 2.0
y1 = 4.0
#Charge2 position
x2 = 6.0
y2 = 4.0
x = np.linspace(0,8,50)
y = np.linspace(0,8,50)
x, y = np.meshgrid(x,y)
r1 = np.sqrt((x - x1)**2 + (y - y1)**2)
r2 = np.sqrt((x - x2)**2 + (y - y2)**2)
V = K*(q1/r1 + q2/r2)
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(x, y, V, rstride=1, cstride=1, cmap=cm.rainbow,
linewidth=0, antialiased=False)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
3D Surface
Now what I want to do is a contour plot with a vector (quiver) plot on top of it. I tried the following code, but I get a bunch of buggy vectors coming out of both charges, even the negative one:
fig2, ax2 = plt.subplots(1,1)
cp = ax2.contourf(x, y, V, cmap=cm.coolwarm)
fig2.colorbar(cp)
v,u = np.gradient(-V, 0.2, 0.2) #E = -∇V
ax2.quiver(x, y, u, v)
ax2.set_title("Point Charges")
plt.show()
Buggy vectors
I suspect that the long vectors are related to a division by zero. The vectors should come out of the positive charge and get into the negative one. But how would I go about fixing them? Thanks in advance.
Welcome to SO, very nice MWE. One option would be to exclude all vectors beyond a certain length by setting them to NaN. Here I use the 95th percentile.
r = np.sqrt(u**2 + v**2)
is_valid = r < np.percentile(r, 95)
u[~is_valid] = np.nan
v[~is_valid] = np.nan
x[~is_valid] = np.nan
y[~is_valid] = np.nan
fig2, ax2 = plt.subplots(1,1)
cp = ax2.contourf(x, y, V, cmap=cm.coolwarm)
fig2.colorbar(cp)
ax2.quiver(x, y, u, v)
ax2.set_title("Point Charges")
ax2.set_xlim(0, 8)
ax2.set_ylim(0, 8)
plt.show()

Find coordinate on curve

I have plotted curve created by a list with several values. How to find out the x-coordinate that correspond with y-coordinate 0.04400918? This value is not exactly included in the list that describes the curve. Thank you very much.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # 3d graph
from mpl_toolkits.mplot3d import proj3d # 3d graph
import matplotlib.pylab as pl
fig=pl.figure()
ax = Axes3D(fig)
x=[0.02554897, 0.02587839, 0.02623991, 0.02663096, 0.02704882, 0.02749103, 0.02795535, 0.02844018, 0.02894404, 0.02946527, 0.03000235]
y=[0.04739086, 0.0460989, 0.04481555, 0.04354088, 0.04227474, 0.04101689, 0.03976702, 0.03852497, 0.03729052, 0.0360633, 0.03484293]
z=[1.05764017e-18, 1.57788964e-18, 2.00281370e-18, 2.40500994e-18, 2.80239565e-18, 3.19420769e-18, 3.58001701e-18, 3.96024361e-18, 4.33484911e-18, 4.70364652e-18, 5.06672528e-18]
y_point=0.04400918
ax.plot3D(x,y,z)
plt.show()
Here is a specific resolution for your problem.
Some works have already been done for solving line-plane equation. This topic explains how to solve it. Even better, this snippet implements a solution.
For now, we only need to adapt it to our problem.
The first step is to find all the time the line is crossing the plan. To do that, we will iterate over the y dataset and collect all consecutive values when y_point is between them:
lines = []
for i in range(len(y) - 1):
if y[i] >= y_point and y_point >= y[i+1]:
lines.append([[x[i], y[i], z[i]], [x[i+1], y[i+1], z[i+1]]])
Then, for all of these lines, we will solve the intersection equation with the plane. We will use the function provided in sources above.
Finally, we will plot the results
Full code:
# Modules
import numpy as np
import matplotlib.pyplot as plt
# Data
x = [0.02554897, 0.02587839, 0.02623991, 0.02663096, 0.02704882, 0.02749103, 0.02795535, 0.02844018, 0.02894404, 0.02946527, 0.03000235]
y = [0.04739086, 0.0460989, 0.04481555, 0.04354088, 0.04227474, 0.04101689, 0.03976702, 0.03852497, 0.03729052, 0.0360633, 0.03484293]
z = [1.05764017e-18, 1.57788964e-18, 2.00281370e-18, 2.40500994e-18, 2.80239565e-18, 3.19420769e-18, 3.58001701e-18, 3.96024361e-18, 4.33484911e-18, 4.70364652e-18, 5.06672528e-18]
y_point = 0.04400918
# Source: https://rosettacode.org/wiki/Find_the_intersection_of_a_line_with_a_plane#Python
# Resolve intersection
def LinePlaneCollision(planeNormal, planePoint, rayDirection, rayPoint, epsilon=1e-6):
ndotu = planeNormal.dot(rayDirection)
if abs(ndotu) < epsilon:
raise RuntimeError("no intersection or line is within plane")
w = rayPoint - planePoint
si = -planeNormal.dot(w) / ndotu
Psi = w + si * rayDirection + planePoint
return Psi
# For all line, apply the solving process
def solveAllPoints(lines, y_point):
collision_points = []
for line in lines:
# Define plane
planeNormal = np.array([0, 1, 0]) # Plane normal (e.g. y vector)
planePoint = np.array([0, y_point, 0]) # Any point on the plane
# Define ray
rayDirection = line[1] - line[0] # Line direction
rayPoint = line[0] # Any point of the line
# Append point
collision_points.append(LinePlaneCollision(planeNormal, planePoint, rayDirection, rayPoint))
return collision_points
# Find all consecutive Y points crossing the plane.
# This function is only working for the given problem (intersection of the line
# with 1 plan defined by a normal vector = [0,1,0])
def getCrossingLines(y_point, x, y, z):
lines = []
for i in range(len(y) - 1):
if y[i] >= y_point and y_point >= y[i+1]:
lines.append([[x[i], y[i], z[i]], [x[i+1], y[i+1], z[i+1]]])
return np.array(lines)
# Get coordinates for drawing our plane
# Related topic: https://stackoverflow.com/questions/53115276/matplotlib-how-to-draw-a-vertical-plane-in-3d-figure
def getXYZPlane(x, y, z):
xs = np.linspace(min(x), max(x), 100)
zs = np.linspace(min(z), max(z), 100)
X, Z = np.meshgrid(xs, zs)
Y = np.array([y_point for _ in X])
return X, Y, Z
# Create plot
plt3d = plt.figure().gca(projection='3d')
ax = plt.gca()
# Draw data line
ax.plot3D(x,y,z)
# Plot plan
X, Y, Z = getXYZPlane(x, y, z)
ax.plot_surface(X, Y, Z)
# Draw crossing points (lines-planes)
lines = getCrossingLines(y_point, x, y , z)
for pt in solveAllPoints(lines, y_point):
ax.scatter(pt[0], pt[1], pt[2], color='green')
plt.show()
Output

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.

Is it possible to edit the inline labels of a contour plot after the inline label values are generated?

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)

Resources