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

jslink not working for dropdown widget, why? #2382

Open
kgiacobbi opened this issue Apr 17, 2019 · 24 comments
Open

jslink not working for dropdown widget, why? #2382

kgiacobbi opened this issue Apr 17, 2019 · 24 comments
Milestone

Comments

@kgiacobbi
Copy link

Hello,

I created a leaflet map on my jupyterlab notebook with WidgetsControl.

I added a widgets.dropdown that allows me to change the layer using a widgets.link that works.

Now, I want to put my map in an HTML page by changing the widgets.link by a widgets.jslink but an error occurs.
TypeError: Dropdown.value can not be synced

import ipywidgets as wid
import ipyleaflet as lf
from ipywidgets.embed import embed_minimal_html

nasa_layer_1 = lf.basemap_to_tiles(lf.basemaps.NASAGIBS.ModisTerraTrueColorCR, "2019-04-15")
nasa_layer_2 = lf.basemap_to_tiles(lf.basemaps.NASAGIBS.ModisTerraTrueColorCR, "2018-04-13")

layers = {}
layers['nasa1'] = (nasa_layer_1,)
layers['nasa2'] = (nasa_layer_2,)


m = lf.Map(
    center=(50.6330372,3.0178653),
    scroll_wheel_zoom = True,
    zoom=2)



''''
Dropdown to select the layer. The layers have to be in the dictionary 'layers'.
'''
layer_selector = wid.Dropdown(
    options=layers,
    description='Layer :',
)

wid.link((layer_selector,'value'),(m,'layers'))

#Control display on the map 
layer_selector_control = lf.WidgetControl(widget=layer_selector, position='bottomright')

m.add_control(layer_selector_control)

embed_minimal_html('export.html', views=[m], title='Widgets export')
m

I would like to know if it's possible or not to do this.

Thanks.

@jasongrout
Copy link
Member

The Dropdown index and labels are synced to javascript, but not the value. Sometimes the value can be a python object which cannot be transferred over to javascript, so you don't have access to the value on the javascript side.

Does jslinking the label work?

CC also @maartenbreddels

@jasongrout jasongrout added this to the Reference milestone Apr 17, 2019
@kgiacobbi
Copy link
Author

I tried to jslink the label with another widget and i got same error

TypeError                                 Traceback (most recent call last)
<ipython-input-62-05c13a9226e1> in <module>
     12 )
     13 
---> 14 link_layer = wid.jslink((layer_selector,'label'),(layer_selector2,'label'))
     15 
     16 #Control display on the map

