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

WebComponent pane to dramatically speed up creating new widgets in Panel #1122

Closed
wants to merge 61 commits into from

Conversation

MarcSkovMadsen
Copy link
Collaborator

@MarcSkovMadsen MarcSkovMadsen commented Feb 28, 2020

I believe I have created a proof of concept for how to use web components in panel to speed up the time to market for creating new widgets.

If you run something like

python -m panel serve 'panel\components\wired\buttons.py' --dev --show

Then you can explore the below

It's not all fully working yet - but the principles have been identified.

web_component_poc

@MarcSkovMadsen MarcSkovMadsen self-assigned this Feb 28, 2020
@MarcSkovMadsen MarcSkovMadsen changed the title Web component WebComponent pane to dramatically speed up creating new widgets in Panel Feb 28, 2020
@philippjfr
Copy link
Member

This is amazing. Just for some background for me. Is there some automated way we could generate a set of components for some library including dynamic generation of parameters? For example if the underlying library ships some JSON spec of the components we could use that to auto-generate the components.

@MarcSkovMadsen
Copy link
Collaborator Author

MarcSkovMadsen commented Feb 28, 2020

I've added 3 reviewers. Mostly because I think this is very important functionality (Otherwise please tell me why not) and if you have the time to review it a bit to improve and learn it would be so awesome.

This is work in progress and will be improved dramatically. I will let you now when it's ready for review.

@philippjfr , @jbednar , @xavArtley

@philippjfr
Copy link
Member

philippjfr commented Feb 28, 2020

As another maybe simpler approach instead of actually generating panel components from some JSON spec it might be nice to at least parse the HTML and make the attributes of the component accessible (and settable?) as a dictionary.

@xavArtley
Copy link
Collaborator

Using python setup.py develop raise typescript compiler errors
Capture d’écran de 2020-02-28 21-26-33

@philippjfr
Copy link
Member

Yeah the PR is still using the old deprecated approach to building extensions.

@xavArtley
Copy link
Collaborator

Was wondering if it was on purpose to not incrase size of the bundle

@philippjfr
Copy link
Member

No, I think @MarcSkovMadsen is still learning how to develop extensions and we still don't have a decent developer guide for it.

@xavArtley
Copy link
Collaborator

@MarcSkovMadsen
I allowed myself to make some changes so that the extension compiles

import panel as pn
pn.config.sizing_mode="stretch_width"

