aggrid.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. from __future__ import annotations
  2. from typing import Dict, List, Optional, cast
  3. from .. import optional_features
  4. from ..awaitable_response import AwaitableResponse
  5. from ..element import Element
  6. try:
  7. import pandas as pd
  8. optional_features.register('pandas')
  9. except ImportError:
  10. pass
  11. class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-community.min.js']):
  12. def __init__(self,
  13. options: Dict, *,
  14. html_columns: List[int] = [],
  15. theme: str = 'balham',
  16. auto_size_columns: bool = True,
  17. ) -> None:
  18. """AG Grid
  19. An element to create a grid using `AG Grid <https://www.ag-grid.com/>`_.
  20. The methods `call_api_method` and `call_column_api_method` can be used to interact with the AG Grid instance on the client.
  21. :param options: dictionary of AG Grid options
  22. :param html_columns: list of columns that should be rendered as HTML (default: `[]`)
  23. :param theme: AG Grid theme (default: 'balham')
  24. :param auto_size_columns: whether to automatically resize columns to fit the grid width (default: `True`)
  25. """
  26. super().__init__()
  27. self._props['options'] = options
  28. self._props['html_columns'] = html_columns
  29. self._props['auto_size_columns'] = auto_size_columns
  30. self._classes = ['nicegui-aggrid', f'ag-theme-{theme}']
  31. @staticmethod
  32. def from_pandas(df: pd.DataFrame, *,
  33. theme: str = 'balham',
  34. auto_size_columns: bool = True,
  35. options: Dict = {}) -> AgGrid:
  36. """Create an AG Grid from a Pandas DataFrame.
  37. Note:
  38. If the DataFrame contains non-serializable columns of type `datetime64[ns]`, `timedelta64[ns]`, `complex128` or `period[M]`,
  39. they will be converted to strings.
  40. To use a different conversion, convert the DataFrame manually before passing it to this method.
  41. See `issue 1698 <https://github.com/zauberzeug/nicegui/issues/1698>`_ for more information.
  42. :param df: Pandas DataFrame
  43. :param theme: AG Grid theme (default: 'balham')
  44. :param auto_size_columns: whether to automatically resize columns to fit the grid width (default: `True`)
  45. :param options: dictionary of additional AG Grid options
  46. :return: AG Grid element
  47. """
  48. date_cols = df.columns[df.dtypes == 'datetime64[ns]']
  49. time_cols = df.columns[df.dtypes == 'timedelta64[ns]']
  50. complex_cols = df.columns[df.dtypes == 'complex128']
  51. period_cols = df.columns[df.dtypes == 'period[M]']
  52. if len(date_cols) != 0 or len(time_cols) != 0 or len(complex_cols) != 0 or len(period_cols) != 0:
  53. df = df.copy()
  54. df[date_cols] = df[date_cols].astype(str)
  55. df[time_cols] = df[time_cols].astype(str)
  56. df[complex_cols] = df[complex_cols].astype(str)
  57. df[period_cols] = df[period_cols].astype(str)
  58. return AgGrid({
  59. 'columnDefs': [{'field': str(col)} for col in df.columns],
  60. 'rowData': df.to_dict('records'),
  61. 'suppressDotNotation': True,
  62. **options,
  63. }, theme=theme, auto_size_columns=auto_size_columns)
  64. @property
  65. def options(self) -> Dict:
  66. """The options dictionary."""
  67. return self._props['options']
  68. def update(self) -> None:
  69. super().update()
  70. self.run_method('update_grid')
  71. def call_api_method(self, name: str, *args) -> AwaitableResponse:
  72. """Call an AG Grid API method.
  73. See `AG Grid API <https://www.ag-grid.com/javascript-data-grid/grid-api/>`_ for a list of methods.
  74. If the function is awaited, the result of the method call is returned.
  75. Otherwise, the method is executed without waiting for a response.
  76. :param name: name of the method
  77. :param args: arguments to pass to the method
  78. :return: AwaitableResponse that can be awaited to get the result of the method call
  79. """
  80. return self.run_method('call_api_method', name, *args)
  81. def call_column_api_method(self, name: str, *args) -> AwaitableResponse:
  82. """Call an AG Grid Column API method.
  83. See `AG Grid Column API <https://www.ag-grid.com/javascript-data-grid/column-api/>`_ for a list of methods.
  84. If the function is awaited, the result of the method call is returned.
  85. Otherwise, the method is executed without waiting for a response.
  86. :param name: name of the method
  87. :param args: arguments to pass to the method
  88. :return: AwaitableResponse that can be awaited to get the result of the method call
  89. """
  90. return self.run_method('call_column_api_method', name, *args)
  91. async def get_selected_rows(self) -> List[Dict]:
  92. """Get the currently selected rows.
  93. This method is especially useful when the grid is configured with ``rowSelection: 'multiple'``.
  94. See `AG Grid API <https://www.ag-grid.com/javascript-data-grid/row-selection/#reference-selection-getSelectedRows>`_ for more information.
  95. :return: list of selected row data
  96. """
  97. result = await self.call_api_method('getSelectedRows')
  98. return cast(List[Dict], result)
  99. async def get_selected_row_ids(self) -> List[int]:
  100. """Get the currently selected rows ids (or nodes in AG Grid)
  101. This method is useful when the grid is configured with ``rowSelection: 'multiple'``.
  102. It returns only the ids of the selected rows, which on default are set by AG Grid
  103. See `AG Grid API <https://www.ag-grid.com/javascript-data-grid/row-selection/#reference-selection-getSelectedNodes>`_
  104. for more information.
  105. :return: list of selected row ids
  106. """
  107. result = await self.client.run_javascript(f"return getElement({self.id}).gridOptions.api.getSelectedNodes().map(x => x.id)")
  108. return cast(List[int], result)
  109. async def get_selected_row(self) -> Optional[Dict]:
  110. """Get the single currently selected row.
  111. This method is especially useful when the grid is configured with ``rowSelection: 'single'``.
  112. :return: row data of the first selection if any row is selected, otherwise `None`
  113. """
  114. rows = await self.get_selected_rows()
  115. return rows[0] if rows else None
  116. async def get_client_data(self) -> List[Dict]:
  117. """Get the data from the client including any edits made by the client.
  118. This method is especially useful when the grid is configured with ``'editable': True``.
  119. See `AG Grid API <https://www.ag-grid.com/javascript-data-grid/accessing-data/>`_ for more information.
  120. Note that when editing a cell, the row data is not updated until the cell exits the edit mode.
  121. This does not happen when the cell loses focus, unless ``stopEditingWhenCellsLoseFocus: True`` is set.
  122. :return: list of row data
  123. """
  124. result = await self.client.run_javascript(f'''
  125. const rowData = [];
  126. getElement({self.id}).gridOptions.api.forEachNode(node => rowData.push(node.data));
  127. return rowData;
  128. ''')
  129. return cast(List[Dict], result)
  130. async def load_client_data(self) -> None:
  131. """Obtain client data and update the element's row data with it.
  132. This syncs edits made by the client in editable cells to the server.
  133. Note that when editing a cell, the row data is not updated until the cell exits the edit mode.
  134. This does not happen when the cell loses focus, unless ``stopEditingWhenCellsLoseFocus: True`` is set.
  135. """
  136. client_row_data = await self.get_client_data()
  137. self.options['rowData'] = client_row_data
  138. self.update()