#!/usr/bin/env python # coding: utf-8 # In[1]: from awesome_panel_extensions.awesome_panel import notebook notebook.Header(folder="examples/reference/widgets", notebook="Tabulator.ipynb") # # Tabulator - Reference Guide # # The Tabulator widget wraps the awesome datagrid from [tabulator.info](https://tabulator.info). # # Please take a look at their [website](https://tabulator.info) to see the potential. They have tons of good examples that now can be used in Panel. # # # # #### Parameters: # # * **``value``** (DataFrame or ColumnDataSource): The data loaded to the viewer. # * **``selection``** (List). The currently selected rows. For example [2,4]. # * **``configuration``** (dict): A dictionary for the initial configuration of the Tabulator javascript element. See [tabulator.info](https://tabulator.info) for a ton of examples. # # The `Tabulator` has the same layout and styling parameters as most other widgets. For example `width` and `sizing_mode`. # # Please **note** that # # - in order to be able to stream and patch in a simple way we currently require that the index should be the **default `RangeIndex` with `start` 0 and step 1**. So please **use `.reset_index()` or `reset_index(drop=True)`** if needed on your DataFrame before assigning it to `value`. # # - We **modify the DataFrame value in place** for efficience reasons. If you don't wan't your original dataframe modified then you should assign a deep copy using `.copy(deep=True)` to the `value` parameter. # # # #### Properties # # * **``selected_values``** (DataFrame or ColumnDataSource). If `value` is a `DataFrame`, then `selected_value` returns a `DataFrame` of the rows corresponding to `selection`. Similarly if `value` is a `ColumnDataSource`. # # #### Functions # # * **``stream``**. Streams (appends) the `stream_value` provided to the existing `value` in an efficient manner and triggers a value changed event. # * **``patch``**. Patches (updates) the existing value with the `patch_value` in an efficient manner and triggers a value changed event. # * **``to_columns_configuration``** (Dict). Returns a nice starter `columns` dictionary from the specified `value` that you can finetune manually. # # #### More # # The `tabulator` module also contains a `TabulatorStylesheet` you can use to change the (css) style dynamically. See the advanced example below for more info. # ___ # # Let's start by importing the **dependencies** # In[2]: import pandas as pd import panel as pn import param from awesome_panel_extensions.widgets.tabulator import Tabulator, TabulatorStylesheet Tabulator.config(css="site") pn.extension() # In order to get this working in the notebook we need to manually import the tabulator javascript. # # I'm working on finding a better way. See [Issue 1529](https://github.com/holoviz/panel/issues/15299) for more information # In[3]: get_ipython().run_cell_magic('html', '', "\n") # ## Basic Example # In[4]: basic_configuration = { "layout": "fitColumns", "data": [ {"x": [1], "y": 'a'}, {"x": [2], "y": 'b'} ], "initialSort":[ {"column":"y", "dir":"desc"}, ], "columns":[ {"title": "Value", "field":"x"}, {"title": "Item", "field":"y", "hozAlign":"right", "formatter":"money"} ], } basic_app = Tabulator(configuration=basic_configuration, sizing_mode="stretch_width", height=100) basic_app # In[5]: # basic_app.show() # ## Data and Configuration # # In order to provide more advanced examples we need to download some data and define the configuration of the Tabulator widget. # In[6]: TABULATOR_DATA_URL = "https://raw.githubusercontent.com/MarcSkovMadsen/awesome-panel-extensions/master/tests/widgets/test_tabulator/tabulator_data.csv" data = pd.read_csv(TABULATOR_DATA_URL) data = data.fillna("nan") # Clean up the data. Tabulator does not work with NaN values. data.head(2) # In[7]: configuration = { "layout": "fitColumns", "responsiveLayout": "hide", "tooltips": True, "addRowPos": "top", "history": True, "pagination": "local", "paginationSize": 20, "movableColumns": True, "resizableRows": True, "initialSort": [{"column": "name", "dir": "asc"},], "selectable": True, "columns": [ {"title": "Name", "field": "name", }, { "title": "Task Progress", "field": "progress", "hozAlign": "left", "formatter": "progress", }, { "title": "Gender", "field": "gender", "width": 95, }, { "title": "Rating", "field": "rating", "formatter": "star", "hozAlign": "center", "width": 100, "editor": True, }, {"title": "Color", "field": "col", "width": 130}, { "title": "Date Of Birth", "field": "dob", "width": 130, "sorter": "date", "hozAlign": "center", }, { "title": "Driver", "field": "car", "width": 90, "hozAlign": "center", "formatter": "tickCross", "sorter": "boolean", }, { "title": "Index", "field": "index", "width": 90, "hozAlign": "right", }, ], } # ## DataFrame Example # # We will now develop an advanced example to show that the following is supported # # - Specify an initial `configuration`. # - Provide a `value` as a Pandas DataFrame. # - Edit cell values in the browser. # - Select rows in the browser or in code. # - `stream` (append) to the `value`. # - `patch` (update) the `value`. # - Change the (css) style using the TabulatorStylesheet. # In[8]: class TabulatorDataFrameApp(pn.Column): """Extension Implementation""" tabulator = param.Parameter() reset = param.Action(label="RESET") replace = param.Action(label="REPLACE") stream = param.Action(label="STREAM") patch = param.Action(label="PATCH") avg_rating = param.Number(default=0, constant=True) value_edits = param.Number(default=-1, constant=True) # The _rename dict is used to keep track of Panel parameters to sync to Bokeh properties. # As dope is not a property on the Bokeh model we should set it to None _rename = { **pn.Column._rename, "tabulator": None, "avg_rating": None, "value_edits": None, "reset": None, "replace": None, "stream": None, "patch": None, } def __init__(self, configuration, data: pd.DataFrame, **params): super().__init__(**params) self.data=data self.tabulator = params["tabulator"]=Tabulator( configuration=configuration, value=self.data.copy(deep=True).iloc[0:10,], sizing_mode="stretch_both", background="salmon", ) self.sizing_mode = "stretch_width" self.height = 950 self.rows_count = len(self.data) self.stream_count = 15 self.reset = self._reset_action self.replace = self._replace_action self.stream = self._stream_action self.patch = self._patch_action stylesheet = TabulatorStylesheet(theme="site") actions_pane = pn.Param( self, parameters=["reset", "replace", "stream", "patch", "avg_rating", "value_edits"], name="Actions" ) tabulator_pane = pn.Param(self.tabulator, parameters=["selection"]) self[:] = [ stylesheet, self.tabulator, pn.WidgetBox( stylesheet.param.theme, actions_pane, tabulator_pane, sizing_mode="fixed", width=400 ), ] self._update_avg_rating() self.tabulator.param.watch(self._update_avg_rating, "value") def _reset_action(self, *events): value = self.data.copy(deep=True).iloc[ 0:10, ] self.tabulator.value = value def _replace_action(self, *events): # Please note that it is required that the index is reset # Please also remember to add drop=True. Otherwise stream and patch raises errors value = self.data.copy(deep=True).iloc[ 10:15, ].reset_index(drop=True) self.tabulator.value = value def _stream_action(self, *events): if self.stream_count == len(self.data): self.stream_count = 15 self._reset_action() else: stream_data = self.data.iloc[ self.stream_count : self.stream_count + 1, ] self.tabulator.stream(stream_data) self.stream_count += 1 def _patch_action(self, *events): def _patch(value): value += 10 if value >= 100: return 0 return value data = self.tabulator.value progress = data["progress"] new_progress = progress.map(_patch) self.tabulator.patch(new_progress) def _update_avg_rating(self, *events): with param.edit_constant(self): self.avg_rating = self.tabulator.value["rating"].mean() self.value_edits +=1 def __repr__(self): return f"Tabulator({self.name})" def __str__(self): return f"Tabulator({self.name})" # In[9]: dataframe_app = TabulatorDataFrameApp(configuration=configuration, data=data, sizing_mode="stretch_width") dataframe_app # In[10]: # dataframe_app.show() # In[11]: get_ipython().run_cell_magic('HTML', '', '\n') # If you need a similar example based on a `ColumnDatasource` you can study the `TabulatorDataCDSApp` [here](https://github.com/MarcSkovMadsen/awesome-panel-extensions/blob/master/tests/widgets/test_tabulator/tabulator_examples.py) # ## Know Problems # # The `Tabulator` widget is new (20200816) and aims to support complex behaviours. So it would definitely need to be used and tested in a lot of use cases. # # My understanding is that you can expect it to work for simple use cases. But in complex examples that combines many of the features there are still things that do not work as I would expect. Most times the problem is not the Tabulator widget. It actually worked as expected. The problem was the code using it. # # ## Road Map # # - Get the js import working in the Notebook. # - Fix `WARNING:bokeh.core.validation.check:W-1005 (FIXED_SIZING_MODE): 'fixed' sizing mode requires width and height to be set: Column(id='2430', ...)`. # - Simplify and refactor the implementation. # - Most of the Python code is general and could be moved into a a `BasicDataFrameWidget` that could be used to easily wrap other grid and tabular `js`libraries into the same api. # - Try out many different use cases, learn and improve.