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 support for custom tabulator options #126

Merged
merged 3 commits into from
Jul 29, 2024
Merged

Add support for custom tabulator options #126

merged 3 commits into from
Jul 29, 2024

Conversation

droumis
Copy link
Member

@droumis droumis commented Jul 16, 2024

Fixes #125

Use tabulator_kwargs and refer to the options https://panel.holoviz.org/reference/widgets/Tabulator.html

TODO:

  • allow for show_indices to be overwritten (another PR)

For instance:

AnnotatorTable(annotator, tabulator_kwargs=dict(pagination='local', page_size=7))
Code
import panel as pn
import holoviews as hv
hv.extension('bokeh')
import pandas as pd
from holonote.annotate import Annotator
from holonote.annotate.connector import SQLiteDB
from holonote.app import PanelWidgets, AnnotatorTable

data = {
    'description': ['T0', 'T1', 'T2'] * 10,
    'start': range(30),
    'end': [i + .8 for i in range(30)]
}

annotations_df = pd.DataFrame(data)

curve_data = {
    'time': range(30),
    'val': [1, 3, 2] * 10,
}

curve_df = pd.DataFrame(curve_data)

plot = hv.Curve(curve_df, 'time', 'val')

annotator = Annotator({'time': float}, fields=["description"],
                      connector=SQLiteDB(filename=':memory:')
                     )

annotator.define_annotations(annotations_df, time=("start", "end"))

annotator.groupby = "description"
annotator.visible = ["T1"]

annotator_widgets = pn.Column(
    PanelWidgets(annotator),
    AnnotatorTable(
        annotator,
        tabulator_kwargs=dict(
            pagination='local',
            page_size=7,
        )
    ),
    width=400
)

# Display the widgets and plot
pn.Row(annotator_widgets, annotator * plot.opts(responsive=True)).servable()

@droumis droumis requested a review from ahuang11 July 16, 2024 23:20
holonote/app/tabulator.py Outdated Show resolved Hide resolved
Copy link

codspeed-hq bot commented Jul 16, 2024

CodSpeed Performance Report

Merging #126 will not alter performance

Comparing tabulator_kwargs (ffe1ce7) with main (ceb60de)

Summary

✅ 6 untouched benchmarks

Copy link

codecov bot commented Jul 16, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 86.34%. Comparing base (ceb60de) to head (ffe1ce7).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #126   +/-   ##
=======================================
  Coverage   86.33%   86.34%           
=======================================
  Files          26       26           
  Lines        2774     2775    +1     
=======================================
+ Hits         2395     2396    +1     
  Misses        379      379           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@droumis droumis enabled auto-merge (squash) July 16, 2024 23:42
@droumis droumis disabled auto-merge July 16, 2024 23:42
holonote/app/tabulator.py Outdated Show resolved Hide resolved
@@ -61,6 +64,7 @@ def new_style(row):
buttons={"delete": '<i class="fa fa-trash"></i>'},
show_index=False,
selectable=True,
**self.tabulator_kwargs,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's a param should this be wrapped in a function decorated with pn.depends('tabulator_kwargs'), running self.tabulator.param.update(**tabulator_kwargs)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing not, since tabulator_kwargs (and _create_tabulator) are intended to only be used once. But it's a good question for someone with a greater understanding to jump in here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that the tabulator_kwargs should be synced with the Tabulator. There actually is an API on Reactive objects to do this, specifically, could you try:

Suggested change
**self.tabulator_kwargs,
refs=self.param.tabulator_kwargs

