Simon Danisch / Jul 23 2019
Makie
About Me
Simon Danisch
- studied Cognitive Science
- worked with the MIT Julia-Lab
- Now Julia expert at Nextjournal
Overview
- How does Makie work
- Recipes & Extensions
- Backends
- Makie's interactivity
- State of Makie
Thanks to:
- Anshul Singhvi for rocking docs + everything
- Pietro Vertechi for StatsMakie & Interact
- Chris Foster for improving OpenGL shaders
Makie, It's all primitives
Makie currently just a combination of ~8 primitives:
Could be cut down to:
hbox(vbox(c, f), vbox(a, e))
Mesh Primitive:
Line Primitive:
What's missing
- refactor to make it easy to cut down primitives
- make it easier to overload specific plots for optimizations
- overload for better support (e.g. text in PDF/SVG)
Backends
- CairoMakie (Print, SVG/PDF, 70% features)
- WGLMakie (Web, fast drawing, 80% features)
- GLMakie (Desktop, high performance, 100% features)
using CairoMakie CairoMakie.activate!(type = "png") line_primitives
What's missing
CairoMakie:
- better text support
- interactivity
- pdf backend
- better 3d mesh support (2d mesh works fairly well)
WGLMakie:
- performance
- proper line shader
- resizing buffers and small other things don't update yet
- picking
Scene Graph: putting things together
- every scene has a transformation (scale, rotation, translation)
- every child applies the parents transformation to its own transformation
WGLMakie.activate!() earth_col = load("earth.png") moon_col = load("moon.jpg") universe = Scene( limits = FRect3D(Vec3f0(-5), Vec3f0(10)), show_axis = false, resolution = (650, 300), center = false ) sun = mesh!(universe, Sphere(Point3f0(0), 0.5f0), color = :yellow) earth = mesh!(Scene(sun), Sphere(Point3f0(0), 1.5f0), color = earth_col) moon = mesh!(Scene(earth), Sphere(Point3f0(0), 0.5f0), color = moon_col) translate!(earth, 0.0, 6.0, 0.0) rotate!(moon, Vec3f0(0, 0, 1), 0.5pi) translate!(moon, 0f0, 3f0, 0f0) update_cam!(universe, Vec3f0(-15, 0, 10), Vec3f0(0.0)) universe
for i in LinRange(0, 2pi, 360) rotate!(sun, Vec3f0(0, 0, 1), i) rotate!(earth, Vec3f0(0, 0, 1), i) WGLMakie.redraw!(getscreen(universe)) # TODO make it update on itself sleep(0.01) end
Layouting
Windows are FREEE :)
set_theme!(resolution = (600, 300)) p = Scene() a = scatter!(Scene(p, Rect(0, 0, 300, 300)), rand(4)) b = scatter!(Scene(p, Rect(300, 0, 300, 300)), rand(4), color = :red) p
resize!(a, Rect(60, 50, 100, 100)); update!(a);
Vbox & Hbox
using MakieGallery using AbstractPlotting: hbox, vbox set_theme!(resolution = (650, 500)) p1 = run_example("Tutorial adding to a scene") p2 = run_example("Contour Function") p3 = run_example("Arrows on Sphere") p4 = run_example("Surface") p5 = run_example("Customize Axes") pscene = vbox( hbox(p1, p2), p3, hbox(p4, p5, sizes = [0.7, 0.3]), sizes = [0.2, 0.6, 0.2] )
- with that we can implement arbitrary layouts
- ... Just have to do the math and figure out nice APIs
- better axis support needed
- Julius Krumbiegel ported a Layout constraint solver:
Recipes
- The act of combining primitives: Recipes
struct MyType end function AbstractPlotting.plot!(p::Plot(MyType)) mytype = p[1] # first argument to plot(...) plot!(p, ...) end
Julia
2.6s
Run (Julia)
using AbstractPlotting import AbstractPlotting: Plot, default_theme, plot!, to_value set_theme!(resolution = (650, 400)); WGLMakie.activate!() struct Simulation grid::Vector{Point3f0} end function default_theme(scene::SceneLike, ::Type{<: Plot(Simulation)}) Theme( advance = 0, molecule_sizes = [0.08, 0.04, 0.04], molecule_colors = [:maroon, :deepskyblue2, :deepskyblue2] ) end function AbstractPlotting.plot!(p::Plot(Simulation)) sim = p[1][] # first argument is the SimulationResult mpos = lift(p.advance) do i sim.grid .+ rand(Point3f0, length(sim.grid)) .* 0.01f0 end N = lift(length, mpos) r3(x) = repeat(x, outer = N[] ÷ 3) sizes = lift(r3, p.molecule_sizes) colors = lift(r3, p.molecule_colors) meshscatter!(p, mpos, markersize = sizes, color = colors) segs = lift(p.plots[end][1]) do x indices = Int[] for i in 1:3:length(x) push!(indices, i, i + 1, i, i + 2) end view(x, indices) end linesegments!(p, segs) end function viz() n = 5 r = LinRange(-1, 1, n) grid = Point3f0.(r, reshape(r, (1, n, 1)), reshape(r, (1, 1, n))) molecules = map(1:(n^3) * 3) do i i3 = ((i - 1) ÷ 3) + 1 xy = 0.1; z = 0.08 i % 3 == 1 && return grid[i3] i % 3 == 2 && return grid[i3] + Point3f0(xy, xy, z) i % 3 == 0 && return grid[i3] + Point3f0(-xy, xy, z) end plot(Simulation(molecules)) end scene = viz() simplot = scene[end] scene
for i in 1:100 simplot.advance = i sleep(1/30) end
What's missing
- multiple scenes from one recipe ( and layouting them)
- more consistent API
- improved Node handling
- ingesting Plots.jl recipes
- documentation
StatsMakie
from the StatsMakie tutorial
iris = RDatasets.dataset("datasets", "iris") scatter(Data(iris), Group(:Species), :SepalLength, :SepalWidth)
using CSV, RDatasets, MakieThemes, StatsMakie, CairoMakie, AbstractPlotting for dataset ∈ (:www, :drivers, :mtcars, :diamonds) $(dataset) = CSV.read( dirname(pathof(MakieThemes))*"/../data/"*$(string(dataset))*".tsv" ) end set_theme!(ggthemr(:fresh)) p1 = lines( Data(www), :Minute, :Users, Group(color = :Measure, marker = :Measure), ) p2 = plot(density, Data(mtcars), :mpg, Group(color = :cyl)); p3 = plot(Position.stack, histogram, Data(diamonds), :price, Group(color = :cut)); p4 = boxplot(Data(drivers), :Year, :Deaths); vbox(hbox(p3, p1), hbox(p4, p2))
What's missing
- legends
- unit support
- log/log10/logn/polar/flipping with axis support
- more Plots.jl convenient functions
Interaction
Everything is a Node (a.k.a Observable, a.k.a Signal)
WGLMakie.activate!() set_theme!(resolution = (600, 300)) data = Node(rand(4)) color = Node(:red) scene = scatter(data, color = color) lines!(Rect(1, 0, 4, 1))
data[] = rand(4) color[] = :green;
scene = lines(Rect(1, 0, 4, 1)) scatter!(rand(4))
scene[end].color = rand(RGBf0) scene[end][1] = rand(4);
for k in propertynames(scene.events) println(k, ": ", getfield(scene.events, k)[]) end
Interact is also build on Observables!
build by @shashi & @piever
using Interact a = Interact.slider(0f0:50f0, label = "a", value = 13) b = Interact.slider(-20f0:20f0, label = "b", value = 10) c = Interact.slider(0f0:20f0, label = "c", value = 2) d = Interact.slider(LinRange(0.0, 0.02, 100), label = "d", value = 0.01) scales = Interact.slider(LinRange(0.01, 0.5, 100), label = "scale", value = 0.1) colorsw = Interact.widget(colorant"red") sliders = Interact.vbox(a, b, c, d, scales, colorsw)
using Colors, Interact, AbstractPlotting, WGLMakie using AbstractPlotting: Node function lorenz(array::Vector, a = 5.0 ,b = 2.0, c = 6.0, d = 0.01) t0 = Point3f0(0.1, 0, 0) for i = eachindex(array) t0 = Point3f0( t0[1] + d * a * (t0[2] - t0[1]), t0[2] + d * (t0[1] * (b - t0[3]) - t0[2]), t0[3] + d * (t0[1] * t0[2] - c * t0[3]), ) array[i] = t0 end array end n1, n2 = 18, 30 N = n1*n2 args_n = (a, b, c, d) v0 = lorenz(zeros(Point3f0, N), getindex.(args_n)...) positions = map(lorenz, Node(v0), args_n...) mesh_scene = meshscatter(positions,markersize = scales, color = colorsw) Interact.hbox( sliders, center!(mesh_scene) )
img = rand(100, 100) scene = Scene(resolution = (500, 500)) heatmap!(scene, img, scale_plot = false) clicks = Node(zeros(Point2f0, 100)) colors = Node(zeros(RGBAf0, 100)) last_idx = Ref(0) on(scene.events.mousebuttons) do buttons pos = mouseposition(scene) last_idx[] += 1 clicks[][last_idx[]] = pos colors[][last_idx[]] = RGBAf0(1, 0, 0, 1) clicks[] = clicks[]; colors[] = colors[] return end scatter!(scene, clicks, color = colors, marker = '+', markersize = 10); scene
What's missing?
- unified API between Interact & Makie
- picking and complex interactions in WebGL + Cairo