10

I am doing a scatter plot with square marker in matplotlib like this one:

small_marker.

I want to achieve something like this:

enter image description here

Which means I have to adjust the marker size and the figure size/ratio in such a way that there are no white space between markers. Also there should be a marker per index unit (x and y are both integers) so if y goes from 60 to 100, there should be 40 markers in y direction. At the moment I am tuning it manually. Any idea on what is the best way to achieve this?

elyase
  • 39,479
  • 12
  • 112
  • 119
  • 7
    Use `plt.imshow` or `plt.pcolor` instead! – David Zwicker May 29 '13 at 16:47
  • Fill your empty data positions with `np.nan` or use a masked array. How the colormap handles bad values is controlled with `set_bad` – tacaswell May 29 '13 at 17:20
  • @tcaswell, I dont have empty data – elyase May 29 '13 at 17:33
  • What are the spaces in your graph without a marker then? – tacaswell May 29 '13 at 17:35
  • 1
    @DavidZwicker, with plt.imshow or plt.color I will have to make transformations to my data(take it to 2D form). I could as well figure out the figure/marker size in my plot, and so I don't have to change my code. – elyase May 29 '13 at 17:37
  • @tcaswell, you are right but that doesn't represent a problem in my case. I thought you meant the empty white space between the markers. – elyase May 29 '13 at 17:39
  • but if you were to re-structure your data to use `imshow` that is how to handle the empty spaces. If you really don't want to do it the easy way, I would just draw each patch yourself. See `patch.Rectangle` and `patchCollection`. – tacaswell May 29 '13 at 18:59
  • `plt.pcolormesh` seems to be the best option for filled coloration of unstructured grids – eqzx Mar 28 '19 at 01:05

1 Answers1

7

I found two ways to go about this:

The first is based on this answer. Basically, you determine the number of pixels between the adjacent data-points and use it to set the marker size. The marker size in scatter is given as area.

fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')

# initialize a plot to determine the distance between the data points in pixel:    
x = [1, 2, 3, 4, 2, 3, 3]
y = [0, 0, 0, 0, 1, 1, 2]
s = 0.0
points = ax.scatter(x,y,s=s,marker='s')
ax.axis([min(x)-1., max(x)+1., min(y)-1., max(y)+1.])

# retrieve the pixel information:
xy_pixels = ax.transData.transform(np.vstack([x,y]).T)
xpix, ypix = xy_pixels.T

# In matplotlib, 0,0 is the lower left corner, whereas it's usually the upper 
# right for most image software, so we'll flip the y-coords
width, height = fig.canvas.get_width_height()
ypix = height - ypix

# this assumes that your data-points are equally spaced
s1 = xpix[1]-xpix[0]

points = ax.scatter(x,y,s=s1**2.,marker='s',edgecolors='none')
ax.axis([min(x)-1., max(x)+1., min(y)-1., max(y)+1.])

fig.savefig('test.png', dpi=fig.dpi)

The downside of this first approach is, that the symbols overlap. I wasn't able to find the flaw in the approach. I could manually tweak s1 to

s1 = xpix[1]-xpix[0] - 13. 

to give better results, but I couldn't determine a logic behind the 13..

Hence, a second approach based on this answer. Here, individual squares are drawn on the plot and sized accordingly. In a way it's a manual scatter plot (a loop is used to construct the figure), so depending on the data-set it could take a while.

This approach uses patchesinstead of scatter, so be sure to include

from matplotlib.patches import Rectangle  

Again, with the same data-points:

x = [1, 2, 3, 4, 2, 3, 3]
y = [0, 0, 0, 0, 1, 1, 2]
z = ['b', 'g', 'r', 'c', 'm', 'y', 'k'] # in your case, this is data
dx = [x[1]-x[0]]*len(x)   # assuming equally spaced data-points

# you can use the colormap like this in your case:
# cmap = plt.cm.hot 

fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
ax.axis([min(x)-1., max(x)+1., min(y)-1., max(y)+1.])

for x, y, c, h in zip(x, y, z, dx):
    ax.add_artist(Rectangle(xy=(x-h/2., y-h/2.), 
                  color=c,             # or, in your case: color=cmap(c)                  
                  width=h, height=h))  # Gives a square of area h*h

fig.savefig('test.png')

One comment on the Rectangle: The coordinates are the lower left corner, hence x-h/2.
This approach gives connected rectangles. If I looked closely at the output here, they still seemed to overlap by one pixel - again, I'm not sure this can be helped.

Community
  • 1
  • 1
Schorsch
  • 7,761
  • 6
  • 39
  • 65
  • I just found [this website](http://www.physics.ucdavis.edu/~dwittman/Matplotlib-examples/). Under *More Spatial Binning* another option is outlined, which may be an alternative, @elyase – Schorsch Jun 03 '13 at 11:40