Skip to content

Commit

Permalink
Add Kino introductory notebook (#364)
Browse files Browse the repository at this point in the history
* Add Kino introductory notebook

* Update lib/livebook/notebook/explore.ex

Co-authored-by: José Valim <jose.valim@dashbit.co>

* Update lib/livebook/notebook/explore/intro_to_kino.livemd

Co-authored-by: José Valim <jose.valim@dashbit.co>

* Update lib/livebook/notebook/explore/intro_to_kino.livemd

Co-authored-by: José Valim <jose.valim@dashbit.co>

* Restructure sections

Co-authored-by: José Valim <jose.valim@dashbit.co>
  • Loading branch information
jonatanklosko and josevalim authored Jun 17, 2021
1 parent 137deeb commit 547e8e3
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 0 deletions.
6 changes: 6 additions & 0 deletions lib/livebook/notebook/explore.ex
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ defmodule Livebook.Notebook.Explore do
image_url: "/images/vm_introspection.png"
)

defnotebook(:intro_to_kino,
description: "Display and control rich and interactive widgets in Livebook.",
image_url: "/images/kino.png"
)

@type notebook_info :: %{
slug: String.t(),
livemd: String.t(),
Expand All @@ -119,6 +124,7 @@ defmodule Livebook.Notebook.Explore do
@distributed_portals_with_elixir,
@elixir_and_livebook,
@intro_to_vega_lite,
@intro_to_kino,
@vm_introspection
# @intro_to_nx, @intro_to_axon,
]
Expand Down
162 changes: 162 additions & 0 deletions lib/livebook/notebook/explore/intro_to_kino.livemd
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Interactions with Kino

## Setup

In this notebook we will explore the possibilities that
[`kino`](https://github.com/elixir-nx/kino) brings
into your notebooks. Kino can be thought of as Livebook's
friend that instructs it how to render certain output
and interact with it.

```elixir
Mix.install([
{:kino, "~> 0.1.0"},
{:vega_lite, "~> 0.1.0"}
])
```

```elixir
alias VegaLite, as: Vl
```

## VegaLite

In the [Plotting with VegaLite](/explore/notebooks/intro-to-vega-lite) notebook we show
numerous ways in which you can visualize your data. However, all of the plots
there are static.

Using Kino, we can dynamically stream data to the plot, so that it keeps updating!
To do that, all you need is a regular VegaLite specification that you then pass
to `Kino.VegaLite.start/1`. You don't have to specify any data up-front.

```elixir
widget =
Vl.new(width: 400, height: 400)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
|> Kino.VegaLite.start()
```

Then you can push data to the plot widget at any point and see it update dynamically:

```elixir
for i <- 1..300 do
point = %{x: i / 10, y: :math.sin(i / 10)}

# The :window option ensures we only show the latest
# 100 data points on the plot
Kino.VegaLite.push(widget, point, window: 100)

Process.sleep(25)
end
```

You can also explicitly clear the data:

```elixir
Kino.VegaLite.clear(widget)
```

### Periodical updates

You may want to have a plot running forever and updating in the background.
There is a dedicated `Kino.VegaLite.periodically/4` function that allows you do do just that!
You just need to specify the interval and the reducer callback like this,
then you interact with the widget as usually.

```elixir
widget =
Vl.new(width: 400, height: 400)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
|> Kino.VegaLite.start()
|> Kino.render()

# Add a callback to run every 25ms
Kino.VegaLite.periodically(widget, 25, 0, fn i ->
point = %{x: i / 10, y: :math.sin(i / 10)}
# Interacting with the widget is as usual
Kino.VegaLite.push(widget, point, window: 100)
# Continue with the new accumulator value
{:cont, i + 1}
end)
```

## Kino.ETS

You can use `Kino.ETS.start/1` to render ETS tables and easily
browse their contents. Let's first create our own table:

```elixir
tid = :ets.new(:users, [:set, :public])
Kino.ETS.start(tid)
```

In fact, Livebook automatically recognises an ETS table and
renders it as such:

```elixir
tid
```

Currently the table is empty, so it's time to insert some rows.

```elixir
for id <- 1..24 do
:ets.insert(tid, {id, "User #{id}", :rand.uniform(100), "Description #{id}"})
end
```

Having the rows inserted, click on the "Refetch" icon in the table output
above to see them.

## Kino.render/1

As we saw, Livebook automatically recognises widgets returned
from each cell and renders them accordingly. However, sometimes
it's useful to explicitly render a widget in the middle of the cell,
similarly to `IO.puts/1` and that's exactly what `Kino.render/1`
does! It works with any type and tells Livebook to render the value
in its special manner.

```elixir
# Arbitrary data structures
Kino.render([%{name: "Ada Lovelace"}, %{name: "Alan Turing"}])

# Static plots
vl =
Vl.new(width: 400, height: 400)
|> Vl.data_from_series(x: 1..100, y: 1..100)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)

Kino.render(vl)
Kino.render(vl)

Kino.render("Plain text")

"Cell result 🚀"
```

Before we saw how you can render and stream data to the plot
from a separate cell, the same could be rewritten in one go
like this:

```elixir
widget =
Vl.new(width: 400, height: 400)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
|> Kino.VegaLite.start()
|> Kino.render()

for i <- 1..300 do
point = %{x: i / 10, y: :math.sin(i / 10)}
Kino.VegaLite.push(widget, point, window: 100)
Process.sleep(25)
end
```
Binary file added priv/static/images/kino.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 547e8e3

Please sign in to comment.