Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork8.1k
IPython Widget#5754
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
IPython Widget#5754
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -2161,7 +2161,8 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, | ||
| origfacecolor = self.figure.get_facecolor() | ||
| origedgecolor = self.figure.get_edgecolor() | ||
| if dpi != 'figure': | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. This avoids an error I saw. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. FWIW: This was fixed recently by#5720 (in the exact same way). | ||
| self.figure.dpi = dpi | ||
| self.figure.set_facecolor(facecolor) | ||
| self.figure.set_edgecolor(edgecolor) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -3,23 +3,27 @@ | ||
| # lib/matplotlib/backends/web_backend/nbagg_uat.ipynb to help verify | ||
| # that changes made maintain expected behaviour. | ||
| from base64 import b64encode | ||
| import json | ||
| import io | ||
| from tempfile import mkdtemp | ||
| import shutil | ||
| import os | ||
| from matplotlib.externals import six | ||
| from uuid import uuid4 as uuid | ||
| from IPython.display import display, HTML | ||
| from IPython import version_info | ||
| try: | ||
| # Jupyter/IPython 4.x or later | ||
| from ipywidgets import DOMWidget | ||
| from traitlets import Unicode, Bool, Float, List, Any | ||
| from notebook.nbextensions import install_nbextension, check_nbextension | ||
| except ImportError: | ||
| # Jupyter/IPython 3.x or earlier | ||
| from IPython.html.widgets import DOMWidget | ||
| from IPython.utils.traitlets import Unicode, Bool, Float, List, Any | ||
| from IPython.html.nbextensions import install_nbextension | ||
| from matplotlib import rcParams | ||
| from matplotlib.figure import Figure | ||
| @@ -33,6 +37,7 @@ | ||
| class Show(ShowBase): | ||
| def __call__(self, block=None): | ||
| from matplotlib._pylab_helpers import Gcf | ||
| @@ -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', | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Can you elaborate on what "export" does? It inserts a static image into the notebook? Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Why is this preferable to the automatic replacement with a static image upon saving? Won't users often forget to push this button? MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. 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. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Maybe we should think about the name. Export makes me think of "save" - I guess this is a kind of "snapshot" type behaviour, right? | ||
| None: None | ||
| } | ||
| @@ -109,84 +115,74 @@ class NavigationIPy(NavigationToolbar2WebAgg): | ||
| _FONT_AWESOME_CLASSES[image_file], name_of_method) | ||
| for text, tooltip_text, image_file, name_of_method | ||
| in (NavigationToolbar2.toolitems + | ||
| (('Download', 'Download plot', 'download', 'download'), | ||
| ('Export', 'Export plot', 'export', 'export'))) | ||
| if image_file in _FONT_AWESOME_CLASSES] | ||
| def export(self): | ||
| buf = io.BytesIO() | ||
| self.canvas.figure.savefig(buf, format='png', dpi='figure') | ||
| data = "<img src='data:image/png;base64,{0}'/>" | ||
| data = data.format(b64encode(buf.getvalue()).decode('utf-8')) | ||
| display(HTML(data)) | ||
| class FigureCanvasNbAgg(DOMWidget, FigureCanvasWebAggCore): | ||
| _view_module = Unicode("matplotlib", sync=True) | ||
| _view_name = Unicode('MPLCanvasView', sync=True) | ||
| _toolbar_items = List(sync=True) | ||
| _closed = Bool(True) | ||
| _id = Unicode('', sync=True) | ||
| # Must declare the superclass private members. | ||
| _png_is_old = Bool() | ||
| _force_full = Bool() | ||
| _current_image_mode = Unicode() | ||
| _dpi_ratio = Float(1.0) | ||
| _is_idle_drawing = Bool() | ||
| _is_saving = Bool() | ||
| _button = Any() | ||
| _key = Any() | ||
| _lastx = Any() | ||
| _lasty = Any() | ||
| _is_idle_drawing = Bool() | ||
| def __init__(self, figure, *args, **kwargs): | ||
| super(FigureCanvasWebAggCore, self).__init__(figure, *args, **kwargs) | ||
| super(DOMWidget, self).__init__(*args, **kwargs) | ||
| self._uid = uuid().hex | ||
| self.on_msg(self._handle_message) | ||
| def _handle_message(self, object, message, buffers): | ||
| # The 'supports_binary' message is relevant to the | ||
| # websocket itself. The other messages get passed along | ||
| # to matplotlib as-is. | ||
| # Every message has a "type" and a "figure_id". | ||
| message = json.loads(message) | ||
| if message['type'] == 'closing': | ||
| self._closed = True | ||
| elif message['type'] == 'supports_binary': | ||
| self.supports_binary = message['value'] | ||
| elif message['type'] == 'initialized': | ||
| _, _, w, h = self.figure.bbox.bounds | ||
| self.manager.resize(w, h) | ||
| self.send_json('refresh') | ||
| else: | ||
| self.manager.handle_json(message) | ||
| def send_json(self, content): | ||
| self.send({'data': json.dumps(content)}) | ||
| def send_binary(self, blob): | ||
| # The comm is ascii, so we always send the image in base64 | ||
| # encoded data URL form. | ||
| data = b64encode(blob) | ||
| if six.PY3: | ||
| data = data.decode('ascii') | ||
| data_uri = "data:image/png;base64,{0}".format(data) | ||
| self.send({'data': data_uri}) | ||
| def new_timer(self, *args, **kwargs): | ||
| return TimerTornado(*args, **kwargs) | ||
| @@ -197,6 +193,31 @@ def stop_event_loop(self): | ||
| FigureCanvasBase.stop_event_loop_default(self) | ||
| class FigureManagerNbAgg(FigureManagerWebAgg): | ||
| ToolbarCls = NavigationIPy | ||
| def __init__(self, canvas, num): | ||
| FigureManagerWebAgg.__init__(self, canvas, num) | ||
| toolitems = [] | ||
| for name, tooltip, image, method in self.ToolbarCls.toolitems: | ||
| if name is None: | ||
| toolitems.append(['', '', '', '']) | ||
| else: | ||
| toolitems.append([name, tooltip, image, method]) | ||
| canvas._toolbar_items = toolitems | ||
| self.web_sockets = [self.canvas] | ||
| def show(self): | ||
| if self.canvas._closed: | ||
| self.canvas._closed = False | ||
| display(self.canvas) | ||
| else: | ||
| self.canvas.draw_idle() | ||
| def destroy(self): | ||
| self._send_event('close') | ||
| def new_figure_manager(num, *args, **kwargs): | ||
| """ | ||
| Create a new figure manager instance | ||
| @@ -229,76 +250,46 @@ def closer(event): | ||
| return manager | ||
| def nbinstall(overwrite=False, user=True): | ||
| """ | ||
| Copies javascript dependencies to the '/nbextensions' folder in | ||
| your IPython directory. | ||
| Parameters | ||
| ---------- | ||
| overwrite : bool | ||
| If True, always install the files, regardless of what mayœ already be | ||
| installed. Defaults to False. | ||
| user : bool | ||
| Whether to install to the user's .ipython/nbextensions directory. | ||
| Otherwise do a system-wide install | ||
| (e.g. /usr/local/share/jupyter/nbextensions). Defaults to False. | ||
| """ | ||
| 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});') | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. What are the downsides with doing this in the file we ship directly (rather than editing it on the fly here)? Itdoes need to work in a "no IPython" context as well, but we do already use JQuery there. MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. There is no downside AFAIK, I was just shying away from making any changes to WebAgg. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. 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. MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. We still need to create the temporary directory either way, Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I think some packages take advantage of an "egg cache" concept in distutils
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. 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.
Certainly the notebook server is talking to the kernel though and could get Javascript content from there... Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more.
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... MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. @mdboom, this is a thorny issue, one under active discussion:https://groups.google.com/forum/#!topic/jupyter/NDc9ktzACF0 Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Thanks for the link. And sorry to be annoying ;) This PR in general isvery very welcome. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more.
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 | ||
| install_nbextension( | ||
| tempdir, | ||
| overwrite=overwrite, | ||
| symlink=False, | ||
| destination='matplotlib', | ||
| verbose=0, | ||
| **({'user': user} if version_info >= (3, 0, 0, '') else {}) | ||
| ) | ||
| #nbinstall() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -18,7 +18,7 @@ | ||
| import io | ||
| import json | ||
| import os | ||
| importdatetime | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Thanks for catching this one! | ||
| import warnings | ||
| import numpy as np | ||
| @@ -501,6 +501,7 @@ def get_javascript(cls, stream=None): | ||
| with io.open(os.path.join( | ||
| os.path.dirname(__file__), | ||
| "web_backend", | ||
| "js", | ||
| "mpl.js"), encoding='utf8') as fd: | ||
| output.write(fd.read()) | ||
| @@ -530,7 +531,7 @@ def get_javascript(cls, stream=None): | ||
| @classmethod | ||
| def get_static_file_path(cls): | ||
| return os.path.join(os.path.dirname(__file__), 'web_backend', 'js') | ||
| def _send_event(self, event_type, **kwargs): | ||
| payload = {'type': event_type} | ||
Uh oh!
There was an error while loading.Please reload this page.