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

Allow sorting by multiple columns in dataframe #10778

Merged
merged 19 commits into from
Mar 12, 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
8 changes: 8 additions & 0 deletions .changeset/odd-bees-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@gradio/atoms": minor
"@gradio/core": minor
"@gradio/dataframe": minor
"gradio": minor
---

feat:Allow sorting by multiple columns in dataframe
8 changes: 7 additions & 1 deletion js/atoms/src/IconButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
export let label = "";
export let show_label = false;
export let pending = false;
export let size: "small" | "large" | "medium" = "small";
export let size: "x-small" | "small" | "large" | "medium" = "small";
export let padded = true;
export let highlight = false;
export let disabled = false;
Expand All @@ -30,6 +30,7 @@
>
{#if show_label}<span>{label}</span>{/if}
<div
class:x-small={size === "x-small"}
class:small={size === "small"}
class:large={size === "large"}
class:medium={size === "medium"}
Expand Down Expand Up @@ -91,6 +92,11 @@
transition: filter 0.2s ease-in-out;
}

.x-small {
width: 10px;
height: 10px;
}

.small {
width: 14px;
height: 14px;
Expand Down
3 changes: 2 additions & 1 deletion js/core/src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"sort_column": "Sort column",
"sort_ascending": "Sort ascending",
"sort_descending": "Sort descending",
"drop_to_upload": "Drop CSV or TSV files here to import data into dataframe"
"drop_to_upload": "Drop CSV or TSV files here to import data into dataframe",
"clear_sort": "Clear sort"
},
"dropdown": {
"dropdown": "Dropdown"
Expand Down
72 changes: 69 additions & 3 deletions js/dataframe/Dataframe.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,8 @@
const cell_400 = canvas.getAllByRole("cell")[5];
await userEvent.click(cell_400);

const open_dialog_btn = await within(cell_400).findByRole("button", {
name: "⋮"
});
const open_dialog_btn =
await within(cell_400).findByLabelText("Open cell menu");
await userEvent.click(open_dialog_btn);

const add_row_btn = canvas.getByText("Add row above");
Expand Down Expand Up @@ -660,3 +659,70 @@
await new Promise((resolve) => setTimeout(resolve, 500));
}}
/>

<Story
name="Dataframe with sorting by multiple columns"
args={{
values: [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
],
headers: ["A", "B", "C"],
col_count: [3, "dynamic"],
row_count: [3, "dynamic"],
editable: true,
sort_columns: [
{ col: 0, direction: "asc" },
{ col: 1, direction: "desc" }
],
sort_state: {
sort_columns: [
{ col: 0, direction: "asc" },
{ col: 1, direction: "desc" }
],
row_order: [0, 1, 2]
}
}}
play={async ({ canvasElement }) => {
const canvas = within(canvasElement);
const user = userEvent.setup();

const header_1 = canvas.getAllByText("A")[1];
await userEvent.click(header_1);

const cell_menu_button = canvas.getAllByLabelText("Open cell menu")[0];
await userEvent.click(cell_menu_button);

const sort_ascending_button = canvas.getByRole("button", {
name: "Sort ascending"
});
await userEvent.click(sort_ascending_button);

const header_2 = canvas.getAllByText("B")[1];
await userEvent.click(header_2);

const cell_menu_button_2 = canvas.getAllByLabelText("Open cell menu")[1];
await userEvent.click(cell_menu_button_2);

const sort_descending_button = canvas.getByRole("button", {
name: "Sort descending"
});
await userEvent.click(sort_descending_button);

const header_3 = canvas.getAllByText("C")[1];
await userEvent.click(header_3);

const cell_menu_button_3 = canvas.getAllByLabelText("Open cell menu")[2];
await userEvent.click(cell_menu_button_3);

const sort_ascending_button_3 = canvas.getByRole("button", {
name: "Sort ascending"
});
await userEvent.click(sort_ascending_button_3);

await userEvent.click(header_3);
await userEvent.click(cell_menu_button_3);
await userEvent.click(canvas.getByText("Clear sort"));
}}
/>
51 changes: 51 additions & 0 deletions js/dataframe/shared/CellMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { onMount } from "svelte";
import CellMenuIcons from "./CellMenuIcons.svelte";
import type { I18nFormatter } from "js/utils/src";
import type { SortDirection } from "./context/table_context";

