Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supports update #164

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions lib/kino/explorer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ defmodule Kino.Explorer do
@legacy_numeric_types [:float, :integer]

@doc """
Creates a new kino displaying a given data frame or series.
Creates a new kino displaying a given data frame or series as a rich table.

## Options

* `:name` - The displayed name of the table. Defaults to `"DataFrame"` or `"Series"`,
depending on the given data
"""
@spec new(DataFrame.t() | Series.t(), keyword()) :: t()
def new(data, opts \\ [])
Expand All @@ -39,21 +44,37 @@ defmodule Kino.Explorer do

def new(%Series{} = s, opts) do
name = Keyword.get(opts, :name, "Series")
column_name = name |> String.replace(" ", "_") |> String.downcase() |> String.to_atom()
df = DataFrame.new([{column_name, s}])
Kino.Table.new(__MODULE__, {df, name}, export: fn state -> {"text", inspect(state.df[0])} end)
Kino.Table.new(__MODULE__, {s, name}, export: fn state -> {"text", inspect(state.df[0])} end)
end

@doc """
Updates the table to display a new data frame.

## Examples

df = Explorer.Datasets.iris()
kino = Kino.Explorer.new(data)

Once created, you can update the table to display new data:

new_df = Explorer.Datasets.fossil_fuels()
Kino.Explorer.update(kino, new_df)
"""
def update(kino, df), do: Kino.Table.update(kino, df)

@impl true
def init({df, name}) do
lazy = lazy?(df)
groups = df.groups
df = DataFrame.ungroup(df)
total_rows = if !lazy, do: DataFrame.n_rows(df)
columns = columns(df, lazy, groups)
{lazy, groups, df, total_rows, columns} = prepare_data(df, name)
info = info(columns, lazy, name)

{:ok, info, %{df: df, total_rows: total_rows, columns: columns, groups: groups}}
{:ok, info, %{df: df, total_rows: total_rows, columns: columns, groups: groups, name: name}}
end

@impl true
def on_update(df, state) do
{_lazy, groups, df, total_rows, columns} = prepare_data(df, state.name)

{:ok, %{state | df: df, total_rows: total_rows, columns: columns, groups: groups}}
end

@impl true
Expand Down Expand Up @@ -229,4 +250,22 @@ defmodule Kino.Explorer do
defp df_to_export(df, rows_spec) do
df |> relocate(rows_spec[:relocates]) |> order_by(rows_spec[:order]) |> DataFrame.collect()
end

defp prepare_data(%DataFrame{} = df, _name), do: prepare_data(df)

defp prepare_data(%Series{} = s, name) do
column_name = name |> String.replace(" ", "_") |> String.downcase() |> String.to_atom()
df = DataFrame.new([{column_name, s}])
prepare_data(df)
end

defp prepare_data(df) do
lazy = lazy?(df)
groups = df.groups
df = DataFrame.ungroup(df)
total_rows = if !lazy, do: DataFrame.n_rows(df)
columns = columns(df, lazy, groups)

