I am trying to animate VegaLite charts in Livebook. The idea that I am currently working towards is to animate a 2D histogram over time. I have seen some work (external to Elixir) to specify animations in VegaLite, but what I am wanting is to simply create a VegaLite chart every frame, so I am not looking for super high framerates. Livebook to replicate the issues below. (Note that you need to install Node.js and some packages for exporting VegaLite charts. See here.)
Let’s use this code as example:
alias VegaLite, as: Vl
chart = fn ->
data =
for month <- ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] do
for day <- 1..31 do
%{month: month, day: day, temperature: Enum.random(32..100)}
end
end
|> List.flatten()
Vl.new(title: "Daily max temperatures")
|> Vl.data_from_values(data)
|> Vl.mark(:rect)
|> Vl.encode_field(:x, "day", type: :ordinal, title: "Day")
|> Vl.encode_field(:y, "month", type: :ordinal, title: "Month")
|> Vl.encode_field(:color, "temperature",
aggregate: :max,
type: :quantitative,
legend: [title: nil]
)
end
Inside a Livebook cell, I can animate it like this:
Stream.interval(500)
|> Stream.take(10)
|> Kino.animate(nil, fn _, _ ->
display = chart.()
{:cont, display, nil}
end)
This works and is fast for animation purposes, but this has highly undesirable blinking and flickering since the chart completely disappears between each frame.
This code exports the chart to SVG and then creates a Kino image:
Stream.interval(100)
|> Stream.take(100)
|> Kino.animate(nil, fn _, _ ->
display =
chart.()
|> Vl.Export.to_svg()
|> Kino.Image.new(:svg)
{:cont, display, nil}
end)
This works as desired to create a smooth animation such that between each frame there is no flickering or resetting of the image, but it is extremely slow. VegaLite.Export.to_svg/1
takes over 1.7 seconds on average. Looking at the implementation, it looks like it has a file write, which may be one reason why it is so slow.
How can I create the behavior of the second example with the speed of the first?