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

Refactor gr.Dataframe #10631

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
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
6 changes: 6 additions & 0 deletions .changeset/crazy-nails-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@gradio/dataframe": minor
"gradio": minor
---

feat:Refactor `gr.Dataframe`
1 change: 1 addition & 0 deletions demo/dataframe_events/run.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: dataframe_events"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import pandas as pd\n", "import numpy as np\n", "\n", "def update_dataframe():\n", " regular_df = pd.DataFrame(np.random.randint(1, 10, size=(5, 5)))\n", " regular_df.columns = [str(col) for col in regular_df.columns]\n", "\n", " wide_df = pd.DataFrame(np.random.randint(1, 10, size=(5, 15)))\n", " wide_df.columns = [f\"col_{col}\" for col in wide_df.columns]\n", "\n", " tall_df = pd.DataFrame(np.random.randint(1, 10, size=(50, 3)))\n", " tall_df.columns = [\"A\", \"B\", \"C\"]\n", "\n", " return regular_df, wide_df, tall_df\n", "\n", "def clear_dataframes():\n", " regular_empty_df = pd.DataFrame(columns=[str(i) for i in range(5)])\n", " wide_empty_df = pd.DataFrame(columns=[f\"col_{i}\" for i in range(15)])\n", " tall_empty_df = pd.DataFrame(columns=[\"A\", \"B\", \"C\"])\n", " return regular_empty_df, wide_empty_df, tall_empty_df\n", "\n", "def increment_select_counter(evt: gr.SelectData, count):\n", " count_val = 1 if count is None else count + 1\n", " return count_val, evt.index, evt.value\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Row():\n", " with gr.Column(scale=1):\n", " initial_regular_df = pd.DataFrame(np.zeros((5, 5), dtype=int))\n", " initial_regular_df.columns = [str(col) for col in initial_regular_df.columns]\n", "\n", " df = gr.Dataframe(\n", " value=initial_regular_df,\n", " interactive=True,\n", " label=\"Interactive Dataframe\",\n", " show_label=True,\n", " elem_id=\"dataframe\",\n", " show_search=\"filter\",\n", " show_copy_button=True,\n", " show_row_numbers=True\n", " )\n", "\n", " with gr.Column(scale=1):\n", " initial_wide_df = pd.DataFrame(np.zeros((5, 15), dtype=int))\n", " initial_wide_df.columns = [f\"col_{col}\" for col in initial_wide_df.columns]\n", "\n", " df_view = gr.Dataframe(\n", " value=initial_wide_df,\n", " interactive=False,\n", " label=\"Non-Interactive View (Scroll Horizontally)\",\n", " show_label=True,\n", " show_search=\"search\",\n", " elem_id=\"non-interactive-dataframe\",\n", " show_copy_button=True,\n", " show_row_numbers=True,\n", " show_fullscreen_button=True\n", " )\n", "\n", " with gr.Row():\n", " initial_tall_df = pd.DataFrame(np.zeros((50, 3), dtype=int))\n", " initial_tall_df.columns = [\"A\", \"B\", \"C\"]\n", "\n", " df_tall = gr.Dataframe(\n", " value=initial_tall_df,\n", " interactive=False,\n", " label=\"Tall Dataframe (Scroll Vertically)\",\n", " show_label=True,\n", " elem_id=\"dataframe_tall\",\n", " show_copy_button=True,\n", " show_row_numbers=True,\n", " max_height=300\n", " )\n", "\n", " with gr.Row():\n", " with gr.Column():\n", " update_btn = gr.Button(\"Update dataframe\", elem_id=\"update_btn\")\n", " clear_btn = gr.Button(\"Clear dataframe\", elem_id=\"clear_btn\")\n", "\n", " with gr.Row():\n", " change_events = gr.Number(value=0, label=\"Change events\", elem_id=\"change_events\")\n", " input_events = gr.Number(value=0, label=\"Input events\", elem_id=\"input_events\")\n", " select_events = gr.Number(value=0, label=\"Select events\", elem_id=\"select_events\")\n", "\n", " with gr.Row():\n", " selected_cell_index = gr.Textbox(label=\"Selected cell index\", elem_id=\"selected_cell_index\")\n", " selected_cell_value = gr.Textbox(label=\"Selected cell value\", elem_id=\"selected_cell_value\")\n", "\n", " update_btn.click(\n", " fn=update_dataframe,\n", " outputs=[df, df_view, df_tall]\n", " )\n", "\n", " clear_btn.click(\n", " fn=clear_dataframes,\n", " outputs=[df, df_view, df_tall]\n", " )\n", "\n", " df.change(\n", " fn=lambda x: x + 1,\n", " inputs=[change_events],\n", " outputs=[change_events]\n", " )\n", "\n", " df.input(\n", " fn=lambda x: x + 1,\n", " inputs=[input_events],\n", " outputs=[input_events]\n", " )\n", "\n", " df.select(\n", " fn=increment_select_counter,\n", " inputs=[select_events],\n", " outputs=[select_events, selected_cell_index, selected_cell_value]\n", " )\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
118 changes: 118 additions & 0 deletions demo/dataframe_events/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import gradio as gr
import pandas as pd
import numpy as np