js = """
Copy link
Member

Choose a reason for hiding this comment

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

Would be nicer to append these urls to pn.config.js_files.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. Eventually they should.

If you take a look the value of js you can see only one of the two imports can be done via pn.config.js_files. In one of the imports I need to specify the type.

js = """
<script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.0.0/webcomponents-loader.js"></script>
<script type="module" src="https://unpkg.com/wired-elements@0.6.4/dist/wired-elements.bundled.js"></script>
"""

In general the import of js web component libraries does not always work for me. It needs some experimentation before it works. Sometimes I need to

  • add type="module"
  • add ``?module` to the url.
  • add additional javascript libraries than what is described on npm or in the documentation.

(I believe)

So I guess there will be a request to improve the pn.config.js_files at some stage. At a minimum be able to specify the type.

Copy link
Member

Choose a reason for hiding this comment

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

I was wondering about that. Will have to do some reading before making any concrete suggestions.

@MarcSkovMadsen
Copy link
Collaborator Author

MarcSkovMadsen commented Feb 29, 2020

Thanks for all your comments and improvements above. I have briefly read them but I wan't to comment how I expect to improve/ finalize the WebComponent concept.

The overall idea/ goal is to allow the user to inherit from the WebComponent and

  • add custom parameters to the child class
    • At a minimum parameters of type String, Number and Boolean should be supported
    • if a clicks= param.Number() attribute is added it will keep track of the number of clicks.
  • specify in an attributes_to_sync = param.Class() parameter (or similar) that keeps track of the attributes/ properties of the python ChildWebComponent and the html/ js web component to sync.
    • The user can specify a list if there is a 1-1 correspondance between all python ChildWebComponent attributes and all the html/ js web component attributes/ properties to sync.
    • The user can specify a dict if there is not a 1-1 correspondance between all python ChildWebComponent attributes and all the html/ js web component attributes/ properties to sync.
    • If a param.DataFrame attribute is specified the data will be transfered effectively back and forth.
      • An example could be a ChildWebComponent attribute table: param.DataFrame that would be made available for use on the js side as a variable table that is used like <my-html-tag table="[[table]]"></my-html-tag> or something similar. (I will have to experiment a bit to understand how this can be implemented).
      • I have an idea that this can be supported for one DataFrame attribute via a private _dataframe: param.DataFrame attribute. I don't know if it can be extended to multiple DataFrame attributes in a simple way.
      • Automatically parse any new html value received from the js side by a function called parse_html or similar. This function will parse the new html value and set the attributes of the ChildWebComponent. This function will use a parse_html_to_json function.
      • The user can override the parse_html function if they need more than simple 1-1 parsing.

I hope this will all work out.

The perspective is that it will be much easier to contribute new widgets to Panel

  • Ordinary users can stick to simple html and python for creating or contributing new widgets.
  • If you have access to a front end developer, then she does not need to learn of Bokeh or Panel. She can just convert an existing js, ts, react, vue or angular component into a web component or alternative develop the new component from scratch as a web component. The user can the integrate it into Panel them selves I believe.

@MarcSkovMadsen
Copy link
Collaborator Author

MarcSkovMadsen commented Feb 29, 2020

One more thing I would like to provide is a js_files: param.Dict (or similar) attribute where the user can specify which javascript files are need an any attributes like type that are needed.

js_files =[
    { "src": "https://unpkg.com/wired-elements@0.6.4/dist/wired-elements.bundled.js", 
      "type"= "module" }, 
}

The user still needs to remember to add them manually pn.config._js_files or semiautomatically via pn.extension("wired") or similar I belive.

@MarcSkovMadsen
Copy link
Collaborator Author

MarcSkovMadsen commented Feb 29, 2020

@philippjfr

After the model web_component.py file was modified in bcff9ae I get the error

image

How do I build and register the model before I run panel serve?

Before the the implementation used the __implementation__ attribute and the model was automatically compiled. Furthermore the web_component.ts file was using import { HTMLBox, HTMLBoxView } from "models/layouts/html_box" not import { HTMLBox, HTMLBoxView } from "@bokehjs/models/layouts/html_box"

I've tried the below without luck

$ python -m panel serve 'panel\tests\components\widgets\test_wired.py' --dev --show
2020-02-29 05:18:52,100 Starting Bokeh server version 1.4.0 (running on Tornado 6.0.3)
2020-02-29 05:18:52,100 User authentication hooks NOT provided (default user enabled)
2020-02-29 05:18:52,100 Bokeh app running at: http://localhost:5006/test_wired
2020-02-29 05:18:52,100 Starting Bokeh server with process id: 4368
Compilation failed:

panel/models/web_component.ts:1:38 - error TS2307: Cannot find module '@bokehjs/models/layouts/html_box'.

1 import { HTMLBox, HTMLBoxView } from "@bokehjs/models/layouts/html_box"
                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
panel/models/web_component.ts:4:20 - error TS2307: Cannot find module '@bokehjs/core/properties'.

4 import * as p from "@bokehjs/core/properties"
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~
panel/models/web_component.ts:16:14 - error TS2339: Property 'connect' does not exist on type 'WebComponentView'.

16         this.connect(this.model.properties.innerHTML.change, () => this.render())
                ~~~~~~~
panel/models/web_component.ts:22:18 - error TS2339: Property 'el' does not exist on type 'WebComponentView'.

22         if (this.el.innerHTML !== this.model.innerHTML) {
                    ~~
panel/models/web_component.ts:22:46 - error TS2339: Property 'innerHTML' does not exist on type 'WebComponent'.

22         if (this.el.innerHTML !== this.model.innerHTML) {
                                                ~~~~~~~~~
panel/models/web_component.ts:23:18 - error TS2339: Property 'el' does not exist on type 'WebComponentView'.

23             this.el.innerHTML = this.model.innerHTML; // Todo: Remove
                    ~~
panel/models/web_component.ts:23:44 - error TS2339: Property 'innerHTML' does not exist on type 'WebComponent'.

23             this.el.innerHTML = this.model.innerHTML; // Todo: Remove
                                              ~~~~~~~~~
panel/models/web_component.ts:24:45 - error TS2339: Property 'el' does not exist on type 'WebComponentView'.

24             this.webComponentElement = this.el.firstElementChild;
                                               ~~
panel/models/web_component.ts:45:24 - error TS2339: Property 'innerHTML' does not exist on type 'WebComponent'.

45         if (this.model.innerHTML !== this.webComponentElement.outerHTML) {
                          ~~~~~~~~~
panel/models/web_component.ts:46:24 - error TS2339: Property 'innerHTML' does not exist on type 'WebComponent'.

46             this.model.innerHTML = this.webComponentElement.outerHTML;
                          ~~~~~~~~~
panel/models/web_component.ts:70:24 - error TS2339: Property 'default_view' does not exist on type 'WebComponent'.

70         this.prototype.default_view = WebComponentView;
                          ~~~~~~~~~~~~
panel/models/web_component.ts:72:14 - error TS2339: Property 'define' does not exist on type 'typeof WebComponent'.

72         this.define<WebComponent.Props>({
                ~~~~~~
(.venv)
MASMA@PC70601 MINGW64 /c/repos/private/panel (web_component)
$ bokeh build
Working directory: C:\repos\private\panel
Not a bokeh extension. Quitting.
(.venv)
MASMA@PC70601 MINGW64 /c/repos/private/panel (web_component)
$ panel build
Working directory: C:\repos\private\panel
Not a bokeh extension. Quitting.
(.venv)
MASMA@PC70601 MINGW64 /c/repos/private/panel (web_component)
$ bokeh build "panel\models\web_component.py"
Working directory: C:\repos\private\panel\panel\models\web_component.py
Not a bokeh extension. Quitting.

@philippjfr
Copy link
Member

Did you compile the models with panel build panel?

@MarcSkovMadsen
Copy link
Collaborator Author

MarcSkovMadsen commented Feb 29, 2020

Nope. I forgot that was what I had to do.

Now it works. Thanks.

@MarcSkovMadsen
Copy link
Collaborator Author

MarcSkovMadsen commented Mar 15, 2020

Added echarts/ pycharts as an example WebComponent.

Really easy to support communication from server to client. The other way is not supported (yet). And the data transfer is a simple Dictionary not using the more efficient (?) ColumnDataSource.

HTML = """
    <div class="echart" style="width:100%;height:100%;"></div>
    <script type="text/javascript">
        var myScript = document.currentScript;
        var myDiv = myScript.parentElement.firstElementChild;
        var myChart = echarts.init(myDiv);
        myDiv.eChart = myChart;
        Object.defineProperty(myDiv, 'option', {
           get: function() { return null; },
           set: function(val) { this.eChart.setOption(val); this.eChart.resize();}
        });
        Object.defineProperty(myDiv, 'resizes', {
           get: function() { return null; },
           set: function(val) { this.eChart.resize();}
        });
    </script>"""

class EChart(pn.pane.WebComponent):
    html = param.String(HTML)
    properties_to_watch = param.Dict({"option": "option", "resizes": "resizes"})

    echart = param.Parameter()
    option = param.Dict()
    resizes = param.Integer()


    def __init__(self, **params):
        if "echart" in params:
            params["option"] = self._to_echart_dict(params["echart"])
        super().__init__(**params)

    @classmethod
    def _to_echart_dict(cls, echart):
        if isinstance(echart, dict):
            return echart
        if 'pyecharts' in sys.modules:
            import pyecharts
            if isinstance(echart, pyecharts.charts.chart.Chart):
                return json.loads(echart.dump_options())

        return {}

    @param.depends("echart", watch=True)
    def update(self):
        self.option = self._to_echart_dict(self.echart)

This only thing I can't get working is resizing. Its like running the eChart.resize() has no effect when done during render() of the bokeh model. But if I run it later it works.

@philippjfr or @xavArtley Any idea how to get this working?

echarts

You can run the example using

python -m panel serve 'panel\tests\test_echart.py' --dev --show

@xavArtley
Copy link
Collaborator

May be like for the vtk panel you have to override after_layout method and call the resize method in it

@MarcSkovMadsen
Copy link
Collaborator Author

Thanks @xavArtley . That did the trick.

echarts

@MarcSkovMadsen
Copy link
Collaborator Author

I cannot find resize functionality on the Google Model Viewer so i've created a support/ feature request here google/model-viewer#1088

modelviewr

@MarcSkovMadsen
Copy link
Collaborator Author

Just for the record. The basic functionality for the perspective-viewer has been implemented and is working well. The things that I would expect not working are streaming and getting selected rows back. But the rest I would expect to work just fine.

perspective

@MarcSkovMadsen
Copy link
Collaborator Author

We've discussed to refactor this pull request into such that

  • The WebComponent becomes a part of the panel package
  • The Perspective, echarts/ pycharts, model-viewer, wiredjs implementations are moved to the examples gallery.

@philippjfr . Could you please confirm this is what you want? (It will require some work on my side, so please confirm).

@philippjfr
Copy link
Member

Could you please confirm this is what you want? (It will require some work on my side, so please confirm).

Yes, I think this makes the most sense. As we refine the WebComponent model and the examples we can eventually consider migrating them into Panel itself or creating one or more panel extension packages.

@philippjfr
Copy link
Member

Superseded by #1122

@philippjfr philippjfr closed this Apr 24, 2020
@ltalirz
Copy link
Contributor

ltalirz commented Jul 14, 2020

In case others stumble across this, it was meant to read "Superseded by #1252"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: WIP type: feature A major new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants