Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying default sort columns for tables #1667

Merged
merged 15 commits into from
Nov 21, 2023
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

- Fix the `"square": True` flag to scatter plot to actually make the plot square ([#2189](https://github.com/ewels/MultiQC/pull/2189))
- Config `table_columns_visible` and `table_columns_name`: support flat config and `table_id` as a group ([#2191](https://github.com/ewels/MultiQC/pull/2191))
- Upgrade the jQuery tablesorter plugin to v2
- Upgrade the jQuery tablesorter plugin to v2 ([#1666](https://github.com/ewels/MultiQC/pull/1666))
- Allow specifying default sort columns for tables with `defaultsort` ([#1667](https://github.com/ewels/MultiQC/pull/1667))

### New Modules

Expand Down
22 changes: 22 additions & 0 deletions docs/core/development/plots.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,28 @@ headers[tablecol] = {
}
```

### Specifying sorting of columns

By default, each table is sorted by sample name alphabetically. You can override the
sorting order using the `defaultsort` option. Here is an example:

```yaml
custom_plot_config:
general_stats_table:
defaultsort:
- column: "Mean Insert Length"
direction: asc
- column: "Starting Amount (ng)"
quast_table:
defaultsort:
- column: "Largest contig"
```

In this case, the general stats table will be sorted by "Mean Insert Length" first,
in ascending order, then by "Starting Amount (ng)", in descending (default) order. The
table with the ID `quast_table` (which you can find by clicking the "Configure Columns"
button above the table in the report) will be sorted by "Largest contig".

## Beeswarm plots (dot plots)

Beeswarm plots work from the exact same data structure as tables, so the
Expand Down
53 changes: 51 additions & 2 deletions multiqc/plots/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,8 @@ def make_table(dt: table_object.DataTable):
html += """
<div id="{tid}_container" class="mqc_table_container">
<div class="table-responsive mqc-table-responsive {cc}">
<table id="{tid}" class="table table-condensed mqc_table" data-title="{title}">
""".format(tid=table_id, title=table_title, cc=collapse_class)
<table id="{tid}" class="table table-condensed mqc_table" data-title="{title}" data-sortlist="{sortlist}">
""".format(tid=table_id, title=table_title, cc=collapse_class, sortlist=_get_sortlist(dt))

# Build the header row
col1_header = dt.pconfig.get("col1_header", "Sample Name")
Expand Down Expand Up @@ -425,3 +425,52 @@ def make_table(dt: table_object.DataTable):
report.saved_raw_data[fn] = dt.raw_vals

return html


def _get_sortlist(dt: table_object.DataTable) -> str:
"""
Custom column sorting order for a table plot. The order is provided in the following form:

```yaml
custom_plot_config:
general_stats_table:
defaultsort:
- column: "Mean Insert Length"
direction: asc
- column: "Starting Amount (ng)"
quast_table:
defaultsort:
- column: "Largest contig"
```

It is returned in a form os a list literal, as expected by the jQuery tablesorter plugin.
"""
defaultsort = dt.pconfig.get("defaultsort")
if defaultsort is None:
return ""

headers = dt.get_headers_in_order()
sortlist = []

# defaultsort is a list of {column, direction} objects
for d in defaultsort:
try:
# The first element of the triple is not actually unique, it's a bucket index,
# so we must re-enumerate ourselves here
idx = next(
idx
for idx, (_, k, header) in enumerate(headers)
if d["column"].lower() in [k.lower(), header["title"].lower()]
)
except StopIteration:
logger.warning(
"Tried to sort by column '%s', but column was not found. Available columns: %s",
d["column"],
[k for (_, k, _) in headers],
)
return ""
idx += 1 # to account for col1_header
direction = 0 if d.get("direction", "").startswith("asc") else 1
sortlist.append([idx, direction])

return str(sortlist)
12 changes: 7 additions & 5 deletions multiqc/plots/table_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import logging
import re
from collections import defaultdict
from typing import List, Tuple, Dict

from multiqc.utils import config, report

Expand Down Expand Up @@ -292,13 +293,14 @@ def __init__(self, data, headers=None, pconfig=None):
self.headers = headers
self.pconfig = pconfig

def get_headers_in_order(self):
"""Gets the headers in the order they want to be displayed.
Returns a list of triplets: (idx, key, header_info)
def get_headers_in_order(self) -> List[Tuple[int, str, Dict]]:
"""
Gets the headers in the order they want to be displayed.
Returns a list of triplets: (bucket_idx, key, header_info)
"""
res = list()
# Scan through self.headers_in_order and just bolt on the actual header info
for bucket in sorted(self.headers_in_order):
for idx, k in self.headers_in_order[bucket]:
res.append((idx, k, self.headers[idx][k]))
for bucket_idx, k in self.headers_in_order[bucket]:
res.append((bucket_idx, k, self.headers[bucket_idx][k]))
return res