I’m generating a simple image with a scatter and two dots and I want the dots to be fully visible, but ax.autoscale_view()
seems not to work properly.
fig, ax = plt.subplots(figsize=(3, 3), dpi=150)
ax.scatter([0, 1], [0, 1], s=2000)
ax.autoscale_view()
Am I doing something wrong? How can I get the dots to be fully visible no matter the marker size s
?
To adjust the axis so that the perimeter of points is inside of the plotting area, we need to:
- Find the x and y extend of the perimeter of the dots in data-coordinate-space.
- Adjust the margins accordingly to fit size of the points.
The size parameter s
in ax.scatter
sets the area of the bounding box (a square) of the marker (the dot) in units of points^2. The marker also has a line drawn around it, controlled by the parameter linewidth
. The default is 1.0
. To get the radius in points and the linewidth in points:
fig, ax = plt.subplots(figsize=(3,3), dpi=150)
c = ax.scatter([0, 1], [0, 1], s=2000)
r_face = sqrt(2000) / 2 # = 22.36 points
lw = c.get_linewidth()[0] # = 1.0 points
r_points = r_face + lw # = 23.36 points
To convert to pixel-coordinate-space, we need to convert from points to inches (72 points per inch), and then multiply by the DPI.
r_pixel = r_points / 72 * 150 # = 48.67 pixels
You can get the center of a point (we will use the point at 1, 1
) and the boundary extent in pixel-coordinate-space using:
x_pixel, y_pixel = ax.transData.transform((1, 1))
bx_pixel = x_pixel + r_pixel
by_pixel = y_pixel + r_pixel
Because the dot radius is fixed in pixel-coordinate-space, it does not scale 1-to-1 with the data coordinates. This makes a direct calculation difficult (I believe it is possible, I just don’t know how). We can iteratively adjust the margins and calculate whether the boundary is within the bounds of the data-coordinates.
bx_data, by_data = ax.transData.inverted().transform((bx_pixel, by_pixel))
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
while (bx_data > xmax) or (by_data > ymax):
margin_x, margin_y = ax.margins()
margin_x += 0.05
margin_y += 0.05
ax.margins(margin_x, margin_y)
x_pixel, y_pixel = ax.transData.transform((1, 1))
bx_pixel = x_pixel + r_pixel
by_pixel = y_pixel + r_pixel
bx_data, by_data = ax.transData.inverted().transform((bx_pixel, by_pixel))
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
ax.autoscale()
2
You can consider adding margins
to your plot. Here’s how you can do it:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(3, 3), dpi=150)
ax.scatter([0, 1], [0, 1], s=1000)
ax.margins(0.15) # Adds 15% margin to both axes, you may need to adjust
ax.autoscale_view()
which generates:
1
Try calling ax.margins()
before ax.autoscale_view()
.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(3, 3), dpi=150)
ax.scatter([0, 1], [0, 1], s=2000)
ax.margins(x=0.25, y=0.25)
ax.autoscale_view()
Am I doing something wrong?
I don’t thin so — from the matplotlib docs it looks like autoscale_view()
only scales based on the limits of the plotted data, not the size of the marker: “Autoscale the view limits using the data limits.”
How can I get the dots to be fully visible no matter the marker size s?
I don’t have a good solution for this one. I’d personally adjust the margins
arguments by hand until you like the way it looks.
The units for marker size s
are “points **2” which is in terms of distance on the screen. The margin units are in x,y coordinates. To get the dots to be fully visible, I think you would have to set the margins to whatever x and y coordinates are equivalent to the radius of the dot which depends on the size of the graph in inches and the x and y limits of the plotted data. I don’t think there’s an easy way to do that conversion, but if you find one please post and prove me wrong.
1