Understanding matplotlib verts - python

I'm trying to create custom markers in matplotlib for a scatter plot, where the markers are rectangles with fix height and varying width. The width of each marker is a function of the y-value. I tried it like this using this code as a template and assuming that if verts is given a list of N 2-D tuples it plots rectangles with the width of the corresponing first value and the height of the second (maybe this is already wrong, but then how else do I accomplish that?).
I have a list of x and y values, each containing angles in degrees. Then, I compute the width and height of each marker by
field_size = 2.
symb_vec_x = [(field_size / np.cos(i * np.pi / 180.)) for i in y]
symb_vec_y = [field_size for i in range(len(y))]
and build the verts list and plot everything with
symb_vec = list(zip(symb_vec_x, symb_vec_y))
fig = plt.figure(1, figsize=(14.40, 9.00))
ax = fig.add_subplot(1,1,1)
sc = ax.scatter(ra_i, dec_i, marker='None', verts=symb_vec)
But the resulting plot is empty, no error message however. Can anyone tell me what I did wrong with defining the verts and how to do it right?
Thanks!

As mentioned 'marker='None' need to be removed then the appropriate way to specify a rectangle with verts is something like
verts = list(zip([-10.,10.,10.,-10],[-5.,-5.,5.,5]))
ax.scatter([0.5,1.0],[1.0,2.0], marker=(verts,0))
The vertices are defined as ([x1,x2,x3,x4],[y1,y2,y3,y4]) so attention must be paid to which get minus signs etc.
This (verts,0) is mentioned in the docs as
For backward compatibility, the form (verts, 0) is also accepted,
but it is equivalent to just verts for giving a raw set of vertices
that define the shape.
However I find using just verts does not give the correct shape.
To automate the process you need to do something like
v_val=1.0
h_val=2.0
verts = list(zip([-h_val,h_val,h_val,-h_val],[-v_val,-v_val,v_val,v_val]))
Basic example:
import pylab as py
ax = py.subplot(111)
v_val=1.0
h_val=2.0
verts = list(zip([-h_val,h_val,h_val,-h_val],[-v_val,-v_val,v_val,v_val]))
ax.scatter([0.5,1.0],[1.0,2.0], marker=(verts,0))
*
edit
Individual markers
So you need to manually create a vert for each case. This will obviously depend on how you want your rectangles to change point to point. Here is an example
import pylab as py
ax = py.subplot(111)
def verts_function(x,y,r):
# Define the vertex's multiplying the x value by a ratio
x = x*r
y = y
return [(-x,-y),(x,-y),(x,y),(-x,y)]
n=5
for i in range(1,4):
ax.scatter(i,i, marker=(verts_function(i,i,0.3),0))
py.show()
so in my simple case I plot the points i,i and draw rectangles around them. The way the vert markers are specified is non intuitive. In the documentation it's described as follows:
verts: A list of (x, y) pairs used for Path vertices. The center of
the marker is located at (0,0) and the size is normalized, such that
the created path is encapsulated inside the unit cell.
Hence, the following are equivalent:
vert = [(-300.0, -1000), (300.0, -1000), (300.0, 1000), (-300.0, 1000)]
vert = [(-0.3, -1), (0.3, -1), (0.3, 1), (-0.3, 1)]
e.g they will produce the same marker. As such I have used a ratio, this is where you need to do put in the work. The value of r (the ratio) will change which axis remains constant.
This is all getting very complicated, I'm sure there must be a better way to do this.

