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

Add table preview to data cards #97

Merged
merged 34 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
72bbdec
Add ideas for table preview
hagenw Jul 22, 2024
c74905f
Use solution with fixed grid
hagenw Jul 22, 2024
111bb66
Add first working CSS table solution
hagenw Jul 22, 2024
fd41bfd
Extract header from table object
hagenw Jul 23, 2024
f55da7d
Include CSS and JS file in sphinx extension
hagenw Jul 23, 2024
dbd26e9
Add ideas how to include table preview
hagenw Jul 23, 2024
71e0c3c
Continue work
hagenw Jul 23, 2024
9ecfc4f
Do not include table preview as text
hagenw Jul 23, 2024
71751ae
Fix border of tables
hagenw Jul 23, 2024
8bc3ac2
Add more comments
hagenw Jul 23, 2024
7cedfc4
Fix CSS
hagenw Jul 23, 2024
6877a79
Fix padding
hagenw Jul 23, 2024
c69bcc6
Add fix for vertical alignment
hagenw Jul 23, 2024
aa9c635
Remove first table
hagenw Jul 23, 2024
040fab3
Reenable sphinx-audeering-theme
hagenw Jul 23, 2024
9bb9e77
Add more CSS fixes
hagenw Jul 23, 2024
91d8310
Enforce normal font size in nested table header
hagenw Jul 24, 2024
5c0f31a
Cleanup CSS file
hagenw Jul 24, 2024
245c8cc
Finetune borders
hagenw Jul 24, 2024
6af8e13
Improve docstring
hagenw Jul 24, 2024
f9ee635
Mark clicked row
hagenw Jul 24, 2024
9e87791
Call nested table preview
hagenw Jul 24, 2024
04d5faa
Adjust expected test results
hagenw Jul 24, 2024
8c907a2
Add doctest test
hagenw Jul 24, 2024
19be244
Try to fix doctests
hagenw Jul 24, 2024
aee6e9f
Cleanup template
hagenw Jul 24, 2024
d31030a
Fix tests
hagenw Jul 24, 2024
a0155ba
Ensure text in table preview is valid
hagenw Jul 25, 2024
c89d5d9
Extend docstring of utils.parse_text()
hagenw Jul 25, 2024
200df0c
Extend docstring of CSS and JS file
hagenw Jul 25, 2024
bbdfcf4
Add docstring to new test
hagenw Jul 25, 2024
1ee1aaa
Manage tables_preview with load_tables
hagenw Jul 25, 2024
bb3820b
Be more explicit in NA handling
hagenw Jul 25, 2024
7b4aaaf
Make parse_text private static method
hagenw Jul 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 85 additions & 5 deletions audbcards/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import inspect
import os
import pickle
import re
import typing

import jinja2
Expand All @@ -13,13 +14,16 @@
import audeer
import audformat

from audbcards.core import utils
from audbcards.core.config import config
from audbcards.core.utils import format_schemes
from audbcards.core.utils import limit_presented_samples


class _Dataset:
_table_related_cached_properties = ["segment_durations", "segments"]
_table_related_cached_properties = [
"segment_durations",
"segments",
"tables_preview",
]
"""Cached properties relying on table data.

Most of the cached properties
Expand Down Expand Up @@ -163,6 +167,33 @@ def _load_pickled(path: str):
with open(path, "rb") as f:
return pickle.load(f)

@staticmethod
def _parse_text(text: str) -> str:
"""Remove unsupported characters and restrict length.

The text is stripped from HTML tags or newlines,
and limited to a maximum length of 100 characters.

Args:
text: input text

Returns:
parsed text

"""
# Missing text
if pd.isna(text):
return ""
# Remove newlines
text = text.replace("\n", "\\n")
# Remove HTML tags
text = re.sub("<[^<]+?>", "", text)
# Limit length
max_characters_per_entry = 100
if len(text) > max_characters_per_entry:
text = text[: max_characters_per_entry - 3] + "..."
return text

@staticmethod
def _save_pickled(obj, path: str):
"""Save object instance to path as pickle."""
Expand Down Expand Up @@ -413,7 +444,7 @@ def schemes_summary(self) -> str:
e.g. ``'speaker: [age, gender, language]'``.

"""
return format_schemes(self.header.schemes)
return utils.format_schemes(self.header.schemes)

