IPython Widget #5754

Merged
merged 4 commits into from May 4, 2016

Conversation

Projects
None yet
Contributor

blink1073 commented Dec 27, 2015

Fixes #4582. Fixes #5111. Fixes #4940. Fixes #5219. Fixes jupyter/notebook#244.

Makes the nbagg canvas an IPython Widget. mpl.js is wrapped in a define() and installed as an nbextension alongside nbagg_mpl.js.

ipython_mpl_widget_interact

Contributor

blink1073 commented Dec 27, 2015

I'll back out the inadvertent changes to the UAT notebook in a subsequent commit. (done).

Contributor

blink1073 commented Dec 27, 2015

Added toolbar.

Contributor

blink1073 commented Dec 27, 2015

We can't install the nb extension from a wheel, so I'd recommend the following: check for the existence of the nb extension when importing backend_nbagg.py. If it does not exist, install it in the user directory. Provide a function to manually update the nb extension, and call that automatically when installing from source or from conda.

Contributor

blink1073 commented Dec 27, 2015

The last commit implements the idea.

Contributor

blink1073 commented Dec 27, 2015

The UAT passes now, except for reshow, is that still needed @tacaswell?

Contributor

blink1073 commented Dec 27, 2015

Interact now works with nbagg (see demo above)!

Contributor

blink1073 commented Dec 27, 2015

Note to self: add an explicit "export" button instead of spitting out an image automatically when the widget is closed. (done).

@blink1073 blink1073 commented on the diff Dec 27, 2015

lib/matplotlib/backend_bases.py
@@ -2148,7 +2148,8 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w',
origfacecolor = self.figure.get_facecolor()
origedgecolor = self.figure.get_edgecolor()
- self.figure.dpi = dpi
+ if dpi != 'figure':
@blink1073

blink1073 Dec 27, 2015

Contributor

This avoids an error I saw.

@mdboom

mdboom Dec 29, 2015

Owner

FWIW: This was fixed recently by #5720 (in the exact same way).

Cool!

@mdboom mdboom commented on the diff Dec 29, 2015

lib/matplotlib/backends/backend_nbagg.py
@@ -98,6 +103,7 @@ def connection_info():
'zoom_to_rect': 'fa fa-square-o icon-check-empty',
'move': 'fa fa-arrows icon-move',
'download': 'fa fa-floppy-o icon-save',
+ 'export': 'fa fa-file-picture-o icon-picture',
@mdboom

mdboom Dec 29, 2015

Owner

Can you elaborate on what "export" does? It inserts a static image into the notebook?

@mdboom

mdboom Dec 29, 2015

Owner

Why is this preferable to the automatic replacement with a static image upon saving? Won't users often forget to push this button?

@blink1073

blink1073 Dec 30, 2015

Contributor

Yes, it inserts a static image into the notebook. I'm looking at this from the point of view where the canvas is one of many widgets in a gui, and you want explicit control over export behavior.

@pelson

pelson Jan 3, 2016

Member

Maybe we should think about the name. Export makes me think of "save" - I guess this is a kind of "snapshot" type behaviour, right?

@mdboom mdboom commented on the diff Dec 29, 2015

lib/matplotlib/backends/backend_nbagg.py
+ if (check_nbextension('matplotlib') or
+ check_nbextension('matplotlib', True)):
+ return
+
+ # Make a temporary directory so we can wrap mpl.js in a requirejs define().
+ tempdir = mkdtemp()
+ path = os.path.join(os.path.dirname(__file__), "web_backend")
+ shutil.copy2(os.path.join(path, "nbagg_mpl.js"), tempdir)
+
+ with open(os.path.join(path, 'mpl.js')) as fid:
+ contents = fid.read()
+
+ with open(os.path.join(tempdir, 'mpl.js'), 'w') as fid:
+ fid.write('define(["jquery"], function($) {\n')
+ fid.write(contents)
+ fid.write('\nreturn mpl;\n});')
@mdboom

mdboom Dec 29, 2015

Owner

What are the downsides with doing this in the file we ship directly (rather than editing it on the fly here)? It does need to work in a "no IPython" context as well, but we do already use JQuery there.

@blink1073

blink1073 Dec 30, 2015

Contributor

There is no downside AFAIK, I was just shying away from making any changes to WebAgg.

@mdboom

mdboom Dec 30, 2015

Owner

Let's go ahead and make the change (and make whatever change it takes to make WebAgg work with a jquery module). This adds a fair bit of complexity that we otherwise don't need.

@blink1073

blink1073 Dec 30, 2015

Contributor