export let x: number;
export let y: number;
Expand All @@ -16,6 +17,10 @@
export let on_delete_col: () => void;
export let can_delete_rows: boolean;
export let can_delete_cols: boolean;
export let on_sort: (direction: SortDirection) => void = () => {};
export let on_clear_sort: () => void = () => {};
export let sort_direction: SortDirection | null = null;
export let sort_priority: number | null = null;

export let i18n: I18nFormatter;
let menu_element: HTMLDivElement;
Expand Down Expand Up @@ -52,6 +57,33 @@
</script>

<div bind:this={menu_element} class="cell-menu">
{#if is_header}
<button
on:click={() => on_sort("asc")}
class:active={sort_direction === "asc"}
>
<CellMenuIcons icon="sort-asc" />
{i18n("dataframe.sort_ascending")}
{#if sort_direction === "asc" && sort_priority !== null}
<span class="priority">{sort_priority}</span>
{/if}
</button>
<button
on:click={() => on_sort("desc")}
class:active={sort_direction === "desc"}
>
<CellMenuIcons icon="sort-desc" />
{i18n("dataframe.sort_descending")}
{#if sort_direction === "desc" && sort_priority !== null}
<span class="priority">{sort_priority}</span>
{/if}
</button>
<button on:click={on_clear_sort}>
<CellMenuIcons icon="clear-sort" />
{i18n("dataframe.clear_sort")}
</button>
{/if}

{#if !is_header && can_add_rows}
<button on:click={() => on_add_row_above()}>
<CellMenuIcons icon="add-row-above" />
Expand Down Expand Up @@ -99,6 +131,7 @@
gap: var(--size-1);
box-shadow: var(--shadow-drop-lg);
min-width: 150px;
z-index: var(--layer-1);
}

.cell-menu button {
Expand All @@ -116,6 +149,11 @@
display: flex;
align-items: center;
gap: var(--size-2);
position: relative;
}

.cell-menu button.active {
background-color: var(--background-fill-secondary);
}

.cell-menu button:hover {
Expand All @@ -126,4 +164,17 @@
fill: currentColor;
transition: fill 0.2s;
}

.priority {
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
font-size: var(--size-2);
background-color: var(--button-secondary-background-fill);
color: var(--body-text-color);
border-radius: var(--radius-sm);
width: var(--size-2-5);
height: var(--size-2-5);
}
</style>
1 change: 1 addition & 0 deletions js/dataframe/shared/CellMenuButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
</script>

<button
aria-label="Open cell menu"
class="cell-menu-button"
on:click={on_click}
on:touchstart={(event) => {
Expand Down
79 changes: 79 additions & 0 deletions js/dataframe/shared/CellMenuIcons.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,83 @@
stroke-linecap="round"
/>
</svg>
{:else if icon == "sort-asc"}
<svg viewBox="0 0 24 24" width="16" height="16">
<path
d="M8 16L12 12L16 16"
stroke="currentColor"
stroke-width="2"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 12V19"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
<path
d="M5 7H19"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
{:else if icon == "sort-desc"}
<svg viewBox="0 0 24 24" width="16" height="16">
<path
d="M8 12L12 16L16 12"
stroke="currentColor"
stroke-width="2"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 16V9"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
<path
d="M5 5H19"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
{:else if icon == "clear-sort"}
<svg viewBox="0 0 24 24" width="16" height="16">
<path
d="M5 5H19"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
<path
d="M5 9H15"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
<path
d="M5 13H11"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
<path
d="M5 17H7"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
<path
d="M17 17L21 21M21 17L17 21"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
{/if}
4 changes: 0 additions & 4 deletions js/dataframe/shared/EditableCell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,6 @@

input {
position: absolute;
top: var(--size-2);
right: var(--size-2);
bottom: var(--size-2);
left: var(--size-2);
flex: 1 1 0%;
transform: translateX(-0.1px);
outline: none;
Expand Down
Loading
Loading