Skip to content

Commit

Permalink
Supports update
Browse files Browse the repository at this point in the history
  • Loading branch information
cristineguadelupe committed May 9, 2024
1 parent 8457c8c commit d733106
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 61 deletions.
58 changes: 48 additions & 10 deletions lib/kino/explorer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ 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"`
"""
@spec new(DataFrame.t() | Series.t(), keyword()) :: t()
def new(data, opts \\ [])
Expand All @@ -39,21 +43,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 +249,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

0 comments on commit d733106

Please sign in to comment.