Matplotlib: Color bar on contour without striping - python

In matplotlib, I'm looking to create an inset color bar to show the scale of my contour plot, but when I create the contour using contour, the color bar has white stripes running through it, whereas when I use contourf, the colorbar has the proper "smooth" appearance:
How can I get that nice smooth colorbar from the filled contour on my normal contour plot? I'd also be OK with a filled contour where the zero-level can be set to white, I imagine.
Here is code to generate this example:
from numpy import linspace, outer, exp
from matplotlib.pyplot import figure, gca, clf, subplots_adjust, subplot
from matplotlib.pyplot import contour, contourf, colorbar, xlim, ylim, title
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
# Make some data to plot - 2D gaussians
x = linspace(0, 5, 100)
y = linspace(0, 5, 100)
g1 = exp(-((x-0.75)/0.2)**2)
g2 = exp(-((y-4.25)/0.1)**2)
g3 = exp(-((x-3.5)/0.15)**2)
g4 = exp(-((y-1.75)/0.05)**2)
z = outer(g1, g2) + outer(g3, g4)
figure(1, figsize=(13,6.5))
clf()
# Create a contour and a contourf
for ii in range(0, 2):
subplot(1, 2, ii+1)
if ii == 0:
ca = contour(x, y, z, 125)
title('Contour')
else:
ca = contourf(x, y, z, 125)
title('Filled Contour')
xlim(0, 5)
ylim(0, 5)
# Make the axis labels
yt = text(-0.35, 2.55, 'y (units)', rotation='vertical', size=14);
xt = text(2.45, -0.4, 'x (units)', rotation='horizontal', size=14)
# Add color bar
ains = inset_axes(gca(), width='5%', height='60%', loc=2)
colorbar(ca, cax=ains, orientation='vertical', ticks=[round(xx*10.0)/10.0 for xx in linspace(0, 1)])
if ii ==1:
ains.tick_params(axis='y', colors='#CCCCCC')
subplots_adjust(left=0.05, bottom=0.09, right=0.98, top=0.94, wspace=0.12, hspace=0.2)
show()
Edit: I realize now that at the lower resolution, the white striping behavior is hard to distinguish from some light transparency. Here's an example with only 30 contour lines which makes the problem more obvious:
Edit 2: While I am still interested in figuring out how to do this in the general general case (like if there's negative values), in my specific case, I have determined that I can effectively create something that looks like what I want by simply setting the levels of a filled contour to start above the zero-level:
ca = contourf(x, y, z, levels=linspace(0.05, 1, 125))
Which basically looks like what I want:

A simple hack is to set the thickness of the lines in the colorbar to some higher value.
E.g. storing the colorbar object as cb and adding the following lines to your example
for line in cb.lines:
line.set_linewidth(3)
gives

Related

Add horizontal line with conditional coloring

I make a contourf plot using matplotlib.pyplot. Now I want to have a horizontal line (or something like ax.vspan would work too) with conditional coloring at y = 0. I will show you what I have and what I would like to get. I want to do this with an array, let's say landsurface that represents either land, ocean or ice. This array is filled with 1 (land), 2 (ocean) or 3 (ice) and has the len(locs) (so the x-axis).
This is the plot code:
plt.figure()
ax=plt.axes()
clev=np.arange(0.,50.,.5)
plt.contourf(locs,height-surfaceheight,var,clev,extend='max')
plt.xlabel('Location')
plt.ylabel('Height above ground level [m]')
cbar = plt.colorbar()
cbar.ax.set_ylabel('o3 mixing ratio [ppb]')
plt.show()
This is what I have so far:
This is what I want:
Many thanks in advance!
Intro
I'm going to use a line collection .
Because I have not your original data, I faked some data using a simple sine curve and plotting on the baseline the color codes corresponding to small, middle and high values of the curve
Code
Usual boilerplate, we need to explicitly import LineCollection
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection
Just to plot something, a sine curve (x r
x = np.linspace(0, 50, 101)
y = np.sin(0.3*x)
The color coding from the curve values (corresponding to your surface types) to the LineCollection colors, note that LineCollection requires that the colors are specified as RGBA tuples but I have seen examples using color strings, bah!
# 1 when near min, 2 when near 0, 3 when near max
z = np.where(y<-0.5, 1, np.where(y<+0.5, 2, 3))
col_d = {1:(0.4, 0.4, 1.0, 1), # blue, near min
2:(0.4, 1.0, 0.4, 1), # green, near zero
3:(1.0, 0.4, 0.4, 1)} # red, near max
# prepare the list of colors
colors = [col_d[n] for n in z]
In a line collection we need a sequence of segments, here I have decided to place my coded line at y=0 but you can just add a constant to s to move it up and down.
I admit that forming the sequence of segments is a bit tricky...
# build the sequence of segments
s = np.zeros(101)
segments=np.array(list(zip(zip(x,x[1:]),zip(s,s[1:])))).transpose((0,2,1))
# and fill the LineCollection
lc = LineCollection(segments, colors=colors, linewidths=5,
antialiaseds=0, # to prevent artifacts between lines
zorder=3 # to force drawing over the curve) lc = LineCollection(segments, colors=colors, linewidths=5) # possibly add zorder=...
Finally, we put everything on the canvas
# plot the function and the line collection
fig, ax = plt.subplots()
ax.plot(x,y)
ax.add_collection(lc)
I would suggest adding an imshow() with proper extent, e.g.:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colorbar as colorbar
import matplotlib.colors as colors
### generate some data
np.random.seed(19680801)
npts = 50
x = np.random.uniform(0, 1, npts)
y = np.random.uniform(0, 1, npts)
X,Y=np.meshgrid(x,y)
z = x * np.exp(-X**2 - Y**2)*100
### create a colormap of three distinct colors for each landmass
landmass_cmap=colors.ListedColormap(["b","r","g"])
x_land=np.linspace(0,1,len(x)) ## this should be scaled to your "location"
## generate some fake landmass types (either 0, 1, or 2) with probabilites
y_land=np.random.choice(3, len(x), p=[0.1, 0.6, 0.3])
print(y_land)
fig=plt.figure()
ax=plt.axes()
clev=np.arange(0.,50.,.5)
## adjust the "height" of the landmass
x0,x1=0,1
y0,y1=0,0.05 ## y1 is the "height" of the landmass
## make sure that you're passing sensible zorder here and in your .contourf()
im = ax.imshow(y_land.reshape((-1,len(x))),cmap=landmass_cmap,zorder=2,extent=(x0,x1,y0,y1))
plt.contourf(x,y,z,clev,extend='max',zorder=1)
ax.set_xlim(0,1)
ax.set_ylim(0,1)
ax.plot()
ax.set_xlabel('Location')
ax.set_ylabel('Height above ground level [m]')
cbar = plt.colorbar()
cbar.ax.set_ylabel('o3 mixing ratio [ppb]')
## add a colorbar for your listed colormap
cax = fig.add_axes([0.2, 0.95, 0.5, 0.02]) # x-position, y-position, x-width, y-height
bounds = [0,1,2,3]
norm = colors.BoundaryNorm(bounds, landmass_cmap.N)
cb2 = colorbar.ColorbarBase(cax, cmap=landmass_cmap,
norm=norm,
boundaries=bounds,
ticks=[0.5,1.5,2.5],
spacing='proportional',
orientation='horizontal')
cb2.ax.set_xticklabels(['sea','land','ice'])
plt.show()
yields:

Python contour plotting wrong values with plot_surface

I want to plot a surface in Matplotlib consisting of zeros everywhere, except for a rectangular region centered in (0, 0), with sides (Dx, Dy), consisting of ones - kind of like a table, if you wil; I can do that using the plot_surface command, no worries there. I also want to plot its projections in the "x" and "y" directions (as in this demo) and that's when the results become weird: Python seems to be interpolating my amplitude values (which, again, should be either zero or one) for the contour plots and showing some lines with values that do not correspond to my data points.
This is what I'm doing:
import numpy
from matplotlib import pylab
from mpl_toolkits.mplot3d import axes3d
Dx = 1. # Define the sides of the rectangle
Dy = 2.
x_2D = numpy.linspace(-Dx, Dx, 100) # Create the mesh points
y_2D = numpy.linspace(-Dy, Dy, 100)
x_mesh, y_mesh = numpy.meshgrid(x_2D, y_2D)
rect_2D = numpy.zeros(x_mesh.shape) # All values of "rect_2D" are zero...
for i in range(x_2D.size):
for j in range(y_2D.size):
if numpy.abs(x_mesh[i, j]) <= Dx/2 and numpy.abs(y_mesh[i, j]) <= Dy/2:
rect_2D[i, j] = 1. # ... except these ones
fig = pylab.figure(figsize=(9, 7))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x_mesh, y_mesh, rect_2D, alpha=0.3)
ax.contour(x_mesh, y_mesh, rect_2D, zdir='x', offset=-1.5, cmap=pylab.cm.brg)
ax.contour(x_mesh, y_mesh, rect_2D, zdir='y', offset=3, cmap=pylab.cm.brg)
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-3, 3)
ax.set_zlim(0., 1.5)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
The resulting figure has a dark green line with amplitude a bit below 0.8 on both the "x" and "y" projections, which does not exist in my "rect_2D" variable. Does anyone knows if this is a bug or if there is a mistake in my code? Any suggestions on how to get rid of it? Thanks in advance!
Add a levels = [0], kwarg to your ax.contour call. This specifies where along the zdir axis your contours are computed. See mpl.axes.Axes.contour docstring for more info.
The problem is that without specifying levels, contour automatically computes the locations to plot contours and one of these contours is selected just past the 'edge of your table', but before your Zdata is 0. At these points contour interpolates between 0 and 1.

Set a colormap under a graph

I know this is well documented, but I'm struggling to implement this in my code.
I would like to shade the area under my graph with a colormap. Is it possible to have a colour, i.e. red from any points over 30, and a gradient up until that point?
I am using the method fill_between, but I'm happy to change this if there is a better way to do it.
def plot(sd_values):
plt.figure()
sd_values=np.array(sd_values)
x=np.arange(len(sd_values))
plt.plot(x,sd_values, linewidth=1)
plt.fill_between(x,sd_values, cmap=plt.cm.jet)
plt.show()
This is the result at the moment. I have tried axvspan, but this doesnt have cmap as an option. Why does the below graph not show a colormap?
I'm not sure if the cmap argument should be part of the fill_between plotting command. In your case probably want to use the fill() command btw.
These fill commands create polygons or polygon collections. A polygon collection can take a cmap but with fill there is no way of providing the data on which it should be colored.
What's (for as far as i know) certainly not possible is to fill a single polygon with a gradient as you wish.
The next best thing is to fake it. You can plot a shaded image and clip it based on the created polygon.
# create some sample data
x = np.linspace(0, 1)
y = np.sin(4 * np.pi * x) * np.exp(-5 * x) * 120
fig, ax = plt.subplots()
# plot only the outline of the polygon, and capture the result
poly, = ax.fill(x, y, facecolor='none')
# get the extent of the axes
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
# create a dummy image
img_data = np.arange(ymin,ymax,(ymax-ymin)/100.)
img_data = img_data.reshape(img_data.size,1)
# plot and clip the image
im = ax.imshow(img_data, aspect='auto', origin='lower', cmap=plt.cm.Reds_r, extent=[xmin,xmax,ymin,ymax], vmin=y.min(), vmax=30.)
im.set_clip_path(poly)
The image is given an extent which basically stretches it over the entire axes. Then the clip_path makes it only showup where the fill polygon is drawn.
I think all you need is to do the plot of the data one at a time, like:
import numpy
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.colors as colors
# Create fake data
x = numpy.linspace(0,4)
y = numpy.exp(x)
# Now plot one by one
bar_width = x[1] - x[0] # assuming x is linealy spaced
for pointx, pointy in zip(x,y):
current_color = cm.jet( min(pointy/30, 30)) # maximum of 30
plt.bar(pointx, pointy, bar_width, color = current_color)
plt.show()
Resulting in:

Grid Lines below the contour in Matplotlib

I have one question about the grid lines matplotlib.
I am not sure if this is possible to do or not.
I am plotting the following graph as shown in the image.
I won't give the entire code, since it is involving reading of files.
However the important part of code is here -
X, Y = np.meshgrid(smallX, smallY)
Z = np.zeros((len(X),len(X[0])))
plt.contourf(X, Y, Z, levels, cmap=cm.gray_r, zorder = 1)
plt.colorbar()
...
# Set Border width zero
[i.set_linewidth(0) for i in ax.spines.itervalues()]
gridLineWidth=0.1
ax.set_axisbelow(False)
gridlines = ax.get_xgridlines()+ax.get_ygridlines()
#ax.set_axisbelow(True)
plt.setp(gridlines, 'zorder', 5)
ax.yaxis.grid(True, linewidth=gridLineWidth, linestyle='-', color='0.6')
ax.xaxis.grid(False)
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
Now, my questions is like this -
If I put the grid lines below the contour, they disappear since they are below it.
If I put the grid line above the contour, they looks like what they are looking now.
However, what I would like to have is the grid lines should be visible, but should be below the black portion of the contour. I am not sure if that is possible.
Thank You !
In addition to specifying the z-order of the contours and the gridlines, you could also try masking the zero values of your contoured data.
Here's a small example:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
y = np.arange(-2*np.pi, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) - np.cos(Y)
Z = np.ma.masked_less(Z, 0) # you use mask_equal(yourData, yourMagicValue)
fig, ax = plt.subplots()
ax.contourf(Z, zorder=5, cmap=plt.cm.coolwarm)
ax.xaxis.grid(True, zorder=0)
ax.yaxis.grid(True, zorder=0)
And the output:

matplotlib: how can I convert a XYZ scatter to a pixel image?

I'm looking for some way in to convert a scatter plot (X vs Y, color normalized by Z) into a 2D "pixel" image. I.e. how can I plot a pixelized image where the pixels are colored according to a third variable?
In my case, I have a list of galaxies, each a with sky coordinate (X,Y) and a distance (Z). I want to make a pixelized image of X vs Y, with the pixels color normalized according to Z (e.g. the median Z value for the galaxies in that pixel).
I know I could do something like this with hexbin, but I would like to have square pixels, not hexagons. (Something more like what imshow produces).
I'm still learning python, so if there is a simple/quick way to do this (or clear instructions on how to do it the complicated way!) that'd be great.
Any help would be much appreciated!
Okay - there are two ways that you can do this. One would be for you to have a discreet number of bins for the distances (like d < 10pc, 10pc < d < 20pc, d> 20pc). This is relatively easy, all you need to do are a few loops - here is an example with 3:
raclose = []
ramid = []
rafar = []
decdlose = []
decmid = []
decfar = []
for ii in range(len(dist)):
if dist[ii] < 10.:
raclose.append(ra[ii])
decclose.append(dec[ii])
elif dist[ii] > 20.:
rafar.append(ra[ii])
decfar.append(dec[ii])
else:
ramid.append(ra[ii])
decmid.append(dec[ii])
plt.clf
ax1 = scatter(raclose, decclose, marker='o', s=20, color="darkgreen", alpha=0.6)
ax2 = scatter(ramid, decmid, marker='o', s=20, color="goldenrod", alpha=0.6)
ax3 = scatter(rafar, decfar, marker='o', s=20, color="firebrick", alpha=0.6)
line1 = Line2D(range(10), range(10), marker='o', color="darkgreen")
line2 = Line2D(range(10), range(10), marker='o',color="goldenrod")
line3 = Line2D(range(10), range(10), marker='o',color="firebrick")
plt.legend((line1,line2,line3),('d < 10pc','20pc > d > 10pc', 'd > 20pc'),numpoints=1, loc=3)
show()
Or you can do a contour plot, such that you stipulate RA on the x-axis and Dec on the y-axis and fill in the plot with the distances. Both RA and Dec are 1D arrays with the respective coordinates. Then you make a 2D array with the distance. Determine what the median/mean value of the distances are and then divide the 2D array by that value to normalize it. Finally, plot using a contour plot (using contourf or imshow), like:
import matplotlib.pyplot as plt
from matplotlib import cm
ax = pylab.contourf(RA,Dec,dists, levels=[1, 5, 10, 15], cmap=plt.cm.spectral)
cbar=pylab.colorbar()

Categories

Resources