I got the solution from Ryan of the matplotlib users mailing list. It's quite elegant, so I will share his example here:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
n = 100
# Get your xy data points, which are the centers of the rectangles.
xy = np.random.rand(n,2)
# Set a fixed height
height = 0.02
# The variable widths of the rectangles
widths = np.random.rand(n)*0.1
# Get a color map and make some colors
cmap = plt.cm.hsv
colors = np.random.rand(n)*10.
# Make a normalized array of colors
colors_norm = colors/colors.max()
# Here's where you have to make a ScalarMappable with the colormap
mappable = plt.cm.ScalarMappable(cmap=cmap)
# Give it your non-normalized color data
mappable.set_array(colors)
rects = []
for p, w in zip(xy, widths):
xpos = p[0] - w/2 # The x position will be half the width from the center
ypos = p[1] - height/2 # same for the y position, but with height
rect = Rectangle( (xpos, ypos), w, height ) # Create a rectangle
rects.append(rect) # Add the rectangle patch to our list
# Create a collection from the rectangles
col = PatchCollection(rects)
# set the alpha for all rectangles
col.set_alpha(0.3)
# Set the colors using the colormap
col.set_facecolor( cmap(colors_norm) )
# No lines
col.set_linewidth( 0 )
#col.set_edgecolor( 'none' )
# Make a figure and add the collection to the axis.
fig = plt.figure()
ax = fig.add_subplot(111)
ax.add_collection(col)
# Add your ScalarMappable to a figure colorbar
fig.colorbar(mappable)
plt.show()
Thank you, Ryan, and everyone who contributed their ideas!

Related

How to adjust the marker size of a scatter plot, so that it matches a given radius? (Using matplotlib transformations)

I want to have the markers of a scatter plot match a radius given in the data coordinates.
I've read in pyplot scatter plot marker size, that the marker size is given as the area of the marker in points^2.
I tried transforming the given radius into points via axes.transData and then calculating the area via pi * r^2, but I did not succeed.
Maybe I did the transformation wrong.
It could also be that running matplotlib from WSL via VcXsrv is causing this problem.
Here is an example code of what I want to accomplish, with the results as an image below the code:
import matplotlib.pyplot as plt
from numpy import pi
n = 16
# create a n x n square with a marker at each point
x_data = []
y_data = []
for x in range(n):
for y in range(n):
x_data.append(x)
y_data.append(y)
fig,ax = plt.subplots(figsize=[7,7])
# important part:
# calculate the marker size so that the markers touch
# radius in data coordinates:
r = 0.5
# radius in display coordinates:
r_ = ax.transData.transform([r,0])[0] - ax.transData.transform([0,0])[0]
# marker size as the area of a circle
marker_size = pi * r_**2
ax.scatter(x_data, y_data, s=marker_size, edgecolors='black')
plt.show()
When I run it with s=r_ I get the result on the left and with s=marker_size I get the result on the right of the following image:
The code looks perfectly fine. You can see this if you plot just 4 points (n=2):
The radius is (almost) exactly the r=0.5 coordinate-units that you wanted to have. wait, almost?!
Yes, the problem is that you determine the coordinate-units-to-figure-points size before plotting, so before setting the limits, which influence the coordinate-units but not the overall figure size...
Sounded strange? Perhaps. The bottom line is that you determine the coordinate transformation with the default axis-limits ((0,1) x (0,1)) and enlarges them afterwards to (-0.75, 15.75)x(-0.75, 15.75)... but you are not reducing the marker-size.
So either set the limits to the known size before plotting:
ax.set_xlim((0,n-1))
ax.set_ylim((0,n-1))
The complete code is:
import matplotlib.pyplot as plt
from numpy import pi
n = 16
# create a n x n square with a marker at each point as dummy data
x_data = []
y_data = []
for x in range(n):
for y in range(n):
x_data.append(x)
y_data.append(y)
# open figure
fig,ax = plt.subplots(figsize=[7,7])
# set limits BEFORE plotting
ax.set_xlim((0,n-1))
ax.set_ylim((0,n-1))
# radius in data coordinates:
r = 0.5 # units
# radius in display coordinates:
r_ = ax.transData.transform([r,0])[0] - ax.transData.transform([0,0])[0] # points
# marker size as the area of a circle
marker_size = pi * r_**2
# plot
ax.scatter(x_data, y_data, s=marker_size, edgecolors='black')
plt.show()
... or scale the markers's size according to the new limits (you will need to know them or do the plotting again)
# plot with invisible color
ax.scatter(x_data, y_data, s=marker_size, color=(0,0,0,0))
# calculate scaling
scl = ax.get_xlim()[1] - ax.get_xlim()[0]
# plot correctly (with color)
ax.scatter(x_data, y_data, s=marker_size/scl**2, edgecolors='blue',color='red')
This is a rather tedious idea, because you need to plot the data twice but you keep the autosizing of the axes...
There obviously remains some spacing. This is due a misunderstanding of the area of the markers. We are not talking about the area of the symbol (in this case a circle) but of a bounding box of the marker (imagine, you want to control the size of a star or an asterix as marker... one would never calculate the actual area of the symbol).
So calculating the area is not pi * r_**2 but rather a square: (2*r_)**2
# open figure
fig,ax = plt.subplots(figsize=[7,7])
# setting the limits
ax.set_xlim((0,n-1))
ax.set_ylim((0,n-1))
# radius in data coordinates:
r = 0.5 # units
# radius in display coordinates:
r_ = ax.transData.transform([r,0])[0] - ax.transData.transform([0,0])[0] # points
# marker size as the area of a circle
marker_size = (2*r_)**2
# plot
ax.scatter(x_data, y_data, s=marker_size,linewidths=1)
#ax.plot(x_data, y_data, "o",markersize=2*r_)
plt.show()
As soon as you add an edge (so a non-zero border around the markers), they will overlap:
If even gets more confusing if you use plot (which is faster if all markers should have the same size as the docs state as "Notes"). The markersize is only the width (not the area) of the marker:
ax.plot(x_data, y_data, "o",markersize=2*r_,color='magenta')

