Visualizations and Animations for Billiards

All plotting and animating for DynamicalBilliards.jl lies within a few well-defined functions that use the Makie ecosystem.

Plotting

DynamicalBilliards.bdplotFunction
bdplot(x; kwargs...) → fig, ax
bdplot!(ax::Axis, x; kwargs...)

Plot an object x from DynamicalBilliards into a given axis (or a new figure). x can be an obstacle, a particle, a vector of particles, or a billiard.

bdplot!(ax,::Axis, o::Obstacle; kwargs...)

Keywords are propagated to lines! or poly!. Functions obfill, obcolor, obls, oblw (not exported) decide global defaults for linecolor, fillcolor, linestyle, linewidth, when plotting obstacles.

bdplot!(ax,::Axis, bd::Billiard; clean = true, kwargs...)

If clean = true, all axis elements are removed and an equal aspect ratio is establised. Other keywords are propagated to the obstacle plots.

bdplot!(ax,::Axis, bd::Billiard, xmin, xmax, ymin, ymax; kwargs...)

This call signature plots periodic billiards: it plots bd along its periodic vectors so that it fills the total amount of space specified by xmin, xmax, ymin, ymax.

bdplot!(ax,::Axis, ps::Vector{<:AbstractParticle}; kwargs...)

Plot particles as a scatter plot (positions) and a quiver plot (velocities). Keywords particle_size = 5, velocity_size = 0.05 set the size of plotted particles. Keyword colors = JULIADYNAMICS_CMAP decides the color of the particles, and can be either a colormap or a vector of colors with equal length to ps. The rest of the keywords are propagated to the scatter plot of the particles.

source
DynamicalBilliards.bdplot_boundarymapFunction
bdplot_boundarymap(bmap, intervals; figkwargs = NamedTuple(), kwargs...)

Plot the output of DynamicalBilliards.boundarymap into an axis that correctly displays information about obstacle arclengths. Return figure, axis.

Also works for the parallelized version of boundary map.

Keyword Arguments

  • figkwargs = NamedTuple() keywords propagated to Figure.
  • color : The color to use for the plotted points. Can be either a single color or a vector of colors of length length(bmap), in order to give each initial condition a different color (for parallelized version).
  • All other keywords propagated to scatter!.
source

Plotting an obstacle with keywords

using DynamicalBilliards, CairoMakie

bd = billiard_sinai()

fig, ax = bdplot(bd[2])
bdplot!(ax, bd[4]; color = "blue", linestyle = :dot, linewidth = 5.0)
bdplot!(ax, bd[1]; color = "yellow", strokecolor = "black")
fig
Example block output

Plotting a billiard

using DynamicalBilliards, CairoMakie
bd = billiard_logo()[1]
fig, ax = bdplot(bd)
fig
Example block output

Plotting some particle trajectories

using DynamicalBilliards, CairoMakie
timeseries! = DynamicalBilliards.timeseries!

bd = billiard_hexagonal_sinai()
p1 = randominside(bd)
p2 = randominside(bd, 1.0)
colors = [:red, :black]
markers = [:circle, :rect]
fig, ax = bdplot(bd)
for (p, c) in zip([p1, p2], colors)
    x, y = timeseries!(p, bd, 20)
    lines!(ax, x, y; color = c)
end
bdplot!(ax, [p1, p2]; colors, particle_size = 10, marker = markers)
fig
Example block output

Periodic billiard plot

Rectangle periodicity:

using DynamicalBilliards, CairoMakie

r = 0.25
bd = billiard_rectangle(2, 1; setting = "periodic")
d = Disk([0.5, 0.5], r)
d2 = Ellipse([1.5, 0.5], 1.5r, 2r/3)
bd = Billiard(bd.obstacles..., d, d2)
p = Particle(1.0, 0.5, 0.1)
xt, yt, vxt, vyt, t = DynamicalBilliards.timeseries!(p, bd, 10)
fig, ax = bdplot(bd, extrema(xt)..., extrema(yt)...)
lines!(ax, xt, yt)
bdplot!(ax, p; velocity_size = 0.1)
fig
Example block output

Hexagonal periodicity:

using DynamicalBilliards, CairoMakie

bd = billiard_hexagonal_sinai(0.3, 1.50; setting = "periodic")
d = Disk([0.7, 0], 0.2)
d2 = Antidot([0.7/2, 0.65], 0.35)
bd = Billiard(bd..., d, d2)

p = MagneticParticle(-0.5, 0.5, π/5, 1.0)

xt, yt = DynamicalBilliards.timeseries(p, bd, 10)
fig, ax = bdplot(bd, extrema(xt)..., extrema(yt)...)
lines!(ax, xt, yt)
bdplot!(ax, p; velocity_size = 0.1)
fig
Example block output

Boundary map plot

using DynamicalBilliards, CairoMakie

bd = billiard_mushroom()

