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