We still need to create the temporary directory either way, install_nbextension grabs everything in the directory. The alternative is to make a new directory with just those two files and wrap mpl.js in a define there.

@WeatherGod

WeatherGod Dec 30, 2015

Member

I think some packages take advantage of an "egg cache" concept in distutils
to deal with issues like this. Never done it myself, but I sometimes find
such directories after using certain packages.
On Dec 29, 2015 7:26 PM, "Steven Silvester" notifications@github.com
wrote:

In lib/matplotlib/backends/backend_nbagg.py
#5754 (comment):

  • if (check_nbextension('matplotlib') or
  •        check_nbextension('matplotlib', True)):
    
  •    return
    
  • Make a temporary directory so we can wrap mpl.js in a requirejs define().

  • tempdir = mkdtemp()
  • path = os.path.join(os.path.dirname(file), "web_backend")
  • shutil.copy2(os.path.join(path, "nbagg_mpl.js"), tempdir)
  • with open(os.path.join(path, 'mpl.js')) as fid:
  •    contents = fid.read()
    
  • with open(os.path.join(tempdir, 'mpl.js'), 'w') as fid:
  •    fid.write('define(["jquery"], function($) {\n')
    
  •    fid.write(contents)
    
  •    fid.write('\nreturn mpl;\n});')
    

We still need to create the temporary directory either way,
install_nbextension grabs everything in the directory. The alternative is
to make a new directory with just those two files and wrap mpl.js in a
define there.


Reply to this email directly or view it on GitHub
https://github.com/matplotlib/matplotlib/pull/5754/files#r48580693.

@mdboom

mdboom Dec 30, 2015

Owner

This is probably the crux of my misunderstanding. Where is the nbextension being installed to and for what purpose? Presumably the content of the extension is already installed alongside matplotlib...

@blink1073

blink1073 Dec 30, 2015

Contributor

The current method is to inject the javascript directly into the notebook. The preferred method for IPython third-party widgets is to store the javascript as an nbextension that is loaded by the notebook server, and it does not get stored with the notebook document. This is served from a central location:

$ ls /Users/ssilvester/Library/Jupyter/nbextensions/
bqplot     matplotlib qgridjs
@mdboom

mdboom Dec 30, 2015

Owner

I see. And Jupyter has no method to present it with Javascript content short of installing it to a specific directory? Would Jupyter be open to a patch to fix that? That seems like a usability speedbump/regression from status quo that would be nice to avoid.

@SylvainCorlay

SylvainCorlay Dec 30, 2015

Member

The long term idea for jupyter widgets is that the javascript to be loaded
should be specified by the kernel to the server in the form of the name
(and version) of an npm package (instead of a requirejs path). Eventually
the notebook server will need to have the corresponding node package
available or to be able to install it via some ux...

Since the notebook server and the kernel may be in different locations, it
is not possible to know where or how to install the js when installing the
python package.
On Dec 30, 2015 5:43 PM, "Michael Droettboom" notifications@github.com
wrote:

In lib/matplotlib/backends/backend_nbagg.py
#5754 (comment):

  • if (check_nbextension('matplotlib') or
  •        check_nbextension('matplotlib', True)):
    
  •    return
    
  • Make a temporary directory so we can wrap mpl.js in a requirejs define().

  • tempdir = mkdtemp()
  • path = os.path.join(os.path.dirname(file), "web_backend")
  • shutil.copy2(os.path.join(path, "nbagg_mpl.js"), tempdir)
  • with open(os.path.join(path, 'mpl.js')) as fid:
  •    contents = fid.read()
    
  • with open(os.path.join(tempdir, 'mpl.js'), 'w') as fid:
  •    fid.write('define(["jquery"], function($) {\n')
    
  •    fid.write(contents)
    
  •    fid.write('\nreturn mpl;\n});')
    

I see. And Jupyter has no method to present it with Javascript content
short of installing it to a specific directory? Would Jupyter be open to a
patch to fix that? That seems like a usability speedbump/regression from
status quo that would be nice to avoid.


Reply to this email directly or view it on GitHub
https://github.com/matplotlib/matplotlib/pull/5754/files#r48614908.

@blink1073

blink1073 Dec 30, 2015

Contributor

Maybe this particular widget should be special-cased and added to ipywidgets itself in the short term?

@blink1073

blink1073 Dec 30, 2015

Contributor

At least, the Javascript part.

@mdboom

mdboom Dec 30, 2015

Owner

Sorry -- I have the feeling I'm walking into a discussion that's already been hashed out and settled somewhere else, so apologies for that. If there's something you want to point me to to read about this, that would be great. Just to be clear, I'm concerned about the added packaging and installation complexity becoming a burden on matplotlib which is already overstretched with such things. Anything Jupyter can do to make that easier on our users would be great, though I doubt I'm versed enough yet to offer any suggestions... Conda and other packaging helps here, obviously, but that should never be a necessity.

Since the notebook server and the kernel may be in different locations, it is not possible to know where or how to install the js when installing the python package.

Certainly the notebook server is talking to the kernel though and could get Javascript content from there...

@mdboom

mdboom Dec 30, 2015

Owner

Maybe this particular widget should be special-cased and added to ipywidgets itself in the short term?

That's not ideal -- the Python and Javascript sides are pretty tightly coupled, by necessity. Very recent changes, such as optimizing the data bandwidth and handling HiDPI displays has required changes to both sides. And it's exactly this sort of "forced decoupling" from both sides that I'm hoping to avoid. Ideally, (and again, I'm not familiar with all the moving parts, so I'm speaking abstractly), Jupyter should get the Javascript content from the currently running matplotlib...

@blink1073

blink1073 Dec 30, 2015

Contributor

@mdboom, this is a thorny issue, one under active discussion: https://groups.google.com/forum/#!topic/jupyter/NDc9ktzACF0

@mdboom

mdboom Dec 30, 2015

Owner

Thanks for the link. And sorry to be annoying ;) This PR in general is very very welcome.

@pelson

pelson Jan 3, 2016

Member

the Python and Javascript sides are pretty tightly coupled, by necessity

In which case, we need to be particularly careful with versioning the js deployed. Are we putting ourselves into a position where we will be unable to run nbagg backends for distinct major versions of matplotlib if we install a single js component to the ipython extensions directory, or are we able to manage versioning too?

Following up, should we be deploying a mpl-<minor>.<major>.<minor>.<patch>.js javascript extension to protect us on this?

@mdboom mdboom commented on the diff Dec 29, 2015

lib/matplotlib/backends/backend_webagg_core.py
@@ -18,7 +18,7 @@
import io
import json
import os
-import time
+import datetime
@mdboom

mdboom Dec 29, 2015

Owner

Thanks for catching this one!

Owner

mdboom commented Dec 29, 2015

This is very cool. Thanks for taking this on.

Fixes #244

I don't see how this relates. Maybe wrong issue number?

We can't install the nb extension from a wheel, so I'd recommend the following: check for the existence of the nb extension when importing backend_nbagg.py. If it does not exist, install it in the user directory. Provide a function to manually update the nb extension, and call that automatically when installing from source or from conda.

Can you elaborate on this? It seems to add a lot of complexity for reasons that are lost to me, as someone who hasn't really followed Jupyter/IPython widgets. How does this work in the context where the matplotlib package is installed system-wide?

Contributor

blink1073 commented Dec 30, 2015

@mdboom, I removed the reference to 244, I can't find the issue I had meant to put there, it was referring to the fact that you could not "print" events to the notebook (as is now shown in the demo above).

For reference, installing bqplot is a two-step process, you install the source, and then install the nbextension. For a system-wide install, this could be done once. A conda recipe could load the extension in the current enviroment. Wheels are not allowed to execute code on installation, they just move source around to known Python locations.

https://github.com/bloomberg/bqplot#installation

Contributor

blink1073 commented Dec 30, 2015

Owner

mdboom commented Dec 30, 2015

Sorry, I still don't understand the need for the complexity of installing the Python code and Javascript code separately. Can't we get to the JavaScript code from where it's installed under the matplotlib package (as we do now)?

mdboom referenced this pull request in jupyter/notebook Dec 30, 2015

Closed

Improving 4.x nbextensions #878

@pelson pelson commented on an outdated diff Jan 3, 2016

