mirror of
https://github.com/gradio-app/gradio.git
synced 2025-02-11 11:19:58 +08:00
Expand and collapse dataframe cells (#10463)
* - truncate long cell values - add expand and collapse logic * add changeset * tweak * add max_chars and single click expanding * - fix test - add story * Update gradio/components/dataframe.py Co-authored-by: Abubakar Abid <abubakar@huggingface.co> --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
parent
ff5f976bbb
commit
ed7a0919ab
6
.changeset/soft-insects-greet.md
Normal file
6
.changeset/soft-insects-greet.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@gradio/dataframe": minor
|
||||||
|
"gradio": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat:Expand and collapse dataframe cells
|
@ -99,6 +99,7 @@ class Dataframe(Component):
|
|||||||
show_fullscreen_button: bool = False,
|
show_fullscreen_button: bool = False,
|
||||||
show_copy_button: bool = False,
|
show_copy_button: bool = False,
|
||||||
show_row_numbers: bool = False,
|
show_row_numbers: bool = False,
|
||||||
|
max_chars: int | None = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Parameters:
|
Parameters:
|
||||||
@ -129,6 +130,7 @@ class Dataframe(Component):
|
|||||||
show_fullscreen_button: If True, will show a button to view the values in the table in fullscreen mode.
|
show_fullscreen_button: If True, will show a button to view the values in the table in fullscreen mode.
|
||||||
show_copy_button: If True, will show a button to copy the table data to the clipboard.
|
show_copy_button: If True, will show a button to copy the table data to the clipboard.
|
||||||
show_row_numbers: If True, will display row numbers in a separate column.
|
show_row_numbers: If True, will display row numbers in a separate column.
|
||||||
|
max_chars: Maximum number of characters to display in each cell before truncating (single-clicking a cell value will still reveal the full content). If None, no truncation is applied.
|
||||||
"""
|
"""
|
||||||
self.wrap = wrap
|
self.wrap = wrap
|
||||||
self.row_count = self.__process_counts(row_count)
|
self.row_count = self.__process_counts(row_count)
|
||||||
@ -165,6 +167,7 @@ class Dataframe(Component):
|
|||||||
self.show_fullscreen_button = show_fullscreen_button
|
self.show_fullscreen_button = show_fullscreen_button
|
||||||
self.show_copy_button = show_copy_button
|
self.show_copy_button = show_copy_button
|
||||||
self.show_row_numbers = show_row_numbers
|
self.show_row_numbers = show_row_numbers
|
||||||
|
self.max_chars = max_chars
|
||||||
super().__init__(
|
super().__init__(
|
||||||
label=label,
|
label=label,
|
||||||
every=every,
|
every=every,
|
||||||
|
@ -606,6 +606,7 @@ class Numpy(components.Dataframe):
|
|||||||
column_widths: list[str | int] | None = None,
|
column_widths: list[str | int] | None = None,
|
||||||
show_row_numbers: bool = False,
|
show_row_numbers: bool = False,
|
||||||
show_fullscreen_button: bool = False,
|
show_fullscreen_button: bool = False,
|
||||||
|
max_chars: int | None = None,
|
||||||
show_copy_button: bool = False,
|
show_copy_button: bool = False,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@ -634,6 +635,7 @@ class Numpy(components.Dataframe):
|
|||||||
min_width=min_width,
|
min_width=min_width,
|
||||||
show_row_numbers=show_row_numbers,
|
show_row_numbers=show_row_numbers,
|
||||||
show_fullscreen_button=show_fullscreen_button,
|
show_fullscreen_button=show_fullscreen_button,
|
||||||
|
max_chars=max_chars,
|
||||||
show_copy_button=show_copy_button,
|
show_copy_button=show_copy_button,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -679,6 +681,7 @@ class Matrix(components.Dataframe):
|
|||||||
column_widths: list[str | int] | None = None,
|
column_widths: list[str | int] | None = None,
|
||||||
show_row_numbers: bool = False,
|
show_row_numbers: bool = False,
|
||||||
show_fullscreen_button: bool = True,
|
show_fullscreen_button: bool = True,
|
||||||
|
max_chars: int | None = None,
|
||||||
show_copy_button: bool = False,
|
show_copy_button: bool = False,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@ -707,6 +710,7 @@ class Matrix(components.Dataframe):
|
|||||||
min_width=min_width,
|
min_width=min_width,
|
||||||
show_row_numbers=show_row_numbers,
|
show_row_numbers=show_row_numbers,
|
||||||
show_fullscreen_button=show_fullscreen_button,
|
show_fullscreen_button=show_fullscreen_button,
|
||||||
|
max_chars=max_chars,
|
||||||
show_copy_button=show_copy_button,
|
show_copy_button=show_copy_button,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -752,6 +756,7 @@ class List(components.Dataframe):
|
|||||||
column_widths: list[str | int] | None = None,
|
column_widths: list[str | int] | None = None,
|
||||||
show_row_numbers: bool = False,
|
show_row_numbers: bool = False,
|
||||||
show_fullscreen_button: bool = True,
|
show_fullscreen_button: bool = True,
|
||||||
|
max_chars: int | None = None,
|
||||||
show_copy_button: bool = False,
|
show_copy_button: bool = False,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@ -780,6 +785,7 @@ class List(components.Dataframe):
|
|||||||
min_width=min_width,
|
min_width=min_width,
|
||||||
show_row_numbers=show_row_numbers,
|
show_row_numbers=show_row_numbers,
|
||||||
show_fullscreen_button=show_fullscreen_button,
|
show_fullscreen_button=show_fullscreen_button,
|
||||||
|
max_chars=max_chars,
|
||||||
show_copy_button=show_copy_button,
|
show_copy_button=show_copy_button,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -299,6 +299,34 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Story
|
||||||
|
name="Dataframe with truncated text"
|
||||||
|
args={{
|
||||||
|
values: [
|
||||||
|
[
|
||||||
|
"This is a very long text that should be truncated",
|
||||||
|
"Short text",
|
||||||
|
"Another very long text that needs truncation"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Short",
|
||||||
|
"This text is also quite long and should be truncated as well",
|
||||||
|
"Medium length text here"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Medium text",
|
||||||
|
"Brief",
|
||||||
|
"This is the longest text in the entire table and it should definitely be truncated"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
headers: ["Column A", "Column B", "Column C"],
|
||||||
|
label: "Truncated Text Example",
|
||||||
|
max_chars: 20,
|
||||||
|
col_count: [3, "dynamic"],
|
||||||
|
row_count: [3, "dynamic"]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Story
|
<Story
|
||||||
name="Dataframe with multiline headers"
|
name="Dataframe with multiline headers"
|
||||||
args={{
|
args={{
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
export let loading_status: LoadingStatus;
|
export let loading_status: LoadingStatus;
|
||||||
export let interactive: boolean;
|
export let interactive: boolean;
|
||||||
export let show_fullscreen_button = false;
|
export let show_fullscreen_button = false;
|
||||||
|
export let max_chars: number | undefined = undefined;
|
||||||
export let show_copy_button = false;
|
export let show_copy_button = false;
|
||||||
|
|
||||||
$: _headers = [...(value.headers || headers)];
|
$: _headers = [...(value.headers || headers)];
|
||||||
@ -106,6 +107,7 @@
|
|||||||
stream_handler={(...args) => gradio.client.stream(...args)}
|
stream_handler={(...args) => gradio.client.stream(...args)}
|
||||||
bind:value_is_output
|
bind:value_is_output
|
||||||
{show_fullscreen_button}
|
{show_fullscreen_button}
|
||||||
|
{max_chars}
|
||||||
{show_copy_button}
|
{show_copy_button}
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
|
@ -23,12 +23,27 @@
|
|||||||
export let line_breaks = true;
|
export let line_breaks = true;
|
||||||
export let editable = true;
|
export let editable = true;
|
||||||
export let root: string;
|
export let root: string;
|
||||||
|
export let max_chars: number | null = null;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
let is_expanded = false;
|
||||||
|
|
||||||
export let el: HTMLInputElement | null;
|
export let el: HTMLInputElement | null;
|
||||||
$: _value = value;
|
$: _value = value;
|
||||||
|
|
||||||
|
function truncate_text(
|
||||||
|
text: string | number,
|
||||||
|
max_length: number | null = null
|
||||||
|
): string {
|
||||||
|
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);
|
||||||
|
|
||||||
function use_focus(node: HTMLInputElement): any {
|
function use_focus(node: HTMLInputElement): any {
|
||||||
if (clear_on_focus) {
|
if (clear_on_focus) {
|
||||||
_value = "";
|
_value = "";
|
||||||
@ -52,11 +67,21 @@
|
|||||||
|
|
||||||
function handle_keydown(event: KeyboardEvent): void {
|
function handle_keydown(event: KeyboardEvent): void {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
value = _value;
|
if (edit) {
|
||||||
dispatch("blur");
|
value = _value;
|
||||||
|
dispatch("blur");
|
||||||
|
} else if (!header) {
|
||||||
|
is_expanded = !is_expanded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dispatch("keydown", event);
|
dispatch("keydown", event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handle_click(): void {
|
||||||
|
if (!edit && !header) {
|
||||||
|
is_expanded = !is_expanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if edit}
|
{#if edit}
|
||||||
@ -76,28 +101,31 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<span
|
<span
|
||||||
on:dblclick
|
on:click={handle_click}
|
||||||
tabindex="-1"
|
on:keydown={handle_keydown}
|
||||||
|
tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
class:edit
|
class:edit
|
||||||
|
class:expanded={is_expanded}
|
||||||
class:multiline={header}
|
class:multiline={header}
|
||||||
on:focus|preventDefault
|
on:focus|preventDefault
|
||||||
style={styling}
|
style={styling}
|
||||||
class="table-cell-text"
|
class="table-cell-text"
|
||||||
|
data-editable={editable}
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
>
|
>
|
||||||
{#if datatype === "html"}
|
{#if datatype === "html"}
|
||||||
{@html value}
|
{@html display_text}
|
||||||
{:else if datatype === "markdown"}
|
{:else if datatype === "markdown"}
|
||||||
<MarkdownCode
|
<MarkdownCode
|
||||||
message={value.toLocaleString()}
|
message={display_text.toLocaleString()}
|
||||||
{latex_delimiters}
|
{latex_delimiters}
|
||||||
{line_breaks}
|
{line_breaks}
|
||||||
chatbot={false}
|
chatbot={false}
|
||||||
{root}
|
{root}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
{editable ? value : display_value || value}
|
{editable ? display_text : display_value || display_text}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@ -118,6 +146,8 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: var(--size-2);
|
padding: var(--size-2);
|
||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
@ -125,6 +155,19 @@
|
|||||||
-ms-user-select: text;
|
-ms-user-select: text;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:where(:not(.header), [data-editable="true"]) {
|
||||||
|
width: calc(100% - var(--size-10));
|
||||||
|
}
|
||||||
|
|
||||||
|
span.expanded {
|
||||||
|
height: auto;
|
||||||
|
min-height: 100%;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +178,8 @@
|
|||||||
.header {
|
.header {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
font-weight: var(--weight-bold);
|
font-weight: var(--weight-bold);
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit {
|
.edit {
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
export let show_fullscreen_button = false;
|
export let show_fullscreen_button = false;
|
||||||
export let show_copy_button = false;
|
export let show_copy_button = false;
|
||||||
export let value_is_output = false;
|
export let value_is_output = false;
|
||||||
|
export let max_chars: number | undefined = undefined;
|
||||||
|
|
||||||
let selected_cells: CellCoordinate[] = [];
|
let selected_cells: CellCoordinate[] = [];
|
||||||
$: selected_cells = [...selected_cells];
|
$: selected_cells = [...selected_cells];
|
||||||
@ -912,6 +913,7 @@
|
|||||||
<div class="cell-wrap">
|
<div class="cell-wrap">
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<EditableCell
|
<EditableCell
|
||||||
|
{max_chars}
|
||||||
bind:value={_headers[i].value}
|
bind:value={_headers[i].value}
|
||||||
bind:el={els[id].input}
|
bind:el={els[id].input}
|
||||||
{latex_delimiters}
|
{latex_delimiters}
|
||||||
@ -1012,6 +1014,7 @@
|
|||||||
}}
|
}}
|
||||||
{clear_on_focus}
|
{clear_on_focus}
|
||||||
{root}
|
{root}
|
||||||
|
{max_chars}
|
||||||
/>
|
/>
|
||||||
{#if editable && should_show_cell_menu([index, j], selected_cells, editable)}
|
{#if editable && should_show_cell_menu([index, j], selected_cells, editable)}
|
||||||
<button
|
<button
|
||||||
@ -1216,11 +1219,11 @@
|
|||||||
|
|
||||||
.cell-wrap {
|
.cell-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
outline: none;
|
outline: none;
|
||||||
height: var(--size-full);
|
|
||||||
min-height: var(--size-9);
|
min-height: var(--size-9);
|
||||||
overflow: hidden;
|
position: relative;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
@ -1254,10 +1257,10 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin-right: var(--spacing-sm);
|
margin-right: var(--spacing-sm);
|
||||||
z-index: var(--layer-1);
|
z-index: var(--layer-1);
|
||||||
}
|
position: absolute;
|
||||||
|
right: var(--size-1);
|
||||||
.cell-menu-button:hover {
|
top: 50%;
|
||||||
background-color: var(--color-bg-hover);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-selected .cell-menu-button {
|
.cell-selected .cell-menu-button {
|
||||||
|
@ -57,6 +57,7 @@ class TestDataframe:
|
|||||||
"column_widths": [],
|
"column_widths": [],
|
||||||
"show_fullscreen_button": False,
|
"show_fullscreen_button": False,
|
||||||
"show_copy_button": False,
|
"show_copy_button": False,
|
||||||
|
"max_chars": None,
|
||||||
}
|
}
|
||||||
dataframe_input = gr.Dataframe()
|
dataframe_input = gr.Dataframe()
|
||||||
output = dataframe_input.preprocess(DataframeData(**x_data))
|
output = dataframe_input.preprocess(DataframeData(**x_data))
|
||||||
@ -103,6 +104,7 @@ class TestDataframe:
|
|||||||
"line_breaks": True,
|
"line_breaks": True,
|
||||||
"column_widths": [],
|
"column_widths": [],
|
||||||
"show_fullscreen_button": False,
|
"show_fullscreen_button": False,
|
||||||
|
"max_chars": None,
|
||||||
"show_copy_button": False,
|
"show_copy_button": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user