Tutorial 5: Scaling the Elements
Introduction
The world is built up of tiny tiny building blocks known as atoms. ⚛️ These atoms come in many different sizes and each has different properties. Let's visualize these atoms and show their uniqueness!
P.S. This tutorial is not 100% representative of real life. If you spot the inaccuracy, feel free to open a PR. 😉
Learning Outcomes 📚
In this tutorial you'll learn:
How to apply scaling to an arbitrary shape
To use
Javis.jl
to interact with the following Julia packages:Ways of creating educational gifs
By the end of this tutorial, you will have made the following animation:
PeriodicTable.jl
and Unitful.jl
As normal with our tutorials, we need to import first the packages we will be using. In this tutorial, we are introducing two new packages:
PeriodicTable.jl
- Periodic table render in JuliaUnitful.jl
- Physical quantities with arbitrary units
These are straightforward to add to your Julia installation by executing the following in your Julia REPL:
julia> ] add Unitful, PeriodicTable
You might be wondering what these packages do. Let's dive into them then!
PeriodicTable.jl
enables one to look at information quickly related to the periodic table of elements. One can even print out such a table in their Julia REPL by doing the following:
julia> using PeriodicTable
julia> elements
Elements(…119 elements…):
H He
Li Be B C N O F Ne
Na Mg Al Si P S Cl Ar
K Ca Sc Ti V Cr Mn Fe Co Ni Cu Zn Ga Ge As Se Br Kr
Rb Sr Y Zr Nb Mo Tc Ru Rh Pd Ag Cd In Sn Sb Te I Xe
Cs Ba Hf Ta W Re Os Ir Pt Au Hg Tl Pb Bi Po At Rn
Fr Ra Rf Db Sg Bh Hs Mt Ds Rg Cn Nh Fl Mc Lv Ts Og
Uue
La Ce Pr Nd Pm Sm Eu Gd Tb Dy Ho Er Tm Yb Lu
Ac Th Pa U Np Pu Am Cm Bk Cf Es Fm Md No Lr
As the famous Mythbuster, Adam Savage once said, "IT'S SCIENTIFIC!" 🧪 🤓 One can even query PeriodicTable
to find out information on specific elements. Let's look up Oxygen (O) here!
julia> elements[8]
Oxygen (O), number 8:
category: diatomic nonmetal
atomic mass: 15.999 u
density: 1.429 g/cm³
melting point: 54.36 K
boiling point: 90.188 K
phase: Gas
shells: [2, 6]
e⁻-configuration: 1s² 2s² 2p⁴
summary: Oxygen is a chemical element with symbol O and atomic number 8. It is a member of the chalcogen group on the periodic table and is a highly reactive nonmetal and oxidizing agent that readily forms compounds (notably oxides) with most elements. By mass, oxygen is the third-most abundant element in the universe, after hydrogen and helium.
discovered by: Carl Wilhelm Scheele
named by: Antoine Lavoisier
source: https://en.wikipedia.org/wiki/Oxygen
spectral image: https://en.wikipedia.org/wiki/File:Oxygen_spectre.jpg
As fellow Julian, Johann-Tobias Schäg, said, one should learn Unitful.jl
if they want to interact with the real world. Unitful.jl
handles physical quantites such as pounds, meters, mols, etc. with minimal overhead in Julia. Further, it helps one to keep track of units and easily convert between different measurement systems.
Setting Up Our Animation
As always, let's import our needed packages:
using Animations
using Javis
using PeriodicTable
using Unitful
NOTE: For this tutorial, we will also use
Animations.jl
to provide what are called "easing functions".
These are used to control the speed at which an animation is drawn. This is further explained in Tutorial 6 so for now, don't worry too much about what we are doing with it.
And let's define our background function. This background function will also write the current frame being drawn:
function ground(video, action, frame)
background("white")
sethue("black")
text("$frame / 550", -240, -230)
end
Finally, let's get started with creating our javis
function:
demo = Video(500, 500)
javis(
demo,
[
BackgroundAction(1:550, ground),
...
],
pathname = "atomic.gif",
framerate = 10,
)
As you can see, the animation we are creating is going to have many frames! This is the longest animation we have made so far. Why? Not only are we going to examine many different elements, this tutorial also serves to illustrate how one can make longer animations to convey ideas. Think of it as your directoral debut! 🎬 🎥
Taming the Elements!
Each element has a different atomic mass. This atomic mass is measured in the unit called a "Dalton" (symbol: u) which is equivalent to 1/12 of the mass of a stationary carbon-12 atom. We can use the Scaling
functionality that Javis.jl
provides to visualize these different masses!
To accomplish this, we need to make a function that shows our currently viewed element:
function element(;color = "black")
sethue(color)
circle(O, 4, :fill)
end
Essentially, all the element
function does is create a circle in the middle of the frame with the initial size of 1.
From there, we need to define more Action
objects for our javis
function for the element we are viewing to scale:
...
Action(1:550,
(args...) -> element(),
subactions = [
SubAction(101:140, Scaling(1, 12)),
SubAction(241:280, Scaling(12, 20)),
SubAction(381:420, Scaling(20, 7)),
SubAction(521:550, Scaling(7, 1))
]
),
...
pathname = "atomic.gif",
framerate = 10
)
...
This will grow our element from 1
to 12
, from 12
to 20
, 20
to 7
, and finally 7
to 1
.
IMPORTANT:
Scaling
does not really scale an object but instead the entire canvas the object is drawn on. This produces the desired effect for two reasons:
- The
Action
is inside a Luxor layer which means that scaling inside this layer does not scale elements outside the layer (e.g. the frame counter in the upper right corner).- As it scales the canvas and not the
Action
, scaling only works nicely if the action is defined at the origin.
If you want to display the element somewhere else, for example, you should not change in the following snippet
function element(;color = "black") sethue(color) circle(O, 4, :fill) end
the point where the circle appears by changing
O
to the desiredPoint(x, y)
. Instead use anotherSubAction
, likeSubAction(1:1, Translation(x, y))
, to move the origin of the object to the desired location. Then scaling will work fine as it is defined on the first frame only.
That scaling looks like this:
Staring at this somewhat makes me think of a black hole... ⚫ But great! The only question now is... What are we looking at? Let's add some more information to this animation! 📝
How Much Does an Atom Weigh? ⚖️
To get the information about an element that we are currently previewing, we need to get information about our element. The first thing we need to get is the atomic mass of the element. So, how do we do that?
First, we must modify our javis
function slightly to store information about the circle object we draw by using the symbol :atom
:
...
Action(1:550,
:atom,
(args...) -> element(),
subactions = [
SubAction(101:140, Scaling(1, 12)),
SubAction(241:280, Scaling(12, 20)),
SubAction(381:420, Scaling(20, 7)),
SubAction(521:550, Scaling(7, 1))
]
),
...
Now, we must update the element
function to return the current scale of the circle being drawn. The reason why we want the scale of the object is that the scale of the circle represents its atomic mass. At this time, Javis.jl
does not allow a user to get the current scale of an object directly. This will most likely be changed in future versions. The following changes need to be made to the element
function:
function element(;color = "black")
sethue(color)
circle(O, 4, :fill)
return val(:_current_scale)[1]
end
The val
method is shorthand for the get_value
method used inside of Javis
(which gets the value saved into the :_current_scale
symbol by the prior action). Since we are getting the current scale value, we have to access an internal object called :_current_scale
. Technically, we should not be accessing this object, but right now, we have no other method. Hopefully this will be fixed in future versions!
Now, whenever the Action
executes in javis
, to draw the circle, the current scale of the circle object is stored into the :atom
symbol. Great! But... How do we know what element it is we are looking at?
To answer that question, we need to create another function to display this information!
What's That Element? 🔍
To identify the element and display its information properly, let's create an info box similar to what we made in Tutorial 2! We do this by creating an info_box
function that takes in a value:
function info_box(args...; value = 0)
fontsize(12)
box(140, -210, 170, 40, :stroke)
for element in elements
if value == round(ustrip(element.atomic_mass))
text(
"Element name: $(element.name)",
140,
-220,
valign = :middle,
halign = :center,
)
text(
"Atomic Mass: $(round(ustrip(element.atomic_mass)))",
140,
-200,
valign = :middle,
halign = :center,
)
end
end
end
Whenever a mass is measured that corresponds to a known element, the name of that element will now be displayed. Of course, we need to further update our javis
function to this:
...
Action(1:550,
:atom,
(args...) -> element(),
subactions = [
SubAction(101:140, Scaling(1, 12)),
SubAction(241:280, Scaling(12, 20)),
SubAction(381:420, Scaling(20, 7)),
SubAction(521:550, Scaling(7, 1))
]
),
Action(
1:100,
(args...) -> info_box(value = val(:atom)),
subactions = [SubAction(1:30, sineio(), appear(:draw_text))],
),
Action(
141:240,
(args...) -> info_box(value = val(:atom)),
subactions = [SubAction(1:30, sineio(), appear(:draw_text))],
),
Action(
281:380,
(args...) -> info_box(value = val(:atom)),
subactions = [SubAction(1:30, sineio(), appear(:draw_text))],
),
Action(
421:520,
(args...) -> info_box(value = val(:atom)),
subactions = [SubAction(1:30, sineio(), appear(:draw_text))],
),
...
The current scale of the circle object is now passed to the info_box
function via the :atom
symbol. Furthermore, we use a SubAction
to have the text appear using the method appear(:draw_text)
and we control the speed at which is appears using sineio()
.
NOTE:
sineio()
comes fromAnimations.jl
and is an easing function.
More on this in Tutorial 6.
This produces the following animation:
We can now see what element is being viewed! Hooray! However, it doesn't tell us much about the element - let's change that!
All one needs to do is update the info_box
function with the following two lines:
function info_box(args...; value = 0)
fontsize(12)
box(140, -210, 170, 40, :stroke)
# Add the following line to your code
box(0, 175, 450, 100, :stroke)
...
text(
"Atomic Mass: $(round(ustrip(element.atomic_mass)))",
140,
-200,
valign = :middle,
halign = :center,
)
# Add the following line to your code
textwrap("Description: $(element.summary)", 400, Point(-200, 125))
...
Which now produces this fully comprehensive animation that tells us all about the element at hand:
Hooray! 🎉🎉🎉 We now have a very educational gif that tells us all about the elements we are viewing. We are basically physicists at this point. 😉
Conclusion
Great work getting through this tutorial! This tutorial was a little more complicated as you learned the following:
- Using
Javis.jl
to scale objects - Having
Javis.jl
interact with other Julia packages - Creating extended animations for use in education
Our hope with this tutorial is that it inspires you to create more comprehensive and informative animations with Javis.jl
Good luck and have fun making more animations!
Full Code
using Animations
using Javis
using PeriodicTable
using Unitful
function ground(video, action, frame)
background("white")
sethue("black")
text("$frame / 550", -240, -230)
end
function element(; color = "black")
sethue(color)
circle(O, 4, :fill)
return val(:_current_scale)[1]
end
function info_box(args...; value = 0)
fontsize(12)
box(140, -210, 170, 40, :stroke)
box(0, 175, 450, 100, :stroke)
for element in elements
if value == round(ustrip(element.atomic_mass))
text(
"Element name: $(element.name)",
140,
-220,
valign = :middle,
halign = :center,
)
text(
"Atomic Radius: $(round(ustrip(element.atomic_mass)))",
140,
-200,
valign = :middle,
halign = :center,
)
textwrap("Description: $(element.summary)", 400, Point(-200, 125))
end
end
end
demo = Video(500, 500)
javis(
demo,
[
BackgroundAction(1:550, ground),
Action(
1:550,
:atom,
(args...) -> element(),
subactions = [
SubAction(101:140, Scaling(1, 12)),
SubAction(241:280, Scaling(12, 20)),
SubAction(381:420, Scaling(20, 7)),
SubAction(521:550, Scaling(7, 1)),
],
),
Action(
1:100,
(args...) -> info_box(value = val(:atom)),
subactions = [SubAction(1:30, sineio(), appear(:draw_text))],
),
Action(
141:240,
(args...) -> info_box(value = val(:atom)),
subactions = [SubAction(1:30, sineio(), appear(:draw_text))],
),
Action(
281:380,
(args...) -> info_box(value = val(:atom)),
subactions = [SubAction(1:30, sineio(), appear(:draw_text))],
),
Action(
421:520,
(args...) -> info_box(value = val(:atom)),
subactions = [SubAction(1:30, sineio(), appear(:draw_text))],
),
],
pathname = "min_atomic.gif",
framerate = 10,
)
Author(s): Jacob Zelko
Date: September 10, 2020
Tag(s): scaling, atoms, elements, unitful, periodictable