{lazy, groups, df, total_rows, columns}
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"},
"explorer": {:hex, :explorer, "0.8.1", "0b7300030407a1d9c90096205395f112daa078148f9fc0b5616df30469b4e080", [:mix], [{:adbc, "~> 0.1", [hex: :adbc, repo: "hexpm", optional: true]}, {:aws_signature, "~> 0.3", [hex: :aws_signature, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:fss, "~> 0.1", [hex: :fss, repo: "hexpm", optional: false]}, {:nx, "~> 0.4", [hex: :nx, repo: "hexpm", optional: true]}, {:rustler, "~> 0.31.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.7", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}, {:table, "~> 0.1.2", [hex: :table, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "4982756e2d1a1ee52a4acc913800a522b7b14ccb43801ed05d839e531724b798"},
"fss": {:hex, :fss, "0.1.1", "9db2344dbbb5d555ce442ac7c2f82dd975b605b50d169314a20f08ed21e08642", [:mix], [], "hexpm", "78ad5955c7919c3764065b21144913df7515d52e228c09427a004afe9c1a16b0"},
"kino": {:git, "https://github.com/livebook-dev/kino.git", "baaa84a76b0440f7a2b8c9e77f806ce161a65f71", []},
"kino": {:git, "https://github.com/livebook-dev/kino.git", "5eb60c207e19bbdf5fcb9dc04b9c5c2a22d15bed", []},
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"},
Expand Down
124 changes: 74 additions & 50 deletions test/kino/explorer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ defmodule Kino.ExplorerTest do
end

test "column definitions include type" do
widget = Kino.Explorer.new(people_df())
data = connect(widget)
kino = Kino.Explorer.new(people_df())
data = connect(kino)

assert %{
features: [:export, :pagination, :sorting, :relocate],
Expand All @@ -34,8 +34,8 @@ defmodule Kino.ExplorerTest do
end

test "rows order matches the given data frame by default" do
widget = Kino.Explorer.new(people_df())
data = connect(widget)
kino = Kino.Explorer.new(people_df())
data = connect(kino)

assert %{
content: %{
Expand All @@ -54,13 +54,13 @@ defmodule Kino.ExplorerTest do
end

test "supports sorting by other columns" do
widget = Kino.Explorer.new(people_df())
kino = Kino.Explorer.new(people_df())

connect(widget)
connect(kino)

push_event(widget, "order_by", %{"key" => "1", "direction" => "desc"})
push_event(kino, "order_by", %{"key" => "1", "direction" => "desc"})

assert_broadcast_event(widget, "update_content", %{
assert_broadcast_event(kino, "update_content", %{
columns: [
%{key: "0", label: "id", type: "number"},
%{key: "1", label: "name", type: "text"},
Expand All @@ -78,8 +78,8 @@ defmodule Kino.ExplorerTest do
test "supports pagination" do
df = Explorer.DataFrame.new(%{n: Enum.to_list(1..25)})

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
Expand All @@ -89,9 +89,9 @@ defmodule Kino.ExplorerTest do
}
} = data

push_event(widget, "show_page", %{"page" => 2})
push_event(kino, "show_page", %{"page" => 2})

assert_broadcast_event(widget, "update_content", %{
assert_broadcast_event(kino, "update_content", %{
page: 2,
max_page: 3,
data: [["11", "12", "13", "14", "15", "16", "17", "18", "19", "20"]]
Expand All @@ -101,8 +101,8 @@ defmodule Kino.ExplorerTest do
test "supports pagination limit" do
df = Explorer.DataFrame.new(%{n: Enum.to_list(1..25)})

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
Expand All @@ -112,23 +112,23 @@ defmodule Kino.ExplorerTest do
}
} = data

push_event(widget, "limit", %{"limit" => 15})
push_event(kino, "limit", %{"limit" => 15})

assert_broadcast_event(widget, "update_content", %{
assert_broadcast_event(kino, "update_content", %{
page: 1,
max_page: 2,
data: [["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"]]
})
end

test "supports relocate" do
widget = Kino.Explorer.new(people_df())
kino = Kino.Explorer.new(people_df())

connect(widget)
connect(kino)

push_event(widget, "relocate", %{"from_index" => 1, "to_index" => 0})
push_event(kino, "relocate", %{"from_index" => 1, "to_index" => 0})

assert_broadcast_event(widget, "update_content", %{
assert_broadcast_event(kino, "update_content", %{
columns: [
%{key: "1", label: "name", type: "text"},
%{key: "0", label: "id", type: "number"},
Expand All @@ -151,8 +151,8 @@ defmodule Kino.ExplorerTest do
woman: [true, false, false, false, nil, nil, nil]
})

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
Expand Down Expand Up @@ -193,8 +193,8 @@ defmodule Kino.ExplorerTest do
test "support data summary for all nils" do
df = Explorer.DataFrame.new(%{id: [nil, nil, nil, nil]})

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
Expand All @@ -213,8 +213,8 @@ defmodule Kino.ExplorerTest do
test "support data summary for lists" do
df = Explorer.DataFrame.new(%{list: Explorer.Series.from_list([[1, 2], [1]])})

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
Expand All @@ -236,8 +236,8 @@ defmodule Kino.ExplorerTest do
test "support data summary for lists with nil" do
df = Explorer.DataFrame.new(%{list: Explorer.Series.from_list([[1, 2], [1], nil])})

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
Expand All @@ -259,8 +259,8 @@ defmodule Kino.ExplorerTest do
test "does not break on lists with internal nulls" do
df = Explorer.DataFrame.new(%{list: Explorer.Series.from_list([[1, 2], [1, nil]])})

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
Expand All @@ -279,8 +279,8 @@ defmodule Kino.ExplorerTest do
})
|> Explorer.DataFrame.group_by(:name)

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
Expand Down Expand Up @@ -310,8 +310,8 @@ defmodule Kino.ExplorerTest do

test "supports infinity" do
df = Explorer.DataFrame.new(a: [:infinity])
widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
Expand Down Expand Up @@ -343,8 +343,8 @@ defmodule Kino.ExplorerTest do
dtypes: [d: :binary]
)

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)
types = ["text", "number", "uri", "binary", "list"]

assert get_in(data.content.columns, [Access.all(), :type]) == types
Expand All @@ -355,8 +355,8 @@ defmodule Kino.ExplorerTest do
Explorer.Datasets.iris()
|> Explorer.DataFrame.filter_with(&Explorer.Series.equal(&1["sepal_length"], 3))

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
features: [:export, :pagination, :sorting, :relocate],
Expand All @@ -377,8 +377,8 @@ defmodule Kino.ExplorerTest do
df =
Explorer.DataFrame.new([x: [1, 2], y: [<<110, 120>>, <<200, 210>>]], dtypes: [y: :binary])

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
features: [:export, :pagination, :sorting, :relocate],
Expand All @@ -390,8 +390,8 @@ defmodule Kino.ExplorerTest do

test "supports lazy data frames" do
df = Explorer.Datasets.iris() |> Explorer.DataFrame.lazy()
widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
features: [:export, :pagination, :sorting, :relocate],
Expand Down Expand Up @@ -437,8 +437,8 @@ defmodule Kino.ExplorerTest do
test "supports export" do
df = Explorer.DataFrame.new(%{n: Enum.to_list(1..25)})

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
export: %{formats: ["CSV", "NDJSON", "Parquet"]},
Expand All @@ -452,7 +452,7 @@ defmodule Kino.ExplorerTest do

for format <- ["CSV", "NDJSON", "Parquet"] do
extension = ".#{String.downcase(format)}"
push_event(widget, "download", %{"format" => format})
push_event(kino, "download", %{"format" => format})
assert_receive({:event, "download_content", {:binary, exported, data}, _})
assert %{format: ^extension} = exported
assert is_binary(data)
Expand All @@ -462,8 +462,8 @@ defmodule Kino.ExplorerTest do
test "supports export for lazy data frames" do
df = Explorer.DataFrame.new(%{n: Enum.to_list(1..25)}, lazy: true)

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
export: %{formats: ["CSV", "NDJSON", "Parquet"]},
Expand All @@ -477,7 +477,7 @@ defmodule Kino.ExplorerTest do

for format <- ["CSV", "NDJSON", "Parquet"] do
extension = ".#{String.downcase(format)}"
push_event(widget, "download", %{"format" => format})
push_event(kino, "download", %{"format" => format})
assert_receive({:event, "download_content", {:binary, exported, data}, _})
assert %{format: ^extension} = exported
assert is_binary(data)
Expand Down Expand Up @@ -510,8 +510,8 @@ defmodule Kino.ExplorerTest do
df = Explorer.DataFrame.new(%{list: Explorer.Series.from_list([[1, 2], [1]])})
rows_spec = %{order: nil, relocates: []}

widget = Kino.Explorer.new(df)
data = connect(widget)
kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{export: %{formats: ["NDJSON", "Parquet"]}} = data

Expand All @@ -521,4 +521,28 @@ defmodule Kino.ExplorerTest do
assert {:ok, %{extension: ^extension}} = exported
end
end

test "supports update" do
df = Explorer.DataFrame.new(%{n: Enum.to_list(1..25)})

kino = Kino.Explorer.new(df)
data = connect(kino)

assert %{
content: %{
page: 1,
max_page: 3,
data: [["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]]
}
} = data

new_df = Explorer.DataFrame.new(%{n: Enum.to_list(25..50)})
Kino.Explorer.update(kino, new_df)

assert_broadcast_event(kino, "update_content", %{
page: 1,
max_page: 3,
data: [["25", "26", "27", "28", "29", "30", "31", "32", "33", "34"]]
})
end
end
Loading