def update_dataframe():
regular_df = pd.DataFrame(np.random.randint(1, 10, size=(5, 5)))
regular_df.columns = [str(col) for col in regular_df.columns]

wide_df = pd.DataFrame(np.random.randint(1, 10, size=(5, 15)))
wide_df.columns = [f"col_{col}" for col in wide_df.columns]

tall_df = pd.DataFrame(np.random.randint(1, 10, size=(50, 3)))
tall_df.columns = ["A", "B", "C"]

return regular_df, wide_df, tall_df

def clear_dataframes():
regular_empty_df = pd.DataFrame(columns=[str(i) for i in range(5)])
wide_empty_df = pd.DataFrame(columns=[f"col_{i}" for i in range(15)])
tall_empty_df = pd.DataFrame(columns=["A", "B", "C"])
return regular_empty_df, wide_empty_df, tall_empty_df

def increment_select_counter(evt: gr.SelectData, count):
count_val = 1 if count is None else count + 1
return count_val, evt.index, evt.value

with gr.Blocks() as demo:
with gr.Row():
with gr.Column(scale=1):
initial_regular_df = pd.DataFrame(np.zeros((5, 5), dtype=int))
initial_regular_df.columns = [str(col) for col in initial_regular_df.columns]

df = gr.Dataframe(
value=initial_regular_df,
interactive=True,
label="Interactive Dataframe",
show_label=True,
elem_id="dataframe",
show_search="filter",
show_copy_button=True,
show_row_numbers=True
)

with gr.Column(scale=1):
initial_wide_df = pd.DataFrame(np.zeros((5, 15), dtype=int))
initial_wide_df.columns = [f"col_{col}" for col in initial_wide_df.columns]

df_view = gr.Dataframe(
value=initial_wide_df,
interactive=False,
label="Non-Interactive View (Scroll Horizontally)",
show_label=True,
show_search="search",
elem_id="non-interactive-dataframe",
show_copy_button=True,
show_row_numbers=True,
show_fullscreen_button=True
)

with gr.Row():
initial_tall_df = pd.DataFrame(np.zeros((50, 3), dtype=int))
initial_tall_df.columns = ["A", "B", "C"]

df_tall = gr.Dataframe(
value=initial_tall_df,
interactive=False,
label="Tall Dataframe (Scroll Vertically)",
show_label=True,
elem_id="dataframe_tall",
show_copy_button=True,
show_row_numbers=True,
max_height=300
)

with gr.Row():
with gr.Column():
update_btn = gr.Button("Update dataframe", elem_id="update_btn")
clear_btn = gr.Button("Clear dataframe", elem_id="clear_btn")

with gr.Row():
change_events = gr.Number(value=0, label="Change events", elem_id="change_events")
input_events = gr.Number(value=0, label="Input events", elem_id="input_events")
select_events = gr.Number(value=0, label="Select events", elem_id="select_events")

with gr.Row():
selected_cell_index = gr.Textbox(label="Selected cell index", elem_id="selected_cell_index")
selected_cell_value = gr.Textbox(label="Selected cell value", elem_id="selected_cell_value")

update_btn.click(
fn=update_dataframe,
outputs=[df, df_view, df_tall]
)

clear_btn.click(
fn=clear_dataframes,
outputs=[df, df_view, df_tall]
)

df.change(
fn=lambda x: x + 1,
inputs=[change_events],
outputs=[change_events]
)

df.input(
fn=lambda x: x + 1,
inputs=[input_events],
outputs=[input_events]
)

df.select(
fn=increment_select_counter,
inputs=[select_events],
outputs=[select_events, selected_cell_index, selected_cell_value]
)

