Skip to content
This repository has been archived by the owner on Nov 28, 2019. It is now read-only.

Added View parameters and widgets #27

Merged
merged 8 commits into from
Feb 23, 2017
Merged

Added View parameters and widgets #27

merged 8 commits into from
Feb 23, 2017

Conversation

philippjfr
Copy link
Member

@philippjfr philippjfr commented Feb 16, 2017

This PR adds the concept of Output parameters and widgets to paramnb. Unlike the other parameter types Output widgets do not respond to changes on the frontend instead the corresponding parameters have callbacks which are triggered when they are updated. This means that the display can be updated by setting the parameter, allowing plots to be switched in response to parameter changes.

Output parameters differ from other parameters in two main ways:

  1. They have a callback attribute, which allows paramnb to install itself as a callback, whenever the parameter value changes the callback is called and the ipywidgets traitlet is updated along with it.

  2. They have a render method, which allows rendering the current parameter value to HTML. This allows having custom output parameters like HoloViewsOutput, which automatically renders any HoloViews object to HTML.

Simple HTML example:

class Example(param.Parameterized):
    
    a = param.Number(1, bounds=(0, 10))
    
    output = paramnb.HTMLOutput()
    
    def update(self, **kwargs):
        self.output = pd.DataFrame(np.random.rand(10,2)*self.a).to_html()

example = Example()
paramnb.Widgets(example, on_init=True, callback=example.update)

screen shot 2017-02-16 at 12 44 58 pm

class Example(param.Parameterized):
    
    a = param.Number(1, bounds=(0, 10))
    
    output = paramnb.HoloViewsOutput()
    
    def update(self, **kwargs):
        self.output = hv.Curve(np.random.rand(10)+self.a)

example = Example()
paramnb.Widgets(example, on_init=True, callback=example.update)

screen shot 2017-02-16 at 12 45 45 pm 1

To do:

  • Factor out holoviews plot size finding code to HoloViews renderers
  • Separate input and output widgets into separate containers

renderer = hv.Store.renderers[backend]
plot = renderer.get_plot(value)
plot.initialize_plot()
size = (plot.state.plot_width, plot.state.plot_height)
Copy link
Member Author

Choose a reason for hiding this comment

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

This should use renderer.get_size (needs changes in holoviews).

@jbednar
Copy link
Member

jbednar commented Feb 16, 2017

Cool!

This functionality might be appropriate to move to a submodule, so that it's accessed as something like paramnb.output.HTML, instead of something mixed in with the other paramnb functionality. It works very differently, and I think it could be confusing otherwise.

Is Output the right terminology here? @jlstevens and I have recently discussed adding output type specification to param, to allow a programmer to declare the result type of an operation like __call__, and I'm not sure whether that functionality or what's in this PR is a better owner of the term "output".

@philippjfr
Copy link
Member Author

philippjfr commented Feb 16, 2017

This functionality might be appropriate to move to a submodule

Sure.

Is Output the right terminology here? @jlstevens and I have recently discussed adding output type specification to param, to allow a programmer to declare the result type of an operation like call, and I'm not sure whether that functionality or what's in this PR is a better owner of the term "output".

Up to you, I make the distinction here between input and output widgets. Regular params are treated as input widgets and therefore respond to UI events changing the trait, while "output" params respond when you set the parameter value on the backend, by pushing that change to the frontend.

@jlstevens
Copy link
Member

Maybe it is more about viewing data?

@philippjfr
Copy link
Member Author

Once holoviz/holoviews#1140 is merged I can tidy this PR up.

@philippjfr
Copy link
Member Author

Maybe it is more about viewing data?

Sounds reasonable, HTMLView and HoloView because HoloViewsView seems a tad redundant?

@jbednar
Copy link
Member

jbednar commented Feb 21, 2017

You mean paramnb.view.HTML and paramnb.view.Holo?

@jlstevens
Copy link
Member

jlstevens commented Feb 21, 2017

Just one other suggestion: paramnb.view.HView.

Not that great either to be honest...but paramnb.view.HTML seems fine to me (I think having it in view may be sufficient without needing the suffix as well).

@philippjfr
Copy link
Member Author

Ended up going with HTML and HView and added a small notebook with examples for both. Ready for review/merge, then I'm probably going to release paramNB v1.1.

@philippjfr philippjfr changed the title Added Output parameters and widgets Added View parameters and widgets Feb 22, 2017
paramnb/view.py Outdated
"""
Output parameters allow representing some output to be displayed.
Output parameters may have a callback, which is called when a new
value is set on the parameter. Additionally they should implement
Copy link
Member

Choose a reason for hiding this comment

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

Docstring may need updating to match new "view" module naming.

paramnb/view.py Outdated

class HView(_View):
"""
HView is an View parameter meant for displaying HoloViews objects.
Copy link
Member

