diff --git a/examples/reference/widgets/Tabulator.ipynb b/examples/reference/widgets/Tabulator.ipynb index 14c8cb8e37..9ee955ced2 100644 --- a/examples/reference/widgets/Tabulator.ipynb +++ b/examples/reference/widgets/Tabulator.ipynb @@ -150,12 +150,12 @@ "source": [ "The list of valid Bokeh formatters includes:\n", " \n", - "* [BooleanFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets.tables.html#bokeh.models.widgets.tables.BooleanFormatter)\n", - "* [DateFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets.tables.html#bokeh.models.widgets.tables.DateFormatter)\n", - "* [NumberFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets.tables.html#bokeh.models.widgets.tables.NumberFormatter)\n", - "* [HTMLTemplateFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets.tables.html#bokeh.models.widgets.tables.HTMLTemplateFormatter)\n", - "* [StringFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets.tables.html#bokeh.models.widgets.tables.StringFormatter)\n", - "* [ScientificFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets.tables.html#bokeh.models.widgets.tables.ScientificFormatter)\n", + "* [BooleanFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets/tables.html#bokeh.models.BooleanFormatter)\n", + "* [DateFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets/tables.html#bokeh.models.DateFormatter)\n", + "* [NumberFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets/tables.html#bokeh.models.NumberFormatter)\n", + "* [HTMLTemplateFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets/tables.html#bokeh.models.HTMLTemplateFormatter)\n", + "* [StringFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets/tables.html#bokeh.models.StringFormatter)\n", + "* [ScientificFormatter](https://docs.bokeh.org/en/latest/docs/reference/models/widgets/tables.html#bokeh.models.ScientificFormatter)\n", "\n", "However in addition to the formatters exposed by Bokeh it is also possible to provide valid formatters built into the *Tabulator* library. These may be defined either as a string or as a dictionary declaring the `type` and other arguments, which are passed to *Tabulator* as the `formatterParams`:" ] diff --git a/panel/tests/widgets/test_tables.py b/panel/tests/widgets/test_tables.py index ab219500f4..59c2143463 100644 --- a/panel/tests/widgets/test_tables.py +++ b/panel/tests/widgets/test_tables.py @@ -2113,3 +2113,26 @@ def test_tabulator_hidden_columns_fix(): table = Tabulator(pd.DataFrame(), show_index=False) table.hidden_columns = ["a", "b", "c"] assert table.hidden_columns == ["a", "b", "c"] + +@pytest.mark.parametrize('align', [{"x": "right"}, "right"], ids=["dict", "str"]) +def test_bokeh_formatter_with_text_align(align): + # https://github.com/holoviz/panel/issues/5807 + data = pd.DataFrame({"x": [1.1, 2.0, 3.47]}) + formatters = {"x": NumberFormatter(format="0.0")} + assert formatters["x"].text_align == "left" # default + model = Tabulator(data, formatters=formatters, text_align=align) + columns = model._get_column_definitions("x", data) + output = columns[0].formatter.text_align + assert output == "right" + +@pytest.mark.parametrize('align', [{"x": "right"}, "right"], ids=["dict", "str"]) +def test_bokeh_formatter_with_text_align_conflict(align): + # https://github.com/holoviz/panel/issues/5807 + data = pd.DataFrame({"x": [1.1, 2.0, 3.47]}) + formatters = {"x": NumberFormatter(format="0.0", text_align="center")} + model = Tabulator(data, formatters=formatters, text_align=align) + msg = r"The 'text_align' in Tabulator\.formatters\['x'\] is overridden by Tabulator\.text_align" + with pytest.warns(RuntimeWarning, match=msg): + columns = model._get_column_definitions("x", data) + output = columns[0].formatter.text_align + assert output == "right" diff --git a/panel/widgets/tables.py b/panel/widgets/tables.py index 5d242b2906..fd0f0f6719 100644 --- a/panel/widgets/tables.py +++ b/panel/widgets/tables.py @@ -32,6 +32,7 @@ BOKEH_JS_NAT, clone_model, datetime_as_utctimestamp, isdatetime, lazy_load, styler_update, updating, ) +from ..util.warnings import warn from .base import Widget from .button import Button from .input import TextInput @@ -176,34 +177,18 @@ def _get_column_definitions(self, col_names: List[str], df: pd.DataFrame) -> Lis col_kwargs = {} kind = data.dtype.kind editor: CellEditor - formatter: CellFormatter + formatter: CellFormatter | None = self.formatters.get(col) if kind == 'i': - formatter = NumberFormatter(text_align='right') editor = IntEditor() elif kind == 'b': - formatter = StringFormatter(text_align='center') editor = CheckboxEditor() elif kind == 'f': - formatter = NumberFormatter(format='0,0.0[00000]', text_align='right') editor = NumberEditor() elif isdatetime(data) or kind == 'M': - if len(data) and isinstance(data.values[0], dt.date): - date_format = '%Y-%m-%d' - else: - date_format = '%Y-%m-%d %H:%M:%S' - formatter = DateFormatter(format=date_format, text_align='right') editor = DateEditor() else: - formatter = StringFormatter() editor = StringEditor() - if isinstance(self.text_align, str): - formatter.text_align = self.text_align - elif col in self.text_align: - formatter.text_align = self.text_align[col] - elif col in self.indexes: - formatter.text_align = 'left' - if col in self.editors and not isinstance(self.editors[col], (dict, str)): editor = self.editors[col] if isinstance(editor, CellEditor): @@ -212,10 +197,43 @@ def _get_column_definitions(self, col_names: List[str], df: pd.DataFrame) -> Lis if col in indexes or editor is None: editor = CellEditor() - if col in self.formatters and not isinstance(self.formatters[col], (dict, str)): - formatter = self.formatters[col] + if formatter is None or isinstance(formatter, (dict, str)): + if kind == 'i': + formatter = NumberFormatter(text_align='right') + elif kind == 'b': + formatter = StringFormatter(text_align='center') + elif kind == 'f': + formatter = NumberFormatter(format='0,0.0[00000]', text_align='right') + elif isdatetime(data) or kind == 'M': + if len(data) and isinstance(data.values[0], dt.date): + date_format = '%Y-%m-%d' + else: + date_format = '%Y-%m-%d %H:%M:%S' + formatter = DateFormatter(format=date_format, text_align='right') + else: + formatter = StringFormatter() + + default_text_align = True + else: if isinstance(formatter, CellFormatter): formatter = clone_model(formatter) + if hasattr(formatter, 'text_align'): + default_text_align = type(formatter).text_align.class_default(formatter) == formatter.text_align + else: + default_text_align = True + + if isinstance(self.text_align, str): + formatter.text_align = self.text_align + if not default_text_align: + msg = f"The 'text_align' in Tabulator.formatters[{col!r}] is overridden by Tabulator.text_align" + warn(msg, RuntimeWarning) + elif col in self.text_align: + formatter.text_align = self.text_align[col] + if not default_text_align: + msg = f"The 'text_align' in Tabulator.formatters[{col!r}] is overridden by Tabulator.text_align[{col!r}]" + warn(msg, RuntimeWarning) + elif col in self.indexes: + formatter.text_align = 'left' if isinstance(self.widths, int): col_kwargs['width'] = self.widths