diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 61b03c5..ac99773 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,6 +18,7 @@ jobs: python-version: "3.10" - name: Install dependencies run: | + python -m pip install "pip3 install git+https://github.com/posit-dev/great-tables.git@feat-interactive" python -m pip install ".[dev]" - uses: quarto-dev/quarto-actions/setup@v2 with: diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 0d796d4..b8cfc17 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -12,6 +12,7 @@ website: - text: Demos menu: - demos/twitter-followers.qmd + - demos/great-tables.qmd #- href: reference/index.qmd # text: Reference right: diff --git a/docs/demos/coffee-sales/aeropress.png b/docs/demos/coffee-sales/aeropress.png new file mode 100644 index 0000000..739f435 Binary files /dev/null and b/docs/demos/coffee-sales/aeropress.png differ diff --git a/docs/demos/coffee-sales/cezve.png b/docs/demos/coffee-sales/cezve.png new file mode 100644 index 0000000..ffacaef Binary files /dev/null and b/docs/demos/coffee-sales/cezve.png differ diff --git a/docs/demos/coffee-sales/chemex.png b/docs/demos/coffee-sales/chemex.png new file mode 100644 index 0000000..c3a2f2a Binary files /dev/null and b/docs/demos/coffee-sales/chemex.png differ diff --git a/docs/demos/coffee-sales/coffee-sales.json b/docs/demos/coffee-sales/coffee-sales.json new file mode 100644 index 0000000..43db02c --- /dev/null +++ b/docs/demos/coffee-sales/coffee-sales.json @@ -0,0 +1 @@ +{"columns":[{"name":"icon","datatype":"String","bit_settings":"","values":["grinder.png","moka-pot.png","cold-brew.png","filter.png","drip-machine.png","aeropress.png","pour-over.png","french-press.png","cezve.png","chemex.png","scale.png","kettle.png","espresso-machine.png",null]},{"name":"product","datatype":"String","bit_settings":"","values":["Grinder","Moka pot","Cold brew","Filter","Drip machine","AeroPress","Pour over","French press","Cezve","Chemex","Scale","Kettle","Espresso Machine","Total"]},{"name":"revenue_dollars","datatype":"Float64","bit_settings":"","values":[904500.0,2045250.0,288750.0,404250.0,2632000.0,2601500.0,846000.0,1113250.0,2512500.0,3137250.0,3801000.0,756250.0,8406000.0,29448500.0]},{"name":"revenue_pct","datatype":"Float64","bit_settings":"","values":[0.03,0.07,0.01,0.01,0.09,0.09,0.03,0.04,0.09,0.11,0.13,0.03,0.29,1.0]},{"name":"profit_dollars","datatype":"Float64","bit_settings":"","values":[567960.0,181080.0,241770.0,70010.0,1374450.0,1293780.0,364530.0,748120.0,1969520.0,817680.0,2910290.0,617520.0,3636440.0,14793150.0]},{"name":"profit_pct","datatype":"Float64","bit_settings":"","values":[0.04,0.01,0.02,0.0,0.09,0.09,0.02,0.05,0.13,0.06,0.2,0.04,0.25,1.0]},{"name":"monthly_sales","datatype":{"List":"Int64"},"bit_settings":"","values":[{"name":"","datatype":"Int64","bit_settings":"","values":[521,494,596,613,667,748,765,686,607,594,568,751]},{"name":"","datatype":"Int64","bit_settings":"","values":[4726,4741,4791,5506,6156,6619,6868,6026,5304,4884,4648,6283]},{"name":"","datatype":"Int64","bit_settings":"","values":[244,249,438,981,1774,2699,2606,2348,1741,896,499,244]},{"name":"","datatype":"Int64","bit_settings":"","values":[2067,1809,1836,2123,2252,2631,2562,2367,2164,2195,2070,2744]},{"name":"","datatype":"Int64","bit_settings":"","values":[2137,1623,1971,2097,2580,2456,2336,2316,2052,1967,1837,2328]},{"name":"","datatype":"Int64","bit_settings":"","values":[6332,5199,6367,7024,7906,8704,8693,7797,6828,6963,6877,9270]},{"name":"","datatype":"Int64","bit_settings":"","values":[1562,1291,1511,1687,1940,2177,2141,1856,1715,1806,1601,2165]},{"name":"","datatype":"Int64","bit_settings":"","values":[3507,2880,3346,3792,3905,4095,4184,4428,3279,3420,3297,4819]},{"name":"","datatype":"Int64","bit_settings":"","values":[12171,11469,11788,13630,15391,16532,17090,14433,12985,12935,11598,15895]},{"name":"","datatype":"Int64","bit_settings":"","values":[4938,4167,5235,6000,6358,6768,7112,6249,5605,6076,4980,7220]},{"name":"","datatype":"Int64","bit_settings":"","values":[1542,1566,1681,2028,2425,2549,2569,2232,2036,2089,1693,3180]},{"name":"","datatype":"Int64","bit_settings":"","values":[1139,1023,1087,1131,1414,1478,1456,1304,1140,1233,1193,1529]},{"name":"","datatype":"Int64","bit_settings":"","values":[686,840,618,598,2148,533,797,996,1002,668,858,2577]},null]}]} \ No newline at end of file diff --git a/docs/demos/coffee-sales/coffee-sales.parquet b/docs/demos/coffee-sales/coffee-sales.parquet new file mode 100644 index 0000000..7399ec8 Binary files /dev/null and b/docs/demos/coffee-sales/coffee-sales.parquet differ diff --git a/docs/demos/coffee-sales/coffee-table.png b/docs/demos/coffee-sales/coffee-table.png new file mode 100644 index 0000000..093380f Binary files /dev/null and b/docs/demos/coffee-sales/coffee-table.png differ diff --git a/docs/demos/coffee-sales/cold-brew.png b/docs/demos/coffee-sales/cold-brew.png new file mode 100644 index 0000000..b506857 Binary files /dev/null and b/docs/demos/coffee-sales/cold-brew.png differ diff --git a/docs/demos/coffee-sales/drip-machine.png b/docs/demos/coffee-sales/drip-machine.png new file mode 100644 index 0000000..a0b6914 Binary files /dev/null and b/docs/demos/coffee-sales/drip-machine.png differ diff --git a/docs/demos/coffee-sales/espresso-machine.png b/docs/demos/coffee-sales/espresso-machine.png new file mode 100644 index 0000000..3a09e5c Binary files /dev/null and b/docs/demos/coffee-sales/espresso-machine.png differ diff --git a/docs/demos/coffee-sales/filter.png b/docs/demos/coffee-sales/filter.png new file mode 100644 index 0000000..1e6b84d Binary files /dev/null and b/docs/demos/coffee-sales/filter.png differ diff --git a/docs/demos/coffee-sales/french-press.png b/docs/demos/coffee-sales/french-press.png new file mode 100644 index 0000000..1c30b65 Binary files /dev/null and b/docs/demos/coffee-sales/french-press.png differ diff --git a/docs/demos/coffee-sales/grinder.png b/docs/demos/coffee-sales/grinder.png new file mode 100644 index 0000000..575439a Binary files /dev/null and b/docs/demos/coffee-sales/grinder.png differ diff --git a/docs/demos/coffee-sales/kettle.png b/docs/demos/coffee-sales/kettle.png new file mode 100644 index 0000000..ac1588c Binary files /dev/null and b/docs/demos/coffee-sales/kettle.png differ diff --git a/docs/demos/coffee-sales/moka-pot.png b/docs/demos/coffee-sales/moka-pot.png new file mode 100644 index 0000000..c03f78f Binary files /dev/null and b/docs/demos/coffee-sales/moka-pot.png differ diff --git a/docs/demos/coffee-sales/noun-drip-machine-6065915.png b/docs/demos/coffee-sales/noun-drip-machine-6065915.png new file mode 100644 index 0000000..13ac350 Binary files /dev/null and b/docs/demos/coffee-sales/noun-drip-machine-6065915.png differ diff --git a/docs/demos/coffee-sales/pour-over.png b/docs/demos/coffee-sales/pour-over.png new file mode 100644 index 0000000..cbb5489 Binary files /dev/null and b/docs/demos/coffee-sales/pour-over.png differ diff --git a/docs/demos/coffee-sales/scale.png b/docs/demos/coffee-sales/scale.png new file mode 100644 index 0000000..21d0817 Binary files /dev/null and b/docs/demos/coffee-sales/scale.png differ diff --git a/docs/demos/coffee-sales/total.png b/docs/demos/coffee-sales/total.png new file mode 100644 index 0000000..62fafec Binary files /dev/null and b/docs/demos/coffee-sales/total.png differ diff --git a/docs/demos/great-tables.qmd b/docs/demos/great-tables.qmd new file mode 100644 index 0000000..0f9d9a5 --- /dev/null +++ b/docs/demos/great-tables.qmd @@ -0,0 +1,337 @@ +--- +title: Great Tables Gallery +format: + html: + code-fold: true + code-summary: "Show the Code" +html-table-processing: none +jupyter: python3 +--- + +```{python} +from react_tables import embed_css, render + +embed_css() + +``` + +:::::: {.column-page} +::::: {.grid} + +:::{.g-col-lg-6 .g-col-12} + +```{python} +import polars as pl +from great_tables import GT, md, html +from great_tables.data import islands +from react_tables import render + + +islands_mini = pl.from_pandas(islands).sort("size", descending=True).head(10) + +gt = ( + GT(islands_mini) + .tab_header(title="Large Landmasses of the World", subtitle="The top ten largest are presented") + .tab_stub(rowname_col="name") + .tab_source_note(source_note="Source: The World Almanac and Book of Facts, 1975, page 406.") + .tab_source_note( + source_note=md("Reference: McNeil, D. R. (1977) *Interactive Data Analysis*. Wiley.") + ) + .tab_stubhead(label="landmass") + .fmt_integer(columns="size") +) + +render(gt) +``` + + +::: + +:::{.g-col-lg-6 .g-col-12} + +```{python} +import polars as pl + +from great_tables import GT, html +from great_tables.data import airquality + +airquality_mini = airquality.head(10).assign(Year=1973) + +gt = ( + GT(pl.from_pandas(airquality_mini)) + .tab_header( + title="New York Air Quality Measurements", + subtitle="Daily measurements in New York City (May 1-10, 1973)", + ) + .tab_spanner(label="Time", columns=["Year", "Month", "Day"]) + .tab_spanner(label="Measurement", columns=["Ozone", "Solar_R", "Wind", "Temp"]) + .cols_move_to_start(columns=["Year", "Month", "Day"]) + .cols_label( + Ozone=html("Ozone,
ppbV"), + Solar_R=html("Solar R.,
cal/m2"), + Wind=html("Wind,
mph"), + Temp=html("Temp,
°F"), + ) +) + +render(gt) +``` + +::: + +:::{.g-col-lg-6 .g-col-12} + +```{python} +from great_tables import GT +from great_tables.data import countrypops +import polars as pl +import polars.selectors as cs + +# Get vectors of 2-letter country codes for each region of Oceania +oceania = { + "Australasia": ["AU", "NZ"], + "Melanesia": ["NC", "PG", "SB", "VU"], + "Micronesia": ["FM", "GU", "KI", "MH", "MP", "NR", "PW"], + "Polynesia": ["PF", "WS", "TO", "TV"], +} + +# Create a dictionary mapping country to region (e.g. AU -> Australasia) +country_to_region = { + country: region for region, countries in oceania.items() for country in countries +} + +wide_pops = ( + pl.from_pandas(countrypops) + .filter( + pl.col("country_code_2").is_in(list(country_to_region)) + & pl.col("year").is_in([2000, 2010, 2020]) + ) + .with_columns(pl.col("country_code_2").replace(country_to_region).alias("region")) + .filter(pl.col("region").is_in(["Australasia", "Melanesia"])) + .pivot(index=["country_name", "region"], columns="year", values="population") + .sort("2020", descending=True) +) + +gt = ( + GT(wide_pops, id="some-table") + .tab_header(title="Some title", subtitle="Some subtitle") + .tab_spanner(label="Spanner label", columns=cs.all()) + .tab_stub(rowname_col="country_name", groupname_col="region") + .tab_stubhead("Stubhead label") + .tab_source_note("footnote1") + .tab_source_note("footnote2") + .fmt_integer() +) + +gt +``` + +::: + +:::{.g-col-lg-6 .g-col-12} + +```{python} +from great_tables import GT, html +from great_tables.data import towny + +towny_mini = ( + towny[["name", "website", "density_2021", "land_area_km2", "latitude", "longitude"]] + .sort_values("density_2021", ascending=False) + .head(10) +) + +towny_mini["url_name"] = ["["] + towny_mini["name"] + ["]"] + ["("] + towny_mini["website"] + [")"] + +towny_mini["location"] = ( + ["[map](http://maps.google.com/?ie=UTF8&hq=&ll="] + + towny_mini["latitude"].astype(str) + + [","] + + towny_mini["longitude"].astype(str) + + ["&z=13)"] +) + +pl_towny = pl.from_pandas(towny_mini[["url_name", "location", "land_area_km2", "density_2021"]]) + +gt = ( + GT( + pl_towny, + rowname_col="url_name", + ) + .tab_header( + title="The Municipalities of Ontario", + subtitle="The top 10 highest population density in 2021", + ) + .tab_stubhead(label="Municipality") + .fmt_markdown(columns=["url_name", "location"]) + .fmt_number(columns=["land_area_km2", "density_2021"], decimals=1) + .cols_label( + land_area_km2=html("land area,
km2"), + density_2021=html("density,
people/km2"), + ) +) + +render(gt) +``` + + +::: + +:::{.g-col-lg-6 .g-col-12} + +```{python} +from great_tables import GT, html +from great_tables.data import sza +import polars as pl +import polars.selectors as cs + +sza_pivot = ( + pl.from_pandas(sza) + .filter((pl.col("latitude") == "20") & (pl.col("tst") <= "1200")) + .select(pl.col("*").exclude("latitude")) + .drop_nulls() + .pivot(values="sza", index="month", columns="tst", sort_columns=True) +) + +gt = ( + GT(sza_pivot, rowname_col="month") + .data_color( + domain=[90, 0], + palette=["rebeccapurple", "white", "orange"], + na_color="white", + ) + .tab_header( + title="Solar Zenith Angles from 05:30 to 12:00", + subtitle=html("Average monthly values at latitude of 20°N."), + ) + .sub_missing(missing_text="") +) + +render(gt) +``` + +::: + +:::{.g-col-lg-6 .g-col-12} + +```{python} +import polars as pl +import polars.selectors as cs +from great_tables import GT, md + + +def create_bar(prop_fill: float, max_width: int, height: int) -> str: + """Create divs to represent prop_fill as a bar.""" + width = round(max_width * prop_fill, 2) + px_width = f"{width}px" + return f"""\ +
\ +
\ +
\ + """ + + +df = pl.read_csv("./sports-earnings/sports_earnings.csv") + +res = ( + df.with_columns( + (pl.col("Off-the-Field Earnings") / pl.col("Total Earnings")).alias("raw_perc"), + ("./sports-earnings/" + pl.col("Sport").str.to_lowercase() + ".png").alias("icon"), + ) + .head(9) + .with_columns( + pl.col("raw_perc") + .map_elements(lambda x: create_bar(x, max_width=75, height=20)) + .alias("Off-the-Field Earnings Perc") + ) + .select("Rank", "Name", "icon", "Sport", "Total Earnings", "Off-the-Field Earnings", "Off-the-Field Earnings Perc") +) + +gt = ( + GT(res, rowname_col="Rank") + .tab_header("Highest Paid Athletes in 2023") + .tab_spanner("Earnings", cs.contains("Earnings")) + #.fmt_number(cs.starts_with("Total"), scale_by = 1/1_000_000, decimals=1) + .cols_label(**{ + "Total Earnings": "Total $M", + "Off-the-Field Earnings": "Off field $M", + "Off-the-Field Earnings Perc": "Off field %" + }) + .fmt_number(["Total Earnings", "Off-the-Field Earnings"], scale_by = 1/1_000_000, decimals=1) + .fmt_image("icon", path="./") + .tab_source_note( + md( + '
' + "Original table: [@LisaHornung_](https://twitter.com/LisaHornung_/status/1752981867769266231)" + " | Sports icons: [Firza Alamsyah](https://thenounproject.com/browse/collection-icon/sports-96427)" + " | Data: Forbes" + "
" + "
" + ) + ) +) +render(gt) +``` + +::: + +:::{.g-col-lg-6 .g-col-12} + +```{python} +import polars as pl +import polars.selectors as cs +from great_tables import GT, loc, style + +coffee_sales = pl.read_json("coffee-sales/coffee-sales.json") + +sel_rev = cs.starts_with("revenue") +sel_prof = cs.starts_with("profit") + + +coffee_table = ( + GT(coffee_sales) + .tab_header("Sales of Coffee Equipment") + .tab_spanner(label="Revenue", columns=sel_rev) + .tab_spanner(label="Profit", columns=sel_prof) + .cols_label( + revenue_dollars="Amount", + profit_dollars="Amount", + revenue_pct="Percent", + profit_pct="Percent", + monthly_sales="Monthly Sales", + icon="", + product="Product", + ) + # formatting ---- + .fmt_number( + columns=cs.ends_with("dollars"), + compact=True, + pattern="${x}", + n_sigfig=3, + ) + .fmt_percent(columns=cs.ends_with("pct"), decimals=0) + # style ---- + .tab_style( + style=style.fill(color="aliceblue"), + locations=loc.body(columns=sel_rev), + ) + .tab_style( + style=style.fill(color="papayawhip"), + locations=loc.body(columns=sel_prof), + ) + .tab_style( + style=style.text(weight="bold"), + locations=loc.body(rows=pl.col("product") == "Total"), + ) + .fmt_nanoplot("monthly_sales", plot_type="bar") + .fmt_image("icon", path="coffee-sales") + .sub_missing(missing_text="") +) + +# coffee_table.save("data/coffee-table.png", scale=2) +render(coffee_table) +``` + +::: + +:::::: +::::: \ No newline at end of file diff --git a/docs/demos/sports-earnings/The_World's_Highest_Paid_Athletes.xlsx b/docs/demos/sports-earnings/The_World's_Highest_Paid_Athletes.xlsx new file mode 100644 index 0000000..828da39 Binary files /dev/null and b/docs/demos/sports-earnings/The_World's_Highest_Paid_Athletes.xlsx differ diff --git a/docs/demos/sports-earnings/basketball.png b/docs/demos/sports-earnings/basketball.png new file mode 100644 index 0000000..23a0cbd Binary files /dev/null and b/docs/demos/sports-earnings/basketball.png differ diff --git a/docs/demos/sports-earnings/boxing.png b/docs/demos/sports-earnings/boxing.png new file mode 100644 index 0000000..7fc1b89 Binary files /dev/null and b/docs/demos/sports-earnings/boxing.png differ diff --git a/docs/demos/sports-earnings/golf.png b/docs/demos/sports-earnings/golf.png new file mode 100644 index 0000000..0c3e8d9 Binary files /dev/null and b/docs/demos/sports-earnings/golf.png differ diff --git a/docs/demos/sports-earnings/soccer.png b/docs/demos/sports-earnings/soccer.png new file mode 100644 index 0000000..cf6993c Binary files /dev/null and b/docs/demos/sports-earnings/soccer.png differ diff --git a/docs/demos/sports-earnings/sports_earnings.csv b/docs/demos/sports-earnings/sports_earnings.csv new file mode 100644 index 0000000..d9aca14 --- /dev/null +++ b/docs/demos/sports-earnings/sports_earnings.csv @@ -0,0 +1,51 @@ +Rank,Name,Sport,Total Earnings,On-the-Field Earnings,Off-the-Field Earnings +1,Cristiano Ronaldo,Soccer,136000000,46000000,90000000 +2,Lionel Messi,Soccer,130000000,65000000,65000000 +3,Kylian Mbappé,Soccer,120000000,100000000,20000000 +4,LeBron James,Basketball,119500000,44500000,75000000 +5,Canelo Alvarez,Boxing,110000000,100000000,10000000 +6,Dustin Johnson,Golf,107000000,102000000,5000000 +7,Phil Mickelson,Golf,106000000,104000000,2000000 +8,Stephen Curry,Basketball,100400000,48400000,52000000 +9,Roger Federer,Tennis,95100000,100000,95000000 +10,Kevin Durant,Basketball,89100000,44100000,45000000 +11,Giannis Antetokounmpo,Basketball,87600000,42600000,45000000 +12,Neymar,Soccer,85000000,50000000,35000000 +12,Russell Wilson,Football,85000000,72000000,13000000 +14,Russell Westbrook,Basketball,82100000,47100000,35000000 +15,Rory McIlroy,Golf,80800000,40800000,40000000 +16,Tiger Woods,Golf,75100000,15100000,60000000 +17,Cameron Smith,Golf,73000000,67000000,6000000 +18,Brooks Koepka,Golf,72000000,66000000,6000000 +19,Kyler Murray,Football,70500000,67000000,3500000 +20,Bryson DeChambeau,Golf,69000000,68000000,1000000 +21,Lewis Hamilton,Auto Racing,65000000,55000000,10000000 +22,Max Verstappen,Auto Racing,64000000,60000000,4000000 +23,Klay Thompson,Basketball,60900000,40900000,20000000 +24,Patrick Mahomes,Football,59300000,39300000,20000000 +25,Damian Lillard,Basketball,58600000,42600000,16000000 +26,Max Scherzer,Baseball,56700000,55700000,1000000 +27,James Harden,Basketball,55100000,33100000,22000000 +28,Anthony Joshua,Boxing,53000000,50000000,3000000 +28,Jon Rahm,Golf,53000000,28000000,25000000 +28,Aaron Rodgers,Football,53000000,42000000,11000000 +28,Mohamed Salah,Soccer,53000000,35000000,18000000 +32,Erling Haaland,Soccer,52000000,40000000,12000000 +32,Patrick Reed,Golf,52000000,49000000,3000000 +34,Paul George,Basketball,51500000,42500000,9000000 +35,Kawhi Leonard,Basketball,50500000,42500000,8000000 +36,Bradley Beal,Basketball,49800000,43300000,6500000 +37,Derek Carr,Football,48900000,45900000,3000000 +38,"Orlando Brown, Jr.",Football,48600000,48500000,100000 +39,Aaron Donald,Football,48500000,46500000,2000000 +40,Anthony Davis,Basketball,48000000,38000000,10000000 +41,Jimmy Butler,Basketball,47800000,37800000,10000000 +41,John Wall,Basketball,47800000,47300000,500000 +43,Jordan Spieth,Golf,47500000,17500000,30000000 +44,Luka Doncic,Basketball,47200000,37200000,10000000 +45,Scottie Scheffler,Golf,47100000,32100000,15000000 +46,Sergio Garcia,Golf,46000000,43000000,3000000 +46,Dak Prescott,Football,46000000,31000000,15000000 +48,Deshaun Watson,Football,45800000,45300000,500000 +49,Serena Williams,Tennis,45300000,300000,45000000 +50,Tom Brady,Football,45200000,1200000,44000000 diff --git a/docs/demos/sports-earnings/tennis.png b/docs/demos/sports-earnings/tennis.png new file mode 100644 index 0000000..19dc5a6 Binary files /dev/null and b/docs/demos/sports-earnings/tennis.png differ diff --git a/docs/get-started/index.qmd b/docs/get-started/index.qmd index 6c7b70c..8681f02 100644 --- a/docs/get-started/index.qmd +++ b/docs/get-started/index.qmd @@ -13,8 +13,9 @@ from great_tables import GT, exibble embed_css() -gt = GT(pl.from_pandas(exibble)) +gt = GT(pl.from_pandas(exibble)).tab_stub("row", "group") render(gt) +# gt ``` diff --git a/react_tables/__init__.py b/react_tables/__init__.py index b7cb112..24bdb0a 100644 --- a/react_tables/__init__.py +++ b/react_tables/__init__.py @@ -1,33 +1,12 @@ from __future__ import annotations from .models import Props -from .widgets import BigblockWidget, embed_css +from .widgets import BigblockWidget, embed_css, bigblock, RT from .options import options +from .render_gt import render __all__ = [ "bigblock", "options", "Props", ] - - -def bigblock(props: Props): - - return BigblockWidget(props=props.to_props()) - - -class RT: - props: Props - widget: "None | BigblockWidget" - - def __init__(self, props: Props): - self.props = Props - self._widget = None - - def _repr_mimebundle_(self, **kwargs: dict) -> tuple[dict, dict] | None: - # TODO: note that this means updates to props won't affect widget - if self._widget is not None: - return self._widget._repr_mimebundle_(**kwargs) - - self._widget = BigblockWidget(props=self.props.to_props()) - return self._widget._repr_mimebundle_(**kwargs) diff --git a/react_tables/render_gt.py b/react_tables/render_gt.py index 142a807..f99e7e4 100644 --- a/react_tables/render_gt.py +++ b/react_tables/render_gt.py @@ -51,14 +51,15 @@ def create_col_groups(spanners: Spanners) -> list[ColGroup]: def create_heading(heading: Heading, use_search: bool) -> html.Tag | None: if heading.is_empty(): return None + el = html.div( html.div( - html.HTML(heading.title), + html.HTML(_process_text(heading.title)), class_="gt_heading gt_title gt_font_normal", style="text-size: bigger;", ), html.div( - html.HTML(heading.subtitle), + html.HTML(_process_text(heading.subtitle)), class_="gt_heading gt_subtitle" + ("gt_bottom_border" if use_search else ""), ), style=dict_to_css( @@ -104,7 +105,8 @@ def create_tr(note: html.Tag) -> html.Tag: return res - combined_notes = html.div(html.HTML(sep.join(source_notes)), style="padding-bottom: 2px;") + text = list(map(_process_text, source_notes)) + combined_notes = html.div(html.HTML(sep.join(text)), style="padding-bottom: 2px;") return html.tags.tfoot(create_tr(combined_notes), class_="gt_sourcenotes") @@ -148,17 +150,20 @@ def _render(self: GT): locale = self._locale # generate Language ------------------------------------------------------- - # TODO: locale.locale can also be None? lang_defs = locale_to_lang(locale) # column info ------------------------------------------------------------- _stub = self._boxhead._get_stub_column() + _group = self._boxhead._get_row_group_column() _defaults = self._boxhead._get_default_columns() + groupname_col = _group.var if _group else None + + col_info = _defaults + if _group: + col_info = [_group, *col_info] if _stub: - col_info = [_stub, *_defaults] - else: - col_info = _defaults + col_info = [_stub, *col_info] visible_col_names = [col.var for col in col_info] @@ -173,7 +178,6 @@ def _render(self: GT): formatted_cols = extract_cells(self, columns=visible_col_names, output="html") # Generate body styles ---------------------------------------------------- - # TODO: implement # gt uses a default Column(style = JS(...)) body_style_info = (style for style in self._styles if style.locname == "data") body_styles: dict[str, list[str]] = {k: [None] * data_n_rows for k in visible_col_names} @@ -188,13 +192,8 @@ def _render(self: GT): else: body_styles[info.colname][info.rownum] = {**crnt_style, **new_style} - # TODO: it seems like style can't be an empty string - for k, v in body_styles.items(): - for ii in range(len(v)): - if v[ii] == "": - v[ii] = None + # create Column definitions (including rownames) -------------- - # create Column definitions (including rownames) -------------------------- columns = [] for col in col_info: @@ -204,10 +203,13 @@ def _render(self: GT): # TODO: rowname col should also have a righthand border? col_def = Column( id=col.var, - # TODO: handle stubhead - name=self._stubhead or "", + cell=lambda ci: formatted_cols[ci.column_name][ci.row_index], + name=_process_text(self._stubhead or ""), + headerStyle={"font-weight": "normal"}, + width=col.column_width, sortable=False, filterable=False, + html=True, ) else: @@ -285,8 +287,10 @@ def _render(self: GT): data=data, columns=columns, columnGroups=col_groups, + defaultExpanded=True, rownames=None, - groupBy=None, + # TODO: reactable always puts groupBy cols first, even before rowname cols + groupBy=groupname_col, # TODO: no ihtml options # sortable=opts["ihtml_use_sorting"], # resizable=opts["ihtml_use_resizing"], @@ -304,7 +308,6 @@ def _render(self: GT): minRows=1, paginateSubRows=False, details=None, - defaultExpanded=False, selection=None, # TODO: selectionId not implemented # selectionId=None, @@ -317,8 +320,8 @@ def _render(self: GT): # striped=opts["row_striping_include_table_body"], # compact=opts["ihtml_use_compact_mode"], # text_wrapping=opts["ihtml_use_text_wrapping"], - showSortIcon=True, - showSortable=True, + # showSortIcon=True, + # showSortable=True, class_=None, style=None, rowClass=None, diff --git a/react_tables/widgets.py b/react_tables/widgets.py index 4776887..8cecd85 100644 --- a/react_tables/widgets.py +++ b/react_tables/widgets.py @@ -1,10 +1,16 @@ +from __future__ import annotations + import ipyreact from importlib_resources import files from pathlib import Path +from typing import TYPE_CHECKING STATIC_FILES = files("react_tables.static") +if TYPE_CHECKING: + from .models import Props + def embed_css(): # https://github.com/widgetti/ipyreact/issues/39 @@ -33,3 +39,25 @@ def tagify(self) -> str: # def to_tag(self): # return htmltool.Tag("Reactable", ) + + +def bigblock(props: Props): + + return BigblockWidget(props=props.to_props()) + + +class RT: + props: Props + widget: "None | BigblockWidget" + + def __init__(self, props: Props): + self.props = Props + self._widget = None + + def _repr_mimebundle_(self, **kwargs: dict) -> tuple[dict, dict] | None: + # TODO: note that this means updates to props won't affect widget + if self._widget is not None: + return self._widget._repr_mimebundle_(**kwargs) + + self._widget = BigblockWidget(props=self.props.to_props()) + return self._widget._repr_mimebundle_(**kwargs)