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

Support column/row deletion in gr.DataFrame #10420

Merged
merged 21 commits into from
Jan 29, 2025
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
7 changes: 7 additions & 0 deletions .changeset/few-items-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@gradio/core": patch
"@gradio/dataframe": patch
"gradio": patch
---

feat:Support column/row deletion in `gr.DataFrame`
1 change: 1 addition & 0 deletions client/python/test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,7 @@ def __call__(self, *args, **kwargs):

assert all(s in messages for s in statuses)

@pytest.mark.flaky
@patch("gradio_client.client.Endpoint.make_end_to_end_fn")
def test_messages_correct_two_concurrent(
self, mock_make_end_to_end_fn, calculator_demo
Expand Down
4 changes: 2 additions & 2 deletions gradio/components/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ def __init__(
Parameters:
value: Default value to display in the DataFrame. If a Styler is provided, it will be used to set the displayed value in the DataFrame (e.g. to set precision of numbers) if the `interactive` is False. If a Callable function is provided, the function will be called whenever the app loads to set the initial value of the component.
headers: List of str header names. If None, no headers are shown.
row_count: Limit number of rows for input and decide whether user can create new rows. The first element of the tuple is an `int`, the row count; the second should be 'fixed' or 'dynamic', the new row behaviour. If an `int` is passed the rows default to 'dynamic'
col_count: Limit number of columns for input and decide whether user can create new columns. The first element of the tuple is an `int`, the number of columns; the second should be 'fixed' or 'dynamic', the new column behaviour. If an `int` is passed the columns default to 'dynamic'
row_count: Limit number of rows for input and decide whether user can create new rows or delete existing rows. The first element of the tuple is an `int`, the row count; the second should be 'fixed' or 'dynamic', the new row behaviour. If an `int` is passed the rows default to 'dynamic'
col_count: Limit number of columns for input and decide whether user can create new columns or delete existing columns. The first element of the tuple is an `int`, the number of columns; the second should be 'fixed' or 'dynamic', the new column behaviour. If an `int` is passed the columns default to 'dynamic'
datatype: Datatype of values in sheet. Can be provided per column as a list of strings, or for the entire sheet as a single string. Valid datatypes are "str", "number", "bool", "date", and "markdown".
type: Type of value to be returned by component. "pandas" for pandas dataframe, "numpy" for numpy array, "polars" for polars dataframe, or "array" for a Python list of lists.
label: the label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.
Expand Down
2 changes: 2 additions & 0 deletions js/core/src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
"new_row": "New row",
"add_row_above": "Add row above",
"add_row_below": "Add row below",
"delete_row": "Delete row",
"delete_column": "Delete column",
"add_column_left": "Add column to the left",
"add_column_right": "Add column to the right"
},
Expand Down
10 changes: 0 additions & 10 deletions js/dataframe/shared/Arrow.svelte

This file was deleted.

30 changes: 21 additions & 9 deletions js/dataframe/shared/CellMenu.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import Arrow from "./Arrow.svelte";
import CellMenuIcons from "./CellMenuIcons.svelte";
import type { I18nFormatter } from "js/utils/src";

export let x: number;
Expand All @@ -12,6 +12,10 @@
export let row: number;
export let col_count: [number, "fixed" | "dynamic"];
export let row_count: [number, "fixed" | "dynamic"];
export let on_delete_row: () => void;
export let on_delete_col: () => void;
export let can_delete_rows: boolean;
export let can_delete_cols: boolean;

export let i18n: I18nFormatter;
let menu_element: HTMLDivElement;
Expand Down Expand Up @@ -50,23 +54,35 @@
<div bind:this={menu_element} class="cell-menu">
{#if !is_header && can_add_rows}
<button on:click={() => on_add_row_above()}>
<Arrow transform="rotate(-90 12 12)" />
<CellMenuIcons icon="add-row-above" />
{i18n("dataframe.add_row_above")}
</button>
<button on:click={() => on_add_row_below()}>
<Arrow transform="rotate(90 12 12)" />
<CellMenuIcons icon="add-row-below" />
{i18n("dataframe.add_row_below")}
</button>
{#if can_delete_rows}
<button on:click={on_delete_row} class="delete">
<CellMenuIcons icon="delete-row" />
{i18n("dataframe.delete_row")}
</button>
{/if}
{/if}
{#if can_add_columns}
<button on:click={() => on_add_column_left()}>
<Arrow transform="rotate(180 12 12)" />
<CellMenuIcons icon="add-column-left" />
{i18n("dataframe.add_column_left")}
</button>
<button on:click={() => on_add_column_right()}>
<Arrow transform="rotate(0 12 12)" />
<CellMenuIcons icon="add-column-right" />
{i18n("dataframe.add_column_right")}
</button>
{#if can_delete_cols}
<button on:click={on_delete_col} class="delete">
<CellMenuIcons icon="delete-column" />
{i18n("dataframe.delete_column")}
</button>
{/if}
{/if}
</div>

Expand Down Expand Up @@ -110,8 +126,4 @@
fill: currentColor;
transition: fill 0.2s;
}

.cell-menu button:hover :global(svg) {
fill: var(--color-accent);
}
</style>
113 changes: 113 additions & 0 deletions js/dataframe/shared/CellMenuIcons.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<script lang="ts">
Copy link
Member Author

@abidlabs abidlabs Jan 24, 2025

Choose a reason for hiding this comment

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

clearer icons

export let icon: string;
</script>

{#if icon == "add-column-right"}
<svg viewBox="0 0 24 24" width="16" height="16">
<rect
x="4"
y="6"
width="4"
height="12"
stroke="currentColor"
stroke-width="2"
fill="none"
/>
<path
d="M12 12H19M16 8L19 12L16 16"
stroke="currentColor"
stroke-width="2"
fill="none"
stroke-linecap="round"
/>
</svg>
{:else if icon == "add-column-left"}
<svg viewBox="0 0 24 24" width="16" height="16">
<rect
x="16"
y="6"
width="4"
height="12"
stroke="currentColor"
stroke-width="2"
fill="none"
/>
<path
d="M12 12H5M8 8L5 12L8 16"
stroke="currentColor"
stroke-width="2"
fill="none"
stroke-linecap="round"
/>
</svg>
{:else if icon == "add-row-above"}
<svg viewBox="0 0 24 24" width="16" height="16">
<rect
x="6"
y="16"
width="12"
height="4"
stroke="currentColor"
stroke-width="2"
/>
<path
d="M12 12V5M8 8L12 5L16 8"
stroke="currentColor"
stroke-width="2"
fill="none"
stroke-linecap="round"
/>
</svg>
{:else if icon == "add-row-below"}
<svg viewBox="0 0 24 24" width="16" height="16">
<rect
x="6"
y="4"
width="12"
height="4"
stroke="currentColor"
stroke-width="2"
/>
<path
d="M12 12V19M8 16L12 19L16 16"
stroke="currentColor"
stroke-width="2"
fill="none"
stroke-linecap="round"
/>
</svg>
{:else if icon == "delete-row"}
<svg viewBox="0 0 24 24" width="16" height="16">
<rect
x="5"
y="10"
width="14"
height="4"
stroke="currentColor"
stroke-width="2"
/>
<path
d="M8 7L16 17M16 7L8 17"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
{:else if icon == "delete-column"}
<svg viewBox="0 0 24 24" width="16" height="16">
<rect
x="10"
y="5"
width="4"
height="14"
stroke="currentColor"
stroke-width="2"
/>
<path
d="M7 8L17 16M17 8L7 16"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
{/if}
66 changes: 52 additions & 14 deletions js/dataframe/shared/Table.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,7 @@
id: string;
}[][] {
const data_row_length = _values.length;
return Array(
row_count[1] === "fixed"
? row_count[0]
: data_row_length < row_count[0]
? row_count[0]
: data_row_length
)
return Array(row_count[1] === "fixed" ? row_count[0] : data_row_length)
.fill(0)
.map((_, i) =>
Array(
Expand Down Expand Up @@ -791,6 +785,42 @@
afterUpdate(() => {
value_is_output = false;
});

async function delete_row(index: number): Promise<void> {
parent.focus();
if (row_count[1] !== "dynamic") return;
if (data.length <= 1) return;
data.splice(index, 1);
data = data;
selected = false;
}

async function delete_col(index: number): Promise<void> {
parent.focus();
if (col_count[1] !== "dynamic") return;
if (data[0].length <= 1) return;

_headers.splice(index, 1);
_headers = _headers;

data.forEach((row) => {
row.splice(index, 1);
});
data = data;
selected = false;
}

function delete_row_at(index: number): void {
delete_row(index);
active_cell_menu = null;
active_header_menu = null;
}

function delete_col_at(index: number): void {
delete_col(index);
active_cell_menu = null;
active_header_menu = null;
}
</script>

<svelte:window on:resize={() => set_cell_widths()} />
Expand Down Expand Up @@ -1050,18 +1080,22 @@
</div>
</div>

{#if active_cell_menu !== null}
{#if active_cell_menu}
<CellMenu
{i18n}
x={active_cell_menu.x}
y={active_cell_menu.y}
row={active_cell_menu?.row ?? -1}
row={active_cell_menu.row}
{col_count}
{row_count}
on_add_row_above={() => add_row_at(active_cell_menu?.row ?? -1, "above")}
on_add_row_below={() => add_row_at(active_cell_menu?.row ?? -1, "below")}
on_add_column_left={() => add_col_at(active_cell_menu?.col ?? -1, "left")}
on_add_column_right={() => add_col_at(active_cell_menu?.col ?? -1, "right")}
on_add_row_above={() => add_row_at(active_cell_menu?.row || 0, "above")}
on_add_row_below={() => add_row_at(active_cell_menu?.row || 0, "below")}
on_add_column_left={() => add_col_at(active_cell_menu?.col || 0, "left")}
on_add_column_right={() => add_col_at(active_cell_menu?.col || 0, "right")}
on_delete_row={() => delete_row_at(active_cell_menu?.row || 0)}
on_delete_col={() => delete_col_at(active_cell_menu?.col || 0)}
can_delete_rows={data.length > 1}
can_delete_cols={data[0].length > 1}
{i18n}
/>
{/if}

Expand All @@ -1078,6 +1112,10 @@
on_add_column_left={() => add_col_at(active_header_menu?.col ?? -1, "left")}
on_add_column_right={() =>
add_col_at(active_header_menu?.col ?? -1, "right")}
on_delete_row={() => delete_row_at(active_cell_menu?.row ?? -1)}
on_delete_col={() => delete_col_at(active_header_menu?.col ?? -1)}
can_delete_rows={false}
can_delete_cols={data[0].length > 1}
/>
{/if}

Expand Down
Loading