gradio-app / gradio

Build and share delightful machine learning apps, all in Python. 🌟 Star to support our work!
http://www.gradio.app
Apache License 2.0
33.5k stars 2.54k forks source link

the height of gr.Dataframe() changed randomly #9854

Open engchina opened 2 hours ago

engchina commented 2 hours ago

Describe the bug

The height of gr.Dataframe() changed randomly, and may occur many spaces as attached image. But it runs okay on gradio==4.41.1.

Problem version: both gradio==4.44.1 (change max_height=50 to height=50 for gradio==4.44.1) and gradio==5.4.0 Okay version: gradio==4.41.1

Have you searched existing issues? 🔎

Reproduction

from dataclasses import dataclass
from typing import Tuple

import gradio as gr
import numpy as np
import pandas as pd

@dataclass
class PaginationState:
    """分页状态"""
    current_page: int = 1
    page_size: int = 10

    @property
    def start_index(self) -> int:
        return (self.current_page - 1) * self.page_size

    @property
    def end_index(self) -> int:
        return self.current_page * self.page_size

class DataFramePagination:
    """DataFrame分页工具类"""

    def __init__(self,
                 df: pd.DataFrame,
                 page_size: int = 10):
        self.df = df
        self.state = PaginationState(page_size=page_size)
        self._calculate_total_pages()

    def _calculate_total_pages(self) -> int:
        """计算总页数"""
        self.total_pages = len(self.df) // self.state.page_size
        if len(self.df) % self.state.page_size > 0:
            self.total_pages += 1
        return max(1, self.total_pages)  # 确保至少有1页

    def get_page_data(self) -> pd.DataFrame:
        """获取当前页数据"""
        return self.df.iloc[self.state.start_index:self.state.end_index]

    def update_df(self, new_df: pd.DataFrame) -> Tuple[pd.DataFrame, gr.Button, gr.Button, gr.Dropdown]:
        """
        更新数据源并返回更新后的UI状态
        :param new_df: 新的DataFrame数据源
        :return: 用于更新UI的数据元组
        """
        self.df = new_df
        self._calculate_total_pages()
        # 确保当前页不超过总页数
        if self.state.current_page > self.total_pages:
            self.state.current_page = max(1, self.total_pages)
        return self._get_update_tuple()

    def next_page(self) -> Tuple[pd.DataFrame, gr.Button, gr.Button, gr.Dropdown]:
        """下一页"""
        if self.state.current_page < self.total_pages:
            self.state.current_page += 1
        return self._get_update_tuple()

    def prev_page(self) -> Tuple[pd.DataFrame, gr.Button, gr.Button, gr.Dropdown]:
        """上一页"""
        if self.state.current_page > 1:
            self.state.current_page -= 1
        return self._get_update_tuple()

    def jump_to_page(self, page: str) -> Tuple[pd.DataFrame, gr.Button, gr.Button, gr.Dropdown]:
        """跳转到指定页"""
        try:
            page_num = int(page)
            if 1 <= page_num <= self.total_pages:
                self.state.current_page = page_num
        except ValueError:
            pass
        return self._get_update_tuple()

    def _get_update_tuple(self) -> Tuple[pd.DataFrame, gr.Button, gr.Button, gr.Dropdown]:
        """获取用于更新UI的数据元组"""
        page_data = self.get_page_data()
        has_prev = self.state.current_page > 1
        has_next = self.state.current_page < self.total_pages
        page_choices = [str(i) for i in range(1, self.total_pages + 1)]
        print(f"{page_choices=}")
        if len(page_choices) == 0:
            page_choices = [str(1)]
        print(f"{page_choices=}")

        current_page = str(self.state.current_page)
        return page_data, \
            gr.Button(interactive=has_prev), \
            gr.Button(interactive=has_next), \
            gr.Dropdown(choices=page_choices, value=current_page)

def create_pagination_controls(df: pd.DataFrame,
                               table: gr.DataFrame,
                               page_size: int = 10) -> Tuple[gr.Button, gr.Button, gr.Dropdown, DataFramePagination]:
    """
    为DataFrame创建分页控件

    :param df: 数据源DataFrame
    :param table: Gradio DataFrame组件
    :param page_size: 每页显示行数
    :return: 分页控件元组(prev_button, next_button, page_dropdown, paginator)
    """
    paginator = DataFramePagination(df, page_size)

    with gr.Row():
        with gr.Column(scale=1):
            prev_button = gr.Button("Prev Page", interactive=False)
        with gr.Column(scale=1):
            page_dropdown = gr.Dropdown(
                choices=[str(i) for i in range(1, paginator.total_pages + 1)],
                value="1",
                label="跳转到页",
                show_label=False,
                allow_custom_value=True,
                container=False,
            )
        with gr.Column(scale=1):
            next_button = gr.Button("Next Page", interactive=True)

    # 设置按钮回调
    prev_button.click(
        fn=lambda: paginator.prev_page(),
        outputs=[table, prev_button, next_button, page_dropdown]
    )

    next_button.click(
        fn=lambda: paginator.next_page(),
        outputs=[table, prev_button, next_button, page_dropdown]
    )

    page_dropdown.change(
        fn=lambda x: paginator.jump_to_page(x),
        inputs=[page_dropdown],
        outputs=[table, prev_button, next_button, page_dropdown]
    )

    # 首次加载时显示第一页数据
    table.value = paginator.get_page_data()

    return prev_button, next_button, page_dropdown, paginator

def create_sample_data(prefix: str, rows: int = 100) -> pd.DataFrame:
    """创建示例数据"""
    return pd.DataFrame({
        'ID': range(1, rows + 1),
        f'{prefix}_Name': [f'{prefix}用户{i}' for i in range(1, rows + 1)],
        f'{prefix}_Score': np.random.randint(60, 100, rows),
        f'{prefix}_Date': pd.date_range(start='2024-01-01', periods=rows).strftime('%Y-%m-%d').tolist()
    })

def main():
    """主函数 - 展示如何处理DataFrame更新的示例"""
    with gr.Blocks() as demo:
        gr.Markdown("# DataFrame更新示例")

        # 创建初始数据
        initial_df = create_sample_data("表1", rows=50)

        # 创建DataFrame组件
        table = gr.DataFrame(
            value=initial_df.head(10),
            interactive=False,
            label="数据表",
            max_height=50,
        )

        # 添加分页控件
        prev_btn, next_btn, page_dropdown, paginator = create_pagination_controls(
            df=initial_df,
            table=table,
            page_size=10
        )

        # 添加更新数据的按钮
        def update_data():
            # 模拟数据更新:创建新的随机数据
            new_df = create_sample_data("新表", rows=0)  # 故意使用不同的行数来测试
            # 更新分页器并获取更新后的UI状态
            return paginator.update_df(new_df)

        update_button = gr.Button("更新数据")
        update_button.click(
            fn=update_data,
            outputs=[table, prev_btn, next_btn, page_dropdown]
        )

    demo.launch()

if __name__ == "__main__":
    main()

Screenshot

image (8)

Logs

No response

System Info

Gradio Environment Information:
------------------------------
Operating System: Linux
gradio version: 5.4.0
gradio_client version: 1.4.2

------------------------------------------------
gradio dependencies in your environment:

aiofiles: 23.2.1
anyio: 3.7.1
audioop-lts is not installed.
fastapi: 0.115.4
ffmpy: 0.4.0
gradio-client==1.4.2 is not installed.
httpx: 0.27.0
huggingface-hub: 0.26.1
jinja2: 3.1.4
markupsafe: 2.1.5
numpy: 1.26.4
orjson: 3.10.7
packaging: 24.1
pandas: 2.2.2
pillow: 10.4.0
pydantic: 2.8.2
pydub: 0.25.1
python-multipart==0.0.12 is not installed.
pyyaml: 6.0.2
ruff: 0.5.7
safehttpx: 0.1.1
typing-extensions: 4.12.2
websockets: 12.0

Severity

I can work around it

engchina commented 2 hours ago

Okay version: gradio==4.41.0 (not gradio==4.41.1)

engchina commented 2 hours ago

It seems the "height"(on gradio==4.44.1) or "max_height"(on gradio==5.4.0) was ignored while scrolling the gr.Dataframe()

engchina commented 1 hour ago

after testing, this problem occurred from gradio >= 4.43.0