if __name__ == "__main__":
demo.launch()
2 changes: 1 addition & 1 deletion gradio/components/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def __init__(
headers: list[str] | None = None,
row_count: int | tuple[int, str] = (1, "dynamic"),
col_count: int | tuple[int, str] | None = None,
datatype: Literal["str", "number", "bool", "date", "markdown", "html"]
datatype: Literal["str", "number", "bool", "date", "markdown", "html", "image"]
| Sequence[
Literal["str", "number", "bool", "date", "markdown", "html"]
] = "str",
Expand Down
21 changes: 6 additions & 15 deletions js/dataframe/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import { StatusTracker } from "@gradio/statustracker";
import type { LoadingStatus } from "@gradio/statustracker";
import type { Headers, Datatype, DataframeValue } from "./shared/utils";
import Image from "@gradio/image";
export let headers: Headers = [];
export let elem_id = "";
export let elem_classes: string[] = [];
Expand All @@ -33,12 +35,13 @@
export let root: string;
export let line_breaks = true;
export let column_widths: string[] = [];
// export let column_widths: string[] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need this?

export let gradio: Gradio<{
change: never;
select: SelectData;
input: never;
clear_status: LoadingStatus;
search: string | null;
}>;
export let latex_delimiters: {
left: string;
Expand All @@ -53,17 +56,6 @@
export let show_copy_button = false;
export let show_row_numbers = false;
export let show_search: "none" | "search" | "filter" = "none";
let search_query: string | null = null;
$: filtered_cell_values = search_query
? value.data?.filter((row) =>
row.some(
(cell) =>
search_query &&
String(cell).toLowerCase().includes(search_query.toLowerCase())
)
)
: null;
export let pinned_columns = 0;
$: _headers = [...(value.headers || headers)];
Expand Down Expand Up @@ -98,7 +90,7 @@
{show_label}
{row_count}
{col_count}
values={filtered_cell_values || value.data}
values={value.data}
{display_value}
{styling}
headers={_headers}
Expand All @@ -109,15 +101,13 @@
}}
on:input={(e) => gradio.dispatch("input")}
on:select={(e) => gradio.dispatch("select", e.detail)}
on:search={(e) => (search_query = e.detail)}
{wrap}
{datatype}
{latex_delimiters}
editable={interactive}
{max_height}
i18n={gradio.i18n}
{line_breaks}
{column_widths}
upload={(...args) => gradio.client.upload(...args)}
stream_handler={(...args) => gradio.client.stream(...args)}
bind:value_is_output
Expand All @@ -127,5 +117,6 @@
{show_row_numbers}
{show_search}
{pinned_columns}
components={{ image: Image }}
/>
</Block>
44 changes: 44 additions & 0 deletions js/dataframe/shared/CellMenuButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts">
export let on_click: (event: MouseEvent) => void;
</script>

<button
class="cell-menu-button"
on:click={on_click}
on:touchstart={(event) => {
event.preventDefault();
const touch = event.touches[0];
const mouseEvent = new MouseEvent("click", {
clientX: touch.clientX,
clientY: touch.clientY,
bubbles: true,
cancelable: true,
view: window
});
on_click(mouseEvent);
}}
>
&#8942;
</button>

<style>
.cell-menu-button {
flex-shrink: 0;
display: none;
align-items: center;
justify-content: center;
background-color: var(--block-background-fill);
border: 1px solid var(--border-color-primary);
border-radius: var(--block-radius);
width: var(--size-5);
height: var(--size-5);
min-width: var(--size-5);
padding: 0;
margin-right: var(--spacing-sm);
z-index: var(--layer-1);
position: absolute;
right: var(--size-1);
top: 50%;
transform: translateY(-50%);
}
</style>
50 changes: 41 additions & 9 deletions js/dataframe/shared/EditableCell.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { MarkdownCode } from "@gradio/markdown-code";

import type { I18nFormatter } from "@gradio/utils";
export let edit: boolean;
export let value: string | number = "";
export let display_value: string | null = null;
Expand All @@ -13,7 +13,8 @@
| "html"
| "number"
| "bool"
| "date" = "str";
| "date"
| "image" = "str";
export let latex_delimiters: {
left: string;
right: string;
Expand All @@ -24,25 +25,34 @@
export let editable = true;
export let root: string;
export let max_chars: number | null = null;
export let components: Record<string, any> = {};
export let i18n: I18nFormatter;

const dispatch = createEventDispatcher<{
blur: void;
keydown: KeyboardEvent;
}>();

const dispatch = createEventDispatcher();
let is_expanded = false;

export let el: HTMLInputElement | null;
$: _value = value;

function truncate_text(
text: string | number,
max_length: number | null = null
max_length: number | null = null,
is_image = false
): string {
if (is_image) return String(text);
const str = String(text);
if (!max_length || str.length <= max_length) return str;
return str.slice(0, max_length) + "...";
}

$: display_text = is_expanded
? value
: truncate_text(display_value || value, max_chars);
$: display_text =
edit || is_expanded
? value
: truncate_text(display_value || value, max_chars, datatype === "image");

function use_focus(node: HTMLInputElement): any {
if (clear_on_focus) {
Expand Down Expand Up @@ -87,6 +97,7 @@
{#if edit}
<input
role="textbox"
aria-label="Edit cell"
bind:this={el}
bind:value={_value}
class:header
Expand All @@ -113,7 +124,17 @@
data-editable={editable}
placeholder=" "
>
{#if datatype === "html"}
{#if datatype === "image" && components.image}
<svelte:component
this={components.image}
value={{ url: display_text }}
show_label={false}
label="cell-image"
show_download_button={false}
{i18n}
gradio={{ dispatch: () => {} }}
/>
{:else if datatype === "html"}
{@html display_text}
{:else if datatype === "markdown"}
<MarkdownCode
Expand Down Expand Up @@ -155,18 +176,22 @@
cursor: text;
width: 100%;
height: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

span.expanded {
height: auto;
min-height: 100%;
white-space: pre-wrap;
word-break: break-word;
white-space: normal;
overflow: visible;
}

.multiline {
white-space: pre-line;
overflow: visible;
}

.header {
Expand All @@ -175,10 +200,17 @@
white-space: normal;
word-break: break-word;
margin-left: var(--size-1);
overflow: visible;
}

.edit {
opacity: 0;
pointer-events: none;
}

span :global(img) {
max-height: 100px;
width: auto;
object-fit: contain;
}
</style>
Loading
Loading