/home/applis/anaconda/envs/py3v19.01/lib/python3.6/site-packages/ipywidgets/widgets/widget_link.py in jslink(attr1, attr2)
     73     >>> c = link((widget1, 'value'), (widget2, 'value'))
     74     """
---> 75     return Link(attr1, attr2)
     76 
     77 

/home/applis/anaconda/envs/py3v19.01/lib/python3.6/site-packages/ipywidgets/widgets/widget_link.py in __init__(self, source, target, **kwargs)
     50         kwargs['source'] = source
     51         kwargs['target'] = target
---> 52         super(Link, self).__init__(**kwargs)
     53 
     54     # for compatibility with traitlet links

/home/applis/anaconda/envs/py3v19.01/lib/python3.6/site-packages/ipywidgets/widgets/widget.py in __init__(self, **kwargs)
    409         """Public constructor"""
    410         self._model_id = kwargs.pop('model_id', None)
--> 411         super(Widget, self).__init__(**kwargs)
    412 
    413         Widget._call_widget_constructed(self)

/home/applis/anaconda/envs/py3v19.01/lib/python3.6/site-packages/traitlets/traitlets.py in __init__(self, *args, **kwargs)
    995             for key, value in kwargs.items():
    996                 if self.has_trait(key):
--> 997                     setattr(self, key, value)
    998                 else:
    999                     # passthrough args that don't set traits to super

/home/applis/anaconda/envs/py3v19.01/lib/python3.6/site-packages/traitlets/traitlets.py in __set__(self, obj, value)
    583             raise TraitError('The "%s" trait is read-only.' % self.name)
    584         else:
--> 585             self.set(obj, value)
    586 
    587     def _validate(self, obj, value):

/home/applis/anaconda/envs/py3v19.01/lib/python3.6/site-packages/traitlets/traitlets.py in set(self, obj, value)
    557 
    558     def set(self, obj, value):
--> 559         new_value = self._validate(obj, value)
    560         try:
    561             old_value = obj._trait_values[self.name]

/home/applis/anaconda/envs/py3v19.01/lib/python3.6/site-packages/traitlets/traitlets.py in _validate(self, obj, value)
    589             return value
    590         if hasattr(self, 'validate'):
--> 591             value = self.validate(obj, value)
    592         if obj._cross_validation_lock is False:
    593             value = self._cross_validate(obj, value)

/home/applis/anaconda/envs/py3v19.01/lib/python3.6/site-packages/traitlets/traitlets.py in validate(self, obj, value)
   2240             return value
   2241 
-> 2242         value = self.validate_elements(obj, value)
   2243 
   2244         return value

/home/applis/anaconda/envs/py3v19.01/lib/python3.6/site-packages/ipywidgets/widgets/widget_link.py in validate_elements(self, obj, value)
     31             raise TypeError("No such trait: %s" % trait_repr)
     32         elif not trait.get_metadata('sync'):
---> 33             raise TypeError("%s cannot be synced" % trait_repr)
     34         return value
     35 

TypeError: Dropdown.label cannot be synced

But this time, it's Dropdown.label which cannot be synced.
Too bad that a simple String label can not be synchronized.

However, I can jslink the index. I will try something else.

@jetheurer
Copy link

Is there a solution for this? Can we use the index to reference the original list within the Text widget?

@jasongrout
Copy link
Member

First, note that this only affects jslink. linking on the python side should work fine.

Can we use the index to reference the original list within the Text widget?

@maartenbreddels had the same idea a while ago. I'm not sure it was pursued.

TypeError: Dropdown.label cannot be synced

It does look like perhaps this is an overly restrictive check. I wonder what happens if we remove the check that we are only jslinking synced attributes, and trust the user to link things that make sense.

@jetheurer
Copy link

@jasongrout My goal is to use the widget with jupyter-sphinx.

I'm not sure I understand what you mean by "linking on the python side". Would I use widgets.link

drop = widgets.Dropdown(options=[1,2])
text = widgets.Text()
l = widgets.link((text, 'value'), (drop, 'value'))
display(drop,text)

@jetheurer
Copy link

I believe I need to link on the js side in order to achieve my goal of using this widget in a static html page

@jasongrout
Copy link
Member

Yes, it does appear that do need jslink for it to work with jupyter-sphinx. Then I think a next step would be to think through the ramifications of removing that check apparently in the link code of only linking traits that are synced, or explore @maartenbreddels idea from a long time about a way to use the index to look up in the list of labels.

@jasongrout jasongrout reopened this Aug 30, 2019
@jasongrout
Copy link
Member

Reopening since it looks like the discussion may be leading to a solution for this.

@jetheurer
Copy link

@jasongrout It seems like @maartenbreddels's idea about using the index to look up labels is the quickest short term solution.

Could you point me to the area in the code where value gets updated? I was thinking I could subclass Text and overwrite one of the methods.

@jasongrout
Copy link
Member

Is this what you are looking for?

_handle_change() {
this.model.set('index', this.listbox.selectedIndex === -1 ? null : this.listbox.selectedIndex);
this.touch();
}

@jetheurer
Copy link

jetheurer commented Aug 30, 2019

@jasongrout I thought I could do something like this:

from ipywidgets.widgets import Textarea
from ipywidgets.widgets.widget_string import _String
import ipywidgets as widgets

OPTIONS=[4,5,6,7,8]

class FunctionText(Textarea):
    def __init__(self, value=None, method=lambda x:x, **kwargs):
        if value is not None:
            kwargs['value'] = method(value)
        super(_String, self).__init__(**kwargs)
        
def method(index):
    return str(OPTIONS[int(index)])


drop = widgets.Dropdown(options=OPTIONS)
text = FunctionText(value='4', method=method)
l = widgets.jslink((text, 'value'), (drop, 'index'))
display(drop,text)

But on second thought, the subclassing would probably have to be on the javascript side and is likely more complex than I was hoping!

@maartenbreddels
Copy link
Member

A kind of general widget that I once created that can take a list of widgest, and an index is here: https://github.com/jupyter-widgets/ipywidgets/pull/2232/files#diff-15c92b08e71d4d11cfc6f037268c839cR76

I've always been a fan of having a more rich jslink. I think this should first live outside of core ipywidgets, and could be very expressive with @martinRenou 's py2vega!

@jetheurer
Copy link

jetheurer commented Aug 31, 2019

Here is a quick hack to the existing classes and methods:

from ipywidgets import RadioButtons, Text, Select, Dropdown, jslink
from ipywidgets.widgets.widget import Widget, register, widget_serialization
from ipywidgets.widgets.widget_core import CoreWidget
from IPython.display import  display

from traitlets import Unicode, Tuple, Instance, TraitError

Dropdown.value.tag(sync=True)

selection = Dropdown(options=[('1','a'),('2','b'),('3','c')])
text = Text(value='a')
l = jslink((text, 'value'), (selection, 'value'))
display(selection,text)

@jetheurer
Copy link

Above works fine in a notebook, but not in a static page.

@jetheurer
Copy link

Also, relevant to this discussion: if jslink could take .a parameter like transformation which was a Python method, that would also solve the issue:

@maartenbreddels
Copy link
Member

I like your hack :) my guess is that it doesn't work in the static version because value does not get serialized in the json.

The issue back then is we didn't know how we would evaluate the transformation, I think with py2vega, we have a much better story now (without allowing arbitrary code execution in the frontend/browser).

@jetheurer
Copy link

jetheurer commented Sep 1, 2019

Actually, I'm not sure what to modify in the serialization? Is there a quick hack for this too?

@jetheurer
Copy link

Is it possible to enumerate and pre-compute all the possible states of the widget? Is that what py2vega does?

@jetheurer
Copy link

@maartenbreddels, @jasongrout I'd like to help this issue along. Is there something I can do to move it forward? Or can you point me in the right direction on how to approach this and I'll take a stab at a PR?

@vidartf
Copy link
Member

vidartf commented Sep 16, 2019

@jetheurer I see two possible options.

  1. Create a richer jslink alternative. I think this could be a decent workflow to achieve that, but I haven't spent very long thinking about it:

    • Start a new package that inherits / copies code from ipywidgets implementation of jslink.
    • Create a new link function (rich_jslink, something more clever?).
    • Have the new function take more clever arguments for the "name" attribute:
      • Support indexing: rich_jslink((widget_a, 'my_list[3]'), (widget_b, 'value'))
      • Support combining different values in a reference: rich_jslink((widget_a, 'mylist[myindex]'), (widget_b, 'value'))
      • Ideally create a better / richer way of combining widget attributes, e.g. by binding a widget reference to a name that you can us in the reference, or by using string formatting and inputting widget IDs...
    • Publish the package.
    • Merge into this repo in the future if/when mature.
  2. Create a tailor made solution for dropdowns:

    • Create a new widget package.
    • Create a new (model) widget that has two traits: selector and value
    • When the selector is set, have the JS code set the value trait by getting selector.get('options')[selectior.get('index')].
    • [Publish the package]

@maartenbreddels
Copy link
Member

Some useful libraries might be (apart from vega/py2vega):

@kingjr
Copy link

kingjr commented Aug 5, 2020

@vidartf +1 for option 2

@vidartf
Copy link
Member

vidartf commented Aug 6, 2020

I would mark that as "help needed", as I don't have enough capacity to work on that at the moment :)

@maartenbreddels
Copy link
Member

My approach now is to create a new widget using ipyvuetify template system:
https://github.com/mariobuikhuizen/ipyvuetify
It's not yet well documented, but you can find plenty examples in the issues, or at https://github.com/glue-viz/glue-jupyter/
There is also an example you can run from mybinder

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

No branches or pull requests

6 participants