Type-Stable Function to Return Plots.Plot

The question below was already raised at This post.

This code

using Plots

function foo()

    #..Initialize the plot.. 
    p = plot()
        
    return p 
end

function gnu()

    #..Initialize the plot.. 
    p = plot()::Plots.Plot{Plots.GRBackend}
        
    # return p 
end

yields the output

@code_warntype foo()
MethodInstance for foo()
  from foo() @ Main In[14]:3
Arguments
  #self#::Core.Const(foo)
Locals
  p::Plots.Plot
Body::Plots.Plot
1 ─     (p = Main.plot())
└──     return p

and

@code_warntype gnu()
MethodInstance for gnu()
  from gnu() @ Main In[14]:11
Arguments
  #self#::Core.Const(gnu)
Locals
  p::Plots.Plot{Plots.GRBackend}
Body::Plots.Plot{Plots.GRBackend}
1 ─ %1 = Main.plot()::Plots.Plot
│   %2 = Plots.Plot::Core.Const(Plots.Plot)
│   %3 = Plots.GRBackend::Core.Const(Plots.GRBackend)
│   %4 = Core.apply_type(%2, %3)::Core.Const(Plots.Plot{Plots.GRBackend})
│   %5 = Core.typeassert(%1, %4)::Plots.Plot{Plots.GRBackend}
│        (p = %5)
└──      return %5

What is a type-stable definition of a function that returns a plot? Thx.

Plots.plot() is not type stable. Its return type depends on global variables:

julia> using Plots

julia> typeof(plot())
Plots.Plot{Plots.GRBackend}

julia> unicodeplots()
Plots.UnicodePlotsBackend()

julia> typeof(plot())
Plots.Plot{Plots.UnicodePlotsBackend}

For most purposes I’d guess the performance problems from this instability is negligible compared to the actual work done by the plot routines.

Many thx!

In the target application, Plots.plot is called inside a for-loop over elements in a array. See function plotMesh below. The array is typically large. The function appears to be slow.

Possibly the code design is faulty to start with. Not sure whether an in-place version, a plotting recipe or an alternative is more appropriate here.

Please advice.

function plotMesh(mesh::Mesh)

    #..Initialize the plot.. 
    p = Plots.plot(xlabel = "x", ylabel = "y",legend = false,aspect_ratio = :equal)
    
    #..loop over number of elements..
    for element in mesh.Elements 
        triangle = Shape([Tuple(element.p1),Tuple(element.p2),Tuple(element.p3)])
        p = Plots.plot!(p,triangle,fillcolor=:blue, alpha=0.7, linecolor=:black)        
    end

    Plots.plot(p)
    
    return p 
end

You can put the loop in a separate function to utilize a function barrier:

function plotloop!(p, Elements)
    for element in Elements
        triangle = ...
        Plots.plot!(p, triangle, ...) 
    end
end

function plotMesh(mesh::Mesh)
    p = Plots.plot(xlabel, ...)
    plotloop!(p, mesh.Elements)
    ...
end

In this way there is a single instability inside plotMesh, but inside plotloop! there is no instability since the type of p is fully known when plotloop! is called. (Note that it’s not necessary to reassign p in p = Plots.plot!(p, ...) since plot! returns the original p, but updated.)

Edit: on second thought, plot! is not specializing on the plot type, so this may not help a lot. It could be that the slowness are from something else, like garbage collection due to the many vectors created when you construct the Shape.

It might be worthwhile to create a separate plot receipe for your Mesh type, but it depends on how much you use such plots: Overview · Plots

Right!

The code below continues to be slow due to many allocations.

function plotloop!(p,mesh::Mesh)
    #..loop over number of elements..
    for element in mesh.Elements 
        triangle = Shape([Tuple(element.p1),Tuple(element.p2),Tuple(element.p3)])
        Plots.plot!(p,triangle,fillcolor=:blue, alpha=0.7, linecolor=:black)        
    end    
    return p 
end

function plotMesh(mesh::Mesh)
 
    #..Initialize the plot.. 
    p = Plots.plot(xlabel = "x", ylabel = "y",legend = false,aspect_ratio = :equal)

    plotloop!(p, mesh)

    Plots.plot(p)
    
    return p     
end
mesh = meshFromGmsh("data/square-10.msh"); @btime plotMesh(mesh)

results in 88.881 allocations.

I agree with @sgaure that type stability is not the bottleneck here. I’d try to make a vector of shapes and then call plot() just once. MWE:

julia> T = [Shape(rand(3), rand(3)) for i in 1:1000]
julia> plot(T, alpha=0.5, legend=false, aspect_ratio=:equal)

I don’t know how many triangles you’re gonna plot but this is pretty fast with the default GR backend:

It takes less than a second to render and display the plot.

Correct! Many thanks! Great!

Assume next that each triangle has a value associated to it. How do I take this value into account in the plot?

Kind wishes, Domenico.

You can pass plot attributes as 1×n matrices. Let’s plot three triangles with different colors:

plot(T[1:3], alpha=0.5, color=[:blue :green :red], legend=false)

Just extract the colors from mesh as you did with the coordinates. Note that plot requires a 1×n matrix, not a vector (see here).

Thx! Requires more thought.