How to animate Sierpinski triangle construction

How do I get an animated gif from the following construction?
Possibly also changing the functions used, if a different function is more suitable for the case.

using Plots
p=plot(tr, st=:scatter, markersize=3, legend=false)
m=0.5 .* ((0,0).+ (1,0))
for i in 1:10000
    m=0.5 .*(m .+ tr[rand(1:3)])

plot!(p,sierpinski,st=:scatter, markersize=1, legend=false)


I tried this way, but it doesn’t work.

julia> using Plots

julia> gr()

julia> tr=[(0,0),(1,0),0.5.*(1,sqrt(3))]
3-element Vector{Tuple{Real, Real}}:
 (0, 0)
 (1, 0)
 (0.5, 0.8660254037844386)

julia> p = scatter(1,xlim = (0, 1),ylim = (0, sqrt(3)/2), legend = false, marker = 1)

julia> m=0.5 .* ((0,0).+ (1,0))
(0.5, 0.0)

julia> @gif for i=1:10000
           m=0.5 .*(m .+ tr[rand(1:3)])
           push!(p, m)
       end every 10
ERROR: UndefVarError: m not defined
 [1] macro expansion
   @ c:\Users\sprmn\.julia\v1.8\plot3.jl:86 [inlined]
 [2] top-level scope
   @ C:\Users\sprmn\.julia\packages\Plots\yJrrq\src\animation.jl:235

I tried to use the same scheme used for the lorenz attractor and in this way I get the animated gif. But I don’t understand why it doesn’t work in the first case and I’m pretty sure there are easier ways to achieve the same result.

using Plots
# define the Lorenz attractor
Base.@kwdef mutable struct Sier
    a = (0,0)
    x=0.5 * (a[1]+ b[1])
    y=0.5 * (a[2]+ b[2])

function step!(m::Sier)[rand(1:3)]
    m.x = 0.5*(m.x+v[1])
    m.y = 0.5*(m.y+v[2])

midpoint = Sier()

