mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-11-11 22:51:43 +08:00
325 lines
9 KiB
Markdown
325 lines
9 KiB
Markdown
# Introduction to Kino
|
|
|
|
```elixir
|
|
Mix.install([
|
|
{:kino, "~> 0.7.0"}
|
|
])
|
|
```
|
|
|
|
## Introduction
|
|
|
|
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 widgets
|
|
and interact with them. You can see `kino` listed as a
|
|
dependency above, let's run the setup cell and get started!
|
|
|
|
<!-- livebook:{"branch_parent_index":0} -->
|
|
|
|
## Kino.Input
|
|
|
|
The [`Kino.Input`](https://hexdocs.pm/kino/Kino.Input.html)
|
|
module contains the most common kinos you will use. They are
|
|
used to define inputs in one cell, which you can read in a
|
|
future cell:
|
|
|
|
```elixir
|
|
name = Kino.Input.text("Your name")
|
|
```
|
|
|
|
and now we can greet the user back:
|
|
|
|
```elixir
|
|
IO.puts("Hello, #{Kino.Input.read(name)}!")
|
|
```
|
|
|
|
There are multiple types of inputs, such as text areas,
|
|
color dialogs, selects, and more. Feel free to explore them.
|
|
|
|
<!-- livebook:{"branch_parent_index":0} -->
|
|
|
|
## Kino.Markdown
|
|
|
|
Given our notebooks already know how to render Markdown,
|
|
you won't be surprised to find we can also render Markdown
|
|
directly from our Code cells. This is done by wrapping
|
|
the Markdown contents in [`Kino.Markdown.new/1`](https://hexdocs.pm/kino/Kino.Markdown.html):
|
|
|
|
````elixir
|
|
Kino.Markdown.new("""
|
|
# Example
|
|
|
|
A regular Markdown file.
|
|
|
|
## Code
|
|
|
|
```elixir
|
|
"Elixir" |> String.graphemes() |> Enum.frequencies()
|
|
```
|
|
|
|
## Table
|
|
|
|
| ID | Name | Website |
|
|
| -- | ------ | ----------------------- |
|
|
| 1 | Elixir | https://elixir-lang.org |
|
|
| 2 | Erlang | https://www.erlang.org |
|
|
""")
|
|
````
|
|
|
|
The way it works is that Livebook automatically detects
|
|
the output is a kino and renders it in Markdown. That's
|
|
the first of many kinos we will explore today. Let's move
|
|
forward.
|
|
|
|
<!-- livebook:{"branch_parent_index":0} -->
|
|
|
|
## Kino.Mermaid
|
|
|
|
You can include Mermaid diagrams in Markdown, however when generating diagrams dynamically, use `Kino.Mermaid.new/1`. This way the graphs will appear in the notebook source, if the user chooses to persist outputs.
|
|
|
|
```elixir
|
|
Kino.Mermaid.new("""
|
|
graph TD;
|
|
A-->B;
|
|
A-->C;
|
|
B-->D;
|
|
C-->D;
|
|
""")
|
|
```
|
|
|
|
<!-- livebook:{"branch_parent_index":0} -->
|
|
|
|
## Kino.DataTable
|
|
|
|
You can render arbitrary tabular data using [`Kino.DataTable.new/1`](https://hexdocs.pm/kino/Kino.DataTable.html), let's have a look:
|
|
|
|
```elixir
|
|
data = [
|
|
%{id: 1, name: "Elixir", website: "https://elixir-lang.org"},
|
|
%{id: 2, name: "Erlang", website: "https://www.erlang.org"}
|
|
]
|
|
|
|
Kino.DataTable.new(data)
|
|
```
|
|
|
|
The data must be an enumerable, with records being maps or
|
|
keyword lists.
|
|
|
|
Now, let's get some more realistic data. Whenever you run
|
|
Elixir code, you have several lightweight processes running
|
|
side-by-side. We can actually gather information about these
|
|
processes and render it as a table:
|
|
|
|
```elixir
|
|
keys = [:registered_name, :initial_call, :reductions, :stack_size]
|
|
|
|
processes =
|
|
for pid <- Process.list(),
|
|
info = Process.info(pid, keys),
|
|
do: info
|
|
|
|
Kino.DataTable.new(processes)
|
|
```
|
|
|
|
Now you can use the table above to sort by the number of
|
|
reductions and identify the most busy processes!
|
|
|
|
<!-- livebook:{"branch_parent_index":0} -->
|
|
|
|
## Kino.ETS
|
|
|
|
Kino supports multiple other data structures to be rendered
|
|
as tables. For example, you can use [`Kino.ETS`](https://hexdocs.pm/kino/Kino.ETS.html)
|
|
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.new(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.
|
|
|
|
<!-- livebook:{"branch_parent_index":0} -->
|
|
|
|
## 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"}])
|
|
Kino.render("Plain text")
|
|
|
|
# Some kinos
|
|
Kino.render(Kino.Markdown.new("**Hello world**"))
|
|
|
|
"Cell result 🚀"
|
|
```
|
|
|
|
<!-- livebook:{"branch_parent_index":0} -->
|
|
|
|
## Kino.Frame and animations
|
|
|
|
`Kino.Frame` allows us to render an empty frame and update it
|
|
as we progress. Let's render an empty frame:
|
|
|
|
```elixir
|
|
frame = Kino.Frame.new()
|
|
```
|
|
|
|
Now, let's render a random number between 1 and 100 directly
|
|
in the frame:
|
|
|
|
```elixir
|
|
Kino.Frame.render(frame, "Got: #{Enum.random(1..100)}")
|
|
```
|
|
|
|
Notice how every time you reevaluate the cell above it updates
|
|
the frame. You can also use `Kino.Frame.append/2` to append to
|
|
the frame:
|
|
|
|
```elixir
|
|
Kino.Frame.append(frame, "Got: #{Enum.random(1..100)}")
|
|
```
|
|
|
|
Appending multiple times will always add new contents. The content
|
|
can be reset by calling `Kino.Frame.render/2` or `Kino.Frame.clear/1`.
|
|
|
|
By using loops, you can use `Kino.Frame` to dynamically add contents
|
|
or animate your livebooks. In fact, there is a convenience function
|
|
called `Kino.animate/2` to be used exactly for this purpose:
|
|
|
|
```elixir
|
|
Kino.animate(100, fn i ->
|
|
Kino.Markdown.new("**Iteration: `#{i}`**")
|
|
end)
|
|
```
|
|
|
|
The above example renders new Markdown output every 100ms.
|
|
You can use the same approach to render regular output
|
|
or images too!
|
|
|
|
There's also `Kino.animate/3`, in case you need to accumulate state or halt the animation at certain point!
|
|
|
|
```elixir
|
|
button = Kino.Control.button("Click") |> Kino.render()
|
|
|
|
button
|
|
|> Kino.Control.stream()
|
|
|> Kino.animate(0, fn _event, counter ->
|
|
new_counter = counter + 1
|
|
md = Kino.Markdown.new("**Clicks: `#{new_counter}`**")
|
|
{:cont, md, new_counter}
|
|
end)
|
|
```
|
|
|
|
Note that this time, instead of refreshing the animation every 100ms, we use an event stream. This way we refresh the animation whenever the button is clicked.
|
|
|
|
Finally, there's `Kino.listen/{2,3}`, that allows you to consume a stream the same way, but doesn't render anything on its own.
|
|
|
|
<!-- livebook:{"branch_parent_index":0} -->
|
|
|
|
## Kino.Layout
|
|
|
|
In case you need to arrange multiple kinos, `Kino.Layout` gives you some options!
|
|
|
|
For one, you can create tabs to show just one thing at a time:
|
|
|
|
```elixir
|
|
data = [
|
|
%{id: 1, name: "Elixir", website: "https://elixir-lang.org"},
|
|
%{id: 2, name: "Erlang", website: "https://www.erlang.org"}
|
|
]
|
|
|
|
Kino.Layout.tabs(
|
|
Table: Kino.DataTable.new(data),
|
|
Raw: data
|
|
)
|
|
```
|
|
|
|
Then, there is a simple grid that you can use for laying out multiple elements:
|
|
|
|
```elixir
|
|
Kino.Layout.grid(["1", "2", "3", "4"], columns: 2)
|
|
```
|
|
|
|
And you can nest grid any way you like:
|
|
|
|
```elixir
|
|
urls = [
|
|
"https://images.unsplash.com/photo-1603203040743-24aced6793b4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
|
|
"https://images.unsplash.com/photo-1578339850459-76b0ac239aa2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
|
|
"https://images.unsplash.com/photo-1633479397973-4e69efa75df2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
|
|
"https://images.unsplash.com/photo-1597838816882-4435b1977fbe?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
|
|
"https://images.unsplash.com/photo-1629778712393-4f316eee143e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
|
|
"https://images.unsplash.com/photo-1638667168629-58c2516fbd22?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80"
|
|
]
|
|
|
|
images =
|
|
for {url, i} <- Enum.with_index(urls, 1) do
|
|
# For in-memory photo we would use Kino.Image
|
|
image = Kino.Markdown.new("")
|
|
label = Kino.Markdown.new("**Image #{i}**")
|
|
Kino.Layout.grid([image, label], boxed: true)
|
|
end
|
|
|
|
Kino.Layout.grid(images, columns: 3)
|
|
```
|
|
|
|
<!-- livebook:{"branch_parent_index":0} -->
|
|
|
|
## dbg
|
|
|
|
Kino hijacks Elixir's [`dbg/2`](https://hexdocs.pm/elixir/Kernel.html#dbg/2)
|
|
to provide Kino-based debugging:
|
|
|
|
```elixir
|
|
dbg(Atom.to_string(:hello))
|
|
```
|
|
|
|
When debugging a pipeline, Kino will render each step of the pipeline, allowing
|
|
to inspect, toggle, and swap each operation along the way:
|
|
|
|
```elixir
|
|
"Elixir is cool!"
|
|
|> String.trim_trailing("!")
|
|
|> String.split()
|
|
|> List.first()
|
|
|> dbg()
|
|
```
|
|
|
|
## Next steps with custom Kinos
|
|
|
|
With this, we finished our introduction to Kino. Most the guides
|
|
ahead of us will use Kino in one way or the other. You can jump
|
|
into [the VegaLite guide](/learn/notebooks/intro-to-vega-lite)
|
|
for plotting charts or [the MapLibre guide](/learn/notebooks/intro-to-maplibre)
|
|
for rendering maps to learn how other packages extend Livebook
|
|
through Kino.
|
|
|
|
We also have a collection of deep dive guides into Kino in the
|
|
[Learn](/learn) page if you want to learn more, including how
|
|
to create your custom widgets.
|