Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding matplotlib verts

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!

like image 344
frixhax Avatar asked Mar 24 '23 04:03

frixhax


2 Answers

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.

like image 109
Greg Avatar answered Apr 01 '23 03:04

Greg


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!

like image 20
frixhax Avatar answered Apr 01 '23 04:04

frixhax