How to return colour of point in scatterplot Python

Is there a way to obtain the colour (or a simple yes/no answer if colour is present) from the x, y coordinates of a matplotlib scatterplot?
Basically I want to give a coordinate (x, y) and know if there is a coloured circle at that position in my plot.
Any help will be appreciated.
To determine if there is a scatter circle at a position (xi,yi) is not straight forward. The problem is that (xi,yi) are given in data coordinates, while the circle is drawn as a circle in display coordinates. This means that the circle in display coordinates might acutally be an ellipse in data coordinates, when the axis scaling is different for x and y axis.
Matplotlib contains some functionality to determine if a point given in display coordinates is within the extent of an artist. I order to use this, the canvas first has to be drawn. One might then simulate a mouse event at the position (xi,yi) and detect if it hits any artist from the scatter. The respective color can then be retrieved.
import numpy as np; np.random.seed(0)
import matplotlib.pyplot as plt
import matplotlib.backend_bases
x = np.random.rayleigh(size=10)
y = np.random.normal(size=10)
c = np.random.rand(10)
fig, ax = plt.subplots()
sc = ax.scatter(x,y,c=c, s=49, picker=True)
fig.canvas.draw()
def color_at_pos(xi,yi):
xi, yi = ax.transData.transform((xi,yi))
me = matplotlib.backend_bases.LocationEvent("no", fig.canvas, xi, yi)
cont, ind = sc.contains(me)
return sc.cmap(sc.norm(sc.get_array()[ind["ind"]]))
col = color_at_pos(1.25931,0.145889)
print col
col = color_at_pos(0.7,0.7)
print col
plt.show()
Here the first point (1.25931,0.145889) is actuall within two circles, so two colors are printed, while the second point is not in any circle and an empty array is printed.
You can use get_color() e.g.
a = plt.plot(x,c, color="blue", linewidth=2.0, linestyle="-")
b = plt.plot(x,s, color="red", linewidth=2.0, linestyle="-")
print a[0].get_color()
print b[0].get_color()
>>blue
>>red
Or you could assign the returned colors to variables to work with:
color_a = a[0].get_color()
if color_a == 'blue':
..do something

Select starting color in matplotlib colormap

