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

Fix: cols_width() should exclude hidden columns #509

Merged
merged 14 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 25 additions & 1 deletion great_tables/_gt_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,16 @@ def defaulted_align(self) -> str:


class Boxhead(_Sequence[ColInfo]):
"""Map columns of the input table to their final rendered placement in the boxhead.

The boxhead is the part of the table that contains the column labels and the stub head. This
class is responsible for the following:

- rendered boxhead: column order, labels, alignment, and visibility
- rendered body: alignment of data values for each column
- rendered stub: this class records which input column is used for the stub
"""

_d: list[ColInfo]

def __new__(
Expand Down Expand Up @@ -396,6 +406,20 @@ def reorder(self, vars: list[str]) -> Self:

return self[new_order]

def final_columns(self, options: Options) -> list[ColInfo]:

row_group_info = self._get_row_group_column()
row_group_column = (
[row_group_info] if row_group_info and options.row_group_as_column.value else []
)

stub_info = self._get_stub_column()
stub_column = [stub_info] if stub_info else []

default_columns = self._get_default_columns()

return [*row_group_column, *stub_column, *default_columns]

# Get a list of columns
def _get_columns(self) -> list[str]:
return [x.var for x in self._d]
Expand Down Expand Up @@ -428,7 +452,7 @@ def _set_column_aligns(self, columns: list[str], align: str) -> Self:

return self.__class__(out_cols)

# Get a list of column widths
# Get a list of all column widths
def _get_column_widths(self) -> list[str | None]:
return [x.column_width for x in self._d]

Expand Down
9 changes: 5 additions & 4 deletions great_tables/_utils_render_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,10 +717,11 @@ def _get_table_defs(data: GTData) -> dict[str, Any]:
# Get the table's width (which or may not have been set)
table_width = data._options.table_width.value

# Get all the widths for the columns as a list where None values mean that the width is
# not set for that column
# TODO: ensure that the stub column is set first in the list
widths = data._boxhead._get_column_widths()
# Get finalized names of the columns (this includes the stub column)
final_columns = data._boxhead.final_columns(options=data._options)

# Get the widths of the columns
widths = [col.column_width for col in final_columns]

# If all of the widths are defined as px values for all columns, then ensure that the width
# values are strictly respected as absolute width values (even if table width already set)
Expand Down
70 changes: 70 additions & 0 deletions tests/test_boxhead.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,73 @@ def test_cols_label_units_text():
assert isinstance(x[0], UnitStr)
assert isinstance(x[1], UnitStr)
assert x[2] == "Zee"


def test_final_columns_stub_move_to_begining():
df = pd.DataFrame({"w": [1], "x": [1], "y": [2], "z": [3]})
gt = GT(df, rowname_col="y")

options = gt._options
final_columns = gt._boxhead.final_columns(options=options)
all_columns = [col.var for col in final_columns]

assert all_columns == ["y", "w", "x", "z"]


def test_final_columns_hidden_columns_removed():
df = pd.DataFrame({"w": [1], "x": [1], "y": [2], "z": [3]})
gt = GT(df).cols_hide(columns=["w", "y"])

options = gt._options
final_columns = gt._boxhead.final_columns(options=options)
all_columns = [col.var for col in final_columns]

assert all_columns == ["x", "z"]


def test_final_columns_row_and_group_cols_handled():
df = pd.DataFrame({"w": [1], "x": [1], "y": [2], "z": [3]})

gt_1 = GT(df, rowname_col="y", groupname_col="x").tab_options(row_group_as_column=True)

options = gt_1._options
final_columns = gt_1._boxhead.final_columns(options=options)
all_columns = [col.var for col in final_columns]

assert all_columns == ["x", "y", "w", "z"]

gt_2 = GT(df, rowname_col="y", groupname_col="x").tab_options(row_group_as_column=False)

options = gt_2._options
final_columns = gt_2._boxhead.final_columns(options=options)
all_columns = [col.var for col in final_columns]

assert all_columns == ["y", "w", "z"]


def test_final_columns_hidden_and_row_group_cols_handled():
df = pd.DataFrame({"w": [1], "x": [1], "y": [2], "z": [3]})

gt_1 = (
GT(df, rowname_col="y", groupname_col="x")
.tab_options(row_group_as_column=True)
.cols_hide(columns=["w"])
)

options = gt_1._options
final_columns = gt_1._boxhead.final_columns(options=options)
all_columns = [col.var for col in final_columns]

assert all_columns == ["x", "y", "z"]

gt_2 = (
GT(df, rowname_col="y", groupname_col="x")
.tab_options(row_group_as_column=False)
.cols_hide(columns=["w"])
)

options = gt_2._options
final_columns = gt_2._boxhead.final_columns(options=options)
all_columns = [col.var for col in final_columns]

assert all_columns == ["y", "z"]
58 changes: 58 additions & 0 deletions tests/test_spanners.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
spanners_print_matrix,
tab_spanner,
)
from great_tables._utils_render_html import _get_table_defs


@pytest.fixture
Expand Down Expand Up @@ -243,6 +244,63 @@ def test_cols_width_fully_set_pct_2():
assert gt_tbl._boxhead[2].column_width == "40%"


def test_cols_width_html_colgroup():
df = pd.DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6]})
gt_tbl = GT(df).cols_width({"a": "10px", "b": "20px", "c": "30px"})

tbl_built = gt_tbl._build_data(context="html")
table_defs = _get_table_defs(tbl_built)

assert (
str(table_defs["table_colgroups"])
== '<colgroup>\n <col style="width:10px;"/>\n <col style="width:20px;"/>\n <col style="width:30px;"/>\n</colgroup>'
)


def test_cols_width_html_colgroup_hidden():
df = pd.DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6]})
gt_tbl = GT(df).cols_width({"a": "10px", "b": "20px", "c": "30px"}).cols_hide(columns="b")

tbl_built = gt_tbl._build_data(context="html")
table_defs = _get_table_defs(tbl_built)

assert (
str(table_defs["table_colgroups"])
== '<colgroup>\n <col style="width:10px;"/>\n <col style="width:30px;"/>\n</colgroup>'
)


def test_cols_width_html_colgroup_stub():
df = pd.DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6]})
gt_tbl = GT(df, rowname_col="b").cols_width({"a": "10px", "b": "20px", "c": "30px"})

tbl_built = gt_tbl._build_data(context="html")
table_defs = _get_table_defs(tbl_built)

assert (
str(table_defs["table_colgroups"])
== '<colgroup>\n <col style="width:20px;"/>\n <col style="width:10px;"/>\n <col style="width:30px;"/>\n</colgroup>'
)


def test_cols_width_html_colgroup_complex():
df = pd.DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6], "d": [7, 8], "e": [9, 10]})
gt_tbl = (
GT(df, rowname_col="c")
.cols_move_to_start(columns="d")
.cols_hide(columns="a")
.cols_width({"a": "10px", "b": "20px", "c": "30px", "d": "40px", "e": "50px"})
)

tbl_built = gt_tbl._build_data(context="html")
table_defs = _get_table_defs(tbl_built)

assert (
str(table_defs["table_colgroups"])
== '<colgroup>\n <col style="width:30px;"/>\n <col style="width:40px;"/>\n <col style="width:20px;"/>\n <col style="width:50px;"/>\n</colgroup>'
)


@pytest.mark.parametrize("DF, columns", [(pd.DataFrame, "a"), (pl.DataFrame, cs.starts_with("a"))])
def test_cols_move_single_col(DF, columns):
df = DF({"a": [1, 2], "b": [3, 4], "c": [5, 6], "d": [7, 8]})
Expand Down
Loading