machow / reactable-py

https://machow.github.io/reactable-py/
38 stars 2 forks source link

Implement Panel integrations #33

Open MarcSkovMadsen opened 18 hours ago

MarcSkovMadsen commented 18 hours ago

Congrats on the launch.

This looks like a powerful and mature table. Would be nice if Panel was integrated or supported. I've crossposted this request with Panel in https://github.com/holoviz/panel/issues/7522.

Its pretty easy to support via ReactComponent.

image

pip install panel reactable
import panel as pn
import param
from panel.custom import ReactComponent
from reactable import Reactable
from reactable.data import cars_93

pn.extension()

# Should probably be local reactable versions to support airgapped environments
REACTABLE_CSS = "https://cdn.jsdelivr.net/gh/machow/reactable-py@main/reactable/static/reactable-py.esm.css"
REACTABLE_JS = "https://cdn.jsdelivr.net/gh/machow/reactable-py@main/reactable/static/reactable-py.esm.js"

class ReactableTable(ReactComponent):
    """ReactableTable Pane that renders Reactable objects."""

    object: Reactable = param.ClassSelector(
        class_=Reactable, doc="The Reactable object to render."
    )

    _data = param.Dict()
    _rename = {"object": None}

    def __init__(self, object: Reactable, **params):
        super().__init__(object=object, **params)

    @param.depends("object", watch=True, on_init=True)
    def _handle_object_change(self):
        if not self.object:
            self._data = {}
        else:
            # One day we should extract the data object directly and transfer it more efficiently via Bokeh ColumnDataSource
            # self._data = self.object.data
            # self._other_props = self.object.to_props(include_data=False)
            self._data = self.object.to_props()

    _importmap = {
        "imports": {
            "reactable": REACTABLE_JS,
        }
    }

    _esm = """
    import Reactable from "reactable"

    export function render({model}) {
      const [props] = model.useState('_data');
      return <Reactable {...props} />
    }
"""
    _stylesheets = [REACTABLE_CSS]

reactable = Reactable(cars_93)
ReactableTable(reactable, sizing_mode="stretch_width").servable()
panel serve app.py --dev

Please either provide a Panel native component in the library or document how it can be used with Panel.

Extra Example

I would have liked to share this example on Py.Cafe. But its not possible (#34).

image

import htmltools
import panel as pn
import param
from panel.custom import ReactComponent
from reactable import CellInfo, Column, Reactable
from reactable.data import cars_93

# Create Reusable Panel Reactable Component

## Should probably be local reactable versions to support airgapped environments
REACTABLE_CSS = "https://cdn.jsdelivr.net/gh/machow/reactable-py@main/reactable/static/reactable-py.esm.css"
REACTABLE_JS = "https://cdn.jsdelivr.net/gh/machow/reactable-py@main/reactable/static/reactable-py.esm.js"

class ReactableTable(ReactComponent):
    """ReactableTable Pane that renders Reactable objects."""

    object: Reactable = param.ClassSelector(
        class_=Reactable, doc="The Reactable object to render."
    )

    _data = param.Dict()
    _rename = {"object": None}

    def __init__(self, object: Reactable, **params):
        super().__init__(object=object, **params)

    @param.depends("object", watch=True, on_init=True)
    def _handle_object_change(self):
        if not self.object:
            self._data = {}
        else:
            ## One day we should extract the data object directly and transfer it more efficiently via Bokeh ColumnDataSource
            ## self._data = self.object.data
            ## self._other_props = self.object.to_props(include_data=False)
            self._data = self.object.to_props()

    _importmap = {
        "imports": {
            "reactable": REACTABLE_JS,
        }
    }

    _esm = """
    import Reactable from "reactable"

    export function render({model}) {
      const [props] = model.useState('_data');
      return <Reactable {...props} />
    }
"""
    _stylesheets = [REACTABLE_CSS]

# Create custom reactable

pn.extension(sizing_mode="stretch_width")

ACCENT = "#fc5185"

HEADER = """
# reactable-py

[reactable-py](https://github.com/machow/reactable-py) generates interactive tables in Python. \
It's a port of the R package [reactable](https://github.com/glin/reactable) by [@glin](https://github.com/glin).

❤️ **It works with [Panel](https://panel.holoviz.org/reference/index.html)** as you can see below:"""

DETAILS = """
## Documentation

See these handy documentation pages:

- [📚 User guide](https://machow.github.io/reactable-py/get-started)
- [🧩 Examples](https://machow.github.io/reactable-py/demos/)

## Features

- **controls**: sorting, filtering, and pagination.
- **structure**: grouping, aggregating, expanding rows.
- **format**: represent numbers, dates, currencies.
- **style**: conditional styling for headers, cell values, and more.

## Installing

```bash
pip install reactable

"""

data = cars_93[:5, ["make", "mpg_city", "mpg_highway"]]

def html_barchart(label, width="100%", height="1rem", fill="#00bfc4", background=None): """Create general purpose html fill bar."""

bar = htmltools.div(style=f"background: {fill}; width: {width}; height: {height}")
chart = htmltools.div(
    bar,
    style=htmltools.css(
        flex_grow=1,
        margin_left="0.5rem",
        background=background,
    ),
)
return htmltools.div(
    label,
    chart,
    style=htmltools.css(
        display="flex",
        align_items="center",
    ),
)

def fmt_barchart(ci: CellInfo, **kwargs): """Format cell value into html fill bar."""

width = f"{ci.value / max(data['mpg_city']) * 100}%"
return html_barchart(ci.value, width=width, **kwargs)

reactable = Reactable( data, columns={ "mpg_city": Column( name="MPG (city)", align="left", cell=fmt_barchart, ), "mpg_highway": Column( name="MPG (highway)", align="left", cell=lambda ci: fmt_barchart(ci, fill="#fc5185", background="#e1e1e1"), ), }, default_page_size=5, ) reactable_table = ReactableTable(reactable)

Layout the components

Should support dark FastListTemplate table css one day

pn.template.FastListTemplate( title="Panel reactable-py", main=[HEADER, reactable_table, DETAILS], accent=ACCENT, main_layout=None, main_max_width="800px", ).servable()

machow commented 4 hours ago

Oh wow -- this is incredible! Sorry, I responded to your App Frameworks Issue (#35) before seeing this. But I get now why you said ipyreact is not necessary! I'm away this week, but will dig more into integrating with panel once I'm back!

I'm on board with making ipyreact optional and adding a Panel implementation. And this example is so helpful!

(cc @schloerke in case you're interested, the Panel example is very similar to our prototype AnyWidget stuff)

MarcSkovMadsen commented 4 hours ago

FYI. Panel supports the AnyWidget AFM API via https://panel.holoviz.org/reference/custom_components/AnyWidgetComponent.html.

Then it should be possible to use the same javascript for Panel and Jupyter/ AnyWidget if you want. Only difference is the Python "models" using Param or Traitlets.

MarcSkovMadsen commented 4 hours ago

Maarten Bredals from Py.Cafe helped show me how I could actually run Panel + Reactable there. So here is a link to a working application https://py.cafe/awesome.panel.org/panel-reactable-table.

image