lib/matplotlib/backends/web_backend/nbagg_mpl.js
-mpl.find_output_cell = function(html_output) {
@pelson

pelson Jan 3, 2016

Member

I'm glad to see the back of this function. 👍

Member

pelson commented Jan 3, 2016

Untested, but 👍 in principle. Now that we are benefiting from hooking into the widget framework, do we also get a plot when the notebook page is refreshed? It might be worth going through the UATs and adding a refresh test.

Contributor

ngoldbaum commented Jan 20, 2016

Just want to say that this is awesome, I'm glad progress is being made on #5111.

@mdboom I think you're interested in what I've proposed here: jupyter/notebook#116

Owner

mdboom commented Jan 21, 2016

@jdfreder: Thanks for the pointer. Indeed, it looks at first glance that jupyter/notebook#116 is moving in the direction that would really help us here. (It also helps me understand all the gory details and opposing requirements much better).

tacaswell added this to the 2.1 (next point release) milestone Jan 29, 2016

izaid commented Apr 28, 2016

Hey all. And directly pinging @blink1073 @SylvainCorlay @mdboom.

I'd really like to see this get merged into Matplotlib. I understand there are issues on the Jupyter side that may be holding this up.

I can devote some of my time (on the order of days, if necessary) to get this resolved. Can someone tell me what needs to be done and what I can do to move things forward?

Owner

tacaswell commented Apr 28, 2016

Did jupyter settle how to do package and distribute the js side of the widgets?

izaid commented Apr 28, 2016

@tacaswell I don't know the answer to that, and I'm not sure who to ask. Pinging @SylvainCorlay again (sorry), because I think he knows. I just want to volunteer my time and effort to finish this, and need some direction on what needs to be done.

If the packaging isn't solved, can we not just merge this as is and have a follow-up PR for that?

Member

fperez commented Apr 28, 2016

Pinging also @willingc & @JamiesHQ in case there's something in this discussion we can pick up regarding the documentation of how to deploy the extensions, so it can be on her radar. There's a chance the above link provided by @blink1073 is sufficient, but in case it leaves unanswered questions, we may want to fix that. The deployment of all the moving parts of extensions is a recurring question, so we want to make sure it's clearly documented.

willingc commented Apr 28, 2016 edited

@fperez Although I didn't read the full message tree, @blink1073's suggestion is the best doc we have at the moment. This should have how to handle Python and JS. Pinging @SylvainCorlay. If folks find stuff missing, please let me know. ipyleaflet by @ellisonbg would likely be helpful as well.

Member

fperez commented Apr 28, 2016

Great, thx @willingc. Just wanted your keen eyes on this one :)

Member

SylvainCorlay commented Apr 28, 2016

That could be a good sprint subject for next week.

  • Use the new jupyter-js-widgets infra
  • make a proper npm package for the matplotlib notebook backend
  • make it follow the scheme for static widgets on npmcdn...

izaid commented Apr 28, 2016

Alright, this all sounds great. Thanks everyone for weighing in.

I had a look at the "Distributing Jupyter Extensions" and don't have any questions so far. I'll take a stab at resolving this shortly, and will let you know if I do.

Contributor

blink1073 commented Apr 28, 2016

@SylvainCorlay, would that approach would still allow this widget to be used without an internet connection?

Member

SylvainCorlay commented Apr 28, 2016

Yeah, the npmcdn is only for embedding in static web pages... And even in that case, it checks locally if you have the required package by the html file.

Contributor

blink1073 commented Apr 28, 2016

Cool, thanks for the clarification.

izaid commented Apr 30, 2016 edited

Alright, I'm trying to get this done now. Basically, my understanding is that all we need to do is add a _jupyter_nbextension_paths() to matplotlib's __init__.py that lists mpl.js and nbagg_mpl.js. And we need to remove the nbinstall from backend_nbagg.py. That's all fine, it's not a big deal.

@blink1073 I had your PR working on my machine, but in the process of trying to achieve the above things broke. I very carefully removed, then reinstalled, Jupyter and your Matplotlib PR, but I'm now getting 404 GET /nbextensions/widgets/widgets/js/widget.js and 404 GET /nbextensions/widgets/widgets/js/manager.js when I try to use your Matplotlib widget. I'm on Jupyter 4.2, I don't know if that's related.

Does anyone know what that error means and how I can sort it out? I am in principle using a fresh installation of a Juypter and the original PR, that's all.

Edit: To be clear, ipywidgets works, but the Matplotlib widget does not.

Contributor

blink1073 commented Apr 30, 2016

I'm AFK right now but I believe you need to reinstall ipywidgets.

izaid commented Apr 30, 2016

Okay, but ipywidgets is working fine?

(And, I reinstalled it right now, just to be safe, and same issue. This is just a problem with the Matplotlib widget.)

Member

SylvainCorlay commented Apr 30, 2016

Irwin the path is now nbextensions/jupyter-js-widgets/extension.js.

I recommend you create a requirejs config map mapping jupyter-js-widgets
to this because it is the name of the npm module too.

You can check the extension.js in bqplot or pythreejs for examples of how
to do this.
On Apr 30, 2016 12:15 PM, "Irwin Zaid" notifications@github.com wrote:

Okay, but ipywidgets is working fine?

(And, I reinstalled it right now, just to be safe, and same issue. This is
just a problem with the Matplotlib widget.)


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#5754 (comment)

