Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Exploring Livebook - Volume 1

exploring_livebook_volume_one.livemd

Exploring Livebook - Volume 1

Mix.install([
  {:kino, "~> 0.7.0"},
  {:vega_lite, "~> 0.1.4"},
  {:kino_vega_lite, "~> 0.1.1"},
  {:kino_db, "~> 0.2.0"},
  {:postgrex, "~> 0.16.3"}
])

Markdown Cells

So this is a markdown cell. It’s the most basic of all the building blocks. You can use a Markdown cell to do all the things you’d normally do in Markdown….

Say things boldly, or with a more subtle emphasis.

Create a table

Name Age Breed
Maisie 3 Australian Shepherd
Chase 4 Goldendoodle
Grace 15 Shiba Inu

Link to some great external content, like the Shaped by Dog Podcast.

Or link to other Livebook notebooks in the same directory: welcome_to_livebook.livemd

One of the cool things about Markdown cells is that you can see a live preview of the rendered output in real time as you type. Don’t let me forget to switch into “edit” mode and show what this looks like!

Code Cells

# This is a code cell.  You can write any Elixir code in here.

text = "Now is the time for all good dogs to come to the aid of the ball."
# This is another code cell.  
# Each code cell will have access to anything that was defined in a previous cell

IO.puts(text)

At the first Omaha Elixir Mixer meetup, Andrew Ek live-coded a function that would take in a string and return a map that showed how many times each word appeared in the text. He did this in iex, and he did a fantastic job.

I thought it might be cool to see if I could replicate that experiment here in Livebook. I feel like a cheater, though. I was exploring Livebook’s code completion features and discovered there’s a built-in function in the Enum module that does the job.

text
|> String.split()

# |> Enum.

Ok, that was a little overly simplified. Andrew’s solution was more robust than that. So I kept going and used the new dbg function that shipped in Elixir 1.14 to help me build up the steps. The output from the dbg function is even cooler with Kino installed. (But more on Kino later - I’m getting ahead of myself.)

text
|> String.upcase()
|> String.split()
|> Enum.map(fn word -> String.replace(word, ~r/\W/, "") end)
|> Enum.frequencies()
|> dbg()

Note to Self

It took me a few tries to remember the syntax for the regex sigil. I opened a new tab and Googled, as one does, completely forgetting that I could have hovered over the ~rin my code block and seen some tips right here, with a link to view all the details in Hexdocs.

# Taking our example just one step further - we can organize code into modules here too

defmodule TextUtil do
  def get_word_count(text) do
    text
    |> String.upcase()
    |> String.split()
    |> Enum.map(fn word -> String.replace(word, ~r/\W/, "") end)
    |> Enum.frequencies()
  end
end

longer_text = """
How is everybody doing?  Are we having fun yet? This concludes our
look at the basic building blocks.  Up next, we will explore some
of the more advanced tools, and then start putting all the pieces 
together to look at some real-world use cases.
"""

TextUtil.get_word_count(longer_text)

Diagrams

Support for Mermaid diagrams is also supported out of the box. I had no idea Mermaid supported so many different kinds of diagrams.

    journey
        title My day
        section Get ready for work
          Make coffee: 5: Me
          Eat breakfast: 4: Me, Maisie, Chase, Grace
          Go outside: 3: Me, Maisie, Chase, Grace
        section Start Work
          Go to office: 3: Me, Maisie, Chase
          Do work: 4: Me
        section End of work
          Eat dinner: 5: Me, Maisie, Chase, Grace

Kino

Kino provides interactive widgets that do lots of useful things. It doesn’t ship with Livebook, but it can be added as a dependency in the Setup block at the top of the page. Check out the Learn section for a series of in-depth tutorials. We will just scratch the surface here.

We can use a Kino Input to create a form element on the page and collect user input.

favorite_breed = Kino.Input.text("Favorite dog breed")

And then we can use that captured input later on…

IO.puts("I agree - the #{Kino.Input.read(favorite_breed)} is pretty great.")

Kino can also render data tables. This example will gather information about the processes currently running in this application and display them in an table. (Credit goes to the Intro to Kino notebook for this code.)

The table has pagination and can be sorted on any column heading.

keys = [:registered_name, :reductions, :stack_size]

processes =
  for pid <- Process.list(),
      info = Process.info(pid, keys),
      do: info

Kino.DataTable.new(processes)

Smart Cells

Smart cells are a new feature that were added to Livebook in v0.6. They serve multiple purposes:

  • demonstrating how to perform common tasks
  • automating the generation of boilerplate code for performing common tasks
  • enabling less technical users to perform tasks without writing code

Livebook ships with smart cells for connecting to several common databases and running queries. Other libraries like VegaLite include additional smart cells. There is even a notebook included in the “Learn” section that walks through how to create your own smart cell.

opts = [
  hostname: "db",
  port: 5432,
  username: "postgres",
  password: System.fetch_env!("LB_DEV_DB_PASSWORD"),
  database: "demo_app_dev"
]

{:ok, conn} = Kino.start_child({Postgrex, opts})
result = Postgrex.query!(conn, "select * from breeds limit 100", [])

The “toggle source” option will toggle between showing the UI widget and the code generated by the Smart Cell. The “convert to code” option will replace the UI with the generated code, which can then be modified at will.

VegaLite

VegaLite is a library that provides capabilities for creating all kinds of different charts and graphs. When added to Livebook, it also makes a new type of SmartCell available for creating charts.

VegaLite.new(width: 400, height: 400, title: "Dog Breeds")
|> VegaLite.data_from_values(result, only: ["group"])
|> VegaLite.mark(:bar)
|> VegaLite.encode(:x, aggregate: :count)
|> VegaLite.encode_field(:y, "group", type: :nominal)
|> VegaLite.encode_field(:color, "group", type: :nominal)

Bringing Things Together

Using Kino inputs, we can collect some parameters to determine what data we want to retrieve.

options = [
  {"Herding", "Herding"},
  {"Non-sporting", "Non-sporting"},
  {"Other", "Other"},
  {"Sporting", "Sporting"},
  {"Working", "Working"}
]

selected_group = Kino.Input.select("Select a group", options)
the_group = Kino.Input.read(selected_group)

And then we can pass the value from those inputs into our database query

result2 =
  Postgrex.query!(conn, "select * from breeds where breeds.group = $1 limit 100", [
    the_group
  ])

And use the results from that query to chart the data

VegaLite.new(width: 400, height: 400, title: "Breed Group by Region")
|> VegaLite.data_from_values(result2, only: ["origin"])
|> VegaLite.mark(:point)
|> VegaLite.encode_field(:x, "origin", type: :nominal)
|> VegaLite.encode(:y, aggregate: :count)
|> VegaLite.encode_field(:color, "origin", type: :nominal)

But Wait - There’s More

In Exploring Livebook - Volume 2, We’ll look at ways to use Livebook for interacting directly with the Demo App itself.