View Source Kino (Kino v0.14.2)
Client-driven interactive widgets for Livebook.
Kino is the library used by Livebook to render rich and interactive outputs directly from your Elixir code.
Getting started
Livebook is distributed with a set of interactive tutorials and examples, including some that specifically focus on Kino. If you're just getting started, going through these is highly recommended.
You can access these notebooks by starting Livebook and clicking on "Learn" in the sidebar.
Built-in kinos
Kino provides several built-in kinos. The Kino.Shorts
module
provides a facade to access and use most of the Kinos in this
project, although you can also use their modules directly as
listed in the sidebar.
For user interactions, Kino.Input
and Kino.Control
provide
a set of widgets for entering data and capturing user events.
See the respective module documentation for examples.
Kino also provides facilities to aid debugging, such as
Kino.Process
and a custom dbg()
implementation that integrates
with Livebook.
Custom kinos
Kino renders any data structure that implements the Kino.Render
protocol, falling back to the Kernel.inspect/2
representation
whenever an implementation is not available. You can customize
how your own data structures are rendered by implementing the
Kino.Render
protocol.
You can also implement your own kinos by writing custom JavaScript,
see Kino.JS
and Kino.JS.Live
for more details.
Packaging
When publishing custom kinos and smart cells, please consider the following guidelines:
prefix package name with
kino_
, usually followed by the name of the integration, such askino_vega_lite
,kino_ecto
namespace all modules under
KinoExample
, notKino.Example
. Note that official packages maintained by the Livebook team expose public APIs underKino.
, because they are essentially direct extensions ofKino
and we make sure no conflicting modules exist. Unofficial packages should follow the usual Elixir conventions with respect to module names
Summary
Functions
Renders a kino that periodically calls the given function to render a new result.
A stateful version of animate/2
.
Same as listen/2
, except each event is processed concurrently.
Returns the directories that contain .beam
files for modules
defined in the notebook.
Configures Kino.
Inspects the given term as cell output.
Interrupts evaluation with the given message.
Starts a process that consumes a stream with fun
without blocking execution.
A stateful version of listen/2
.
Returns a special value that results in no visible output.
Recompiles dependencies.
Renders the given term as cell output.
Starts a process under the Kino supervisor.
Similar to start_child/2
but returns the new pid or raises an error.
Terminates a child started with start_child/1
.
Returns a temporary directory that gets removed when the runtime terminates.
Types
@type nothing() :: :"do not show this result in output"
Functions
@spec animate(Enumerable.t() | pos_integer(), (term() -> any())) :: nothing()
Renders a kino that periodically calls the given function to render a new result.
The callback receives a stream element and should return a term to be rendered.
This function uses Kino.Frame
as the underlying kino.
It returns nothing (a non-printable result).
Examples
An animation is created by turning a stream of values into subsequent animation frames:
Stream.interval(100)
|> Stream.take(100)
|> Kino.animate(fn i ->
Kino.Markdown.new("**Iteration: `#{i}`**")
end)
Alternatively an integer may be passed as a shorthand for
Stream.interval/1
:
# Render new Markdown every 100ms
Kino.animate(100, fn i ->
Kino.Markdown.new("**Iteration: `#{i}`**")
end)
@spec animate( Enumerable.t() | pos_integer(), state, (term(), state -> {:cont, term(), state} | :halt) ) :: nothing() when state: term()
A stateful version of animate/2
.
The callback receives a stream element and the accumulated state and it should return either of:
{:cont, term_to_render, state}
- to continue:halt
- to no longer schedule callback evaluation
Examples
This function is primarily useful to consume Kino.Control
events:
button = Kino.Control.button("Click")
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)
@spec async_listen(Enumerable.t() | pos_integer(), (term() -> any())) :: pid()
Same as listen/2
, except each event is processed concurrently.
@spec beam_paths() :: [String.t()]
Returns the directories that contain .beam
files for modules
defined in the notebook.
@spec configure(keyword()) :: :ok
Configures Kino.
The supported options are:
:inspect
They are discussed individually in the sections below.
Inspect
A keyword list containing inspect options used for printing usual evaluation results. Defaults to pretty formatting with a limit of 50 entries.
To show more entries, you configure a higher limit:
Kino.configure(inspect: [limit: 200])
You can also show all entries by setting the limit to :infinity
,
but keep in mind that for large data structures it is memory-expensive
and is not an advised configuration in this case. Instead prefer
the use of IO.inspect/2
with :infinity
limit when needed.
See Inspect.Opts
for the full list of options.
Inspects the given term as cell output.
This works essentially the same as IO.inspect/2
, except it
always produces colored text and respects the configuration
set with configure/1
.
Opposite to render/1
, it does not attempt to render the given
term as a kino.
Interrupts evaluation with the given message.
This function raises a specific error to let Livebook know that
evaluation should be stopped. The error message and a Continue
button are shown to the user, who can then attempt to resolve the
source of the interrupt before resuming execution.
Do not use interrupt inside listeners
Since
interrupt!/2
aborts the execution, it cannot be used insideKino.listen/2
or other event listeners. In such cases, you can useKino.Frame
and render any messages directly within the frame, usingKino.Text
orKino.Markdown
.
Examples
text =
Kino.Input.text("Input")
|> Kino.render()
|> Kino.Input.read()
if text == "" do
Kino.interrupt!(:error, "Input required")
end
# This will not be run if the `interrupt!` is called above
Kino.Markdown.new("**You entered:** #{text}")
@spec listen(Enumerable.t() | pos_integer(), (term() -> any())) :: pid()
Starts a process that consumes a stream with fun
without blocking execution.
It returns the PID of the started process. The process can be terminated
with Kino.terminate_child/1
.
Note that events are processed by fun
sequentially. If you want
to process them concurrently, use async_listen/2
.
Examples
This function is primarily useful to consume Kino.Control
events:
Kino.Control.button("Greet")
|> Kino.listen(fn event -> IO.inspect(event) end)
You can also merge multiple controls into a single stream. For example, in order to merge them and tag each with a distinct event:
button = Kino.Control.button("Hello")
input = Kino.Input.checkbox("Check")
stream = Kino.Control.tagged_stream([hello: button, check: input])
Kino.listen(stream, fn
{:hello, event} -> ...
{:check, event} -> ...
end)
Any other stream works as well:
Stream.interval(100)
|> Stream.take(10)
|> Kino.listen(fn i -> IO.puts("Ping #{i}") end)
Finally, an integer may be passed as a shorthand for Stream.interval/1
:
Kino.listen(100, fn i -> IO.puts("Ping #{i}") end)
@spec listen( Enumerable.t() | pos_integer(), state, (term(), state -> {:cont, state} | :halt) ) :: pid() when state: term()
A stateful version of listen/2
.
The callback should return either of:
{:cont, state}
- to continue:halt
- to stop listening
Examples
button = Kino.Control.button("Click")
Kino.listen(button, 0, fn _event, counter ->
new_counter = counter + 1
IO.puts("Clicks: #{new_counter}")
{:cont, new_counter}
end)
@spec nothing() :: nothing()
Returns a special value that results in no visible output.
Examples
This is especially handy when you wish to suppress the default output of a cell. For instance, a cell containing this would normally result in verbose response output:
resp = Req.get!("https://example.org")
That output can be suppressed by appending a call to nothing/0
:
resp = Req.get!("https://example.org")
Kino.nothing()
@spec recompile() :: :ok
Recompiles dependencies.
Once you have installed dependencies with Mix.install/1
, this will
recompile any outdated path dependencies declared during the install.
Reproducibility
Keep in mind that recompiling dependency modules is not going to mark any cells as stale. This means that the given notebook state may no longer be reproducible. This function is meant as a utility when prototyping alongside a Mix project.
Renders the given term as cell output.
This effectively allows any Livebook cell to have multiple evaluation results.
@spec start_child(Supervisor.child_spec() | {module(), term()} | module()) :: DynamicSupervisor.on_start_child()
Starts a process under the Kino supervisor.
The process is automatically terminated when the current process terminates or the current cell reevaluates.
If you want to terminate the started process, use
terminate_child/1
. If you terminate the process manually,
the Kino supervisor might restart it if the child's :restart
strategy says so.
Nested start
It is not possible to use
start_child/1
while initializing another process started this way. In other words, you generally cannot callstart_child/1
inside callbacks such asGenServer.init/1
orKino.JS.Live.init/2
. If you do that, starting the process will block forever.On creation, many kinos use
start_child/1
underneath, which means that you cannot use functions such asKino.DataTable.new/1
inGenServer.init/1
. If you need to do that, you must either create the kinos beforehand and pass in theGenServer
argument, or create them inGenServer.handle_continue/2
.
@spec start_child!(Supervisor.child_spec() | {module(), term()} | module()) :: pid()
Similar to start_child/2
but returns the new pid or raises an error.
@spec terminate_child(pid()) :: :ok | {:error, :not_found}
Terminates a child started with start_child/1
.
Returns :ok
if the child was found and terminated, or
{:error, :not_found}
if the child was not found.
@spec tmp_dir() :: String.t() | nil
Returns a temporary directory that gets removed when the runtime terminates.