I have the figure shown below. Presently the figure's colorscheme uses the entire range of the colormap (mpl.cm.Paired). What I want to do, and have been unable to figure out, is how to limit matplotlib to use only a subset of the colormap. In this case I am trying to get the starting color to be a darker shade of blue. Here's the plotting section of my code:
Figure = plt.figure(figsize=(22,10))
Map = Basemap(projection='robin', lon_0=0, resolution='l')
x, y = Map(LONS, LATS)
levels = np.arange(0, 4100, 100)
fcp = Map.contourf(x, y, data, levels, interpolation="bicubic", cmap=mpl.cm.Paired)
cb = Map.colorbar(fcp, "bottom", size="5%", pad='5%', extendrect=False)
cb.ax.tick_params(labelsize=18)
cb.solids.set_edgecolor("face")
cb.set_label("metres",fontsize=18)
cb.ax.set_aspect(0.047)
Map.drawcoastlines(linewidth=1)
Map.drawmapboundary(linewidth=1)
Map.drawmeridians([-150,-100,-50,0,50,100, 150],labels=[1,1,1,0],fontsize=18)
Map.drawparallels([-60,-30,0,30,60],labels=[1,1,1,1],fontsize=18)
One way to do this would be to call the function mpl.cm.Paired() for a subset of the normalised range (i.e., [0-1]) and then use the list of colors that it returns to define a new colormap:
import matplotlib.colors as mcol
lvTmp = np.linspace(0.1,1.0,len(levels)-1)
cmTmp = mlp.cm.Paired(lvTmp)
newCmap = mcol.ListedColormap(cmTmp)
You'll need to fiddle about with the 0.1 value in that linspace to get the start color that you want from the built in colormap.

How to assign colors to circles in matplotlib?

Somehow, assigning colors to circles works different from assigning colors in scatter plots:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6,6)) # give plots a rectangular frame
N = 4
r = 0.1
pos = 2.*np.random.rand(N,2) -1
# give different points different color
col = 1./N*np.arange(0,N)
# Method 1
for i,j,k in zip(pos[:,0],pos[:,1],col):
circle = plt.Circle((i,j), r, color = k)
fig.gca().add_artist(circle)
plt.show()
# Method 2
plt.scatter(pos[:,0],pos[:,1], c = col)
plt.show()
Why does Method 2 work while Method 1 gives the following error:
ValueError: to_rgba: Invalid rgba arg "0.0"
to_rgb: Invalid rgb arg "0.0"
cannot convert argument to rgb sequence
The error you're getting is because you need to use the string representation of the float rather than the float value directly, for example:
circle = plt.Circle((i,j), r, color=`k`) # or str(k)
Note in the above I'm using backward ticks, a shorthand for str(k), which converts a float to string, like str(.75) = "0.75", and will give different colors for each k value.
Here are the docs on to_rgba to which the error refers.
Edit:
There are many ways to specify a color in matplotlib. In the above, you set the float that references a colormap through a string representation of a float. The colormap for this could then be set through a PolyCollection.
In your case, to use Circle more like scatter, it's probably easiest to just set the color directly, and that can be done using an rgba tuple, for example, one that can be looked up from a colormap.
Below is an example using three different colormaps for the different y ranges.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as clrs
import matplotlib
N, r = 200, .1
cms = matplotlib.cm
maps = [cms.jet, cms.gray, cms.autumn]
fig = plt.figure(figsize=(6,6)) # give plots a rectangular frame
ax = fig.add_subplot(111)
pos = 2.999*np.random.rand(N,2)
for x, y in pos:
cmi = int(y) # an index for which map to use based on y-value
#fc = np.random.random() # use this for random colors selected from regional map
fc = x/3. # use this for x-based colors
color = maps[cmi](fc) # get the right map, and get the color from the map
# ie, this is like, eg, color=cm.jet(.75) or color=(1.0, 0.58, 0.0, 1.0)
circle = plt.Circle((x,y), r, color=color) # create the circle with the color
ax.add_artist(circle)
ax.set_xlim(0, 3)
ax.set_ylim(0, 3)
plt.show()
In the above I made the color for each band vary with x because I thought it looked good, but you can also do random colors, of course. Just switch which fc line is being used:
In order to use the predetermined colors of matplot lib, you should pass strings to the color field. In this case 'k' will be black color instead of simply k.
This code did not give errors for me:
for i,j,k in zip(pos[:,0],pos[:,1],col):
circle = plt.Circle((i,j), r, color = 'k')
fig.gca().add_artist(circle)
plt.show()
Please make sure in your next question you provide code that is runnable. In this case variables N and r were not defined.

