How to plot circles with given radii using Plots

I want to create movies of biological cells which are represented as circles.
My main problem is speed. Generating the videos I need takes more than 10 minutes.

Currently, I am using this approach to plot circles

However, if I could use the scatter method, it would be much faster. But the size argument in scatter scales the size input. So, I don’t know how to set the size in scatter to obtain the correct physical radii in the plots.

PS: I’m inside a Pluto notebook, therefore Makie seems to be less appropriate.

2 Likes

Is markersize what you are trying? If so, what is the problem exactly? I think you can tune the relation of the size of the markers with the plot size to get what you want:

julia> plot()

julia> for s in [ 10, 20, 30 ]
         scatter!(rand(5),rand(5),markersize=s,label="$s")
       end

julia> plot!(size=(500,500))

Gives:

plot

Edit:

A very ugly workaround is the following. Plot one figure with the desired size and a symbol with markersize=100, and save it svg, for example:

scatter([0.5],[0.5],markersize=100,size=(500,500),xlims=(0,1),ylims=(0,1))
savefig("plot.svg")

plot1

Open the plot.svg file in Inkscape, and click on the x-axis, above you will see its width (in my case, 452.106).

Click on the circle, and see its width (diameter), in my case 181.

Therefore, a scatter circle of diameter 181 has diameter 181/452.106 in x-axis units, and corresponds
to markersize=100. Therefore, to plot a symbol with diameter 1.0 you need markersize=100*(452.106/181). In other words, set

markerunit = 100*(452.106/181)

Finally, use these units to set the scatter size:


scatter([0.5],[0.5],markersize=markerunit,size=(500,500),xlims=(0,1),ylims=(0,1))
scatter!([0.25],[0.25],markersize=0.5*markerunit,size=(500,500),xlims=(0,1),ylims=(0,1))

plot2

Of course this only works if you maintain the overall plot appearance (size, legends, titles, etc), constant from the measure of sizes to the final plotting.

1 Like

I’m not sure what the desired outcome is but, just in case this might be helpful, you can pass a vector to the markersize keyword argument:

using Plots

diameters = rand(5.0:0.2:15.0, 10)

scatter(rand(10),markersize=diameters,legend=false)

radii

1 Like

Have you considered using Javis.jl?

3 Likes

The desired outcome is that the circles have exactly the diameter as given. The diameters in your example as not the diameters of the circles, but scaled in the output. (In some way which depends on the backend and the size of the plot.) Therefore, markersize does not do the job for me as long as I cannot prevent the automatic scaling.

Thanks for the hint. I will give it a try! Does Javis.jl have the ability to draw an x-y-axis?

I don’t think there’s a built-in way, but manually I think it should be fairly easy: you could use draw_line and take inspiration from the 2nd tutorial (which has some fixed lines).

If you go this route I think you should probably just start an issue directly on the repo and ask for help there, as I’m sure the contributors will happily help and could add your example (or something similar) in a gallery or a tutorial! (I’m actually thinking I might do that soon myself for something similar).

2 Likes

GMT lets you provide the symbol size as an extra column of the data. Now, because I do lots of guess work to soften the use of pure GMT in GMT.jl, one of the guesses short-circuited that ability. But we can still make it work if reading the data from a file and specifying the fig limits. The data is only a x y diameter ascii file.

using GMT
# Create a data file. Third column is diameter in cm
gmtwrite("lixo.dat", [0 0 1; 0.5 0.5 2; 1 1 0.5])

# plot it
plot("lixo.dat", marker=:circ, fill=:green, limits=(-0.1,1.1,-0.1,1.1), show=true, fmt=:png)

2 Likes

It’s not clear just how fast this needs to be, or whether this solution would also class as ‘too slow’, but passing a Vector{Plots.Shape} seems acceptably fast for me with thousands of Shapes.

The following example will give you a load of randomly-wiggling circles.

using Plots

function circle(x, y, r=1; n=30)
    θ = 0:360÷n:360
    Plots.Shape(r*sind.(θ) .+ x, r*cosd.(θ) .+ y)
end

function move_circles!(circles; Δ=0.01)
    for c in circles
        dx = Δ*(2rand() - 1)
        dy = Δ*(2rand() - 1)
        c.x .+= dx
        c.y .+= dy
    end
    nothing
end

nframes = 100
ncircles = 1000

circles = circle.([i*rand(ncircles) for i in (1, 1, 0.01)]...)

plot_kwargs = (aspect_ratio=:equal, fontfamily="Helvetica", legend=false, line=nothing,
    color=:black, grid=false, xlim=(0,1), ylim=(0,1))

anim = @animate for _ in 1:nframes
    move_circles!(circles)
    plot(circles; plot_kwargs...)
end
gif(anim, "anim.gif")

anim

8 Likes

Wow, this looks amazing.

I was constructing the circles so far one by one, since I was not sure how to call it at once. Your solution is much faster. I will implement it later. Since my code already uses Plots.jl, I guess this is my preferred approach.

rs, I thought that that was not fast enough :slight_smile:

@lmiq I will try to compare the speed of this approach and @anowacki 's solution. Thanks for the idea with hacking the SVG!

On my computer the amount of plot() calls influences the speed of the plots,
i.e. calling 100 times plot() for 100 circles seems to be much slower than one plot() call with 100 circles packed into the data. So, reducing the number of plot calls helped me.
(I cannot compare the speeds now, but I will post this evening with the timings.)