From 8f72d0175e8843db3f793f4d16e6b69919d06702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Fri, 29 Apr 2022 13:45:33 +0200 Subject: [PATCH] Update references to kino (#1148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update references to kino * Update changelog * Make the Chart cell section branching * Apply suggestions from code review Co-authored-by: José Valim * Capitalization * Add link to the Iris dataset * Pass keys to Process.info/2 Co-authored-by: José Valim --- CHANGELOG.md | 2 + lib/livebook.ex | 4 +- lib/livebook/notebook/explore.ex | 15 - .../explore/elixir_and_livebook.livemd | 2 +- .../notebook/explore/intro_to_axon.livemd | 3 - .../notebook/explore/intro_to_nx.livemd | 641 ------------------ .../explore/intro_to_vega_lite.livemd | 75 +- .../notebook/explore/kino/chat_app.livemd | 2 +- .../notebook/explore/kino/custom_kinos.livemd | 8 +- .../explore/kino/intro_to_kino.livemd | 16 +- .../notebook/explore/kino/pong.livemd | 2 +- .../notebook/explore/kino/smart_cells.livemd | 2 +- .../explore/kino/vm_introspection.livemd | 4 +- lib/livebook/runtime/dependencies.ex | 8 +- lib/livebook/runtime/elixir_standalone.ex | 25 +- static/images/axon.png | Bin 33372 -> 0 bytes static/images/nx.png | Bin 21480 -> 0 bytes test/livebook/runtime/dependencies_test.exs | 46 +- test/livebook/session_test.exs | 6 +- test/livebook_web/live/session_live_test.exs | 8 +- test/test_helper.exs | 10 +- 21 files changed, 141 insertions(+), 738 deletions(-) delete mode 100644 lib/livebook/notebook/explore/intro_to_axon.livemd delete mode 100644 lib/livebook/notebook/explore/intro_to_nx.livemd delete mode 100644 static/images/axon.png delete mode 100644 static/images/nx.png diff --git a/CHANGELOG.md b/CHANGELOG.md index e808c29e1..7780b983c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Warning when running in the cloud and system memory is low ([#1122](https://github.com/livebook-dev/livebook/pull/1122)) - Notification when a new Livebook version is available ([#1121](https://github.com/livebook-dev/livebook/pull/1121)) - Insert button for Markdown cell with a Mermaid diagram ([#1134](https://github.com/livebook-dev/livebook/pull/1134)) +- Taskbar icon for the desktop app ([#1119](https://github.com/livebook-dev/livebook/pull/1119)) +- Notebook discussing Smart cells ([#1141](https://github.com/livebook-dev/livebook/pull/1141)) ### Changed diff --git a/lib/livebook.ex b/lib/livebook.ex index bc95987a6..588d87161 100644 --- a/lib/livebook.ex +++ b/lib/livebook.ex @@ -30,11 +30,11 @@ defmodule Livebook do [ %{ - dependency: {:kino, "~> 0.5.2"}, + dependency: {:kino, "~> 0.6.0"}, description: "Interactive widgets for Livebook", name: "kino", url: "https://hex.pm/packages/kino", - version: "0.5.2" + version: "0.6.0" } ] diff --git a/lib/livebook/notebook/explore.ex b/lib/livebook/notebook/explore.ex index cd8e889af..f01ecda6d 100644 --- a/lib/livebook/notebook/explore.ex +++ b/lib/livebook/notebook/explore.ex @@ -71,21 +71,6 @@ defmodule Livebook.Notebook.Explore do cover_url: "/images/vega_lite.png" } }, - %{ - path: Path.join(__DIR__, "explore/intro_to_nx.livemd"), - details: %{ - description: - "Enter Numerical Elixir, experience the power of multi-dimensional arrays of numbers.", - cover_url: "/images/nx.png" - } - }, - # %{ - # path: Path.join(__DIR__, "explore/intro_to_axon.livemd"), - # details: %{ - # description: "Build Neural Networks in Elixir using a high-level, composable API.", - # cover_url: "/images/axon.png" - # } - # }, %{ ref: :kino_intro, path: Path.join(__DIR__, "explore/kino/intro_to_kino.livemd") diff --git a/lib/livebook/notebook/explore/elixir_and_livebook.livemd b/lib/livebook/notebook/explore/elixir_and_livebook.livemd index b95f17d6a..e1fc4c1b0 100644 --- a/lib/livebook/notebook/explore/elixir_and_livebook.livemd +++ b/lib/livebook/notebook/explore/elixir_and_livebook.livemd @@ -56,7 +56,7 @@ double-click on "Notebook dependencies and setup", add and run the code. ```elixir Mix.install([ - {:kino, "~> 0.5.0"} + {:kino, "~> 0.6.0"} ]) ``` diff --git a/lib/livebook/notebook/explore/intro_to_axon.livemd b/lib/livebook/notebook/explore/intro_to_axon.livemd deleted file mode 100644 index 24ced0adf..000000000 --- a/lib/livebook/notebook/explore/intro_to_axon.livemd +++ /dev/null @@ -1,3 +0,0 @@ -# Neural Networks with Axon - -TODO: content 🐈 diff --git a/lib/livebook/notebook/explore/intro_to_nx.livemd b/lib/livebook/notebook/explore/intro_to_nx.livemd deleted file mode 100644 index 7d8734303..000000000 --- a/lib/livebook/notebook/explore/intro_to_nx.livemd +++ /dev/null @@ -1,641 +0,0 @@ -# Introduction to Nx - -```elixir -Mix.install([ - {:nx, "~> 0.1.0-dev", github: "elixir-nx/nx", sparse: "nx", override: true} -]) -``` - -## Numerical Elixir - -Elixir's primary numerical datatypes and structures are not optimized -for numerical programming. Nx is a library built to bridge that gap. - -[Elixir Nx](https://github.com/elixir-nx/nx) is a numerical computing library -to smoothly integrate to typed, multidimensional data implemented on other -platforms (called tensors). This support extends to the compilers and -libraries that support those tensors. Nx has three primary capabilities: - -* In Nx, tensors hold typed data in multiple, named dimensions. -* Numerical definitions, known as `defn`, support custom code with - tensor-aware operators and functions. -* [Automatic differentiation](https://arxiv.org/abs/1502.05767), also known as - autograd or autodiff, supports common computational scenarios - such as machine learning, simulations, curve fitting, and probabilistic models. - -Here's more about each of those capabilities. Nx [tensors]() can hold -unsigned integers (u8, u16, u32, u64), -signed integers (s8, s16, s32, s64), -floats (f32, f64), and brain floats (bf16). -Tensors support backends implemented outside of Elixir, including Google's -Accelerated Linear Algebra (XLA) and LibTorch. - -Numerical definitions have compiler support to allow just-in-time compilation -that support specialized processors to speed up numeric computation including -TPUs and GPUs. - -To know Nx, we'll get to know tensors first. This rapid overview will touch -on the major libraries. Then, future notebooks will take a deep dive into working -with tensors in detail, autograd, and backends. Then, we'll dive into specific -problem spaces like Axon, the machine learning library. - -## Nx and tensors - -Systems of equations are a central theme in numerical computing. -These equations are often expressed and solved with multidimensional -arrays. For example, this is a two dimensional array: - -$$ -\begin{bmatrix} - 1 & 2 \\ - 3 & 4 -\end{bmatrix} -$$ - -Elixir programmers typically express a similar data structure using -a list of lists, like this: - -```elixir -[ - [1, 2], - [3, 4] -] -``` - -This data structure works fine within many functional programming -algorithms, but breaks down with deep nesting and random access. - -On top of that, Elixir numeric types lack optimization for many numerical -applications. They work fine when programs -need hundreds or even thousands of calculations. They tend to break -down with traditional STEM applications when a typical problem -needs millions of calculations. - -In Nx, we express multi-dimensional data using typed tensors. Simply put, -a tensor is a multi-dimensional array with a predetermined shape and -type. To interact with them, Nx relies on tensor-aware operators rather -than `Enum.map/2` and `Enum.reduce/3`. - -In this section, we'll look at some of the various tools for -creating and interacting with tensors. The IEx helpers will assist our -exploration of the core tensor concepts. - -```elixir -import IEx.Helpers -``` - -Now, everything is set up, so we're ready to create some tensors. - - - -### Creating tensors - -Start out by getting a feel for Nx through its documentation. -Do so through the IEx helpers, like this: - - - -```elixir -h Nx -``` - -Immediately, you can see that tensors are at the center of the -API. The main API for creating tensors is `Nx.tensor/2`: - - - -```elixir -h Nx.tensor -``` - -We use it to create tensors from raw Elixir lists of numbers, like this: - -```elixir -tensor = - 1..4 - |> Enum.chunk_every(2) - |> Nx.tensor(names: [:y, :x]) -``` - -The result shows all of the major fields that make up a tensor: - -* The data, presented as the list of lists `[[1, 2], [3, 4]]`. -* The type of the tensor, a signed integer 64 bits long, with the type `s64`. -* The shape of the tensor, going left to right, with the outside dimensions listed first. -* The names of each dimension. - -We can easily convert it to a binary: - -```elixir -binary = Nx.to_binary(tensor) -``` - -A tensor of type s64 uses four bytes for each integer. The binary -shows the individual bytes that make up the tensor, so you can see -the integers `1..4` interspersed among the zeros that make -up the tensor. If all of our data only uses positive numbers from -`0..255`, we could save space with a different type: - -```elixir -Nx.tensor([[1, 2], [3, 4]], type: {:u, 8}) |> Nx.to_binary() -``` - -If you have already have binary, you can directly convert it to a tensor -by passing the binary and the type: - -```elixir -Nx.from_binary(<<0, 1, 2>>, {:u, 8}) -``` - -This function comes in handy when working with published datasets -because they must often be processed. Elixir binaries make quick work -of dealing with numerical data structured for platforms other than -Elixir. - -We can get any cell of the tensor: - -```elixir -tensor[0][1] -``` - -Now, try getting the first row of the tensor: - -```elixir -# ...your code here... -``` - -We can also get a whole dimension: - -```elixir -tensor[x: 1] -``` - -or a range: - -```elixir -tensor[y: 0..1] -``` - -Now, - -* create your own `{3, 3}` tensor with named dimensions -* return a `{2, 2}` tensor containing the first two columns - of the first two rows - -We can get information about this most recent term with -the IEx helper `i`, like this: - - - -```elixir -i tensor -``` - -The tensor is a struct that supports the usual `Inspect` protocol. -The struct has keys, but we typically treat the `Nx.Tensor` -as an _opaque data type_ (meaning we typically access the contents and -shape of a tensor using the tensor's API instead of the struct). - -Primarily, a tensor is a struct, and the -functions to access it go through a specific backend. We'll get to -the backend details in a moment. For now, use the IEx `h` helper -to get more documentation about tensors. We could also open a Code -cell, type Nx.tensor, and hover the cursor over the word `tensor` -to see the help about that function. - -We can get the shape of the tensor with `Nx.shape/1`: - -```elixir -Nx.shape(tensor) -``` - -We can also create a new tensor with a new shape using `Nx.reshape/2`: - -```elixir -Nx.reshape(tensor, {1, 4}, names: [:batches, :values]) -``` - -This operation reuses all of the tensor data and simply -changes the metadata, so it has no notable cost. - -The new tensor has the same type, but a new shape. - -Now, reshape the tensor to contain three dimensions with -one batch, one row, and four columns. - -```elixir -# ...your code here... -``` - -We can create a tensor with named dimensions, a type, a shape, -and our target data. A dimension is called an _axis_, and axes -can have names. We can specify the tensor type and dimension names -with options, like this: - -```elixir -Nx.tensor([[1, 2, 3]], names: [:rows, :cols], type: {:u, 8}) -``` - -We created a tensor of the shape `{1, 3}`, with the type `u8`, -the values `[1, 2, 3]`, and two axes named `rows` and `cols`. - -Now we know how to create tensors, so it's time to do something with them. - -## Tensor aware functions - -In the last section, we created a `s64[2][2]` tensor. In this section, -we'll use Nx functions to work with it. Here's the value of `tensor`: - -```elixir -tensor -``` - -We can use `IEx.exports/1` or code completion to find -some functions in the `Nx` module that operate on tensors: - - - -```elixir -exports Nx -``` - -You might recognize that many of those functions have names that -suggest that they would work on primitive values, called scalars. -Indeed, a tensor can be a scalar: - -```elixir -pi = Nx.tensor(3.1415, type: {:f, 32}) -``` - -Take the cosine: - -```elixir -Nx.cos(pi) -``` - -That function took the cosine of `pi`. We can also call them -on a whole tensor, like this: - -```elixir -Nx.cos(tensor) -``` - -We can also call a function that aggregates the contents -of a tensor. For example, to get a sum of the numbers -in `tensor`, we can do this: - -```elixir -Nx.sum(tensor) -``` - -That's `1 + 2 + 3 + 4`, and Nx went to multiple dimensions to get that sum. -To get the sum of values along the `x` axis instead, we'd do this: - -```elixir -Nx.sum(tensor, axes: [:x]) -``` - -Nx sums the values across the `x` dimension: `1 + 2` in the first row -and `3 + 4` in the second row. - -Now, - -* create a `{2, 2, 2}` tensor -* with the values `1..8` -* with dimension names `[:z, :y, :x]` -* calculate the sums along the `y` axis - -```elixir -# ...your code here... -``` - -Sometimes, we need to combine two tensors together with an -operator. Let's say we wanted to subtract one tensor from -another. Mathematically, the expression looks like this: - -$$ -\begin{bmatrix} - 5 & 6 \\ - 7 & 8 -\end{bmatrix} - -\begin{bmatrix} - 1 & 2 \\ - 3 & 4 -\end{bmatrix} = -\begin{bmatrix} - 4 & 4 \\ - 4 & 4 -\end{bmatrix} -$$ - -To solve this problem, add each integer on the left with the -corresponding integer on the right. Unfortunately, we cannot -use Elixir's built-in subtraction operator as it is not tensor-aware. -Luckily, we can use the `Nx.subtract/2` function to solve the -problem: - -```elixir -tensor2 = Nx.tensor([[5, 6], [7, 8]]) -Nx.subtract(tensor2, tensor) -``` - -We get a `{2, 2}` shaped tensor full of fours, exactly as we expected. -When calling `Nx.subtract/2`, both operands had the same shape. -Sometimes, you might want to process functions where the dimensions -don't match. To solve this problem, Nx takes advantage of -a concept called _broadcasting_. - -## Broadcasts - -Often, the dimensions of tensors in an operator don't match. -For example, you might want to subtract a `1` from every -element of a `{2, 2}` tensor, like this: - -$$ -\begin{bmatrix} - 1 & 2 \\ - 3 & 4 -\end{bmatrix} - 1 = -\begin{bmatrix} - 0 & 1 \\ - 2 & 3 -\end{bmatrix} -$$ - -Mathematically, it's the same as this: - -$$ -\begin{bmatrix} - 1 & 2 \\ - 3 & 4 -\end{bmatrix} - -\begin{bmatrix} - 1 & 1 \\ - 1 & 1 -\end{bmatrix} = -\begin{bmatrix} - 0 & 1 \\ - 2 & 3 -\end{bmatrix} -$$ - -That means we need a way to convert `1` to a `{2, 2}` tensor. -`Nx.broadcast/2` solves that problem. This function takes -a tensor or a scalar and a shape. - -```elixir -Nx.broadcast(1, {2, 2}) -``` - -This broadcast takes the scalar `1` and translates it -to a compatible shape by copying it. Sometimes, it's easier -to provide a tensor as the second argument, and let `broadcast/2` -extract its shape: - -```elixir -Nx.broadcast(1, tensor) -``` - -The code broadcasts `1` to the shape of `tensor`. In many operators -and functions, the broadcast happens automatically: - -```elixir -Nx.subtract(tensor, 1) -``` - -This result is possible because Nx broadcasts _both tensors_ -in `subtract/2` to compatible shapes. That means you can provide -scalar values as either argument: - -```elixir -Nx.subtract(10, tensor) -``` - -Or subtract a row or column. Mathematically, it would look like this: - -$$ -\begin{bmatrix} - 1 & 2 \\ - 3 & 4 -\end{bmatrix} - -\begin{bmatrix} - 1 & 2 -\end{bmatrix} = -\begin{bmatrix} - 0 & 0 \\ - 2 & 2 -\end{bmatrix} -$$ - -which is the same as this: - -$$ -\begin{bmatrix} - 1 & 2 \\ - 3 & 4 -\end{bmatrix} - -\begin{bmatrix} - 1 & 2 \\ - 1 & 2 -\end{bmatrix} = -\begin{bmatrix} - 0 & 0 \\ - 2 & 2 -\end{bmatrix} -$$ - -This rewrite happens in Nx too, also through a broadcast. We want to -broadcast the tensor `[1, 2]` to match the `{2, 2}` shape, like this: - -```elixir -Nx.broadcast(Nx.tensor([1, 2]), {2, 2}) -``` - -The `subtract` function in `Nx` takes care of that broadcast -implicitly, as before: - -```elixir -Nx.subtract(tensor, Nx.tensor([1, 2])) -``` - -The broadcast worked as advertised, copying the `[1, 2]` row -enough times to fill a `{2, 2}` tensor. A tensor with a -dimension of `1` will broadcast to fill the tensor: - -```elixir -[[1], [2]] |> Nx.tensor() |> Nx.broadcast({1, 2, 2}) -``` - -```elixir -[[[1, 2, 3]]] -|> Nx.tensor() -|> Nx.broadcast({4, 2, 3}) -``` - -Both of these examples copy parts of the tensor enough -times to fill out the broadcast shape. You can check out the -Nx broadcasting documentation for more details: - - - -```elixir -h Nx.broadcast -``` - -Much of the time, you won't have to broadcast yourself. Many of -the functions and operators Nx supports will do so automatically. - -We can use tensor-aware operators via various `Nx` functions and -many of them implicitly broadcast tensors. - -Throughout this section, we have been invoking `Nx.subtract/2` and -our code would be more expressive if we could use its equivalent -mathematical operator. Fortunately, Nx provides a way. Next, we'll -dive into numerical definitions using `defn`. - -## Numerical definitions (defn) - -The `defn` macro simplifies the expression of mathematical formulas -containing tensors. Numerical definitions have two primary benefits -over classic Elixir functions. - -* They are _tensor-aware_. Nx replaces operators like `Kernel.-/2` - with the `Defn` counterparts — which in turn use `Nx` functions - optimized for tensors — so the formulas we express can use - tensors out of the box. - -* `defn` definitions allow for building computation graph of all the - individual operations and using a just-in-time (JIT) compiler to emit - highly specialized native code for the desired computation unit. - -We don't have to do anything special to get access to -get tensor awareness beyond importing `Nx.Defn` and writing -our code within a `defn` block. - -To use Nx in a Mix project or a notebook, we need to include -the `:nx` dependency and import the `Nx.Defn` module. The -dependency is already included, so import it in a Code cell, -like this: - -```elixir -import Nx.Defn -``` - -Just as the Elixir language supports `def`, `defmacro`, and `defp`, -Nx supports `defn`. There are a few restrictions. It allows only -numerical arguments in the form of primitives or tensors as arguments -or return values, and has a subset of the language within -the `defn` block. - -The subset of Elixir allowed within `defn` is quite broad, though. We can -use macros, pipes, and even conditionals, so we're not giving up -much when you're declaring mathematical functions. - -Additionally, despite these small concessions, `defn` provides huge benefits. -Code in a `defn` block uses tensor aware operators and types, so the math -beneath your functions has a better chance to shine through. Numerical -definitions can also run on accelerated numerical processors like GPUs and -TPUs. Here's an example numerical definition: - -```elixir -defmodule TensorMath do - import Nx.Defn - - defn subtract(a, b) do - a - b - end -end -``` - -This module has a numerical definition that will be compiled. -If we wanted to specify a compiler for this module, we could add -a module attribute before the `defn` clause. One of such compilers -is [the EXLA compiler](https://github.com/elixir-nx/nx/tree/main/exla). -You'd add the `mix` dependency for EXLA and do this: - - - -```elixir -@defn_compiler EXLA -defn subtract(a, b) do - a - b -end -``` - -Now, it's your turn. Add a `defn` to `TensorMath` -that accepts two tensors representing the lengths of sides of a -right triangle and uses the pythagorean theorem to return the -[length of the hypotenuse](https://www.mathsisfun.com/pythagoras.html). -Add your function directly to the previous Code cell. - -The last major feature we'll cover is called auto-differentiation, or autograd. - -## Automatic differentiation (autograd) - -An important mathematical property for a function is the -rate of change, or the gradient. These gradients are critical -for solving systems of equations and building probabilistic -models. In advanced math, derivatives, or differential equations, -are used to take gradients. Nx can compute these derivatives -automatically through a feature called automatic differentiation, -or autograd. - -Here's how it works. - - - -```elixir -h Nx.Defn.grad -``` - -We'll build a module with a few functions, -and then create another function to create the gradients of those -functions. The function `grad/1` takes a function, and returns -a function returning the gradient. We have two functions: `poly/1` -is a simple numerical definitin, and `poly_slope_at/1` returns -its gradient: - -$$ - poly: f(x) = 3x^2 + 2x + 1 \\ -$$ - -$$ - polySlopeAt: g(x) = 6x + 2 -$$ - -Here's the Elixir equivalent of those functions: - -```elixir -defmodule Funs do - defn poly(x) do - 3 * Nx.power(x, 2) + 2 * x + 1 - end - - def poly_slope_at(x) do - grad(&poly/1).(x) - end -end -``` - -Notice the second `defn`. It uses `grad/1` to take its -derivative using autograd. It uses the intermediate `defn` AST -and mathematical composition to compute the derivative. You can -see it at work here: - -```elixir -Funs.poly_slope_at(2) -``` - -Nice. If you plug the number 2 into the function $$ 6x + 2 $$ -you get 14! Said another way, if you look at the graph at -exactly 2, the rate of increase is 14 units of `poly(x)` -for every unit of `x`, precisely at `x`. - -Nx also has helpers to get gradients corresponding to a number of inputs. -These come into play when solving systems of equations. - -Now, you try. Find a function computing the gradient of a `sin` wave. - -```elixir -# your code here -``` diff --git a/lib/livebook/notebook/explore/intro_to_vega_lite.livemd b/lib/livebook/notebook/explore/intro_to_vega_lite.livemd index f2b6a3f3c..578ed8f07 100644 --- a/lib/livebook/notebook/explore/intro_to_vega_lite.livemd +++ b/lib/livebook/notebook/explore/intro_to_vega_lite.livemd @@ -2,8 +2,8 @@ ```elixir Mix.install([ - {:vega_lite, "~> 0.1.2"}, - {:kino, "~> 0.5.0"} + {:vega_lite, "~> 0.1.4"}, + {:kino_vega_lite, "~> 0.1.0"} ]) ``` @@ -14,8 +14,8 @@ We need two libraries for plotting in Livebook: * The [`vega_lite`](https://github.com/elixir-nx/vega_lite) package allows us to define our graph specifications -* The [`kino`](https://github.com/elixir-nx/kino) package - renders our specifications +* The [`kino_vega_lite`](https://github.com/elixir-nx/kino) package + instructs Livebook how to render our specifications Let's install them by running the setup cell above. @@ -28,6 +28,69 @@ alias VegaLite, as: Vl +## The Chart smart cell + +Before we get into exploring all the various chart types, let's have +a look at an awesome feature that comes with `kino_vega_lite` - the +**Chart** smart cell! + +Coding up a chart usually involves a couple steps. If you don't know +the API of a particular library, it may be a bit challenging. On the +other hand, if you know the API in-and-out, it's a rather repetitive +task. That's where the **Chart** smart cell comes in, it is a +high-level UI that helps us write our chart code. It is great +a tool for learning and for automating our workflows. Let's +give it a try! + +First, we need some data to work with, here's a small excerpt from +the popular [Iris dataset](https://www.kaggle.com/datasets/uciml/iris): + +```elixir +iris = [ + %{"petal_length" => 5.1, "petal_width" => 1.9, "species" => "Iris-virginica"}, + %{"petal_length" => 4.0, "petal_width" => 1.3, "species" => "Iris-versicolor"}, + %{"petal_length" => 1.6, "petal_width" => 0.2, "species" => "Iris-setosa"}, + %{"petal_length" => 1.6, "petal_width" => 0.2, "species" => "Iris-setosa"}, + %{"petal_length" => 4.6, "petal_width" => 1.4, "species" => "Iris-versicolor"}, + %{"petal_length" => 4.8, "petal_width" => 1.8, "species" => "Iris-virginica"}, + %{"petal_length" => 5.6, "petal_width" => 2.2, "species" => "Iris-virginica"}, + %{"petal_length" => 5.1, "petal_width" => 1.6, "species" => "Iris-versicolor"}, + %{"petal_length" => 1.5, "petal_width" => 0.3, "species" => "Iris-setosa"}, + %{"petal_length" => 4.5, "petal_width" => 1.6, "species" => "Iris-versicolor"} +] + +:ok +``` + +Now, to insert a new Chart cell, place your cursor between cells, +click on the + Smart button and select Chart. + +You can see an example of that below. Click on the "Evaluate" +button to see the chart, then see how it changes as you customize +the parameters. + + + +```elixir +Vl.new(width: 400, height: 200, title: "Iris") +|> Vl.data_from_values(iris, only: ["petal_length", "petal_width", "species"]) +|> Vl.mark(:point) +|> Vl.encode_field(:x, "petal_length", type: :quantitative) +|> Vl.encode_field(:y, "petal_width", type: :quantitative) +|> Vl.encode_field(:color, "species") +``` + +Under cell actions there is a "Source" button, click on it to +see the source code of your chart. You can even convert it to +a regular Code cell for further adjustments! + +The Chart smart cell is one of many Smart cells available +in Livebook ⚡ Not only that, you can create your own Smart +cells too, which we discuss in the +[Exploring Smart cells](/explore/notebooks/smart-cells) notebook! + + + ## Basic concepts Composing a basic Vega-Lite graphic usually consists of the following steps: @@ -36,7 +99,7 @@ Composing a basic Vega-Lite graphic usually consists of the following steps: # Initialize the specification, optionally with some top-level properties Vl.new(width: 400, height: 400) # Specify data source for the graphic using one of the data_from_* functions -|> Vl.data_from_series(iteration: 1..100, score: 1..100) +|> Vl.data_from_values(iteration: 1..100, score: 1..100) # Pick a visual mark |> Vl.mark(:line) # Map data fields to visual properties of the mark, in this case point positions @@ -437,7 +500,7 @@ the shape, conflating the two dimensions. # Source: https://vega.github.io/vega-lite/examples/arc_radial.html Vl.new() -|> Vl.data_from_series(data: [12, 23, 47, 6, 52, 19]) +|> Vl.data_from_values(data: [12, 23, 47, 6, 52, 19]) |> Vl.encode_field(:theta, "data", type: :quantitative, stack: true) |> Vl.encode_field(:radius, "data", scale: [type: :sqrt, zero: true, range_min: 20]) |> Vl.encode_field(:color, "data", type: :nominal, legend: nil) diff --git a/lib/livebook/notebook/explore/kino/chat_app.livemd b/lib/livebook/notebook/explore/kino/chat_app.livemd index 88010d94b..e6f275b02 100644 --- a/lib/livebook/notebook/explore/kino/chat_app.livemd +++ b/lib/livebook/notebook/explore/kino/chat_app.livemd @@ -2,7 +2,7 @@ ```elixir Mix.install([ - {:kino, "~> 0.5.0"} + {:kino, "~> 0.6.0"} ]) ``` diff --git a/lib/livebook/notebook/explore/kino/custom_kinos.livemd b/lib/livebook/notebook/explore/kino/custom_kinos.livemd index 421ce8964..bd50d5033 100644 --- a/lib/livebook/notebook/explore/kino/custom_kinos.livemd +++ b/lib/livebook/notebook/explore/kino/custom_kinos.livemd @@ -2,7 +2,7 @@ ```elixir Mix.install([ - {:kino, "~> 0.5.0"} + {:kino, "~> 0.6.0"} ]) ``` @@ -43,10 +43,8 @@ end Let's break it down. -To define a custom kino we need to create a new module, -conventionally under the `Kino.` prefix, so that the end -user can easily autocomplete all available kinos. In -this case we went with `KinoGuide.HTML`. +To define a custom kino we need to create a new module. In +this case we go with `KinoGuide.HTML`. We start by adding `use Kino.JS`, which makes our module asset-aware. In particular, it allows us to use the `asset/2` diff --git a/lib/livebook/notebook/explore/kino/intro_to_kino.livemd b/lib/livebook/notebook/explore/kino/intro_to_kino.livemd index d80ce90e6..1a6b0a204 100644 --- a/lib/livebook/notebook/explore/kino/intro_to_kino.livemd +++ b/lib/livebook/notebook/explore/kino/intro_to_kino.livemd @@ -2,7 +2,7 @@ ```elixir Mix.install([ - {:kino, "~> 0.5.0"} + {:kino, "~> 0.6.0"} ]) ``` @@ -96,16 +96,14 @@ side-by-side. We can actually gather information about these processes and render it as a table: ```elixir -processes = Process.list() |> Enum.map(&Process.info/1) -``` +keys = [:registered_name, :initial_call, :reductions, :stack_size] -We can pick the data keys that are relevant for us: +processes = + for pid <- Process.list(), + info = Process.info(pid, keys), + do: info -```elixir -Kino.DataTable.new( - processes, - keys: [:registered_name, :initial_call, :reductions, :stack_size] -) +Kino.DataTable.new(processes) ``` Now you can use the table above to sort by the number of diff --git a/lib/livebook/notebook/explore/kino/pong.livemd b/lib/livebook/notebook/explore/kino/pong.livemd index 235722e0c..4628860aa 100644 --- a/lib/livebook/notebook/explore/kino/pong.livemd +++ b/lib/livebook/notebook/explore/kino/pong.livemd @@ -2,7 +2,7 @@ ```elixir Mix.install([ - {:kino, "~> 0.5.0"} + {:kino, "~> 0.6.0"} ]) ``` diff --git a/lib/livebook/notebook/explore/kino/smart_cells.livemd b/lib/livebook/notebook/explore/kino/smart_cells.livemd index 8ad3d6897..b6da38b0b 100644 --- a/lib/livebook/notebook/explore/kino/smart_cells.livemd +++ b/lib/livebook/notebook/explore/kino/smart_cells.livemd @@ -2,7 +2,7 @@ ```elixir Mix.install([ - {:kino, github: "livebook-dev/kino"}, + {:kino, "~> 0.6.0"}, {:jason, "~> 1.3"} ]) ``` diff --git a/lib/livebook/notebook/explore/kino/vm_introspection.livemd b/lib/livebook/notebook/explore/kino/vm_introspection.livemd index d20995cce..3600a4579 100644 --- a/lib/livebook/notebook/explore/kino/vm_introspection.livemd +++ b/lib/livebook/notebook/explore/kino/vm_introspection.livemd @@ -2,8 +2,8 @@ ```elixir Mix.install([ - {:vega_lite, "~> 0.1.2"}, - {:kino, "~> 0.5.0"} + {:kino, "~> 0.6.0"}, + {:kino_vega_lite, "~> 0.1.0"} ]) alias VegaLite, as: Vl diff --git a/lib/livebook/runtime/dependencies.ex b/lib/livebook/runtime/dependencies.ex index c75a088a5..5d85de0a0 100644 --- a/lib/livebook/runtime/dependencies.ex +++ b/lib/livebook/runtime/dependencies.ex @@ -102,11 +102,11 @@ defmodule Livebook.Runtime.Dependencies do ## Examples - iex> Livebook.Runtime.Dependencies.parse_term(~s|{:kino, "~> 0.5.0"}|) - {:ok, {:kino, "~> 0.5.0"}} + iex> Livebook.Runtime.Dependencies.parse_term(~s|{:jason, "~> 1.3.0"}|) + {:ok, {:jason, "~> 1.3.0"}} - iex> Livebook.Runtime.Dependencies.parse_term(~s|{:kino, "~> 0.5.0", runtime: false, meta: 'data'}|) - {:ok, {:kino, "~> 0.5.0", runtime: false, meta: 'data'}} + iex> Livebook.Runtime.Dependencies.parse_term(~s|{:jason, "~> 1.3.0", runtime: false, meta: 'data'}|) + {:ok, {:jason, "~> 1.3.0", runtime: false, meta: 'data'}} iex> Livebook.Runtime.Dependencies.parse_term(~s|%{name: "Jake", numbers: [1, 2, 3.4]}|) {:ok, %{name: "Jake", numbers: [1, 2, 3.4]}} diff --git a/lib/livebook/runtime/elixir_standalone.ex b/lib/livebook/runtime/elixir_standalone.ex index 3a7678d6b..1624c6c4e 100644 --- a/lib/livebook/runtime/elixir_standalone.ex +++ b/lib/livebook/runtime/elixir_standalone.ex @@ -17,37 +17,38 @@ defmodule Livebook.Runtime.ElixirStandalone do server_pid: pid() | nil } - kino_dep = {:kino, github: "livebook-dev/kino"} + kino_vega_lite_dep = {:kino_vega_lite, "~> 0.1.0"} + kino_db_dep = {:kino_db, "~> 0.1.0"} @extra_smart_cell_definitions [ %{ - kind: "Elixir.Kino.SmartCell.DBConnection", + kind: "Elixir.KinoDB.ConnectionCell", name: "Database connection", requirement: %{ - name: "Kino", + name: "KinoDB", variants: [ - %{name: "PostgreSQL", dependencies: [kino_dep, {:postgrex, "~> 0.16.1"}]}, - %{name: "MySQL", dependencies: [kino_dep, {:myxql, "~> 0.6.1"}]} + %{name: "PostgreSQL", dependencies: [kino_db_dep, {:postgrex, "~> 0.16.3"}]}, + %{name: "MySQL", dependencies: [kino_db_dep, {:myxql, "~> 0.6.2"}]} ] } }, %{ - kind: "Elixir.Kino.SmartCell.SQL", + kind: "Elixir.KinoDB.SQLCell", name: "SQL query", requirement: %{ - name: "Kino", + name: "KinoDB", variants: [ - %{name: "Default", dependencies: [kino_dep]} + %{name: "Default", dependencies: [kino_db_dep]} ] } }, %{ - kind: "Elixir.Kino.SmartCell.ChartBuilder", - name: "Chart builder", + kind: "Elixir.KinoVegaLite.ChartCell", + name: "Chart", requirement: %{ - name: "Kino", + name: "KinoVegaLite", variants: [ - %{name: "Default", dependencies: [kino_dep, {:vega_lite, "~> 0.1.3"}]} + %{name: "Default", dependencies: [kino_vega_lite_dep]} ] } } diff --git a/static/images/axon.png b/static/images/axon.png deleted file mode 100644 index da551dae8717ee082045b5388e41341c5ca0e61e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33372 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

Hf+Qm)Xu@)#vKl!%h(!cksF)l&!-FrD4zCE5 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 APyhe` diff --git a/static/images/nx.png b/static/images/nx.png deleted file mode 100644 index 26eb955755865279c87afd71f173019fcdfa11bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21480 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 p%eh 0.5.0"} + @jason {:jason, "~> 1.3.0"} describe "add_mix_deps/2" do test "prepends Mix.install/2 call if there is none" do - assert Dependencies.add_mix_deps("", [@kino]) == + assert Dependencies.add_mix_deps("", [@jason]) == {:ok, """ Mix.install([ - {:kino, "~> 0.5.0"} + {:jason, "~> 1.3.0"} ])\ """} - assert Dependencies.add_mix_deps("# Comment", [@kino]) == + assert Dependencies.add_mix_deps("# Comment", [@jason]) == {:ok, """ Mix.install([ - {:kino, "~> 0.5.0"} + {:jason, "~> 1.3.0"} ]) # Comment\ @@ -37,12 +37,12 @@ defmodule Livebook.Runtime.DependenciesTest do # Final comment\ """, - [@kino] + [@jason] ) == {:ok, """ Mix.install([ - {:kino, "~> 0.5.0"} + {:jason, "~> 1.3.0"} ]) # Outer comment @@ -62,13 +62,13 @@ defmodule Livebook.Runtime.DependenciesTest do {:req, "~> 0.2.0"} ])\ """, - [@kino] + [@jason] ) == {:ok, """ Mix.install([ {:req, "~> 0.2.0"}, - {:kino, "~> 0.5.0"} + {:jason, "~> 1.3.0"} ])\ """} @@ -84,7 +84,7 @@ defmodule Livebook.Runtime.DependenciesTest do # Result :ok\ """, - [@kino] + [@jason] ) == {:ok, """ @@ -92,7 +92,7 @@ defmodule Livebook.Runtime.DependenciesTest do Mix.install([ # Inner comment leading {:req, "~> 0.2.0"}, - {:kino, "~> 0.5.0"} + {:jason, "~> 1.3.0"} # Inner comment trailing ]) @@ -111,14 +111,14 @@ defmodule Livebook.Runtime.DependenciesTest do ] )\ """, - [@kino] + [@jason] ) == {:ok, """ Mix.install( [ {:req, "~> 0.2.0"}, - {:kino, "~> 0.5.0"} + {:jason, "~> 1.3.0"} ], system_env: [ # {"XLA_TARGET", "cuda111"} @@ -130,34 +130,34 @@ defmodule Livebook.Runtime.DependenciesTest do test "does not add the dependency if it already exists" do code = """ Mix.install([ - {:kino, "~> 0.5.2"} + {:jason, "~> 1.3.0"} ])\ """ - assert Dependencies.add_mix_deps(code, [@kino]) == {:ok, code} + assert Dependencies.add_mix_deps(code, [@jason]) == {:ok, code} code = """ Mix.install([ - {:kino, "~> 0.5.2", runtime: false} + {:jason, "~> 1.3.0", runtime: false} ])\ """ - assert Dependencies.add_mix_deps(code, [@kino]) == {:ok, code} + assert Dependencies.add_mix_deps(code, [@jason]) == {:ok, code} end test "given multiple dependencies adds the missing ones" do assert Dependencies.add_mix_deps( """ Mix.install([ - {:kino, "~> 0.5.2"} + {:jason, "~> 1.3.0"} ])\ """, - [{:vega_lite, "~> 0.1.3"}, {:kino, "~> 0.5.0"}, {:req, "~> 0.2.0"}] + [{:vega_lite, "~> 0.1.3"}, {:jason, "~> 1.3.0"}, {:req, "~> 0.2.0"}] ) == {:ok, """ Mix.install([ - {:kino, "~> 0.5.2"}, + {:jason, "~> 1.3.0"}, {:vega_lite, "~> 0.1.3"}, {:req, "~> 0.2.0"} ])\ @@ -165,11 +165,11 @@ defmodule Livebook.Runtime.DependenciesTest do code = """ Mix.install([ - {:kino, "~> 0.5.2", runtime: false} + {:jason, "~> 1.3.0", runtime: false} ])\ """ - assert Dependencies.add_mix_deps(code, [@kino]) == {:ok, code} + assert Dependencies.add_mix_deps(code, [@jason]) == {:ok, code} end test "returns an error if the code has a syntax error" do @@ -178,7 +178,7 @@ defmodule Livebook.Runtime.DependenciesTest do # Comment [,1] """, - [@kino] + [@jason] ) == {:error, """ diff --git a/test/livebook/session_test.exs b/test/livebook/session_test.exs index bf3aa9818..777913fe0 100644 --- a/test/livebook/session_test.exs +++ b/test/livebook/session_test.exs @@ -142,7 +142,7 @@ defmodule Livebook.SessionTest do Session.subscribe(session.id) - Session.add_dependencies(session.pid, [{:kino, "~> 0.5.0"}]) + Session.add_dependencies(session.pid, [{:jason, "~> 1.3.0"}]) session_pid = session.pid assert_receive {:operation, {:apply_cell_delta, ^session_pid, "setup", :primary, _delta, 1}} @@ -154,7 +154,7 @@ defmodule Livebook.SessionTest do %{ source: """ Mix.install([ - {:kino, "~> 0.5.0"} + {:jason, "~> 1.3.0"} ])\ """ } @@ -173,7 +173,7 @@ defmodule Livebook.SessionTest do Session.subscribe(session.id) - Session.add_dependencies(session.pid, [{:kino, "~> 0.5.0"}]) + Session.add_dependencies(session.pid, [{:json, "~> 1.3.0"}]) assert_receive {:error, "failed to add dependencies to the setup cell, reason:" <> _} end diff --git a/test/livebook_web/live/session_live_test.exs b/test/livebook_web/live/session_live_test.exs index 39f260eb0..c562b0ed2 100644 --- a/test/livebook_web/live/session_live_test.exs +++ b/test/livebook_web/live/session_live_test.exs @@ -905,12 +905,12 @@ defmodule LivebookWeb.SessionLiveTest do # Search the predefined dependencies in the embedded runtime search_view |> element(~s{form[phx-change="search"]}) - |> render_change(%{"search" => "ki"}) + |> render_change(%{"search" => "ja"}) page = render(view) - assert page =~ "kino" - assert page =~ "Interactive widgets for Livebook" - assert page =~ "0.5.2" + assert page =~ "jason" + assert page =~ "A blazing fast JSON parser and generator in pure Elixir" + assert page =~ "1.3.0" end end diff --git a/test/test_helper.exs b/test/test_helper.exs index 477bf8afa..2f5718cd4 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -22,11 +22,11 @@ defmodule Livebook.Runtime.Embedded.Dependencies do def entries() do [ %{ - dependency: {:kino, "~> 0.5.2"}, - description: "Interactive widgets for Livebook", - name: "kino", - url: "https://hex.pm/packages/kino", - version: "0.5.2" + dependency: {:jason, "~> 1.3.0"}, + description: "A blazing fast JSON parser and generator in pure Elixir", + name: "jason", + url: "https://hex.pm/packages/jason", + version: "1.3.0" } ] end