Matplotlib Rectangle With Color Gradient Fill

I want to draw a rectangle, with a gradient color fill from left to right, at an arbitrary position with arbitrary dimensions in my axes instance (ax1) coordinate system.
My first thought was to create a path patch and somehow set its fill as a color gradient. But according to THIS POST there isn't a way to do that.
Next I tried using a colorbar. I created a second axes instance ax2 using fig.add_axes([left, bottom, width, height]) and added a color bar to that.
ax2 = fig.add_axes([0, 0, width, height/8])
colors = [grad_start_color, grad_end_color]
index = [0.0, 1.0]
cm = LinearSegmentedColormap.from_list('my_colormap', zip(index, colors))
colorbar.ColorbarBase(ax2, cmap=cm, orientation='horizontal')
But the positional parameters passed to fig.add_axes() are in the coordinate system of fig, and don't match up with the coordinate system of ax1.
How can I do this?
I have been asking myself a similar question and spent some time looking for the answer to find in the end that this can quite easily be done by imshow:
from matplotlib import pyplot
pyplot.imshow([[0.,1.], [0.,1.]],
cmap = pyplot.cm.Greens,
interpolation = 'bicubic'
)
It is possible to specify a colormap, what interpolation to use and much more. One additional thing, I find very interesting, is the possibility to specify which part of the colormap to use. This is done by means of vmin and vmax:
pyplot.imshow([[64, 192], [64, 192]],
cmap = pyplot.cm.Greens,
interpolation = 'bicubic',
vmin = 0, vmax = 255
)
Inspired by this example
Additional Note:
I chose X = [[0.,1.], [0.,1.]] to make the gradient change from left to right. By setting the array to something like X = [[0.,0.], [1.,1.]], you get a gradient from top to bottom. In general, it is possible to specify the colour for each corner where in X = [[i00, i01],[i10, i11]], i00, i01, i10 and i11 specify colours for the upper-left, upper-right, lower-left and lower-right corners respectively. Increasing the size of X obviously allows to set colours for more specific points.
did you ever solve this? I wanted the same thing and found the answer using the coordinate mapping from here,
#Map axis to coordinate system
def maptodatacoords(ax, dat_coord):
tr1 = ax.transData.transform(dat_coord)
#create an inverse transversion from display to figure coordinates:
fig = ax.get_figure()
inv = fig.transFigure.inverted()
tr2 = inv.transform(tr1)
#left, bottom, width, height are obtained like this:
datco = [tr2[0,0], tr2[0,1], tr2[1,0]-tr2[0,0],tr2[1,1]-tr2[0,1]]
return datco
#Plot a new axis with a colorbar inside
def crect(ax,x,y,w,h,c,**kwargs):
xa, ya, wa, ha = maptodatacoords(ax, [(x,y),(x+w,y+h)])
fig = ax.get_figure()
axnew = fig.add_axes([xa, ya, wa, ha])
cp = mpl.colorbar.ColorbarBase(axnew, cmap=plt.get_cmap("Reds"),
orientation='vertical',
ticks=[],
**kwargs)
cp.outline.set_linewidth(0.)
plt.sca(ax)
Hopefully this helps anyone in the future who needs similar functionality. I ended up using a grid of patch objects instead.

Categories