4

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!

frixhax
  • 1,325
  • 3
  • 18
  • 30
  • It has to be marker=None, then I get litte marker-bars that are all of the same width, so something still doesn't work – frixhax Apr 24 '13 at 13:36
  • Indeed `marker='None'` kills the markers but I don't think this is the correct use of verts. I'm trying to find out at the minute but no luck. – Greg Apr 24 '13 at 13:42
  • Thanks! I've tried myself but I cannot find a comprehensive overview of how verts works in this context. And I don't understand why it works in the example, or rather what the difference in use is. – frixhax Apr 24 '13 at 14:10

2 Answers2

5

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

enter image description here*

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

enter image description here

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.

Joooeey
  • 3,394
  • 1
  • 35
  • 49
Greg
  • 11,654
  • 3
  • 44
  • 50
  • Thanks, that looks promising! Just to make sure (because in your example there is only one marker shape for both data points and I need individual rectangle sizes for each data point) - you say that each marker is defined by its own set of four individual corner positions, x1/y1 to x4/y4, relative to its x/y center? And that verts has to hold N tuples of those corner coordinates, if I have N x/y data points to plot and want each point to have its individual marker? In my example that would mean to keep y1 to y4 constant and change x1 to x4 as a function of the individual y-value, correct? – frixhax Apr 24 '13 at 14:50
  • That would seem correct to me so you would need to have verts as a N length list of tuples. It may be better to do this manually. If this becomes complex personally I would just use a for loop over N and set `h_val` in each iteration. – Greg Apr 24 '13 at 15:10
  • Ok, it doesn't work with individual markers. I tried verts as a list of N lists, each containing the four x/y tuples, and as a list of N tuples, each consisting of the four tuples. In both cases I get the error `assert vertices.ndim == 2`, so I can only give verts two elements. Any idea how this could work for N different markers? – frixhax Apr 24 '13 at 20:17
  • Okay so I would advise your do this manually, this is somewhat tricky and will require some effort I will update my solution as there is not much space here. – Greg Apr 24 '13 at 21:14
  • I tried something like that already, but when I plot each data point individually in a for-loop, I see no way to use a colormap and colorbar for all of them. Is there? – frixhax Apr 24 '13 at 21:28
  • Probably not, why do you need to? Perhaps I am not understanding your question still. – Greg Apr 24 '13 at 21:50
  • I have a intensity value z corresponding to every x/y data point, that I need colorcoded with a colormap. So each data points needs an individual color according to that map in addition to an individual marker shape. – frixhax Apr 24 '13 at 22:38
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/28867/discussion-between-greg-and-frixhax) – Greg Apr 25 '13 at 07:52
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!

frixhax
  • 1,325
  • 3
  • 18
  • 30