Choose a reason for hiding this comment

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

"is a View"

paramnb/view.py Outdated
HView is an View parameter meant for displaying HoloViews objects.
In combination with HoloViews streams this parameter may be used
to build complex dashboards.
"""
Copy link
Member

Choose a reason for hiding this comment

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

Can the docstring somehow mark this functionality as optional, making it clear paramnb does not depend on hv? I'm not sure if this functionality belongs here or in hv, but it seems like it's cleaner here? It's tricky to have things like this that link the two unrelated projects together so closely.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, the only reason why I've included it is because it handles stuff which would be a pain otherwise, specifically correctly setting the size of the container.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm. I wonder if that could be generalized, such that paramnb supports things that can return the size of the container, and holoviews provides an object with such capability, and these two just happen to match? That way paramnb doesn't know about hv, and hv doesn't know about paramnb, but they still work well together, and other non-hv packages could also work well with paramnb in the same way?

Copy link
Member

Choose a reason for hiding this comment

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

The idea is just to be extremely explicit about what we assume about this external object, and for hv to be extremely explicit about what that object provides, so that the point of intersection between the two projects is as small as possible and very clearly demarcated.

Copy link
Member Author

@philippjfr philippjfr Feb 22, 2017

Choose a reason for hiding this comment

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

Could also express it differently and have a generic View Parameter, which lets you supply a custom rendering function and returns the HTML or a tuple of the HTML and the size.

Then we'd just need to put this function somewhere:

def render(value):
     import holoviews as hv
     backend = hv.Store.current_backend
     renderer = hv.Store.renderers[backend]
     plot = renderer.get_plot(value)
     plot.initialize_plot()
     size = renderer.get_size(plot)
     return renderer.html(plot), size

Copy link
Member Author

@philippjfr philippjfr Feb 22, 2017

Choose a reason for hiding this comment

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

Or better yet we could define something like this on the holoviews Renderer class:

    def html_with_size(self, obj, fmt=None, css=None, comm=True, **kwargs):
        plot = self.get_plot(obj)
        self.initialize_plot()
        size = self.get_size(plot)
        html = self.html(plot, fmt, css, comm, **kwargs)
        return html, size

Then you could do:

paramnb.view.View(default=hv.Curve(...), renderer=hv.Store.renderers['bokeh'].html_with_size)

Copy link
Member

Choose a reason for hiding this comment

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

That sounds perfect, thanks!

@jbednar
Copy link
Member

jbednar commented Feb 22, 2017

The changes look good to me, though I did have trouble following some of the logic and thus I wasn't able to fully convince myself that I know precisely how it's working.

@philippjfr
Copy link
Member Author

The changes look good to me, though I did have trouble following some of the logic and thus I wasn't able to fully convince myself that I know precisely how it's working.

Here's a rough summary: The View parameters have a callback slot, which paramnb hooks onto. When a View parameter is set, the callback is triggered, the render method converts the parameter state to HTML and finally the traitlet and therefore the display is updated.

@jbednar
Copy link
Member

jbednar commented Feb 22, 2017

Sounds good in principle.

@philippjfr
Copy link
Member Author

@jbednar There's now only two View parameters, one for HTML output and one for Image (i.e. png output), and View parameters now accept a renderer function. The example in the notebook looks like this:

import holoviews as hv
import holoviews.plotting.mpl
renderer = hv.Store.renderers['matplotlib']

class Example(param.Parameterized):
    
    amplitude = param.Number(default=2, bounds=(2, 5))
    
    frequency = param.Number(default=2, bounds=(1, 10))
    
    element = param.ObjectSelector(default=hv.Curve,
                                   objects=[hv.Curve, hv.Scatter, hv.Area],
                                   precedence=0)
    
    output = paramnb.view.Image(renderer=lambda x: renderer(x)[0])

    def update(self):
        self.output = self.element(self.amplitude*np.sin(np.linspace(0, np.pi*self.frequency)))

example = Example()
paramnb.Widgets(example, callback=example.update, view_position='right')

screen shot 2017-02-23 at 3 12 15 am

@jbednar
Copy link
Member

jbednar commented Feb 23, 2017

Sometimes I feel like my only useful function in life is to poke a stick at you until what you come up with is sufficiently brilliant. Looks beautiful to me. Ready to merge?

@philippjfr
Copy link
Member Author

Sometimes I feel like my only useful function in life is to poke a stick at you until what you come up with is sufficiently brilliant.

:-)

Ready to merge?

Yes!

@jbednar jbednar merged commit 344886a into master Feb 23, 2017
@jbednar jbednar deleted the output_widgets branch February 23, 2017 13:43
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants