The beginning of Javis.jl


Creation date: 2020-08-04

Tags: julia, javis, visualization, animation

I'm back! Sorry that I didn't blog for more than two weeks. I might have gotten a bit too much into the streaming world and programmed more than blogging about it. This post starts with some housekeeping and then explains the newest project I'm working on. Feel free to jump to the point you are most interested in.

Housekeeping

The last post here was about visualizing kinda random numbers :D Visualizing digits. It might be the one that brought me to building Javis but more on that later.

I'm currently mostly working on two projects:

ConstraintSolver.jl where I try to build a constraint solver from scratch in Julia. I started with some refactoring lately and streamed about it via Twitch. The videos are saved on YouTube. The refactoring is still going and I probably will write a post about that once it's done just to have a better structure of the outcome. Streaming is unstructured and hard to watch later I suppose. I'm still trying to figure out the best way to combine my possibilities to teach julia.

The new project is Javis.jl which I explain in the next section.

Sometimes I work on Juniper.jl as it is my small baby. It's my first project in Julia and the most used one I've built up til now. I mostly do bugfixes for it at the moment but might come back to it to add some new awesome features.

Javis.jl

Okay what is Javis?

Javis stands for Julia Mathematical visualizations and animations. (Currently at least ๐Ÿ˜„)

A small side note maybe: The Juniper package I was talking about was called MINLPBnB.jl which is the worst name possible, so I might have gotten better at naming things. I have to say it's very easy to see what the package does: Mixed Iteger Non-Linear Programming using Branch and Bound. Well, some folks told me it should have a proper name... We debated to call it Bacon.jl and I can't remember what it stands for.

... The thread apparently it was for "Branch Optimization Nonlinear". Just saw that I suggested BOREDOM as well. Oh man fun times...

Any way I liked Javis this time and just tried to find out what it might stand for.

To answer the actual question:

Javis tries to make it as easy and extensible as possible to create small animations like this one:

Dancing circles

I'll show the code for it later. ๐Ÿ˜‰ In general I want to have a simple way to animate all the ideas that might get into my head. Just some examples

Standing on the shoulders of giants

Normally I try to build things from scratch (in a high level programming language ๐Ÿ˜„). This time I'm more aware than ever that I'm standing on the shoulders of giants.

It is of course built with Julia which should be no surprise. BTW a new version (v1.5) is official now ๐Ÿ‘

Besides that it is highly dependent on the awesome package Luxor.jl which is used for all the drawing onto a canvas. It provides simple functions like line, circle and so on but also gave rise to the way we do animations.

Luxor.jl is itself built using Cairo.jl which is a wrapper around the C library Cairo.

If you're interested in 2D graphics, you should definitely check out the awesome Luxor Tutorial.

State of Javis

Edit: 12.08
We're in rapid development so this is already out of date. At some points I have an edit like this one to show when something changed. We haven't released v0.1.0 yet so you don't get deprecation warnings if something fails ๐Ÿ˜•

You might be interested in the current state of Javis.jl as the documentation is very sparse (or non-existent for stable) and the README doesn't show anything especially entertaining ๐Ÿ˜„

Me an my fellow Julian and streamer Jacob Zelko started this project about two weeks ago. Furthermore, I was away for a week so everything is very new.

The basic functionality

Before I give a view into the future you might want to see some code, right?

How to do the animation from above?

I think we should start with the very very basics before I show you the 40 lines of code.

using Javis, Luxor
Edit: 12.08
Now it's only using Javis as we started to reexport everything that Luxor does and change some methods to work in our favor ๐Ÿ˜ƒ

We normally need both of the packages as we want to do some animations (Javis) and want to do some drawing (Luxor).

I don't like to work in the global scope so let's create a function:

function dancing()
    video = Video(500, 500)
    javis(video,
        [
         ...
        ]
    ,tempdirectory="test/current/images", deletetemp=true, creategif=true, pathname="test/current/dancing_circles.gif")
end
Edit: 12.08
I promise I write another post when we release v0.1 :) deletetemp and creategif don't exist anymore.

It has quite a few parameters to create a simple gif out of it, so I think this needs improvement but that's for another day.

First of all we create our video object which is nothing more than a struct. The object sets the width and height in pixel of the video and stores information in between but that is of no concern to the user.

Then we have our javis function which is the main function of our project. It takes at least two arguments: the video and a list of actions that shall be performed during the video. The ... is just a placeholder for now.

We have a list of keyword arguments:

Okay now to Action.

Let's create our first "Animation":

function ground(args...) 
    background("white")
    sethue("black")
end

function dancing()
    video = Video(500, 500)
    javis(video,
        [
         BackgroundAction(1:70, ground)
        ]
    ,tempdirectory="test/current/images", deletetemp=true, creategif=true, pathname="test/current/dancing_circles.gif")
end

The ground function sets the background to white and the paint brush color to black.

Why do I need the args...? I'm glad you ask. Each user function gets three arguments video, action, frame where the first is the Video struct, the second is the Action struct and frame is just the current frame number. They are irrelevant for the background such that we don't need to write them down explicitly. "Unfortunately" we need to write args... such that Julia actually knows that we have a method that accepts those three arguments. The ... basically stands for as many arguments as you want.

Note
sethue is the same as setcolor but doesn't mess with the opacity

More interesting is the BackgroundAction(1:70, ground) which has just two arguments here:

I hope it is clear what you can expect from the 70 frames we just created ๐Ÿ˜‰

Feel free to click to see the boringness Test

Okay it's time to actually draw something with adding another action:

...
    BackgroundAction(1:70, ground),
    Action(1:70, (args...)->line(O, Point(25, 25), :stroke), Translation(O, Point(0, 100)))
...

This time we define an Action instead of a BackgroundAction which basically means: Everything in this action stays in the action and is not exposed to the outside world. You might wanna call it the Vegas of actions. Sorry for the bad joke...

There are some new parts in this:

Some more information before you have a look at the spoiler.

The canvas has its origin in the center as mentioned but it also has the y-axis going down instead of up. I'm not sure whether we want to change that for Javis.

Did you expect this? Falling line

So the Translation is animated by interpolation between the start and end translation. In the future we will make it possible to use easing functions to change this behavior.

Additionally, the Translation translates the plane such that drawing the line from (0,0) to (25, 25) results in drawing it from (0, 100) to (25, 125) in the last frame.

I hope that it makes sense so far.

Okay how do I draw circle that is moving down at the same rate by defining a new Action?

...
    BackgroundAction(1:70, ground),
    Action(1:70, (args...)->line(O, Point(25, 25), :stroke), Translation(O, Point(0, 100))),
    Action(1:70, (args...)->circle(Point(-25, 0), 10, :fill), Translation(O, Point(0, 100)))
...

Simple, right?

Okay this is the boring way of doing it but let's have a look at the circle function first.

The circle function takes a Point a radius and an action. For the action I chose :fill instead of :stroke this time.

For a bit more fun we want to define the first action a bit differently using a named function.

function draw_line(args...)
    line(O, Point(25, 25), :stroke)
    return Transformation(O, 0.0)
end

and then:

...
    BackgroundAction(1:70, ground),
    Action(1:70, :line, draw_line, Translation(O, Point(0, 100))),
    Action(1:70, (args...)->circle(pos(:line) .+ Point(-25, 0), 10, :fill))
...

This adds quite some more complexity but hopefully it's not too hard to understand and it is a very powerful tool.

Action(1:70, :line, draw_line, Translation(O, Point(0, 100))),

is not an anonymous function anymore but it also has a name :line as the second argument.

Furthermore, the function now return something:

return Transformation(O, 0.0)

It does return the same position (the origin, tail of the line) every time. The second argument is the angle of the transformation which is set to 0.0 as we don't use it. There is already a PR to be able to use return O instead ๐Ÿ˜‰

Edit: 07.08
The PR got merged such that you can write return Point(x,y) now instead of using a Transformation.

Now in the circle action:

Action(1:70, (args...)->circle(pos(:line) .+ Point(-25, 0), 10, :fill))

