Makie examples

ENV["DISPLAY"] = ":0"
using Makie
# work around the fact that nextjournal doesn't have display incorperated correctly
output(x::String) = nothing
output(x::Scene) = Makie.save("/results/test.png", x)
output(x::Stepper) = nothing

1.
Tutorial simple scatter

using Makie

result = let
    x = rand(10)
    y = rand(10)
    colors = rand(10)

    scene = scatter(x, y, color = colors)

end
output(result)

2.
Tutorial markersize

using Makie

result = let
    x = 1:10
    y = 1:10
    sizevec = [s for s = 1:length(x)] ./ 10

    scene = scatter(x, y, markersize = sizevec)

end
output(result)

3.
Tutorial simple line

using Makie

result = let
    x = range(0, stop = 2pi, length = 40)
    f(x) = sin.(x)
    y = f(x)

    scene = lines(x, y, color = :blue)

end
output(result)

4.
Tutorial adding to a scene

using Makie

result = let
    x = range(0, stop = 2pi, length = 80)
    f1(x) = sin.(x)
    f2(x) = exp.(-x) .* cos.(2pi*x)
    y1 = f1(x)
    y2 = f2(x)

    scene = lines(x, y1, color = :blue)
    scatter!(scene, x, y1, color = :red, markersize = 0.1)

    lines!(scene, x, y2, color = :black)
    scatter!(scene, x, y2, color = :green, marker = :utriangle, markersize = 0.1)

end
output(result)

5.
Tutorial adjusting scene limits

using Makie

result = let
    x = range(0, stop = 10, length = 40)
    y = x
    #= specify the scene limits, note that the arguments for FRect are
        x_min, y_min, x_dist, y_dist,
        therefore, the maximum x and y limits are then x_min + x_dist and y_min + y_dist
    =#
    limits = FRect(-5, -10, 20, 30)

    scene = lines(x, y, color = :blue, limits = limits)

end
output(result)

6.
Tutorial basic theming

using Makie

result = let
    x = range(0, stop = 2pi, length = 40)
    f(x) = cos.(x)
    y = f(x)
    scene = lines(x, y, color = :blue)

    axis = scene[Axis] # get the axis object from the scene
    axis[:grid][:linecolor] = ((:red, 0.5), (:blue, 0.5))
    axis[:names][:textcolor] = ((:red, 1.0), (:blue, 1.0))
    axis[:names][:axisnames] = ("x", "y = cos(x)")
    scene

end
output(result)

7.
Tutorial heatmap

using Makie

result = let
    data = rand(50, 50)
    scene = heatmap(data)

end
output(result)

8.
Tutorial linesegments

using Makie

result = let
    points = [
        Point2f0(0, 0) => Point2f0(5, 5);
        Point2f0(15, 15) => Point2f0(25, 25);
        Point2f0(0, 15) => Point2f0(35, 5);
        ]
    scene = linesegments(points, color = :red, linewidth = 2)

end
output(result)

9.
Tutorial barplot

using Makie

result = let
    data = sort(randn(100))
    barplot(data)

end
output(result)

10.
Test heatmap + image overlap

using Makie

result = let
    heatmap(rand(32, 32))
    image!(map(x->RGBAf0(x,0.5, 0.5, 0.8), rand(32,32)))

end
output(result)

11.
Interaction

using Makie

result = let
    scene = Scene()
    f(t, v, s) = (sin(v + t) * s, cos(v + t) * s)
    time = Node(0.0)
    p1 = scatter!(scene, lift(t-> f.(t, range(0, stop = 2pi, length = 50), 1), time))[end]
    p2 = scatter!(scene, lift(t-> f.(t * 2.0, range(0, stop = 2pi, length = 50), 1.5), time))[end]
    lines = lift(p1[1], p2[1]) do pos1, pos2
        map((a, b)-> (a, b), pos1, pos2)
    end
    linesegments!(scene, lines)
    N = 150
    record(scene, "/results/interaction.gif", range(0, stop = 10, length = N)) do i
        push!(time, i)
   end

end

12.
barplot

using Makie

result = let
    barplot(rand(10), color = rand(10))
    # barplot(rand(3), color = [:red, :blue, :green])

end
output(result)

13.
quiver

using Makie
using ImageFiltering