izaid commented Apr 30, 2016

Thanks @SylvainCorlay, the issue was indeed a difference between Jupyter 4.1 and 4.2. @blink1073's PR works in 4.1, not 4.2, because of the path differences you pointed out.

Should we be worrying about backwards compatibility here -- e.g., should Matplotlib be supporting both 4.1 and 4.2? Or is it sufficient to support only 4.2?

Owner

tacaswell commented Apr 30, 2016

This is a new feature that mpl has not shipped before we have no one who
expects this to keep working. Just target 4.2+ unless supporting both is
trivial.

On Sat, Apr 30, 2016, 13:38 Irwin Zaid notifications@github.com wrote:

Thanks @SylvainCorlay https://github.com/SylvainCorlay, the issue was
indeed a difference between Jupyter 4.1 and 4.2. @blink1073
https://github.com/blink1073's PR works in 4.1, not 4.2, because of the
path differences you pointed out.

Should we be worrying about backwards compatibility here -- e.g., should
Matplotlib be supporting both 4.1 and 4.2? Or is it sufficient to support
only 4.2?


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#5754 (comment)

Member

SylvainCorlay commented Apr 30, 2016 edited

I would write the code for 4.2 onward. ipywidgets 5.x targets notebook 4.2 - and this is the version where we stabilized the Javascript APIs into the npm package jupyter-js-widgets.

izaid commented Apr 30, 2016

New PR: #6355. Discussion can move there if this PR isn't going to be updated.

blink1073 added some commits Dec 26, 2015

@blink1073 blink1073 nbagg widget refactor
nbagg widget refactor

wip nbagg widget refactor

wip ipython widget

Display the static figure when closed

Revert changes to uat notebook

Add support for the toolbar

Fix embed logic and show() logic

Better handling of class variables

Clean up imports and add timer support

Add an nbinstall function

Implement resize on start.

Implement explicit export

IPython 3 compat

Remove debug in nbinstall

Simplify define()

Progress on distributing the js files

Reorganize javascript files

Update paths
3f42311
@blink1073 blink1073 Fix path and make mpl.js a UMD file
566e97a
Contributor

blink1073 commented May 2, 2016

@SylvainCorlay, I am actually at a loss at the moment. I'm going to have to leave this to you.

Contributor

blink1073 commented May 2, 2016

I thought I had it working at one point, but I've spent all morning on this and have not gotten it figured out.

izaid commented May 2, 2016

Yeah, I'm stumped here too. @SylvainCorlay, if you can drop in and save the day, that would be really cool.

Contributor

blink1073 commented May 2, 2016

Okay, I've almost got it sorted.

@blink1073 blink1073 Clean up the includes
de652b7
Contributor

blink1073 commented May 2, 2016 edited

The latest change allows it to work, but the extension needs to installed on a per environment basis and enabled for the user in order to allow different versions per environment IIUC.

@blink1073 blink1073 Update the package data
1ec611a

izaid commented May 2, 2016

I just confirmed that it works on my local version of Jupyter 4.2. This is really great, thanks for picking this up again!

Re: Local installation. Isn't that the model for Jupyter now?

QuLogic added the GUI/nbagg label May 2, 2016

izaid commented May 4, 2016

@tacaswell @blink1073 So, I've been using this branch for a few days now, and my understanding is it is done. Any chance we can get this merged into master? I wouldn't mind building a few things on top of it, but I'd rather do that after merge in separate, unrelated PRs.

Contributor

blink1073 commented May 4, 2016

I agree, this should be good to go.

blink1073 changed the title from [WIP] IPython Widget to IPython Widget May 4, 2016

Owner

tacaswell commented May 4, 2016

I am going to merge this on the good word of @blink1073 .

@tacaswell tacaswell merged commit 9c100c9 into matplotlib:master May 4, 2016

2 of 3 checks passed

coverage/coveralls Coverage decreased (-0.003%) to 69.646%
Details
continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

tacaswell removed the needs_review label May 4, 2016

Owner

tacaswell commented May 4, 2016

If people were doing things in the notebook which relied on the previous version of the js we shipped we will re-address adding it back.

Contributor

blink1073 commented May 4, 2016

🎉

izaid commented May 4, 2016

This is actually a pretty big deal -- we can arrange plots in the IPython notebook now. Great work, @blink1073!

Contributor

danielballan commented May 4, 2016

👏

Contributor

ngoldbaum commented May 4, 2016

and callbacks that raise exceptions no longer get silently ignored 💯

Member

fperez commented May 5, 2016

Thanks everyone!! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment