Skip to content

Commit

Permalink
feat: add style.from_column, implement for loc.body
Browse files Browse the repository at this point in the history
  • Loading branch information
machow committed Dec 13, 2023
1 parent 0d1fe5d commit 672085f
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 6 deletions.
1 change: 1 addition & 0 deletions docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ quartodoc:
- style.fill
- style.text
- style.borders
- style.from_column
- title: Helper functions
desc: >
An assortment of helper functions is available in the **Great Tables** package. The `md()`
Expand Down
5 changes: 3 additions & 2 deletions great_tables/_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,13 @@ def _(loc: LocTitle, data: GTData, style: List[CellStyle]) -> GTData:

@set_style.register
def _(loc: LocBody, data: GTData, style: List[CellStyle]) -> GTData:
positions = resolve(loc, data)
positions: List[CellPos] = resolve(loc, data)

all_info: list[StyleInfo] = []
for col_pos in positions:
row_styles = [entry._from_row(data._tbl_data, col_pos.row) for entry in style]
crnt_info = StyleInfo(
locname="data", locnum=5, colname=col_pos.colname, rownum=col_pos.row, styles=style
locname="data", locnum=5, colname=col_pos.colname, rownum=col_pos.row, styles=row_styles
)
all_info.append(crnt_info)

Expand Down
59 changes: 56 additions & 3 deletions great_tables/_styles.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,74 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Literal, List
from dataclasses import dataclass, fields, replace
from typing import Any, Callable, Literal, List
from typing_extensions import Self

from ._tbl_data import TblData, _get_cell


# Cell Styles ==========================================================================
# TODO: stubbed out the styles in helpers.R as dataclasses while I was reading it,
# but have no worked on any runtime validation, etc..


@dataclass
class FromColumn:
"""Specify that a style value should be fetched from a column in the data.
Examples
--------
```{python}
import pandas as pd
from great_tables import GT, exibble, loc, style
df = pd.DataFrame({"x": [1, 2], "color": ["red", "blue"]})
gt = GT(df)
gt.tab_style(
style = style.text(color = style.from_column("color")),
locations = loc.body(columns = ["x"])
)
```
"""

column: str
# TODO: na_value currently unused
na_value: Any | None = None
fn: Callable[[Any], Any] | None = None


# TODO: what goes into CellStyle?
@dataclass
class CellStyle:
"""A style specification."""

def _to_html_style(self) -> str:
raise NotImplementedError

def _from_row(self, data: TblData, row: int) -> Self:
"""Return a new object with FromColumn replaced with values from row.
Note that if no FromColumn fields are present, this returns the original object.
"""

new_fields: dict[str, Any] = {}
for field in fields(self):
attr = getattr(self, field.name)
if isinstance(attr, FromColumn):
# TODO: could validate that the value fetched from data is allowed.
# e.g. that color is a string, etc..
val = _get_cell(data, row, attr.column)

new_fields[field.name] = attr.fn(val) if attr.fn is not None else val

if not new_fields:
return self

return replace(self, **new_fields)


@dataclass
class CellStyleText(CellStyle):
Expand Down Expand Up @@ -75,7 +128,7 @@ class CellStyleText(CellStyle):
properties.
"""

color: str | None = None
color: str | FromColumn | None = None
font: str | None = None
size: str | None = None
align: Literal["center", "left", "right", "justify"] | None = None
Expand Down
3 changes: 2 additions & 1 deletion great_tables/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
CellStyleText as text,
CellStyleFill as fill,
CellStyleBorders as borders,
FromColumn as from_column,
)

__all__ = ("text", "fill", "borders")
__all__ = ("text", "fill", "borders", "from_column")
20 changes: 20 additions & 0 deletions tests/test_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
resolve_vector_i,
resolve_cols_i,
resolve_rows_i,
set_style,
)
from great_tables._styles import CellStyleText, FromColumn
from great_tables._gt_data import Spanners, SpannerInfo
from great_tables import GT

Expand Down Expand Up @@ -84,3 +86,21 @@ def test_resolve_column_spanners_error_missing():

with pytest.raises(ValueError):
resolve(loc, spanners)


def test_set_style_loc_body_from_column():
df = pd.DataFrame({"x": [1, 2], "color": ["red", "blue"]})
gt_df = GT(df)
loc = LocBody(["x"], [1])
style = CellStyleText(color=FromColumn("color"))

new_gt = set_style(loc, gt_df, [style])

# 1 style info added
assert len(new_gt._styles) == 1
cell_info = new_gt._styles[0]

# style info has single cell style, with new color
assert len(cell_info.styles) == 1
assert isinstance(cell_info.styles[0], CellStyleText)
assert cell_info.styles[0].color == "blue"
26 changes: 26 additions & 0 deletions tests/test_styles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import pandas as pd

from great_tables._styles import FromColumn, CellStyleText


def test_from_column_replace():
"""FromColumn is replaced by the specified column's value in a row of data"""

df = pd.DataFrame({"x": [1, 2], "color": ["red", "blue"]})
from_col = FromColumn("color")

style = CellStyleText(color=from_col)
new_style = style._from_row(df, 0)

assert style.color is from_col
assert new_style.color == "red"


def test_from_column_fn():
df = pd.DataFrame({"x": [1, 2], "color": ["red", "blue"]})
from_col = FromColumn("color", fn=lambda x: x.upper())

style = CellStyleText(color=from_col)
new_style = style._from_row(df, 0)

assert new_style.color == "RED"

0 comments on commit 672085f

Please sign in to comment.