# Exploring built-in Kinos ```elixir Mix.install([ {:kino, "~> 0.18.0"} ]) ``` ## Introduction Throughout the Learning section, we have used Kino several times. Sometimes we use built-in Kinos, such as using `Kino.Control` and `Kino.Frame` to [deploy applications](/learn/notebooks/deploy-apps) or for [plotting](/learn/notebooks/intro-to-vega-lite). In this notebook, we will explore several of the built-in Kinos. `kino` is already listed as a dependency, so let's get started. ## 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. One important feature of inputs is that they are shared. Once someone changes an input, it will reflect on all users currently seeing the notebook. ## Kino.Control The [`Kino.Control`](https://hexdocs.pm/kino/Kino.Control.html) module represents forms, buttons, and other interactive controls. Opposite to `Kino.Input`, each user has their own control and the main way to interact with controls is by listening to their events. ```elixir button = Kino.Control.button("Click me!") ``` ```elixir Kino.listen(button, fn event -> IO.inspect(event) end) ``` ## 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. ## 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; """) ``` ## 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! ## 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. ## Kino.Tree By default cell results are inspected with a limit on the text size. Inspecting large data structures with no limit makes the representation impractical to read, that's where `Kino.Tree` comes in! ```elixir data = Process.info(self()) Kino.Tree.new(data) ``` ## Kino.render/1 and Kino.Frame 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 🚀" ``` The `Kino.Frame` construct we used when deploying our chat app is a generalization of `Kino.render`, which gives us more control over when to update, append, or clear the output: ```elixir frame = Kino.Frame.new() ``` By default, a frame will update in place. Try running the cell below several times: ```elixir Kino.Frame.render(frame, "Got: #{Enum.random(1..100)}") ``` but you can use `append` and `clear` to get different results. ## 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("![](#{url})") label = Kino.Markdown.new("**Image #{i}**") Kino.Layout.grid([image, label], boxed: true) end Kino.Layout.grid(images, columns: 3) ``` ## Kino.Shorts The `Kino` library also provides a [`Kino.Shorts`](https://hexdocs.pm/kino/Kino.Shorts.html) module, which wraps and unifies most of the functionality we've seen so far. Let's import it and give it a try: ```elixir import Kino.Shorts ``` For example, we use `Kino.Input` to render and then read an input. With `Kino.Shorts`, this can be achieved with a single step: ```elixir name = read_text("Your name: ") ``` Once you execute the cell, the input will appear and, the first time around, the value with be an empty string. You can pair this with `Kino.interrupt!/2` to read and validate inputs: ```elixir name = read_text("Your name: ") if name == "" do Kino.interrupt!(:error, "You must fill in your name") end ``` There are several other helpers in `Kino.Shorts`. For example, in the previous section we used grids with markdown to display and link several images. Using `Kino.Shorts`, we can simplify it as: ```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 image = markdown("![](#{url})") label = markdown("**Image #{i}**") grid([image, label], boxed: true) end grid(images, columns: 3) ``` ## 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 We have learned many new Kinos in this section. In the next guide, we will put some of our new found knowledge into practice [by rendering inputs, plotting graphs, and drawing diagrams with information retrieved from the notebook runtime](/learn/notebooks/vm-introspection).