Skip to content

Commit

Permalink
Choose position to freeze tabulator columns. (#6309)
Browse files Browse the repository at this point in the history
Co-authored-by: J01024 <J01024@uniper.energy>
  • Loading branch information
CTPassion and joshua-brumpton-uniper authored Feb 9, 2024
1 parent f1d1d37 commit 723768e
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 16 deletions.
30 changes: 29 additions & 1 deletion examples/reference/widgets/Tabulator.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
"* **`expanded`** (`list`): The currently expanded rows as a list of integer indexes.\n",
"* **`filters`** (`list`): A list of client-side filter definitions that are applied to the table.\n",
"* **`formatters`** (`dict`): A dictionary mapping from column name to a bokeh `CellFormatter` instance or *Tabulator* formatter specification.\n",
"* **`frozen_columns`** (`list`): List of columns to freeze, preventing them from scrolling out of frame. Column can be specified by name or index.\n",
"* **`frozen_columns`** (`list` or `dict`): Defines the frozen columns:\n",
" * `list`\n",
" List of columns to freeze, preventing them from scrolling out of frame. Column can be specified by name or index.\n",
" * `dict`\n",
" Dict of columns to freeze and the position in table (`'left'` or `'right'`) to freeze them in. Column names or index can be used as keys. If value does not match\n",
" `left` or `right` then the default behaviour is to not be frozen at all.\n",
"* **`frozen_rows`**: (`list`): List of rows to freeze, preventing them from scrolling out of frame. Rows can be specified by positive or negative index.\n",
"* **`groupby`** (`list`): Groups rows in the table by one or more columns.\n",
"* **`header_align`** (`dict` or `str`): A mapping from column name to header alignment or a fixed header alignment, which should be one of `'left'`, `'center'`, `'right'`.\n",
Expand Down Expand Up @@ -635,6 +640,29 @@
"pn.widgets.Tabulator(df, frozen_columns=['index'], width=400)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By default, columns given in the list format are frozen to the left hand side of the table. If you want to customize where columns are frozen to on the table, you can specify this with a dictionary:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pn.widgets.Tabulator(df, frozen_columns={'index': 'left', 'float': 'right'}, width=400)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The 'index' column will be frozen on the left side of the table, and the 'float' on the right. Non-frozen columns will scroll between these two."
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
74 changes: 74 additions & 0 deletions panel/tests/ui/widgets/test_tabulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,80 @@ def test_tabulator_frozen_columns(page, df_mixed):
assert int_bb == page.locator('text="int"').bounding_box()


def test_tabulator_frozen_columns_with_positions(page, df_mixed):
widths = 100
width = int(((df_mixed.shape[1] + 1) * widths) / 2)
frozen_cols = {"float": "left", "int": "right"}
widget = Tabulator(df_mixed, frozen_columns=frozen_cols, width=width, widths=widths)

serve_component(page, widget)

expected_text = """
float
index
str
bool
date
datetime
int
3.14
idx0
A
true
2019-01-01
2019-01-01 10:00:00
1
6.28
idx1
B
true
2020-01-01
2020-01-01 12:00:00
2
9.42
idx2
C
true
2020-01-10
2020-01-10 13:00:00
3
-2.45
idx3
D
false
2019-01-10
2020-01-15 13:00:00
4
"""
# Check that the whole table content is on the page, it is not in the
# same order as if the table was displayed without frozen columns
table = page.locator('.pnx-tabulator.tabulator')
expect(table).to_have_text(
expected_text,
use_inner_text=True
)

float_bb = page.locator('text="float"').bounding_box()
int_bb = page.locator('text="int"').bounding_box()
str_bb = page.locator('text="str"').bounding_box()

# Check that the float column is rendered before the int col
assert float_bb['x'] < int_bb['x']

# Check the bool is before int column
assert str_bb['x'] < int_bb['x']

# Scroll to the right, and give it a little extra time
page.locator('text="2019-01-01 10:00:00"').scroll_into_view_if_needed()

# Check that the position of one of the non-frozen columns has indeed moved
wait_until(lambda: page.locator('text="str"').bounding_box()['x'] < str_bb['x'], page)

# Check that the two frozen columns haven't moved after scrolling right
assert float_bb == page.locator('text="float"').bounding_box()
assert int_bb == page.locator('text="int"').bounding_box()


def test_tabulator_frozen_rows(page):
arr = np.array(['a'] * 10)

Expand Down
41 changes: 26 additions & 15 deletions panel/widgets/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1051,9 +1051,12 @@ class Tabulator(BaseTable):
List of client-side filters declared as dictionaries containing
'field', 'type' and 'value' keys.""")

frozen_columns = param.List(default=[], nested_refs=True, doc="""
List indicating the columns to freeze. The column(s) may be
selected by name or index.""")
frozen_columns = param.ClassSelector(class_=(list, dict), default=[], nested_refs=True, doc="""
One of:
- List indicating the columns to freeze. The column(s) may be
selected by name or index.
- Dict indicating columns to freeze as keys and their freeze location
as values, freeze location is either 'right' or 'left'.""")

frozen_rows = param.List(default=[], nested_refs=True, doc="""
List indicating the rows to freeze. If set, the
Expand Down Expand Up @@ -1771,23 +1774,31 @@ def _config_columns(self, column_objs: List[TableColumn]) -> List[Dict[str, Any]
"frozen": True,
"width": 40,
})

ordered = []
for col in self.frozen_columns:
if isinstance(col, int):
ordered.append(column_objs.pop(col))
else:
cols = [c for c in column_objs if c.field == col]
if cols:
ordered.append(cols[0])
column_objs.remove(cols[0])
ordered += column_objs
if isinstance(self.frozen_columns, dict):
left_frozen_columns = [col for col in column_objs if
self.frozen_columns.get(col.field, self.frozen_columns.get(column_objs.index(col))) == "left"]
right_frozen_columns = [col for col in column_objs if
self.frozen_columns.get(col.field, self.frozen_columns.get(column_objs.index(col))) == "right"]
non_frozen_columns = [col for col in column_objs if
col.field not in self.frozen_columns and column_objs.index(col) not in self.frozen_columns]
ordered_columns = left_frozen_columns + non_frozen_columns + right_frozen_columns
else:
ordered_columns = []
for col in self.frozen_columns:
if isinstance(col, int):
ordered_columns.append(column_objs.pop(col))
else:
cols = [c for c in column_objs if c.field == col]
if cols:
ordered_columns.append(cols[0])
column_objs.remove(cols[0])
ordered_columns += column_objs

grouping = {
group: [str(gc) for gc in group_cols]
for group, group_cols in self.groups.items()
}
for i, column in enumerate(ordered):
for i, column in enumerate(ordered_columns):
field = column.field
matching_groups = [
group for group, group_cols in grouping.items()
Expand Down

0 comments on commit 723768e

Please sign in to comment.