# Run as: iex --dot-iex path/to/notebook.exs
# Title: Exploring built-in Kinos
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 ── (⎇ from Introduction)
# 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:
# name = Kino.Input.text("Your name")
# and now we can greet the user back:
# 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 ── (⎇ from Introduction)
# 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.
# button = Kino.Control.button("Click me!")
# Kino.listen(button, fn event -> IO.inspect(event) end)
# ── Kino.Markdown ── (⎇ from Introduction)
# 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):
# 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 ── (⎇ from Introduction)
# 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.
# Kino.Mermaid.new("""
# graph TD;
# A-->B;
# A-->C;
# B-->D;
# C-->D;
# """)
# ── Kino.DataTable ── (⎇ from Introduction)
# You can render arbitrary tabular data using [`Kino.DataTable.new/1`](https://hexdocs.pm/kino/Kino.DataTable.html), let's have a look:
# 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:
# 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 ── (⎇ from Introduction)
# 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:
# tid = :ets.new(:users, [:set, :public])
# Kino.ETS.new(tid)
# In fact, Livebook automatically recognises an ETS table and
# renders it as such:
# tid
# Currently the table is empty, so it's time to insert some rows.
# 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 ── (⎇ from Introduction)
# 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!
# data = Process.info(self())
# Kino.Tree.new(data)
# ── Kino.render/1 and Kino.Frame ── (⎇ from Introduction)
# 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.
# # 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:
# frame = Kino.Frame.new()
# By default, a frame will update in place. Try running the cell below
# several times:
# Kino.Frame.render(frame, "Got: #{Enum.random(1..100)}")
# but you can use `append` and `clear` to get different results.
# ── Kino.Layout ── (⎇ from Introduction)
# 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:
# 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:
# Kino.Layout.grid(["1", "2", "3", "4"], columns: 2)
# And you can nest grid any way you like:
# 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)
# ── Kino.Shorts ── (⎇ from Introduction)
# 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:
# 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:
# 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:
# 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:
# 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("")
# label = markdown("**Image #{i}**")
# grid([image, label], boxed: true)
# end
# grid(images, columns: 3)
# ── dbg ── (⎇ from Introduction)
# Kino hijacks Elixir's [`dbg/2`](https://hexdocs.pm/elixir/Kernel.html#dbg/2)
# to provide Kino-based debugging:
# 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 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).