Building slides with Pluto.jl
January 14, 2021
I recently gave my first talk (available on GitHub) using nothing but a Pluto notebook as slides. Here are some things I learned.
What is a Pluto notebook?
If you haven't heard of it before, the Pluto.jl package is a very powerful contender to Jupyter notebooks.
While Jupyter is somewhat language-agnostic (can you spot the three programming languages hiding in its name?), Pluto is made specifically for Julia.
One of its core features is knowing what other cells a cell depends on and updating the dependents accordingly!
Say, one cell defines x = 3
and another y = x + 4
, then y
is automatically updated whenever you change x
.
Another nicety is the possibility to hide code cells (by clicking on the eye-button) and/or hide outputs (by appending a ;
to the code).
This is very useful if you're concerned with how things look, as you probably are when it comes to slides.
Definitely go check this package out if you haven't already, it's probably one of the greatest contributions to the Julia ecosystem so far, in my opinion.
Building slides
For the rest of this article, I assume that you know what Pluto is about and can work with it. Let's start by writing down what we would like to achieve:
- Have slides (duh!)
- Have interactivity
- Show animations
- Layout and style content
- Have it snappy, time is money!
Slides
As obvious as this goal is, it's actually not (yet) trivial in Pluto. It boils down to what we can find in this issue.
Pluto has an undocumented feature: It starts a hidden presentation mode when the JavaScript function present()
is called (not the Julia function!).
This has the following effects:
- Space is inserted before headings of first and second order (
<h1>...</h1>
and<h2>...</h2>
in HTML or# ...
and## ...
in Markdown), such that consecutive ones have a distance of more than one screen height. (My impression is that first order headings are good for your title slide or maybe other slides with almost no other content, while second order headings work well as slide titles.) - Navigation symbols (next/previous slide) become visible in the bottom right corner.
- The Live docs button is hidden.
This may feel like a hack and it surely is one but it does work quite nicely.
For a real slide-appearance, though, I found it necessary to manually zoom in in the browser (200 % worked for me) and put my browser into fullscreen mode (press F11 or something like that (yeah, you probably know that yourself, I just recently found out there is a <kbd>
tag in HTML and wanted to brag with that skill)).
To make calling that JavaScript function convenient, you can add the following to the beginning of your notebook:
html"<button onclick='present()'>present</button>"
Now you only have to click a button. Clicking it again will leave presentation mode.
Taking the traditional route, your slides will have a good amount of textual information, so the md""
(or md""" """
for multiline content) string macro is your valuable companion.
Using it effectively is the goal of the next section.
Textual information
Markdown supports quite a range of typography, especially headings, lists, and highlighting. Use it as it suits your needs (keep text on slides compact, you know the deal). Here is what I found to be particularly useful:
Strong text (inside ** **
) is nice for highlighting single words but can be too weak for really important statements.
Use low order headings (order 5 or 6) for that.
A symbol can express in maximal brevity what you would, otherwise, need many words for.
Make use of Julia's excellent Unicode support and Pluto's tab-completion!
Need an arrow? \rightarrow
Tab is your friend.
Or what about \rightwavearrow
or \downzigzagarrow
for your fancy proof by contradiction?
Speaking of proofs, Julia's markdown parser supports maths typesetting.
With ``\sin(x \cdot \pi)``
for inline maths and
```math
\int \mathrm{e}^{-x^2} \; \mathrm{d}x
```
for display style maths, you can summon the powers of LaTeX (or MathJax, rather).
Sometimes you want to have text that clearly stands out from its surrounding, be it for a more/less important note, an interjection, or whatever comes to your mind. Pluto formats blockquotes using a rounded box with gray background so you can use that:
md"""
I don't really care if you actually read this...
> But this is important!
> I will ask this in the exam!
And now you can go back to playing on your phone.
"""
Layouting
While Pluto strictly places content as a large vertical stack, it also allows for arbitrary HTML which we can use for advanced layouting. I present two ideas how we can utilise Julia's type- and IO-system to make our lifes easier.
Foldable content
You might want to have content that is only visible after explicitly clicking a certain element.
HTML offers the <details>
tag that does exactly that:
<details>
<summary> A secret </summary>
<p> Pluto is fun </p>
</details>
becomes
A secret
Pluto is fun
Now let's define a type Foldable
that represents a title and some content, as well as telling Julia how we would like to have it displayed by overriding Base.show
:
struct Foldable{C}
title::String
content::C
end
function Base.show(io, mime::MIME"text/html", fld::Foldable)
write(io,"<details><summary>$(fld.title)</summary><p>")
show(io, mime, fld.content)
write(io,"</p></details>")
end
It basically says, whenever a Foldable
is supposed to be shown on a display capable of HTML (as Pluto is), first write this HTML, then do whatever its content
does when it's displayed as HTML, and finally close the HTML tags.
We can now just write Foldable("What is the gravitational acceleration?", md"Correct, it's ``\pi^2``.")
or even Foldable("Some cool plot:", plot(0:10, x -> x^2))
.
Side by side
Having content arranged in two columns, like some text on the left and an image on the right, can look quite nice. The HTML makes use of CSS flexbox this time, but the rest is very similar to the above:
struct TwoColumn{L, R}
left::L
right::R
end
function Base.show(io, mime::MIME"text/html", tc::TwoColumn)
write(io, """<div style="display: flex;"><div style="flex: 50%;">""")
show(io, mime, tc.left)
write(io, """</div><div style="flex: 50%;">""")
show(io, mime, tc.right)
write(io, """</div></div>""")
end
You can use it as TwoColumn(md"Note the kink at ``x=0``!", plot(-5:5, abs))
.
Varying the column widths or introducing a further parameter for that is possible, of course.
Interactivity
Until now, you might wonder why you shouldn't just use LaTeX Beamer (or even PowerPoint *shiver*). And if your slides are rather static there's really no point in using Pluto for them. But if you want cool interactivity, then Pluto has your back!
One cannot overstate how cool Pluto's reactiveness is. As said in the beginning, whenever you change some piece of code, everything dependent on that is updated. You might, for example, have this kind of code:
result = some_fancy_computation(parameters)
followed by this cell:
md"Some text where $result is interpolated."
Now changing parameters
automatically changes this Markdown text.
Showing code
While you probably hide most of the code cells in a presentation notebook (especially those cosmetic ones we considered so far), Julia's syntax is quite presentable and can be displayed to show directly what happens. Why not reveal a cell that says
plot(data.time, data.gdp)
and be able to change data.gdp
to, say, data.avg_income
if you had not planned to present this but are asked to do so by someone in the audience?
Or, if your code did not turn out so concise, you can just wrap it in a simple function with few parameters (Clean interfaces, everyone! It always pays off! :p).
I think displaying code is generally helpful for discussions like "Nice finding, but what if we do …?" during a talk.
PlutoUI
The Pluto package has a little sister, PlutoUI, which makes interacting with the code visually much more appealing. Check out its documentation for all the features, I'll just gloss over what I have found most useful for presentations.
The fundamental concept here is Pluto's @bind
macro.
With @bind x some_ui_element
, you can get arbitrarily complex UI elements that the user can interact with to change x
.
Most of the time, you will probably use
@bind x Slider(1:100, default = 42)
which will give you a .
If you need to chose a category instead, try @bind cat Select(["yesterday", "today", "tomorrow"])
.
It happens that you'd like to change a variable at mutliple places. While Pluto does not allow redefining a variable, you can have
slider = @bind x Slider(10:20)
and then put slider
into any slide you need.
This spares you going back and forth a few slides just to adjust x
, but note that the appearance of the UI element is not automatically updated at all sites, so it will look wrong!
Graphics
No need to convince you of their importance, right? I guess you generally have three types of graphics: preexisting images, drawings, and plots.
Preexisting images
This is best done using PlutoUI again. You either use
Resource("https://julialang.org/assets/infra/logo.svg")
for online sources (preferred) or
LocalResource("../some/local/image.png")
for local files (discouraged, unless you somehow make sure to always distribute them with the notebook). These two types are not restricted to images, by the way, you can display audio and video as well.
Drawings
If you're the kind of person that heavily uses TikZ or diagrams in slides, you might look for something like Luxor.jl.
It allows for a concise, imperative style of building graphics, quite a bit simpler than TikZ, I would say.
I recommend using its @draw
macro (instead of @png
or @svg
) to prevent your working directory from being cluttered with graphics files:
@draw begin
text("A", Point(0, 0), halign=:center)
arrow(Point(0, 10), Point(0, 40))
text("B", Point(0, 50), halign=:center)
end
For networky stuff consider GraphPlot.jl as well.
Plots
You probably know the deal with plots. Use the Plots.jl package and its vast ecosystem. When it comes to choosing a backend, Plotly is nice for its interactivity while GR is faster (I think) and supports animations, see the next section. Of course, it's a matter of taste to some extent, so consider the other backends as well. (PlutoUI has a feature to display terminal output if you insist on using UnicodePlots, for example ;))
Animations
Your plots don't need to be static! Maybe you have a dynamic simulation or you need that extra time dimension to visualise your high dimensional data? There are basically two possibilities here:
The easiest thing is to have an automatic, looping animation.
All you need is the @gif
macro from Plots:
@gif for φ in 0 : .1 : 2π
plot(0 : .1 : 2π, t -> sin(t + φ))
end
Or, if you need more control, you can use @animate
together with the gif()
function:
let
# show the first 10 builtin colours:
animation = @animate for i in 1:10
scatter([0], [0], msize=10, shape=:hexagon, mcolour=i)
end
gif(animation, fps=10)
end
Suppose you have a fairly complex dynamics you'd like to demonstrate and you want to step through your animation manually. You could combine the following three cells to achieve this:
@bind plot_time Slider(1:42)
plots = let
state = init_my_complex_system()
collect(
let
update_state!(state, t)
x, y = extract_x_and_y(state)
plot(x, y)
end
for t in 1:42
)
end;
plots[plot_time]
Now you can take your time discussing each step in the greatest detail without "Oh and it's gone. Let's wait a moment until it comes again … Ah, here! Nope, gone again."
Miscellaneous and final remarks
You're probably quite invested in making your code fast anyway. But especially when it comes to interactive slides, optimise for minimal execution time! All interactivity is lost when you and your audience have to wait a minute for that new plot.
Also, make sure the notebook loads fast. You might not always have the opportunity to load it long before your talk starts. And you don't want to be known as the one with the fancy pants notebook who always talks for ages until the slides are finally ready. I see two good solutions to this issue:
- Avoid high-latency packages. The Time To First … problem is quite infamous among Julia users. Plots.jl seems to be alright by now, but just look out for that. Maybe a hand-written Euler-integration works for demonstration purposes and you can drop DifferentialEquations.jl?
- If you can, run the notebook remotely. What I mean is run
julia -e "using Pluto; Pluto.run(launch_browser = false)"
on a server and note down the URL that Pluto emits, then from your own computer SSH into the server usingssh user@host -NL 1234:localhost:1234
(this is supposed to output nothing, in case you wonder), and then open the URL you got from Pluto earlier on your own computer.
I once could enjoy my laptop crashing just when I wanted to start talking, and not only did I have to reboot and reconnect to the Zoom meeting but also start up the notebook and load DifferentialEquations again. Don't be like me and use the tips above.
This one is maybe trivial, but exploit that you can move cells freely in Pluto. Dependencies are figured out in a more complicated way anyway, so just move all utility code to the bottom where no one can see it. If you like, put two empty second order headings before that to produce an empty slide.
Pluto has just one scope for all cells which is nice in general but, if you can, use let
blocks to keep the global scope clean, as I did in some examples above.
Now is probably the best time you'll ever have for Pluto based presentations. Live changing code is difficult from the middle of a stage, but in a virtual meeting, in front of your own computer, interacting with the notebook is really convenient. You might want to look into my notebook mentioned in the beginning for inspiration. Give it a try, I hope this article helps.