Search code examples
pythonmatplotlibplotscatter-plot

True Python scatter function


I am writing this to have a reference post when it comes to plotting circles with a scatter plot but the user wants to provide a real radius for the scattered circles instead of an abstract size.

I have been looking around and there are other posts that explain the theory of it, but have not come accross a ready-to-use function. I have tried myself and hope that I am close to finding the solution.

This is what I have so far:

import matplotlib.pyplot as plt
import numpy as np

def true_scatter(x, y, r, ax, **kwargs):
    # Should work for an equal aspect axis
    ax.set_aspect('equal')
    
    # Access the DPI and figure size
    dpi = ax.figure.dpi
    fig_width_inch, _ = ax.figure.get_size_inches()

    # Calculate plot size in data units
    xlim = ax.get_xlim()
    plot_width_data_units = xlim[1] - xlim[0]

    # Calculate the scale factor: pixels per data unit
    plot_width_pixels = fig_width_inch * dpi
    scale = plot_width_pixels / plot_width_data_units

    # Convert radius to pixels, then area to points squared
    radius_pixels = r * scale
    area_pixels_squared = np.pi * (radius_pixels ** 2)
    area_points_squared = area_pixels_squared * (72 / dpi) ** 2

    # Scatter plot with converted area
    scatter = ax.scatter(x, y, s=area_points_squared, **kwargs)
    return scatter

# Example with single scatter
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
scatter = true_scatter(0, 0, 1, ax)
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
plt.grid()
plt.show()

Wrong single scatter plot

Unfortunately, it is not quite the answer. I get a circle of radius ~1.55 instead of 1. Would anyone be able to spot what is wrong with my approach?

Thank you!


Solution

  • It is not very clear whether you want r to be the radius or the diameter of the circle. The code below supposes it is the radius (just leave out the * 2 if you want the diameter.

    For a discussion about how the dot size is measured, see this post.

    The code below converts the radius in data units to pixels, and then to "points". Three different sizes are tested.

    import matplotlib.pyplot as plt
    
    def true_scatter(x, y, r, ax, **kwargs):
        # the radius is given in data coordinates in the x direction
        # Should work for an equal aspect axis
        ax.set_aspect('equal')
    
        # measure the data coordinates in pixels
        radius_in_pixels, _ = ax.transData.transform((r, 0)) - ax.transData.transform((0, 0))
        # one "point" is 1/72 of an inch
        radius_in_points = radius_in_pixels * 72.0 / ax.figure.dpi
        # the scatter plot size is set in square point units
        area_points_squared = (radius_in_points * 2) ** 2
    
        # Scatter plot with converted area
        scatter = ax.scatter(x, y, s=area_points_squared, **kwargs)
        return scatter
    
    # Example with three scatter dots with different radii
    fig, ax = plt.subplots(figsize=(5, 5))
    ax.set_xlim(-2, 2)
    ax.set_ylim(-2, 2)
    scatter = true_scatter(0, 0, 1.5, ax)
    scatter = true_scatter(0, 0, 1, ax)
    scatter = true_scatter(0, 0, 0.5, ax)
    ax.grid()
    plt.show()
    

    scatter plot with radius in data units