plt = scatter(
    xlim = (0, 1),
    ylim = (0, sqrt(3)/2),
    legend = false,
    marker = 1,

@gif for i=1:10000
    push!(plt, midpoint.x, midpoint.y)
end every 10

I get this error (I ran update Plots command)

julia> gif(p, [sierpinski[1:i] for i in 1:length(sierpinski)], st=:scatter, markersize=1, legend=false, filename="sierpinski 1.gif")
ERROR: MethodError: no method matching gif(::Plots.Plot{Plots.GRBackend}, ::Vector{Vector{Any}}; st=:scatter, markersize=1, legend=false, filename="sierpinski 1.gif")
Closest candidates are:
  gif(::Animation, ::Any; kw...) at C:\Users\sprmn\.julia\packages\Plots\yJrrq\src\animation.jl:65
 [1] top-level scope
   @ c:\Users\sprmn\.julia\v1.8\plot3.jl:113

(v1.8) pkg> status Plots
Status `C:\Users\sprmn\.julia\v1.8\Project.toml`
⌃ [91a5bcdd] Plots v1.32.0
Info Packages marked with ⌃ have new versions available

Could you upload the gif you get?

Excuse me, @devvinish, but are getting your answers from chatGPT? Both the language of your explanations (which is very good), the clarity of the code, as well as the incongruent oddness of the errors in the code remind me a lot of the bot-generated code.

I suggest that you actually test that your code runs before sharing it here. And if the code and answers are written by chatGPT, please indicate so in your posts.


Try this (edited to get rid of global as per @DNF advice):

using Plots; gr(dpi=100)

function sierpinski_gif(n)
    p = scatter(1, xlim=(0,1), ylim=(0,√3/2), legend=false, ms=1.5, msw=0, marker=:utriangle, c=:black)
    m = 0.5 .* ((0,0) .+ (1,0))
    anim = @animate for i=1:n
        m = 0.5 .* (m .+ rand([(0,0), (1,0), 0.5 .* (1, √3)]))
        push!(p, m)
    end every 10
    gif(anim, "sierpinski.gif")
    return p

sierpinski_gif(5000)        # 21 s (40 M allocs: 889 MiB, 0.4% gc time)

The GIF generation takes too long about 7 seconds to produce one png.

Maybe the Graphics card or RAM is not powerful enough for my laptop. Thanks for the code anyway…

7000 seconds to create one gif or 116 minutes

Your proposal has been around for a long time and I didn’t wait for it to conclude.
I did however exploit making the variable m global in my first attempt and this seems to achieve what I was looking for…

using Plots



p = scatter(1,xlim = (0, 1),ylim = (0, sqrt(3)/2), legend = false, marker = 1)

m=0.5 .* ((0,0).+ (1,0))

 @gif for i=1:10000
          **global** m=0.5 .*(m .+ tr[rand(1:3)])
           push!(p, m)
       end every 10

Come on! Don’t give this advice :wink: You know this should be wrapped in a function, and that global is poison.


Makie version:

using CairoMakie

function sierpinski()
    # create observable holding scatter points
    tr = Observable(Point2f[(0, 0), (1, 0), 0.5.*(1, sqrt(3))])

    # create a scatter plot of that observable
    f, ax, sc = scatter(tr, markersize = 3)

    # create the starting point for the iterative algorithm
    m = Point2f(0.5 .* ((0,0).+ (1,0)))
    # make a recording of figure `f` with 300 frames
    record(f, "sierpinski.mp4", 1:300) do frame
        # add 10 new points per frame
        for i in 1:10
            # calculate a new m
            m = 0.5f0 * (m + tr[][rand(1:3)])
            # and push it to the vector contained in the observable tr
            # without updating the observable, yet
            push!(tr[], m)
        # after all new points are added, update the observable tr once
        # so the scatter plot is updated as well for the new frame
        # that is being created each time that this closure returns

why 'global` is poison?

I asked chatGPT, which answered the following:

Global variables are variables that are declared outside of any function or class and can be accessed from anywhere in the program. Using global variables can lead to several problems.

First, global variables can make it difficult to understand the flow of a program. Because global variables can be accessed and modified from anywhere, it’s hard to keep track of how and when they are being used. This can make it difficult to debug the program and understand how it works.

Second, global variables can cause conflicts if multiple parts of the program try to access or modify the same global variable. This can lead to unpredictable behavior and errors.

Third, global variables can make it difficult to reuse code. If a function or class relies on a global variable, it can only be used in a specific context where that global variable is defined. This can limit the reusability of the code and make it difficult to write modular and flexible programs.

In general, it’s best to avoid using global variables whenever possible. Instead, it’s usually better to use local variables and pass them as arguments to functions or methods. This can make the code easier to understand, debug, and reuse.

My own remark: they are particularly bad in Julia because they hurt performance, unless they are const, which it isn’t here and cannot be.

(chatGPT will enable some some extremely lazy answers, but I’m still a bit fascinated with trying it out. This question seemed like a reasonable usecase).

1 Like

Yes I understand now about global variables concept, then I look for chatGPT, and I think it is amazing to have answer like that from chatGPT.

Very interesting.
But not easy to follow for those unfamiliar with the functions used.
Could you explain the logic of the different lines of code?

I’ve added some comments

to be included in BMakie?

I think you meant “… has been running for a long time …” :slight_smile:

In any case, the Plots.jl animations performance seems to depend heavilly on the dpi selected, in addition to the step size to not capture all the plot frames.

I have edited my post above using macro @animate and putting everything inside a function.

Colpa di Google translator :slight_smile:

1 Like

I think there’s potential for some interesting variations on this theme. Here’s one that looked promising:


No code for this - let those AI bots do the work for themselves … :slight_smile:

1 Like

I got error for this code:

LoadError: syntax: use “x^y” instead of "xy" for exponentiation, and “x…” instead of "x" for splatting.

I try this code from JULIA REPL and get this in return:

**sierpinski (generic function with 1 method)**

what should I do to generate the .mp4 or the animation then?

Call the function :slight_smile: