VegaLite errors in LiveBook, but not in iex

There is this totally normal mix Elixir project/repository (code and tests and stuff - just a library for now, no Phoenix or anything).
Then added a LiveBook notebook at notebooks/dicect.livemd - needed some exploratory data visualization.

When this code is executed from the inside the LiveBook:

data = [
  %{"category" => "A", "score" => 28},
  %{"category" => "B", "score" => 55}
]

Vl.new()
|> Vl.data_from_values(data)

It returns:

** (Protocol.UndefinedError) protocol Enumerable not implemented for %Table.Mapper{enumerable: [%{"category" => "A", "score" => 28}, %{"category" => "B", "score" => 55}], fun: #Function<1.129280379/1 in Table.Mapper.map/2>} of type Table.Mapper (a struct). This protocol is implemented for the following type(s): Date.Range, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, Jason.OrderedObject, List, Map, MapSet, Range, Stream
    (elixir 1.17.2) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir 1.17.2) lib/enum.ex:166: Enumerable.reduce/3
    (elixir 1.17.2) lib/enum.ex:4423: Enum.map/2
    (vega_lite 0.1.10) lib/vega_lite.ex:304: VegaLite.data_from_values/3
    #cell:r6qixazynpxprvhi:9: (file)

But, when it is run from the iex -S mix prompt, it returns a VegaLite data structure.

What is missing?

The setup part of the LiveBook:

Mix.install(
  [
    {:dicect, path: Path.join(__DIR__, ".."), env: :dev}
  ],
  config_path: :dicect,
  lockfile: :dicect
)

Current dependencies in mix.exs are:

  ...
  defp deps do
    [
      {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
      {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
      {:logger_json, "~> 6.1"},
      {:vega_lite, "~> 0.1.10"},
      {:kino, "~> 0.14.1"},
      {:tucan, "~> 0.3.0"},
      {:kino_vega_lite, "~> 0.1.8"}
    ]
  end
  ...
  1. Also tried tucan , had a similar story - since it uses VegaLite I guess.

How are you installing:vega_lite and :kino_vega_lite packages? Are they in your library’s mix.exs (which I’d recommend you share here in its entirety, it’s very helpful)?

My suspicion is that because you are not installing them in your livebook, but your library; and because you have your library set to compile with env: :dev, something might be off with protocol consolidation.

Thanks. Those packages are already in mix.exs - also updated the question with the list of dependencies.

1 Like

I get a different error in livebook when trying a minimal repro with unlocked versions of those dependencies:

The error message there isn’t from Elixir, but from JS-land. Livebook is implicitly trying to render the final result of the code block; try adding final :ok atom and the error goes away as the VegaLite struct is not rendered. Add the rendering back explicitly and you’ll see the same error:

alias VegaLite, as: Vl

data = [
  %{"category" => "A", "score" => 28},
  %{"category" => "B", "score" => 55}
]

Vl.new()
|> Vl.data_from_values(data)
|> Kino.VegaLite.new()
|> Kino.render()

:ok

So the problem isn’t in generating a VegaLite struct (as your iex code proves), it is in rendering it. The struct you are producing isn’t ready to be rendered, you need to do more massaging to make it renderable. This works for me:

alias VegaLite, as: Vl

data = [
  %{"category" => "A", "score" => 28},
  %{"category" => "B", "score" => 55}
]

Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "category", type: :nominal)
|> Vl.encode_field(:y, "score", type: :quantitative)

Try that, in addition to unlocking and updating your vega_lite-related dependencies.

@dc0d your error looks like an issue with protocol consolidation, but I cannot reproduce it. Try removing the _build directory in the project.

On a sidenote, you generally don’t want to put notebook-specific dependencies inside mix.exs, because then your project unnecessarily depends on them. You could make them only: :dev, but the recommended approach would be to have these dependencies directly in the notebook. Like this:

Mix.install(
  [
    {:dicect, path: Path.join(__DIR__, ".."), env: :dev},
    {:vega_lite, "~> 0.1.10"},
    {:kino, "~> 0.14.1"},
    {:tucan, "~> 0.3.0"},
    {:kino_vega_lite, "~> 0.1.8"}
  ],
  config_path: :dicect,
  lockfile: :dicect
)
1 Like

A temporary solution that works fine for my problem is:

chart =
  Vl.new(width: 400, height: 400)
  |> Vl.mark(:bar)
  |> Vl.encode_field(:x, "r", type: :quantitative)
  |> Vl.encode_field(:y, "p", type: :quantitative)
  |> Kino.VegaLite.render()

Kino.VegaLite.push_many(chart, data)

Where data is:

[
  %{p: 2.78, r: 2},
  ...
]

Will go back to the original problem later in future.