소스 검색

Merge pull request #1983 from zilch42/ui.table-from-_pandas

UI.table from_pandas method
Falko Schindler 1 년 전
부모
커밋
efe275585a

+ 53 - 1
nicegui/elements/table.py

@@ -1,9 +1,18 @@
+from __future__ import annotations
+
 from typing import Any, Callable, Dict, List, Literal, Optional, Union
 
+from .. import optional_features
 from ..element import Element
 from ..events import GenericEventArguments, TableSelectionEventArguments, handle_event
 from .mixins.filter_element import FilterElement
 
+try:
+    import pandas as pd
+    optional_features.register('pandas')
+except ImportError:
+    pass
+
 
 class Table(FilterElement, component='table.js'):
 
@@ -25,7 +34,7 @@ class Table(FilterElement, component='table.js'):
         :param row_key: name of the column containing unique data identifying the row (default: "id")
         :param title: title of the table
         :param selection: selection type ("single" or "multiple"; default: `None`)
-        :param pagination: A dictionary correlating to a pagination object or number of rows per page (`None` hides the pagination, 0 means "infinite"; default: `None`).
+        :param pagination: a dictionary correlating to a pagination object or number of rows per page (`None` hides the pagination, 0 means "infinite"; default: `None`).
         :param on_select: callback which is invoked when the selection changes
 
         If selection is 'single' or 'multiple', then a `selected` property is accessible containing the selected rows.
@@ -54,6 +63,49 @@ class Table(FilterElement, component='table.js'):
             handle_event(on_select, arguments)
         self.on('selection', handle_selection, ['added', 'rows', 'keys'])
 
+    @staticmethod
+    def from_pandas(df: pd.DataFrame,
+                    row_key: str = 'id',
+                    title: Optional[str] = None,
+                    selection: Optional[Literal['single', 'multiple']] = None,
+                    pagination: Optional[Union[int, dict]] = None,
+                    on_select: Optional[Callable[..., Any]] = None) -> Table:
+        """Create a table from a Pandas DataFrame.
+
+        Note:
+        If the DataFrame contains non-serializable columns of type `datetime64[ns]`, `timedelta64[ns]`, `complex128` or `period[M]`,
+        they will be converted to strings.
+        To use a different conversion, convert the DataFrame manually before passing it to this method.
+        See `issue 1698 <https://github.com/zauberzeug/nicegui/issues/1698>`_ for more information.
+
+        :param df: Pandas DataFrame
+        :param row_key: name of the column containing unique data identifying the row (default: "id")
+        :param title: title of the table
+        :param selection: selection type ("single" or "multiple"; default: `None`)
+        :param pagination: a dictionary correlating to a pagination object or number of rows per page (`None` hides the pagination, 0 means "infinite"; default: `None`).
+        :param on_select: callback which is invoked when the selection changes
+        :return: table element
+        """
+        date_cols = df.columns[df.dtypes == 'datetime64[ns]']
+        time_cols = df.columns[df.dtypes == 'timedelta64[ns]']
+        complex_cols = df.columns[df.dtypes == 'complex128']
+        period_cols = df.columns[df.dtypes == 'period[M]']
+        if len(date_cols) != 0 or len(time_cols) != 0 or len(complex_cols) != 0 or len(period_cols) != 0:
+            df = df.copy()
+            df[date_cols] = df[date_cols].astype(str)
+            df[time_cols] = df[time_cols].astype(str)
+            df[complex_cols] = df[complex_cols].astype(str)
+            df[period_cols] = df[period_cols].astype(str)
+
+        return Table(
+            columns=[{'name': col, 'label': col, 'field': col} for col in df.columns],
+            rows=df.to_dict('records'),
+            row_key=row_key,
+            title=title,
+            selection=selection,
+            pagination=pagination,
+            on_select=on_select)
+
     @property
     def rows(self) -> List[Dict]:
         """List of rows."""

+ 35 - 0
tests/test_table.py

@@ -1,5 +1,7 @@
+from datetime import datetime, timedelta
 from typing import List
 
+import pandas as pd
 from selenium.webdriver.common.by import By
 
 from nicegui import ui
@@ -158,3 +160,36 @@ def test_replace_rows(screen: Screen):
     screen.should_not_contain('Bob')
     screen.should_not_contain('Lionel')
     screen.should_contain('Carol')
+
+
+def test_create_from_pandas(screen: Screen):
+    df = pd.DataFrame({'name': ['Alice', 'Bob'], 'age': [18, 21], 42: 'answer'})
+    ui.table.from_pandas(df)
+
+    screen.open('/')
+    screen.should_contain('Alice')
+    screen.should_contain('Bob')
+    screen.should_contain('18')
+    screen.should_contain('21')
+    screen.should_contain('42')
+    screen.should_contain('answer')
+
+
+def test_problematic_datatypes(screen: Screen):
+    df = pd.DataFrame({
+        'Datetime_col': [datetime(2020, 1, 1)],
+        'Timedelta_col': [timedelta(days=5)],
+        'Complex_col': [1 + 2j],
+        'Period_col': pd.Series([pd.Period('2021-01')]),
+    })
+    ui.table.from_pandas(df)
+
+    screen.open('/')
+    screen.should_contain('Datetime_col')
+    screen.should_contain('Timedelta_col')
+    screen.should_contain('Complex_col')
+    screen.should_contain('Period_col')
+    screen.should_contain('2020-01-01')
+    screen.should_contain('5 days')
+    screen.should_contain('(1+2j)')
+    screen.should_contain('2021-01')

+ 3 - 3
website/more_documentation/aggrid_documentation.py

@@ -113,9 +113,9 @@ def more() -> None:
             ],
         })
 
-    @text_demo('Create Grid from Pandas Dataframe', '''
-        You can create an AG Grid from a Pandas Dataframe using the `from_pandas` method.
-        This method takes a Pandas Dataframe as input and returns an AG Grid.
+    @text_demo('Create Grid from Pandas DataFrame', '''
+        You can create an AG Grid from a Pandas DataFrame using the `from_pandas` method.
+        This method takes a Pandas DataFrame as input and returns an AG Grid.
     ''')
     def aggrid_from_pandas():
         import pandas as pd

+ 4 - 6
website/more_documentation/table_documentation.py

@@ -127,17 +127,15 @@ def more() -> None:
         ''')
         table.on('rename', rename)
 
-    @text_demo('Table from pandas dataframe', '''
-        Here is a demo of how to create a table from a pandas dataframe.
+    @text_demo('Table from Pandas DataFrame', '''
+        You can create a table from a Pandas DataFrame using the `from_pandas` method. 
+        This method takes a Pandas DataFrame as input and returns a table.
     ''')
     def table_from_pandas_demo():
         import pandas as pd
 
         df = pd.DataFrame(data={'col1': [1, 2], 'col2': [3, 4]})
-        ui.table(
-            columns=[{'name': col, 'label': col, 'field': col} for col in df.columns],
-            rows=df.to_dict('records'),
-        )
+        ui.table.from_pandas(df).classes('max-h-40')
 
     @text_demo('Adding rows', '''
         It's simple to add new rows with the `add_rows(dict)` method.

+ 1 - 1
website/static/search_index.json

@@ -796,7 +796,7 @@
   },
   {
     "title": "Table: Table from pandas dataframe",
-    "content": "Here is a demo of how to create a table from a pandas dataframe.",
+    "content": "You can create a table from a pandas dataframe using the from_pandas method. This method takes a Pandas Dataframe as input and returns a table.",
     "url": "/documentation/table#table_from_pandas_dataframe"
   },
   {