This will update keep the tabulator_kwargs dictionary synced with the Tabulator parameters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So my understanding (which I just pushed changes for) is that it is appropriate to make tabulator_kwargs into a param (I'm guessing now requiring allow_refs=True?), and that it will be synced solely by using the refs= API, without the dependence on the depends decorator.

Just curious, what is the reasoning that tabulator_kwargs should remain synced with Tabulator beyond initialization? Is it just the general design inclination to allow for future flexibility in case some property about the Tabulator will be internally modified?

Also, am I correct in assuming the allow_refs=True is now required or am I confusing concepts?

Thanks for the guidance!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, if my recent changes look good, please approve and merge! thank you

@droumis
Copy link
Member Author

droumis commented Jul 19, 2024

Note to self, I was thinking about adding input validation warnings for dict items like this:

class _TabulatorOpts(param.Dict):
    def __init__(self, default={}, doc="kwargs to pass to tabulator", allow_refs=True):
        super().__init__(default=default, doc=doc, allow_refs=allow_refs)

    def _validate(self, val) -> None:
        super()._validate(val)
        warn_opts = ("show_index", "selectable", "buttons")
        for k in val:
            if k in warn_opts:
                warn(f"`{k}` is set directly and should not be passed through `tabulator_opts`")

But then the doc entry doesn't appear in the docstring, so I think I'll leave it out and just let the validation be a side effect of passing in a duplicate kwarg of one that is set directly

@ahuang11
Copy link
Contributor

You could create your own param type with validation, e.g.

tabulator_kwargs = param.TabulatorKwargs()

As an example, Panel does it for a few like Margin.
https://github.com/holoviz/panel/blob/9f412a0cb6a3093c0a0f34489ae24db85bea60c6/panel/_param.py#L51

@droumis
Copy link
Member Author

droumis commented Jul 19, 2024

I think that's pretty much what I proposed in the comment above, no? I think I'll avoid for now because I prefer the docs to show up, but thanks. How does this PR look currently?

@ahuang11
Copy link
Contributor

ahuang11 commented Jul 19, 2024

I think the PR looks fine; I'm just not sure if allow_refs=True is necessary or not since I don't have any experience with it so I was hoping for Philipp to review :P Are you able to confirm it doesn't work when allow_refs=False?

I think that's pretty much what I proposed in the comment above,

Oops yeah, I didn't recognize it since I'm used to seeing inheriting from param.Parameter

the docs to show up

I'm a bit confused about this one.

from param import Parameter, Parameterized


class Aspect(Parameter):
    """
    A Parameter type to validate aspect ratios. Supports numeric values
    and auto.
    """

    def __init__(self, default=None, allow_None=True, **params):
        super().__init__(default=default, allow_None=allow_None, **params)
        self._validate(default)

    def _validate(self, val):
        self._validate_value(val, self.allow_None)

    def _validate_value(self, val, allow_None):
        ...


class Test(Parameterized):

    tabulator_aspect = Aspect(doc="Just testing")


Test?

results in

Init signature: Test(*, tabulator_aspect, name)
Docstring:     
Parameters of 'Test'
====================

Parameters changed from their default values are marked in red.
Soft bound values are marked in cyan.
C/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None

Name              Value   Type     Mode  

tabulator_aspect   None  Aspect  V RW AN 

Parameter docstrings:
=====================

tabulator_aspect: Just testing
Type:           ParameterizedMetaclass
Subclasses:     

@philippjfr
Copy link
Member

There's no reason not to enable allow_refs but it isn't strictly needed. Tabulator allows refs so that's all that's necessary, enabling it here would make it possible to bind references to these objects which is also nice.

@droumis droumis requested a review from hoxbro July 23, 2024 17:42
@@ -14,6 +14,7 @@ class AnnotatorTable(pn.viewable.Viewer):
annotator = param.Parameter(allow_refs=False)
tabulator = param.Parameter(allow_refs=False)
dataframe = param.DataFrame()
tabulator_kwargs = param.Dict(default={}, doc="kwargs to pass to tabulator", allow_refs=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do something like this to validate the values:

@param.depends('tabulator_kwargs')
def _validated_tabulator_kwargs(self):
    ... 
    return self.tabulator_kwargs

Copy link
Member

@philippjfr philippjfr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with this as is but if you want to validate the kwargs I'd be happy with that too.

@droumis droumis merged commit 928809b into main Jul 29, 2024
24 of 25 checks passed
@droumis droumis deleted the tabulator_kwargs branch July 29, 2024 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Propagate tabulator kwargs to the AnnotatorTable
4 participants