Collection of markers with size set in data units

I am trying to make scatter plot, where I can specify the marker size in data units, instead of display-area units.

I tried to look at the PathCollection object produced by Axes.scatter, but I wasn’t able to work out how to set the transforms. I’m willing to mess around with _ methods, but ideally there’s a way to do that.

I was also thrown off by the offset=screen, but I see there’s already an issue for that: https://github.com/matplotlib/matplotlib/issues/16496.

Hi Gustavo,

There’s a couple of things at play here, but you shouldn’t have to use any private methods for this.

Generate your plot first, and set the x and y axis limits. Once this is done, you can use the standard transformation functions documented in https://matplotlib.org/3.2.1/tutorials/advanced/transforms_tutorial.html
to get the conversion factor from data units to pixels, then just call scatter with the appropriate scaling factor applied to your “s” kwarg.

desired_data_width = 0.5

fig, ax = plt.subplots()
ax.set_xlim([-1, 1])
ax.set_ylim([-1, 1])
M = ax.transData.get_matrix()
xscale = M[0,0]
yscale = M[1,1]
# has desired_data_width of width
plt.scatter(0, -1, marker='s', s=(xscale*desired_data_width)**2)
# has desired_data_width of height
plt.scatter(-1, 0, marker='s', s=(yscale*desired_data_width)**2)

# # for plt.plot, don't square
# plt.plot(0, -1, marker='s', markersize=xscale*desired_data_width)

That said, please note that you may not get the “width” you desire from some markers, since marker’s “default” sizes don’t really make much sense (see e.g. https://github.com/matplotlib/matplotlib/issues/16623). A fix is in the works but will take some time.

For now, if you want to get the “real” widths or heights of the markers, you’ll have to pull from https://github.com/matplotlib/matplotlib/pull/16832, then use code like the following to get the “true” width and height of the marker (for markersize=1):

star = MarkerStyle('*')
bbox = star.get_path().transformed(star.get_transform()).get_extents()
star_unit_width = bbox.width
star_unit_height = bbox.height

Then to get a star with real width of desired_data_width, you’d need to do

plt.scatter(0, -1, marker='s', s=(xscale*desired_data_width/star_unit_width)**2)

If you have a more intricate use case (using e.g. log scales), then you can’t use this simple trick and only then will you need to start dealing with the Transform of the PathCollection manually.

Thanks for the help!

I’m interested in the interactivity, which would require me to set the markersize on any change of ax.transData. So if there is a way to access the transformation pipeline from marker path to screen data, I am hoping I could set it once, and then let matplotlib take care of the rest.

There are essentially two solutions to this, both lined out in

Depending on your actual use case you may need a bit more of tweaking - in which case a minimal example would be nice.