we have access to the position of the line with pos(:line) and we removed the Translation.

The pos(:line) is actually not the origin all the time as it had its own Translation. Javis "assumes" that you're interested in the actual canvas position of :line and not the one that is returned directly. Internally the position is therefore converted into the global coordinate system.

This is an important concept so let me try to explain it in different words ๐Ÿ˜‰

I think you know what happens Line and circle

Do you show the code now?

Okay yeah I think I can finally explain the code for the animation I showed in the beginning:

function dc()
    p1 = Point(100,0)
    p2 = Point(100,80)
    path_of_blue = Point[]
    path_of_red = Point[]

    video = Video(500, 500)
    javis(video, [
        BackgroundAction(1:70, ground),
        Action(1:70, :red_ball, (args...)->circ(p1, "red"), Rotation(0.0, 2ฯ€)),
        Action(1:70, :blue_ball, (args...)->circ(p2, "blue"), Rotation(2ฯ€, 0.0, :red_ball)),
        Action(1:70, (video, args...)->path!(path_of_red, pos(:red_ball), "red")),
        Action(1:70, (video, args...)->path!(path_of_blue, pos(:blue_ball), "blue")),
        Action(1:70, (args...)->rad(pos(:red_ball), pos(:blue_ball), "black"))
    ], tempdirectory="test/current/images", deletetemp=true, creategif=true, pathname="test/current/dancing_circles.gif")
    return video
end
Edit: 07.08

You don't need to define the frames every time anymore. It's possible to define it for BackgroundAction and define later actions like:

Action(:red_ball, (args...)->circ(p1, "red"), Rotation(0.0, 2ฯ€))

without defining frames. Then it will use the same frames as the previous action.

First of all I just defined some points at the beginning to change them in an easier way.

The functions itself are not that interesting:

function circ(p=O, color="black")
    sethue(color)
    circle(p, 25, :fill)
    return Transformation(p, 0.0)
end

function path!(points, pos, color)
    sethue(color)
    push!(points, pos)
    circle.(points, 2, :fill)
end

function rad(p1, p2, color)
    sethue(color)
    line(p1,p2, :stroke)
end

Let's get back to the actions:

BackgroundAction(1:70, ground),
Action(1:70, :red_ball, (args...)->circ(p1, "red"), Rotation(0.0, 2ฯ€)),
Action(1:70, :blue_ball, (args...)->circ(p2, "blue"), Rotation(2ฯ€, 0.0, :red_ball)),

Instead of using Translation we now use Rotation which takes in either two or three arguments.

Here we use :red_ball as the center of rotation for the :blue_ball.

Action(1:70, (video, args...)->path!(path_of_red, pos(:red_ball), "red")),
Action(1:70, (video, args...)->path!(path_of_blue, pos(:blue_ball), "blue")),

with path!

function path!(points, pos, color)
    sethue(color)
    push!(points, pos)
    circle.(points, 2, :fill)
end

just adds the new position of either the red or blue ball with pos to a list of points and draws the path by just drawing a circle for each position of points.

This is done with the broadcast dot notation circle..

The last action:

Action(1:70, (args...)->rad(pos(:red_ball), pos(:blue_ball), "black"))

just draw the line between the red and blue ball to see that the radius is really not changing.

View into the future

There are some things that are on the list:

There is so much more to do! If you want to get involved: Just reach out.

Even after I've published the poster just a few days ago there are a lot of new changes. You might just wanna check out the repository from time to time or watch it. (You might get quite a few E-mails). GitHub: Javis.jl

See you next time :)

Thanks for reading and special thanks to my 10 patrons!

List of patrons paying more than 4$ per month:

Currently I get more than 20$ per month using Patreon and PayPal.

For a donation of a single dollar per month you get early access to the posts. Try it out at the start of a month and if you don't enjoy it just cancel your subscription before pay day (end of month).

I'll keep you updated on Twitter OpenSourcES as well as my more personal one: Twitter Wikunia_de



Want to be updated? Consider subscribing and receiving a mail whenever a new post comes out.

Powered by Buttondown.


Subscribe to RSS