n = 100 # how many particles to create
t = 200 # how long to evolve each one

bmap, arcs = parallelize(boundarymap, bd, t, n)

randomcolor(args...) = RGBAf(0.9 .* (rand(), rand(), rand())..., 0.75)

colors = [randomcolor() for i in 1:n] # random colors

fig, ax = bdplot_boundarymap(bmap, arcs, color = colors)
fig
Example block output

Interactive GUI

DynamicalBilliards.bdplot_interactiveFunction
bdplot_interactive(bd::Billiard, ps::Vector{<:AbstractParticle}; kwargs...)

Create a new figure with bd plotted, and in it initialize various data for animating the evolution of ps in bd. Return fig, phs, chs, where fig is a figure instance and phs, chs can be used for making custom animations, see below.

Keywords (interactivity-related)

  • playback_controls = true: If true, add controls that allow live-interaction with the figure, such as pause/play, reset, and creating new particles by clicking and dragging on the billiard plot.

Keywords (visualization-related)

  • dt = 0.001: The animation always occurs in steps of time dt. A slider can decide how many steps dt to evolve before updating the plots.
  • plot_bmap = false: If true, add a second plot with the boundary map.
  • colors = JULIADYNAMICS_CMAP : If a symbol (colormap name) each particle gets a color from the map. If Vector of length N, each particle gets a color form the vector. If Vector with length < N, linear interpolation across contained colors is done.
  • tail_length = 1000: The last tail_length positions of each particle are visualized.
  • tail_width = 1: Width of the dtail plot.
  • fade = true: Tail color fades away.
  • plot_particles = true: Besides their tails, also plot the particles as a scatter and quiver plot.
  • particle_size = 5: Marker size for particle scatter plot.
  • velocity_size = 0.05: Multiplication of particle velocity before plotted as quiver.
  • bmap_size = 4: Marker size of boundary map scatter plot.
  • figure = NamedTuple(): Keywords propagated to Figure creation.
  • kwargs...: Remaining keywords are propagated to the billiard plotting.

Custom Animations

Two helper structures are defined for each particle:

  1. ParticleHelper: Contains quantities that are updated each dt step: the particle, time elapsed since last collision, total time ellapsed, tail (positions in the last tail_length dt-sized steps).
  2. CollisionHelper: Contains quantities that are only updated at collisions: index of obstacle to be collided with, time to next collision, total collisions so far, boundary map point at upcoming collision.

These two helpers are necessary to transform the simulation into real-time stepping (1 step = dt time), instead of the traditional DynamicalBilliards.jl setup of discrete time stepping (1 step = 1 collision).

The returned phs, chs are two observables, one having vector of ParticleHelpers, the other having vector of CollisionHelpers. Every plotted element is lifted from these observables.

An exported high-level function bdplot_animstep!(phs, chs, bd, dt; update, intervals) progresses the simulation for one dt step. Users should be using bdplot_animstep! for custom-made animations, examples are shown in the documentation online. The only thing the update keyword does is notify!(phs). You can use false for it if you want to step for several dt steps before updating plot elements. Notice that chs is always notified when collisions occur irrespectively of update. They keyword intervals is nothing by default, but if it is arcintervals(bd) instead, then the boundary map field of chs is also updated at collisions.

source

For example, the animation above was done with:

using DynamicalBilliards, GLMakie
l, w, r = 0.5, 0.75, 1.0
bd = billiard_mushroom(l, w, r)
N = 20
ps = vcat(
    [MushroomTools.randomchaotic(l, w, r) for i in 1:N],
    [MushroomTools.randomregular(l, w, r) for i in 1:N],
)
colors = [i ≤ N ? RGBf(0.1, 0.4 + 0.3rand(), 0) : RGBf(0.4, 0, 0.6 + 0.4rand()) for i in 1:2N]
fig, phs, chs = bdplot_interactive(bd, ps;
    colors, plot_bmap = true, bmap_size = 8, tail_length = 2000,
);

Custom Billiards Animations

To do custom animations you need to have a good idea of how Makie's animation system works. Have a look at this tutorial if you are not familiar yet.

Following the docstring of bdplot_interactive let's add a couple of new plots that animate some properties of the particles. We start with creating the billiard plot and obtaining the observables:

using DynamicalBilliards, CairoMakie