result = let
    x = range(-2, stop = 2, length = 21)
    y = x
    z = x .* exp.(-x .^ 2 .- (y') .^ 2)
    scene = contour(x, y, z, levels = 10, linewidth = 3)
    u, v = ImageFiltering.imgradients(z, KernelFactors.ando3)
    arrows!(x, y, u, v, arrowsize = 0.05)
end
output(result)

14.
image

using Makie

result = let
    AbstractPlotting.vbox(
        image(Makie.logo(), scale_plot = false),
        image(rand(100, 500), scale_plot = false),
    )


end
output(result)

15.
scatter colormap

using Makie

result = let
    scatter(rand(10), rand(10), color = rand(10))

end
output(result)

16.
FEM polygon 2D

using Makie

result = let
    coordinates = [
        0.0 0.0;
        0.5 0.0;
        1.0 0.0;
        0.0 0.5;
        0.5 0.5;
        1.0 0.5;
        0.0 1.0;
        0.5 1.0;
        1.0 1.0;
    ]
    connectivity = [
        1 2 5;
        1 4 5;
        2 3 6;
        2 5 6;
        4 5 8;
        4 7 8;
        5 6 9;
        5 8 9;
    ]
    color = [0.0, 0.0, 0.0, 0.0, -0.375, 0.0, 0.0, 0.0, 0.0]
    poly(coordinates, connectivity, color = color, strokecolor = (:black, 0.6), strokewidth = 4)

end
output(result)

17.
FEM mesh 2D

using Makie

result = let
    coordinates = [
        0.0 0.0;
        0.5 0.0;
        1.0 0.0;
        0.0 0.5;
        0.5 0.5;
        1.0 0.5;
        0.0 1.0;
        0.5 1.0;
        1.0 1.0;
    ]
    connectivity = [
        1 2 5;
        1 4 5;
        2 3 6;
        2 5 6;
        4 5 8;
        4 7 8;
        5 6 9;
        5 8 9;
    ]
    color = [0.0, 0.0, 0.0, 0.0, -0.375, 0.0, 0.0, 0.0, 0.0]
    scene = mesh(coordinates, connectivity, color = color, shading = false)
    wireframe!(scene[end][1], color = (:black, 0.6), linewidth = 3)

end
output(result)

18.
colored triangle

using Makie

result = let
    mesh(
        [(0.0, 0.0), (0.5, 1.0), (1.0, 0.0)], color = [:red, :green, :blue],
        shading = false
    )

end
output(result)

19.
heatmap interpolation

using Makie

result = let
    data = rand(100, 50)
    p1 = heatmap(data, interpolate = true)
    p2 = heatmap(data, interpolate = false)
    scene = AbstractPlotting.vbox(p1, p2)
    text!(campixel(p1), "Interpolate = true", position = widths(p1) .* Vec(0.5, 1), align = (:center, :top), raw = true)
    text!(campixel(p2), "Interpolate = false", position = widths(p2) .* Vec(0.5, 1), align = (:center, :top), raw = true)
    scene

end
output(result)

20.
colored triangle

using Makie

result = let
    poly(
        [(0.0, 0.0), (0.5, 1.0), (1.0, 0.0)],
        color = [:red, :green, :blue],
        strokecolor = :black, strokewidth = 2
    )

end
output(result)

21.
Subscenes

using Makie

result = let
    img = rand(RGBAf0, 100, 100)
    scene = image(img, show_axis = false)
    subscene = Scene(scene, IRect(100, 100, 300, 300))
    scatter!(subscene, rand(100) * 200, rand(100) * 200, markersize = 4)
    scene

end
output(result)

22.
Polygons

using Makie
 using GeometryTypes

result = let
    scene = Scene(resolution = (500, 500))
    points = decompose(Point2f0, Circle(Point2f0(50), 50f0))
    pol = poly!(scene, points, color = :gray, strokewidth = 10, strokecolor = :red)
    # Optimized forms
    poly!(scene, [Circle(Point2f0(50+300), 50f0)], color = :gray, strokewidth = 10, strokecolor = :red)
    poly!(scene, [Circle(Point2f0(50+i, 50+i), 10f0) for i = 1:100:400], color = :red)
    poly!(scene, [Rectangle{Float32}(50+i, 50+i, 20, 20) for i = 1:100:400], strokewidth = 2, strokecolor = :green)
    linesegments!(scene,
        [Point2f0(50 + i, 50 + i) => Point2f0(i + 70, i + 70) for i = 1:100:400], linewidth = 8, color = :purple
    )

end
output(result)

23.
Contour Function

using Makie

result = let
    r = range(-10, stop = 10, length = 512)
    z = ((x, y)-> sin(x) + cos(y)).(r, r')
    contour(r, r, z, levels = 5, color = :viridis, linewidth = 3)

end
output(result)

24.
contour

using Makie

result = let
    y = range(-0.997669, stop = 0.997669, length = 23)
    contour(range(-0.99, stop = 0.99, length = 23), y, rand(23, 23), levels = 10)

end
output(result)

25.
Heatmap

using Makie

result = let
    heatmap(rand(32, 32))

end
output(result)

26.
Animated Scatter

using Makie

result = let
    N = 10
    r = [(rand(7, 2) .- 0.5) .* 25 for i = 1:N]
    scene = scatter(r[1][:, 1], r[1][:, 2], markersize = 1, limits = FRect(-25/2, -25/2, 25, 25))
    s = scene[end] # last plot in scene
    record(scene, "/results/animated_scatter.gif", r) do m
        s[1] = m[:, 1]
        s[2] = m[:, 2]
   end

end
output(result)

27.
Text Annotation

using Makie

result = let
    text(
        ". This is an annotation!",
        position = (300, 200),
        align = (:center,  :center),
        textsize = 60,
        font = "Blackchancery"
    )

end
output(result)

28.
Text rotation

using Makie

result = let
    scene = Scene(resolution = (500, 500))
    pos = (500, 500)
    posis = Point2f0[]
    for r in range(0, stop = 2pi, length = 20)
        p = pos .+ (sin(r)*100.0, cos(r) * 100)
        push!(posis, p)
        t = text!(
            scene, "test",
            position = p,
            textsize = 50,
            rotation = 1.5pi - r,
            align = (:center, :center)
        )
    end
    scatter!(scene, posis, markersize = 10)

end
output(result)

29.
The famous iris example

using Makie
Pkg.pkg"add DataFrames RDatasets RData"
using DataFrames, RDatasets # do Pkg.add.(["DataFrames", "RDatasets"]) if you don't have these packages installed

result = let
    iris = dataset("datasets", "iris")

    x = iris[:SepalWidth]
    y = iris[:SepalLength]

    scene = Scene()
    colors = [:red, :green, :blue]
    global i = 1 #color incrementer
    for sp in unique(iris[:Species])
        idx = iris[:Species] .== sp
        sel = iris[idx, [:SepalWidth, :SepalLength]]
        scatter!(scene, sel[:,1], sel[:,2], color = colors[i], limits = FRect(1.5, 4.0, 3.0, 4.0))
        global i = i+1
    end
    scene
    axis = scene[Axis] # get axis
    axis[:names][:axisnames] = ("Sepal width", "Sepal length")
    scene

end
output(result)

30.
Volume Function

using Makie

result = let
    Makie.volume(rand(32, 32, 32), algorithm = :mip)

end
output(result)

31.
Textured Mesh

using Makie
 using FileIO

result = let
    scene = Scene(resolution = (500, 500))
    catmesh = FileIO.load(Makie.assetpath("cat.obj"), GLNormalUVMesh)
    mesh(catmesh, color = Makie.loadasset("diffusemap.tga"))

end
output(result)

32.
Load Mesh

using Makie

result = let
    mesh(Makie.loadasset("cat.obj"))

end
output(result)

33.
Colored Mesh

using Makie

result = let
    x = [0, 1, 2, 0]
    y = [0, 0, 1, 2]
    z = [0, 2, 0, 1]
    color = [:red, :green, :blue, :yellow]
    i = [0, 0, 0, 1]
    j = [1, 2, 3, 2]
    k = [2, 3, 1, 3]
    # indices interpreted as triangles (every 3 sequential indices)
    indices = [1, 2, 3,   1, 3, 4,   1, 4, 2,   2, 3, 4]
    mesh(x, y, z, indices, color = color)

end
output(result)

34.
Wireframe of a Mesh

using Makie

result = let
    wireframe(Makie.loadasset("cat.obj"))

end
output(result)

35.
Wireframe of Sphere

using Makie

result = let
    wireframe(Sphere(Point3f0(0), 1f0))

end
output(result)

36.
Wireframe of a Surface

using Makie

result = let
    function xy_data(x, y)
        r = sqrt(x^2 + y^2)
        r == 0.0 ? 1f0 : (sin(r)/r)
    end
    N = 30
    lspace = range(-10, stop = 10, length = N)
    z = Float32[xy_data(x, y) for x in lspace, y in lspace]
    r = range(0, stop = 3, length = N)
    wireframe(r, r, z)

end
output(result)

37.
Surface

using Makie

result = let
    N = 30
    function xy_data(x, y)
        r = sqrt(x^2 + y^2)
        r == 0.0 ? 1f0 : (sin(r)/r)
    end
    lspace = range(-10, stop = 10, length = N)
    z = Float32[xy_data(x, y) for x in lspace, y in lspace]
    r = range(0, stop = 3, length = N)
    surface(
        r, r, z,
        colormap = :Spectral
    )

end
output(result)

38.
Surface with image

using Makie

result = let
    N = 30
    function xy_data(x, y)
        r = sqrt(x^2 + y^2)
        r == 0.0 ? 1f0 : (sin(r)/r)
    end
    interval = range(-2, stop = 2, length = N)
    surf_func(i) = [Float32(xy_data(x*i, y*i)) for x = interval, y = interval]
    surface(
        interval, interval, surf_func(10),
        color = rand(RGBAf0, 124, 124)
    )

end
output(result)

39.
Line Function

using Makie

result = let
    scene = Scene()
    x = range(0, stop = 3pi, length = 200)
    lines!(scene, x, sin.(x))
    lines!(scene, x, cos.(x), color = :blue)

end
output(result)

40.
Meshscatter Function

using Makie
 using GeometryTypes

result = let
    large_sphere = Sphere(Point3f0(0), 1f0)
    positions = decompose(Point3f0, large_sphere)
    colS = [RGBAf0(rand(), rand(), rand(), 1.0) for i = 1:length(positions)]
    sizesS = [rand(Point3f0) .* 0.05f0 for i = 1:length(positions)]
    meshscatter(positions, color = colS, markersize = sizesS)

end
output(result)

41.
scatter

using Makie

result = let
    scatter(rand(20), rand(20), markersize = 0.03)

end
output(result)

42.
Marker sizes

using Makie

result = let
    scatter(rand(20), rand(20), markersize = rand(20)./20, color = to_colormap(:Spectral, 20))

end
output(result)

43.
Record Video

using Makie

result = let
    scene = Scene()

    f(t, v, s) = (sin(v + t) * s, cos(v + t) * s, (cos(v + t) + sin(v)) * s)
    t = Node(Base.time()) # create a life signal
    limits = FRect3D(Vec3f0(-1.5, -1.5, -3), Vec3f0(3, 3, 6))
    p1 = meshscatter!(scene, lift(t-> f.(t, range(0, stop = 2pi, length = 50), 1), t), markersize = 0.05)[end]
    p2 = meshscatter!(scene, lift(t-> f.(t * 2.0, range(0, stop = 2pi, length = 50), 1.5), t), markersize = 0.05)[end]

    lines = lift(p1[1], p2[1]) do pos1, pos2
        map((a, b)-> (a, b), pos1, pos2)
    end
    linesegments!(scene, lines, linestyle = :dot, limits = limits)
    # record a video
    N = 150
    record(scene, "/results/record_video.gif", 1:N) do i
        push!(t, Base.time())
   end

end
output(result)

44.
3D Contour with 2D contour slices

using Makie
 using LinearAlgebra

result = let
    function test(x, y, z)
        xy = [x, y, z]
        ((xy') * Matrix(I, 3, 3) * xy) / 20
    end
    x = range(-2pi, stop = 2pi, length = 100)
    scene = Scene()
    c = contour!(scene, x, x, x, test, levels = 6, alpha = 0.3)[end]
    xm, ym, zm = minimum(scene.limits[])
    # c[4] == fourth argument of the above plotting command
    contour!(scene, x, x, map(v-> v[1, :, :], c[4]), transformation = (:xy, zm), linewidth = 10)
    heatmap!(scene, x, x, map(v-> v[:, 1, :], c[4]), transformation = (:xz, ym))
    contour!(scene, x, x, map(v-> v[:, :, 1], c[4]), fillrange = true, transformation = (:yz, xm))

end
output(result)

45.
Contour3d

using Makie

result = let
    function xy_data(x, y)
        r = sqrt(x*x + y*y)
        r == 0.0 ? 1f0 : (sin(r)/r)
    end
    r = range(-1, stop = 1, length = 100)
    contour3d(r, r, (x,y)-> xy_data(10x, 10y), levels = 20, linewidth = 3)

end
output(result)

46.
Arrows 3D

using Makie
 using LinearAlgebra

result = let
    function SphericalToCartesian(r::T,θ::T,ϕ::T) where T<:AbstractArray
        x = @.r*sin(θ)*cos(ϕ)
        y = @.r*sin(θ)*sin(ϕ)
        z = @.r*cos(θ)
        Point3f0.(x, y, z)
    end
    n = 100^2 #number of points to generate
    r = ones(n);
    θ = acos.(1 .- 2 .* rand(n))
    φ = 2π * rand(n)
    pts = SphericalToCartesian(r,θ,φ)
    arrows(pts, (normalize.(pts) .* 0.1f0), arrowsize = 0.02, linecolor = :green, arrowcolor = :darkblue)

end
output(result)

47.
Image on Surface Sphere

using Makie

result = let
    n = 20
    θ = [0;(0.5:n-0.5)/n;1]
    φ = [(0:2n-2)*2/(2n-1);2]
    x = [cospi(φ)*sinpi(θ) for θ in θ, φ in φ]
    y = [sinpi(φ)*sinpi(θ) for θ in θ, φ in φ]
    z = [cospi(θ) for θ in θ, φ in φ]
    rand([-1f0, 1f0], 3)
    pts = vec(Point3f0.(x, y, z))
    surface(x, y, z, color = Makie.logo())

end
output(result)

48.
Arrows on Sphere

using Makie
 using LinearAlgebra

result = let
    n = 20
    f   = (x,y,z) -> x*exp(cos(y)*z)
    ∇f  = (x,y,z) -> Point3f0(exp(cos(y)*z), -sin(y)*z*x*exp(cos(y)*z), x*cos(y)*exp(cos(y)*z))
    ∇ˢf = (x,y,z) -> ∇f(x,y,z) - Point3f0(x,y,z)*dot(Point3f0(x,y,z), ∇f(x,y,z))

    θ = [0;(0.5:n-0.5)/n;1]
    φ = [(0:2n-2)*2/(2n-1);2]
    x = [cospi(φ)*sinpi(θ) for θ in θ, φ in φ]
    y = [sinpi(φ)*sinpi(θ) for θ in θ, φ in φ]
    z = [cospi(θ) for θ in θ, φ in φ]

    pts = vec(Point3f0.(x, y, z))
    ∇ˢF = vec(∇ˢf.(x, y, z)) .* 0.1f0
    surface(x, y, z)
    arrows!(
        pts, ∇ˢF,
        arrowsize = 0.03, linecolor = (:white, 0.6), linewidth = 3
    )

end
output(result)

49.
surface + contour3d

using Makie

result = let
    vx = -1:0.01:1
    vy = -1:0.01:1

    f(x, y) = (sin(x*10) + cos(y*10)) / 4

    p1 = surface(vx, vy, f)
    p2 = contour3d(vx, vy, (x, y) -> f(x,y), levels = 15, linewidth = 3)

    scene = AbstractPlotting.vbox(p1, p2)
    text!(campixel(p1), "surface", position = widths(p1) .* Vec(0.5, 1), align = (:center, :top), raw = true)
    text!(campixel(p2), "contour3d", position = widths(p2) .* Vec(0.5, 1), align = (:center, :top), raw = true)
    scene

end
output(result)

50.
FEM mesh 3D

using Makie
 using GeometryTypes

result = let
    cat = Makie.loadasset("cat.obj")
    vertices = decompose(Point3f0, cat)
    faces = decompose(Face{3, Int}, cat)
    coordinates = [vertices[i][j] for i = 1:length(vertices), j = 1:3]
    connectivity = [faces[i][j] for i = 1:length(faces), j = 1:3]
    mesh(
        coordinates, connectivity,
        color = rand(length(vertices))
    )

end
output(result)

51.
Axis + Surface

using Makie

result = let
    vx = -1:0.01:1
    vy = -1:0.01:1

    f(x, y) = (sin(x*10) + cos(y*10)) / 4
    scene = Scene(resolution = (500, 500))
    # One way to style the axis is to pass a nested dictionary / named tuple to it.
    surface!(scene, vx, vy, f, axis = NT(frame = NT(linewidth = 2.0)))
    psurf = scene[end] # the surface we last plotted to scene
    # One can also directly get the axis object and manipulate it
    axis = scene[Axis] # get axis

    # You can access nested attributes likes this:
    axis[:names, :axisnames] = ("\\bf{ℜ}[u]", "\\bf{𝕴}[u]", " OK\n\\bf{δ}\n γ")
    tstyle = axis[:names] # or just get the nested attributes and work directly with them

    tstyle[:textsize] = 10
    tstyle[:textcolor] = (:red, :green, :black)
    tstyle[:font] = "helvetica"


    psurf[:colormap] = :RdYlBu
    wh = widths(scene)
    t = text!(
        campixel(scene),
        "Multipole Representation of first resonances of U-238",
        position = (wh[1] / 2.0, wh[2] - 20.0),
        align = (:center,  :center),
        textsize = 20,
        font = "helvetica",
        raw = :true
    )
    c = lines!(scene, Circle(Point2f0(0.1, 0.5), 0.1f0), color = :red, offset = Vec3f0(0, 0, 1))
    scene
    #update surface
    # TODO explain and improve the situation here
    psurf.converted[3][] = f.(vx .+ 0.5, (vy .+ 0.5)')
    scene

end
output(result)

52.
Fluctuation 3D

using Makie
 using GeometryTypes, Colors

result = let
    scene = Scene()
    # define points/edges
    perturbfactor = 4e1
    N = 3; nbfacese = 30; radius = 0.02
    large_sphere = Sphere(Point3f0(0), 1f0)
    positions = decompose(Point3f0, large_sphere, 30)
    np = length(positions)
    pts = [positions[k][l] for k = 1:length(positions), l = 1:3]
    pts = vcat(pts, 1.1 * pts + randn(size(pts)) / perturbfactor) # light position influence ?
    edges = hcat(collect(1:np), collect(1:np) + np)
    ne = size(edges, 1); np = size(pts, 1)
    # define markers meshes
    meshC = GLNormalMesh(
        Makie.Cylinder{3, Float32}(
            Point3f0(0., 0., 0.),
            Point3f0(0., 0, 1.),
            Float32(1)
        ), nbfacese
    )
    meshS = GLNormalMesh(large_sphere, 20)
    # define colors, markersizes and rotations
    pG = [Point3f0(pts[k, 1], pts[k, 2], pts[k, 3]) for k = 1:np]
    lengthsC = sqrt.(sum((pts[edges[:,1], :] .- pts[edges[:, 2], :]) .^ 2, dims = 2))
    sizesC = [Vec3f0(radius, radius, lengthsC[i]) for i = 1:ne]
    sizesC = [Vec3f0(1., 1., 1.) for i = 1:ne]
    colorsp = [RGBA{Float32}(rand(), rand(), rand(), 1.) for i = 1:np]
    colorsC = [(colorsp[edges[i, 1]] + colorsp[edges[i, 2]]) / 2. for i = 1:ne]
    sizesC = [Vec3f0(radius, radius, lengthsC[i]) for i = 1:ne]
    Qlist = zeros(ne, 4)
    for k = 1:ne
        ct = GeometryTypes.Cylinder{3, Float32}(
            Point3f0(pts[edges[k, 1], 1], pts[edges[k, 1], 2], pts[edges[k, 1], 3]),
            Point3f0(pts[edges[k, 2], 1], pts[edges[k, 2], 2], pts[edges[k, 2], 3]),
            Float32(1)
        )
        Q = GeometryTypes.rotation(ct)
        r = 0.5 * sqrt(1 + Q[1, 1] + Q[2, 2] + Q[3, 3]); Qlist[k, 4] = r
        Qlist[k, 1] = (Q[3, 2] - Q[2, 3]) / (4 * r)
        Qlist[k, 2] = (Q[1, 3] - Q[3, 1]) / (4 * r)
        Qlist[k, 3] = (Q[2, 1] - Q[1, 2]) / (4 * r)
    end
    rotationsC = [Makie.Vec4f0(Qlist[i, 1], Qlist[i, 2], Qlist[i, 3], Qlist[i, 4]) for i = 1:ne]
    # plot
    hm = meshscatter!(
        scene, pG[edges[:, 1]],
        color = colorsC, marker = meshC,
        markersize = sizesC,  rotations = rotationsC,
    )
    hp = meshscatter!(
        scene, pG,
        color = colorsp, marker = meshS, markersize = radius,
    )

end
output(result)

53.
Connected Sphere

using Makie

result = let
    large_sphere = Sphere(Point3f0(0), 1f0)
    positions = decompose(Point3f0, large_sphere)
    linepos = view(positions, rand(1:length(positions), 1000))
    scene = lines(linepos, linewidth = 0.1, color = :black)
    scatter!(scene, positions, strokewidth = 10, strokecolor = :white, color = RGBAf0(0.9, 0.2, 0.4, 0.6))
    scene

end
output(result)

54.
image scatter

using Makie
 using LinearAlgebra

result = let
    scatter(
        1:10, 1:10, rand(10, 10) .* 10,
        rotations = normalize.(rand(Quaternionf0, 10*10)),
        markersize = 1,
        # can also be an array of images for each point
        # need to be the same size for best performance, though
        marker = Makie.logo()
    )

end
output(result)

55.
Simple meshscatter

using Makie

result = let
    large_sphere = Sphere(Point3f0(0), 1f0)
    positions = decompose(Point3f0, large_sphere)
    meshscatter(positions, color = RGBAf0(0.9, 0.2, 0.4, 1), markersize = 0.05)

end
output(result)

56.
Animated surface and wireframe

using Makie

result = let
    scene = Scene();
    function xy_data(x, y)
        v = sqrt(x^2 + y^2)
        v == 0.0 ? 1f0 : (sin(v)/v)
    end

    r = range(-2, stop = 2, length = 50)
    surf_func(i) = [Float32(xy_data(x*i, y*i)) for x = r, y = r]
    z = surf_func(20)
    surf = surface!(scene, r, r, z)[end]

    wf = wireframe!(scene, r, r, Makie.lift(x-> x .+ 1.0, surf[3]),
        linewidth = 2f0, color = Makie.lift(x-> to_colormap(x)[5], surf[:colormap])
    )
    N = 150
    scene
    record(scene, "/results/animated_surface_and_wireframe.gif", range(5, stop = 40, length = N)) do i
        surf[3] = surf_func(i)
   end

end
output(result)

57.
Normals of a Cat

using Makie
 using LinearAlgebra

result = let
    x = Makie.loadasset("cat.obj")
    mesh(x, color = :black)
    pos = map(x.vertices, x.normals) do p, n
        p => p .+ (normalize(n) .* 0.05f0)
    end
    linesegments!(pos, color = :blue)

end
output(result)

58.
Sphere Mesh

using Makie

result = let
    mesh(Sphere(Point3f0(0), 1f0), color = :blue)

end
output(result)

59.
Stars

using Makie

result = let
    stars = 100_000
    scene = Scene(backgroundcolor = :black)
    scatter!(
        scene,
        (rand(Point3f0, stars) .- 0.5) .* 10,
        glowwidth = 0.005, glowcolor = :white, color = RGBAf0(0.8, 0.9, 0.95, 0.4),
        markersize = rand(range(0.0001, stop = 0.01, length = 100), stars),
        show_axis = false
    )
    update_cam!(scene, FRect3D(Vec3f0(-2), Vec3f0(4)))
    scene

end
output(result)

60.
Unicode Marker

using Makie

result = let
    scene = Scene(resolution = (500, 500))
    scatter!(scene, Point3f0[(1,0,0), (0,1,0), (0,0,1)], marker = [:x, :circle, :cross])

end
output(result)

61.
Merged color Mesh

using Makie
 using GeometryTypes

result = let
    x = Vec3f0(0); baselen = 0.2f0; dirlen = 1f0
    # create an array of differently colored boxes in the direction of the 3 axes
    rectangles = [
        (HyperRectangle(Vec3f0(x), Vec3f0(dirlen, baselen, baselen)), RGBAf0(1,0,0,1)),
        (HyperRectangle(Vec3f0(x), Vec3f0(baselen, dirlen, baselen)), RGBAf0(0,1,0,1)),
        (HyperRectangle(Vec3f0(x), Vec3f0(baselen, baselen, dirlen)), RGBAf0(0,0,1,1))
    ]
    meshes = map(GLNormalMesh, rectangles)
    mesh(merge(meshes))

end
output(result)

62.
Moire

using Makie

result = let
    function cartesian(ll)
        return Point3f0(
            cos(ll[1]) * sin(ll[2]),
            sin(ll[1]) * sin(ll[2]),
            cos(ll[2])
        )
    end
    fract(x) = x - floor(x)
    function calcpositions(rings, index, time, audio)
        movement, radius, speed, spin = 1, 2, 3, 4;
        position = Point3f0(0.0)
        precision = 0.2f0
        for ring in rings
            position += ring[radius] * cartesian(
                precision *
                index *
                Point2f0(ring[spin] + Point2f0(sin(time * ring[speed]), cos(time * ring[speed])) * ring[movement])
            )
        end
        amplitude = audio[round(Int, clamp(fract(position[1] * 0.1), 0, 1) * (25000-1)) + 1]; # index * 0.002
        position *= 1.0 + amplitude * 0.5;
        position
    end
    rings = [(0.1f0, 1.0f0, 0.00001f0, Point2f0(0.2, 0.1)), (0.1f0, 0.0f0, 0.0002f0, Point2f0(0.052, 0.05))]
    N2 = 25000
    t_audio = sin.(range(0, stop = 10pi, length = N2)) .+ (cos.(range(-3, stop = 7pi, length = N2)) .* 0.6) .+ (rand(Float32, N2) .* 0.1) ./ 2f0
    start = time()
    t = (time() - start) * 100
    pos = calcpositions.((rings,), 1:N2, t, (t_audio,))

    scene = lines(pos, color = RGBAf0.(to_colormap(:RdBu, N2), 0.6), thickness = 0.6f0, show_axis = false)
    linesegments!(scene, FRect3D(Vec3f0(-1.5), Vec3f0(3)), raw = true, linewidth = 3, linestyle = :dot)
    eyepos = Vec3f0(5, 1.5, 0.5)
    lookat = Vec3f0(0)
    update_cam!(scene, eyepos, lookat)
    l = scene[1]
    N = 150
    record(scene, "/results/moire.gif", 1:N) do i
        t = (time() - start) * 700
        pos .= calcpositions.((rings,), 1:N2, t, (t_audio,))
        l[1] = pos # update argument 1
        rotate_cam!(scene, 0.0, 0.01, 0.01)
   end

end
output(result)

63.
Line GIF

using Makie

result = let
    us = range(0, stop = 1, length = 100)
    scene = Scene()
    scene = linesegments!(scene, FRect3D(Vec3f0(0, -1, 0), Vec3f0(1, 2, 2)))
    p = lines!(scene, us, sin.(us .+ time()), zeros(100), linewidth = 3)[end]
    global lineplots = [p]
    translate!(p, 0, 0, 0)
    colors = to_colormap(:RdYlBu)
    #display(scene) # would be needed without the record
    N = 150
    path = record(scene, "/results/line_gif.gif", 1:N) do i
        lineplots, scene
        if length(lineplots) < 20
            p = lines!(
                scene,
                us, sin.(us .+ time()), zeros(100),
                color = colors[length(lineplots)],
                linewidth = 3
            )[end]
            pushfirst!(lineplots, p)
            translate!(p, 0, 0, 0)
            #TODO automatically insert new plots
            insert!(Makie.global_gl_screen(), scene, p)
        else
            lineplots = circshift(lineplots, 1)
            lp = first(lineplots)
            lp[2] = sin.(us .+ time())
            translate!(lp, 0, 0, 0)
        end
        for lp in Iterators.drop(lineplots, 1)
            z = translation(lp)[][3]
            translate!(lp, 0, 0, z + 0.1)
        end
    end
    path

end
output(result)

64.
Surface + wireframe + contour

using Makie

result = let
    N = 51
    x = range(-2, stop = 2, length = N)
    y = x
    z = (-x .* exp.(-x .^ 2 .- (y') .^ 2)) .* 4

    scene = wireframe(x, y, z)
    xm, ym, zm = minimum(scene.limits[])
    scene = surface!(scene, x, y, z)
    contour!(scene, x, y, z, levels = 15, linewidth = 2, transformation = (:xy, zm))
    scene

end
output(result)

65.
Interaction with Mouse

using Makie
 using LinearAlgebra

result = let
    scene = Scene()
    r = range(0, stop = 3, length = 4)
    cam2d!(scene)
    time = Node(0.0)
    pos = lift(scene.events.mouseposition, time) do mpos, t
        map(range(0, stop = 2pi, length = 60)) do i
            circle = Point2f0(sin(i), cos(i))
            mouse = to_world(scene, Point2f0(mpos))
            secondary = (sin((i * 10f0) + t) * 0.09) * normalize(circle)
            (secondary .+ circle) .+ mouse
        end
    end
    scene = lines!(scene, pos, raw = true)
    p1 = scene[end]
    p2 = scatter!(
        scene,
        pos, markersize = 0.1f0,
        marker = :star5,
        color = p1[:color],
        raw = true
    )[end]
    scene
    display(Makie.global_gl_screen(), scene)

    p1[:color] = RGBAf0(1, 0, 0, 0.1)
    # p2[:marker] = 'π' #TODO fix this
    p2[:markersize] = 0.2

    # push a reasonable mouse position in case this is executed as part
    # of the documentation
    push!(scene.events.mouseposition, (250.0, 250.0))
    N = 50
    record(scene, "/results/interaction_with_mouse.gif", range(0.01, stop = 0.4, length = N)) do i
        push!(scene.events.mouseposition, (250.0, 250.0))
        p2[:markersize] = i
        push!(time, time[] + 0.1)
   end

end
output(result)

66.
Mouse Picking

using Makie

result = let
    img = rand(100, 100)
    scene = Scene()
    heatmap!(scene, img, scale_plot = false)
    clicks = Node(Point2f0[(0,0)])
    foreach(scene.events.mousebuttons) do buttons
       if ispressed(scene, Mouse.left)
           pos = to_world(scene, Point2f0(scene.events.mouseposition[]))
           push!(clicks, push!(clicks[], pos))
       end
       return
    end
    scatter!(scene, clicks, color = :red, marker = '+', markersize = 10, raw = true)

end
output(result)

67.
pong

using Makie

result = let
    global xyvec = rand(Point2f0, (2)) .* 5 .+ 1
    global velvec = rand(Point2f0, (2)) .* 10
    # define some other parameters
    global t = 0
    ts = 0.03
    balldiameter = 1
    origin = Point2f0(0, 0)
    xybounds = Point2f0(10, 10)
    N = 200
    scene = scatter(
        xyvec,
        markersize = balldiameter,
        color = rand(RGBf0, 2),
        limits = FRect(0, 0, xybounds)
    )
    s = scene[end] # last plot in scene

    record(scene, "/results/pong.gif", 1:N) do i
        # calculate new ball position
        global t = t + ts
        global xyvec = xyvec .+ velvec .* ts
        global velvec = map(xyvec, xybounds, origin, velvec) do p, b, o, vel
            boolvec = ((p .+ balldiameter/2) .> b) .| ((p .- balldiameter/2) .< o)
            velvec = map(boolvec, vel) do b, v
                b ? -v : v
            end
        end
        # plot
        s[1] = xyvec
   end

end
output(result)

68.
pulsing marker

using Makie

result = let
    N = 100
    scene = scatter([0], [0], marker = '❤', markersize = 0.5, color = :red, raw = true)
    s = scene[end] # last plot in scene
    record(scene, "/results/pulsing_marker.gif", range(0, stop = 10pi, length = N)) do i
        s[:markersize] = (cos(i) + 1) / 4 + 0.2
   end

end
output(result)

69.
Travelling wave

using Makie

result = let
    scene = Scene()
    time = Node(0.0)
    f(v, t) = sin(v + t)
    scene = lines!(
        scene,
        lift(t -> f.(range(0, stop = 2pi, length = 50), t), time),
        color = :blue
    )
    p1 = scene[end];
    N = 100
    record(scene, "/results/travelling_wave.gif", range(0, stop = 4pi, length = N)) do i
        time[] = i
   end

end
output(result)

70.
colormaps

using Makie

result = let
    global h = 0.0
    global offset = 0.1
    scene = Scene()
    cam2d!(scene)
    plot = map(AbstractPlotting.colorbrewer_names) do cmap
        global h
        c = to_colormap(cmap)
        cbar = image!(
            scene,
            range(0, stop = 10, length = length(c)),
            range(0, stop = 1, length = length(c)),
            reshape(c, (1, length(c))),
            show_axis = false
        )[end]
        text!(
            scene,
            string(cmap, ":"),
            position = Point2f0(-0.1, 0.5 + h),
            align = (:right, :center),
            show_axis = false,
            textsize = 0.4
        )
        translate!(cbar, 0, h, 0)
        h -= (1 + offset)
    end
    scene

end
output(result)