textual/docs/examples/widgets/data_table_sort.py
Josh Duncan 4f95d30619
DataTable sort by function (or other callable) (#3090)
* DataTable sort by function (or other callable)

The `DataTable` widget now takes the `by` argument instead of `columns`, allowing the table to also be sorted using a custom function (or other callable). This is a breaking change since it requires all calls to the `sort` method to include an iterable of key(s) (or a singular function/callable). Covers #2261 using [suggested function signature](https://github.com/Textualize/textual/pull/2512#issuecomment-1580277771) from @darrenburns on PR #2512.

* argument change and functionaloty update

Changed back to orinal `columns` argument and added a new `key` argument
which takes a function (or other callable). This allows the PR to NOT BE
a breaking change.

* better example for docs

- Updated the example file for the docs to better show the functionality
of the change (especially when using `columns` and `key` together).
- Added one new tests to cover a similar situation to the example
  changes

* removed unecessary code from example

- the sort by clicked column function was bloat in my opinion

* requested changes

* simplify method and terminology

* combine key_wrapper and default sort

* Removing some tests from DataTable.sort as duplicates. Ensure there is test coverage of the case where a key, but no columns, is passed to DataTable.sort.

* Remove unused import

* Fix merge issues in CHANGELOG, update DataTable sort-by-key changelog PR link

---------

Co-authored-by: Darren Burns <darrenburns@users.noreply.github.com>
Co-authored-by: Darren Burns <darrenb900@gmail.com>
2023-10-31 13:14:47 +00:00

92 lines
3.2 KiB
Python

from rich.text import Text
from textual.app import App, ComposeResult
from textual.widgets import DataTable, Footer
ROWS = [
("lane", "swimmer", "country", "time 1", "time 2"),
(4, "Joseph Schooling", Text("Singapore", style="italic"), 50.39, 51.84),
(2, "Michael Phelps", Text("United States", style="italic"), 50.39, 51.84),
(5, "Chad le Clos", Text("South Africa", style="italic"), 51.14, 51.73),
(6, "László Cseh", Text("Hungary", style="italic"), 51.14, 51.58),
(3, "Li Zhuhao", Text("China", style="italic"), 51.26, 51.26),
(8, "Mehdy Metella", Text("France", style="italic"), 51.58, 52.15),
(7, "Tom Shields", Text("United States", style="italic"), 51.73, 51.12),
(1, "Aleksandr Sadovnikov", Text("Russia", style="italic"), 51.84, 50.85),
(10, "Darren Burns", Text("Scotland", style="italic"), 51.84, 51.55),
]
class TableApp(App):
BINDINGS = [
("a", "sort_by_average_time", "Sort By Average Time"),
("n", "sort_by_last_name", "Sort By Last Name"),
("c", "sort_by_country", "Sort By Country"),
("d", "sort_by_columns", "Sort By Columns (Only)"),
]
current_sorts: set = set()
def compose(self) -> ComposeResult:
yield DataTable()
yield Footer()
def on_mount(self) -> None:
table = self.query_one(DataTable)
for col in ROWS[0]:
table.add_column(col, key=col)
table.add_rows(ROWS[1:])
def sort_reverse(self, sort_type: str):
"""Determine if `sort_type` is ascending or descending."""
reverse = sort_type in self.current_sorts
if reverse:
self.current_sorts.remove(sort_type)
else:
self.current_sorts.add(sort_type)
return reverse
def action_sort_by_average_time(self) -> None:
"""Sort DataTable by average of times (via a function) and
passing of column data through positional arguments."""
def sort_by_average_time_then_last_name(row_data):
name, *scores = row_data
return (sum(scores) / len(scores), name.split()[-1])
table = self.query_one(DataTable)
table.sort(
"swimmer",
"time 1",
"time 2",
key=sort_by_average_time_then_last_name,
reverse=self.sort_reverse("time"),
)
def action_sort_by_last_name(self) -> None:
"""Sort DataTable by last name of swimmer (via a lambda)."""
table = self.query_one(DataTable)
table.sort(
"swimmer",
key=lambda swimmer: swimmer.split()[-1],
reverse=self.sort_reverse("swimmer"),
)
def action_sort_by_country(self) -> None:
"""Sort DataTable by country which is a `Rich.Text` object."""
table = self.query_one(DataTable)
table.sort(
"country",
key=lambda country: country.plain,
reverse=self.sort_reverse("country"),
)
def action_sort_by_columns(self) -> None:
"""Sort DataTable without a key."""
table = self.query_one(DataTable)
table.sort("swimmer", "lane", reverse=self.sort_reverse("columns"))
app = TableApp()
if __name__ == "__main__":
app.run()