bd = billiard_stadium(1, 1)
N = 100
ps = particlebeam(1.0, 0.6, 0, N, 0.001)
fig, phs, chs = bdplot_interactive(bd, ps; playback_controls=false)
(Scene (800px, 600px):
  0 Plots
  1 Child Scene:
    └ Scene (800px, 600px), Observable(DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}[DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.5995]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995]  …  [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.5995101010101009]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101]  …  [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599520202020202]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202]  …  [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599530303030303]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303]  …  [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599540404040404]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404]  …  [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599550505050505]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505]  …  [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599560606060606]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606]  …  [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599570707070707]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707]  …  [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599580808080808]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808]  …  [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.5995909090909091]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909]  …  [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909]])  …  DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004090909090909]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091]  …  [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004191919191919]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916]  …  [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004292929292929]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293]  …  [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004393939393939]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937]  …  [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600449494949495]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495]  …  [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600459595959596]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596]  …  [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600469696969697]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697]  …  [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600479797979798]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798]  …  [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600489898989899]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899]  …  [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004999999999999]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005]  …  [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005]])]), Observable(DynamicalBilliardsVisualizations.CollisionHelper{Float64}[DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899997448978928, 0, [0.8855668664872378, -0.19900000000000007]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899976936649391, 0, [0.8855771736673416, -0.19902020202020193]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48999564221517133, 0, [0.8855874808905959, -0.19904040404040396]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899935905485867, 0, [0.8855977881570055, -0.19906060606060602]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899915386651826, 0, [0.8856080954665754, -0.19908080808080802]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48998948656495617, 0, [0.8856184028193104, -0.19910101010101006]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899874342479048, 0, [0.8856287102152155, -0.19912121212121198]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48998538171402567, 0, [0.8856390176542955, -0.19914141414141412]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48998332896331614, 0, [0.8856493251365555, -0.1991616161616161]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899812759957734, 0, [0.8856596326620003, -0.19918181818181818])  …  DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48981426527083705, 0, [0.886494686092584, -0.20081818181818178]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4898121945136692, 0, [0.8865049971759564, -0.20083838383838376]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4898101235394408, 0, [0.8865153083029229, -0.2008585858585859]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48980805234814917, 0, [0.8865256194734884, -0.20087878787878788]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4898059809397915, 0, [0.8865359306876579, -0.20089898989898997]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.489803909314365, 0, [0.8865462419454363, -0.20091919191919203]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48980183747186706, 0, [0.8865565532468285, -0.20093939393939397]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48979976541229475, 0, [0.8865668645918394, -0.20095959595959606]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4897976931356454, 0, [0.8865771759804741, -0.2009797979797981]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48979562064191634, 0, [0.8865874874127374, -0.2009999999999999])]))

Then, we add some axis

layout = fig[2,1] = GridLayout()
axd = Axis(layout[1,1]; ylabel = "log(⟨d⟩)", alignmode = Outside())
axs = Axis(layout[2,1]; ylabel = "std", xlabel = "time", alignmode = Outside())
hidexdecorations!(axd; grid = false)
rowsize!(fig.layout, 1, Auto(2))
fig
Example block output

Our next step is to create new observables to plot in the new axis, by lifting phs, chs. Let's plot the distance between two particles and the std of the particle y position.

using Statistics: std
# Define observables
d_p(phs) = log(sum(sqrt(sum(phs[1].p.pos .- phs[j].p.pos).^2) for j in 2:N)/N)
std_p(phs) = std(p.p.pos[1] for p in phs)
t = Observable([0.0]) # Time axis
d = Observable([d_p(phs[])])
s = Observable([std_p(phs[])])
# Trigger observable updates
on(phs) do phs
    push!(t[], phs[1].T)
    push!(d[], d_p(phs))
    push!(s[], std_p(phs))
    notify.((t, d))
    autolimits!(axd); autolimits!(axs)
end
# Plot observables
lines!(axd, t, d; color = Cycled(1))
lines!(axs, t, s; color = Cycled(2))
nothing

The figure hasn't changed yet of course, but after we step the animation, it does:

dt = 0.001
for j in 1:1000
    for i in 1:9
        bdplot_animstep!(phs, chs, bd, dt; update = false)
    end
    bdplot_animstep!(phs, chs, bd, dt; update = true)
end
fig
Example block output

Of course, you can produce a video of this using Makie's record function.

Video output

DynamicalBilliards.bdplot_videoFunction
bdplot_video(file::String, bd::Billiard, ps::Vector{<:AbstractParticle}; kwargs...)

Create an animation of ps evolving in bd and save it into file. This function shares all visualization-related keywords with bdplot_interactive. Other keywords are:

  • steps = 10: How many dt-steps are taken between producing a new frame.
  • frames = 1000: How many frames to produce in total.
  • framerate = 60.
source

Here is an example that changes plotting defaults to make an animation in the style of 3Blue1Brown.

using DynamicalBilliards, CairoMakie
BLUE = "#7BC3DC"
BROWN = "#8D6238"
colors = [BLUE, BROWN]
# Overwrite default color of obstacles to white (to fit with black background)
bd = billiard_stadium(1, 1)
ps = particlebeam(1.0, 0.6, 0, 200, 0.01)
# Notice that keyword `color = :white` is propagated to billiard plot
bdplot_video(
    "3b1billiard.mp4", bd, ps;
    frames = 120, colors, dt = 0.01, tail_length = 100,
    figure = (backgroundcolor = :black,), framerate = 10, color = :white,
)