问题
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!
回答1:
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.
回答2:
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!
来源:https://stackoverflow.com/questions/16189928/understanding-matplotlib-verts