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

Implement interactive API for all data backends #505

Merged
merged 9 commits into from Nov 14, 2020
Merged

Conversation

philippjfr
Copy link
Member

@philippjfr philippjfr commented Aug 31, 2020

Implements interactive API as described in pydata/xarray#3709.

Depends on holoviz/holoviews#4578

  • Add documentation
  • Add tests

@philippjfr philippjfr changed the title Implement initial cut of interactive API Implement interactive API for all data backends Aug 31, 2020
Copy link
Member

@jbednar jbednar left a comment

Choose a reason for hiding this comment

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

Looks great! Would be nice to open an issue somewhere (I guess on hvplot) outlining what it would take to implement this outside of hvPlot and/or outside of HoloViews someday. (Not that I'm suggesting you actually do such an implementation, but it would be good to record for posterity what those possibilities are, so that people can evaluate those pros and cons in case there are opportunities to apply this approach in cases where HoloViews isn't an appropriate dependency. Plus those pros/cons might motivate splitting HoloViews into core and plotting, in some scenarios.)

hvplot/interactive.py Outdated Show resolved Hide resolved
return self._clone(transform)
def __gt__(self, other):
other = other._transform if isinstance(other, Interactive) else other
transform = type(self._transform)(self._transform, operator.gt, other)
Copy link
Member

@jbednar jbednar Aug 31, 2020

Choose a reason for hiding this comment

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

Is there a way to avoid specifying each of these delegations explicitly like this? The answer may well be no, but it seems like a shame to have so much boilerplate code here, which makes it hard to see the real code, and would cover up any special processing that might be happening in one of these seemingly boilerplate methods.

transform = type(self._transform)(self._transform, operator.truediv, other, reverse=True)
return self._clone(transform)

def plot(self, *args, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a docstring explaining why plot needed this special code? Does it mean that .interactive won't work with other .plot-API packages like cufflinks/pdvega, and/or related packages like Plotly Express?

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'll add a comment, and no. It's really just matplotlib which has this implicit state nonsense, which requires me to work around. I also still have to add special handling for Pandas plot engines.

hvplot/interactive.py Outdated Show resolved Hide resolved
elif loc in ('left_top', 'right_top'):
widgets = Column(widget_box, VSpacer())
elif loc in ('left_bottom', 'right_bottom'):
widgets = Column(VSpacer(), widget_box)
Copy link
Member

Choose a reason for hiding this comment

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

Seems like this layout code ought to be pulled out into its own function to make this method shorter and easier to follow. Plus this functionality seems similar to interact, DynamicMaps, and other cases where widgets are generated and added to a layout, and it would be nice to have a consistent approach for controlling those.

_patch_interactive.__doc__ = Interactive.__call__.__doc__
interactive_prop = property(_patch_plot)
setattr(pd.DataFrame, name, interactive_prop)
setattr(pd.Series, name, interactive_prop)
Copy link
Member

@jbednar jbednar Aug 31, 2020

Choose a reason for hiding this comment

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

Is there a supported way to make the interactive support available without import hvplot.pandas or similar sideeffect-y imports? Personally, I'll use import hvplot.pandas, but I'm curious if there is a more awkward but less magic alternative, either to explicitly perform the monkeypatching or to have a non-monkeypatch alternative (e.g. a global function instead of a method).

Copy link
Member Author

Choose a reason for hiding this comment

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

Interactive(ds) will work, I can also provide a helper to do the monkey-patching manually.

@martindurant
Copy link

I want to include these in Intake plot specs, so that source.plot() can produce interactive things!
Any thoughts on how that might happen?

@jbednar
Copy link
Member

jbednar commented Sep 17, 2020

I want to include these in Intake plot specs, so that source.plot() can produce interactive things!
Any thoughts on how that might happen?

Good question. Intake plot specs are YAML, like:

      plots:
        dropoff_scatter:
          kind: scatter
          x: dropoff_x
          y: dropoff_y
          datashade: True
          width: 800
          height: 600

I would guess that it's already possible to get some limited interactivity, e.g. by adding groupby: time to generate a HoloMap over time. But it would take some brainstorming to figure out how to support interactivity in the more general sense of this PR, e.g. to get a widget to select the column to plot as x and y, or to use for the value of datashade here.

Maybe some syntax like x: select [dropoff_x, pickup_x] or datashade: boolean [True]" or width: integer [1, 800, 2000]? It gets tricky even in this simple example, though, because x and y should be selected together, leading to wanting a selection [dropoff, pickup] and x: select [dropoff, pickup]+_x and y: ...somehow same widget as x ...+_y. Obviously a declarative spec can't reasonably handle all the cases, but maybe there's a feasible and valuable subset.

@martindurant
Copy link

Something like this?

slider = pn.widgets.IntSlider(name='time', start=0, end=10)
ds.air.interactive.isel(time=slider).plot()

->

plots:
  example:
    kind: pipeline
    pipeline:
      - getattr: air
      - isel:
        time:
          widget: IntSlider
          name: time
          start: 0
          end: 10
    plotargs: {}

For the API, it'd also be great to be able to call with some value for the widgets to get the output data that would be plotted. That would also cover the "process my data in a pipeline" request that we've wondered about for a while (i.e., use this model even when there are no interactive elements).

Of course, this is quite long and verbose, and maybe doesn't all belong in the "plots" section, especially if we want to make the derived data available. In short, I think it's all doable, we just have to decide what feels convenient for the user.

@coveralls
Copy link

coveralls commented Nov 14, 2020

Coverage Status

Coverage decreased (-5.2%) to 75.302% when pulling 90d8bde on interactive_api into c6aaa64 on master.

@philippjfr philippjfr merged commit a4fb538 into master Nov 14, 2020
@maximlt maximlt deleted the interactive_api branch November 18, 2021 08:31
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.

None yet

4 participants