from awesome_panel_extensions.awesome_panel import notebook
notebook.Header(folder="examples/reference/widgets", notebook="Tabulator.ipynb")
The Tabulator widget wraps the awesome datagrid from tabulator.info.
Please take a look at their website to see the potential. They have tons of good examples that now can be used in Panel.
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 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.
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
.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.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
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 for more information
%%html
<script>
requirejs.config(
{paths: { 'tabulator': ['https://unpkg.com/tabulator-tables@4.7.2/dist/js/tabulator.min']},}
);
if(!window.Tabulator) {
require(['tabulator'],function(tabulator) {window.Tabulator=tabulator;});
}
</script>
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
# basic_app.show()
In order to provide more advanced examples we need to download some data and define the configuration of the Tabulator widget.
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)
id | name | progress | gender | rating | col | dob | car | lucky_no | activity | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Oli Bob | 12 | male | 1 | red | 19/02/1984 | 1 | 5 | [1, 20, 5, 3, 10, 13, 17, 15, 9, 11, 10, 12, 1... |
1 | 2 | Mary May | 1 | female | 2 | blue | 14/05/1982 | True | 10 | [10, 12, 14, 16, 13, 9, 7, 11, 10, 13, 1, 2, 5... |
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",
},
],
}
We will now develop an advanced example to show that the following is supported
configuration
.value
as a Pandas DataFrame.stream
(append) to the value
.patch
(update) the value
.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})"
dataframe_app = TabulatorDataFrameApp(configuration=configuration, data=data, sizing_mode="stretch_width")
dataframe_app
# dataframe_app.show()
%%HTML
<video style="max-width:100%;max-height:600px;" autoplay controls>
<source src="https://raw.githubusercontent.com/MarcSkovMadsen/awesome-panel-extensions/master/assets/videos/Tabulator.mp4" type="video/mp4" controls>
You browser does not support video
</video>
If you need a similar example based on a ColumnDatasource
you can study the TabulatorDataCDSApp
here
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.
WARNING:bokeh.core.validation.check:W-1005 (FIXED_SIZING_MODE): 'fixed' sizing mode requires width and height to be set: Column(id='2430', ...)
.BasicDataFrameWidget
that could be used to easily wrap other grid and tabular js
libraries into the same api.