@functools.cached_property
def schemes_table(self) -> typing.List[typing.List[str]]:
Expand Down Expand Up @@ -479,6 +510,51 @@ def tables(self) -> typing.List[str]:
tables = list(db)
return tables

@functools.cached_property
def tables_preview(self) -> typing.Dict[str, typing.List[typing.List[str]]]:
"""Table preview for each table of the dataset.

Shows the header
and the first 5 lines for each table
as a list of lists.
All table values are converted to strings,
stripped from HTML tags or newlines,
and limited to a maximum length of 100 characters.

Returns:
dictionary with table IDs as keys
and table previews as values

Examples:
>>> from tabulate import tabulate
>>> ds = Dataset("emodb", "1.4.1")
>>> preview = ds.tables_preview["speaker"]
>>> print(tabulate(preview, headers="firstrow", tablefmt="github"))
| speaker | age | gender | language |
|-----------|-------|----------|------------|
| 3 | 31 | male | deu |
| 8 | 34 | female | deu |
| 9 | 21 | female | deu |
| 10 | 32 | male | deu |
| 11 | 26 | male | deu |

"""
preview = {}
for table in list(self.header):
df = audb.load_table(
self.name,
table,
version=self.version,
verbose=False,
)
df = df.reset_index()
header = [df.columns.tolist()]
body = df.head(5).astype("string").values.tolist()
# Remove unwanted chars and limit length of each entry
body = [[self._parse_text(column) for column in row] for row in body]
preview[table] = header + body
return preview

@functools.cached_property
def tables_table(self) -> typing.List[str]:
"""Tables of the dataset."""
Expand Down Expand Up @@ -623,7 +699,7 @@ def _scheme_to_list(self, scheme_id):
label[:-1] + r"\_" if label.endswith("_") else label
for label in labels
]
labels = limit_presented_samples(
labels = utils.limit_presented_samples(
labels,
15,
replacement_text="[...]",
Expand Down Expand Up @@ -776,6 +852,10 @@ def _load_pickled(path: str):
ds = _Dataset._load_pickled(path)
return ds

@staticmethod
def _parse_text(text: str) -> str:
return _Dataset._parse_text(text)

@staticmethod
def _save_pickled(obj, path: str):
"""Save object instance to path as pickle."""
Expand Down
57 changes: 51 additions & 6 deletions audbcards/core/templates/datacard_tables.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,56 @@
Tables
^^^^^^

.. csv-table::
:header-rows: 1
:widths: 20, 10, 70
.. raw:: html

<table class="clickable docutils align-default">
{# Table header is given by first row #}
<thead>
<tr class="row-odd grid header">
{% for column in tables_table[0] %}
<th class="head"><p>{{ column }}</p></th>
{% endfor %}
</tr>
</thead>
{# Table body by remaining rows #}
<tbody>
{% for row in tables_table %}
{% if not loop.first %}
<tr onClick="toggleRow(this)" class="row-{{ loop.cycle('odd', 'even') }} clickable grid">
{% for column in row %}
<td><p>{{ column }}</p></td>
{% endfor %}
<td class="expanded-row-content hide-row">

{##### START TABLE PREVIEW #####}

<table class="docutils field-list align-default preview">
<thead>
<tr>
{% for column in tables_preview[row[0]][0] %}
<th class="head"><p>{{ column }}</p></th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in tables_preview[row[0]] %}
{% if not loop.first %}
<tr>
{% for column in row %}
<td><p>{{ column }}</p></td>
{% endfor %}
{% endif %}
{% endfor %}
</tbody>
</table>

{##### END TABLE PREVIEW #####}

</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>

{% for row in tables_table %}
"{{ row|join('", "') }}"
{% endfor %}
{% endif %}
6 changes: 6 additions & 0 deletions audbcards/sphinx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@


__version__ = "0.1.0"
table_preview_css_file = audeer.path(audeer.script_dir(), "table-preview.css")
table_preview_js_file = audeer.path(audeer.script_dir(), "table-preview.js")


# ===== MAIN FUNCTION SPHINX EXTENSION ====================================
Expand Down Expand Up @@ -54,6 +56,10 @@ def builder_inited(app: sphinx.application.Sphinx):
# Read config values
sections = app.config.audbcards_datasets

# Add CSS and JS files for table preview feature
app.add_css_file(table_preview_css_file)
app.add_js_file(table_preview_js_file)

# Gather and build data cards for each requested section
for path, header, repositories, example in sections:
# Clear existing data cards
Expand Down
82 changes: 82 additions & 0 deletions audbcards/sphinx/table-preview.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* Expand rows in Tables table to show preview of each tables content.
/*
/* Implementation based on https://github.com/chhikaradi1993/Expandable-table-row
*/
.expanded-row-content {
font-size: 13px;
/* Add scroll bar if table preview is too big */
overflow: auto !important;
/* Let column appear as additional row in next line */
display: grid;
grid-column: 1/-1;
justify-content: flex-start;
border-left: none;
}
.hide-row {
display: None;
}
table.clickable {
/* Ensure we don't get double border lines */
border-bottom: none;
border-right: none;
/* Force to use full width */
width: 100%;
}
table.clickable td,
table.clickable th {
/* Ensure we don't get double border lines */
border-left: none;
border-top: none;
}
table.preview td {
/* Remove all borders inside preview table cells */
border-left: none;
border-top: none;
border-bottom: none;
}
table.clickable td:not(.expanded-row-content),
table.clickable th {
/* Allow to center cell copntent with `margin: auto` */
display: flex;
}
table.clickable td:not(.expanded-row-content) p,
table.clickable th p {
/* Verrtically center cell content */
margin: auto 0;
}
table.clickable td:not(.expanded-row-content) p:last-child,
table.clickable th p:last-child {
/* Verrtically center cell content for ReadTheDocs based themes*/
margin: auto 0 !important;
}
table.clickable td.expanded-row-content td,
table.clickable td.expanded-row-content th {
display: table-cell;
}
table.clickable tr.grid {
/* Fixed grid of 3 columns */
display: grid;
grid-template-columns: repeat(1, 1.1fr) 15% repeat(1, 1fr);
}
table.clickable tr.clickable {
/* Show pointer as cursor to highlight the row can be clicked */
cursor: pointer;
/* Overflow of table preview column */
justify-content: flex-start;
}
table.clickable tr.clicked td:not(.expanded-row-content) {
/* Remove bottom border on clicked row when preview is shown */
border-bottom: none;
}
table.preview {
/* Padding around table preview */
padding: 10px;
}
table.preview td {
/* Ensure minimal distance between columns */
padding-right: 0.3em;
}
table.preview th {
/* Use normal font in header row of preview table */
font-weight: normal !important;
}
17 changes: 17 additions & 0 deletions audbcards/sphinx/table-preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Expand rows in Tables table to show preview of each tables content.
//
// Implementation based on https://github.com/chhikaradi1993/Expandable-table-row
//
const toggleRow = (row) => {
// Toggle visibility of table preview
row.getElementsByClassName('expanded-row-content')[0].classList.toggle('hide-row');
// Toggle clicked attribute on clicked table row.
// This can be used to adjust appearance of clicked table,
// e.g. remove bottom border
if (row.className.indexOf("clicked") === -1) {
row.classList.add("clicked");
} else {
row.classList.remove("clicked");
}
console.log(event);
}
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ classifiers = [
requires-python = '>=3.9'
dependencies = [
'audb >=1.7.0',
'audeer >=2.2.0',
'audplot >=1.4.6',
'jinja2',
'pandas >=2.1.0',
Expand Down Expand Up @@ -70,6 +71,10 @@ skip = './audbcards.egg-info,./build'
[tool.pytest.ini_options]
cache_dir = '.cache/pytest'
xfail_strict = true
addopts = '''
--doctest-modules
--ignore=audbcards/sphinx/
'''


# ----- ruff --------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
audb >=1.6.5 # for audb.Dependencies.__eq__()
audeer >=1.21.0
pytest
tabulate
Loading