|
| 1 | +var comm_websocket_adapter = function(comm) { |
| 2 | + // Create a "websocket"-like object which calls the given IPython comm |
| 3 | + // object with the appropriate methods. Currently this is a non binary |
| 4 | + // socket, so there is still some room for performance tuning. |
| 5 | + var ws = {}; |
| 6 | + |
| 7 | + ws.close = function() { |
| 8 | + comm.close() |
| 9 | + }; |
| 10 | + ws.send = function(m) { |
| 11 | + //console.log('sending', m); |
| 12 | + comm.send(m); |
| 13 | + }; |
| 14 | + // Register the callback with on_msg. |
| 15 | + comm.on_msg(function(msg) { |
| 16 | + //console.log('receiving', msg['content']['data'], msg); |
| 17 | + // Pass the mpl event to the overriden (by mpl) onmessage function. |
| 18 | + ws.onmessage(msg['content']['data']) |
| 19 | + }); |
| 20 | + return ws; |
| 21 | +} |
1 | 22 |
|
| 23 | +mpl.mpl_figure_comm = function(comm, msg) { |
| 24 | + // This is the function which gets called when the mpl process |
| 25 | + // starts-up an IPython Comm through the "matplotlib" channel. |
2 | 26 |
|
3 | | -define(['jupyter-js-widgets', '/nbextensions/matplotlib/mpl.js'], function(widgets, mpl) { |
4 | | - |
5 | | - var MPLCanvasView = widgets.WidgetView.extend({ |
6 | | - |
7 | | - render: function() { |
8 | | - var that = this; |
9 | | - |
10 | | - var id = this.model.get('_id'); |
11 | | - |
12 | | - var element = this.$el; |
13 | | - |
14 | | - this.ws_proxy = this.comm_websocket_adapter(this.model.comm); |
15 | | - |
16 | | - function ondownload(figure, format) { |
17 | | - window.open(figure.imageObj.src); |
18 | | - } |
19 | | - |
20 | | - mpl.toolbar_items = this.model.get('_toolbar_items') |
21 | | - |
22 | | - var fig = new mpl.figure(id, this.ws_proxy, |
23 | | - ondownload, |
24 | | - element.get(0)); |
25 | | - |
26 | | - // Call onopen now - mpl needs it, as it is assuming we've passed it a real |
27 | | - // web socket which is closed, not our websocket->open comm proxy. |
28 | | - this.ws_proxy.onopen(); |
29 | | - |
30 | | - fig.parent_element = element.get(0); |
31 | | - |
32 | | - // subscribe to incoming messages from the MPLCanvasWidget |
33 | | - this.model.on('msg:custom', this.ws_proxy.onmessage, this); |
34 | | - |
35 | | - this.send(JSON.stringify({ type: 'initialized' })); |
36 | | - }, |
37 | | - |
38 | | - comm_websocket_adapter: function(comm) { |
39 | | - // Create a "websocket"-like object which calls the given IPython comm |
40 | | - // object with the appropriate methods. Currently this is a non binary |
41 | | - // socket, so there is still some room for performance tuning. |
42 | | - var ws = {}; |
43 | | - var that = this; |
| 27 | + var id = msg.content.data.id; |
| 28 | + // Get hold of the div created by the display call when the Comm |
| 29 | + // socket was opened in Python. |
| 30 | + var element = $("#" + id); |
| 31 | + var ws_proxy = comm_websocket_adapter(comm) |
44 | 32 |
|
45 | | - ws.close = function() { |
46 | | - comm.close() |
47 | | - }; |
48 | | - ws.send = function(m) { |
49 | | - that.send(m); |
50 | | - }; |
51 | | - return ws; |
52 | | - } |
| 33 | + function ondownload(figure, format) { |
| 34 | + window.open(figure.imageObj.src); |
| 35 | + } |
53 | 36 |
|
54 | | - }); |
| 37 | + var fig = new mpl.figure(id, ws_proxy, |
| 38 | + ondownload, |
| 39 | + element.get(0)); |
55 | 40 |
|
56 | | - mpl.figure.prototype.handle_close = function(fig, msg) { |
57 | | - var width = fig.canvas.width/mpl.ratio |
58 | | - fig.root.unbind('remove') |
| 41 | + // Call onopen now - mpl needs it, as it is assuming we've passed it a real |
| 42 | + // web socket which is closed, not our websocket->open comm proxy. |
| 43 | + ws_proxy.onopen(); |
59 | 44 |
|
60 | | - // Re-enable the keyboard manager in IPython - without this line, in FF, |
61 | | - // the notebook keyboard shortcuts fail. |
62 | | - IPython.keyboard_manager.enable() |
63 | | - fig.close_ws(fig, msg); |
| 45 | + fig.parent_element = element.get(0); |
| 46 | + fig.cell_info = mpl.find_output_cell("<div id='" + id + "'></div>"); |
| 47 | + if (!fig.cell_info) { |
| 48 | + console.error("Failed to find cell for figure", id, fig); |
| 49 | + return; |
64 | 50 | } |
65 | 51 |
|
66 | | - mpl.figure.prototype.close_ws = function(fig, msg){ |
67 | | - fig.send_message('closing', msg); |
68 | | - // fig.ws.close() |
| 52 | + var output_index = fig.cell_info[2] |
| 53 | + var cell = fig.cell_info[0]; |
| 54 | + |
| 55 | +}; |
| 56 | + |
| 57 | +mpl.figure.prototype.handle_close = function(fig, msg) { |
| 58 | + var width = fig.canvas.width/mpl.ratio |
| 59 | + fig.root.unbind('remove') |
| 60 | + |
| 61 | + // Update the output cell to use the data from the current canvas. |
| 62 | + fig.push_to_output(); |
| 63 | + var dataURL = fig.canvas.toDataURL(); |
| 64 | + // Re-enable the keyboard manager in IPython - without this line, in FF, |
| 65 | + // the notebook keyboard shortcuts fail. |
| 66 | + IPython.keyboard_manager.enable() |
| 67 | + $(fig.parent_element).html('<img src="' + dataURL + '" width="' + width + '">'); |
| 68 | + fig.close_ws(fig, msg); |
| 69 | +} |
| 70 | + |
| 71 | +mpl.figure.prototype.close_ws = function(fig, msg){ |
| 72 | + fig.send_message('closing', msg); |
| 73 | + // fig.ws.close() |
| 74 | +} |
| 75 | + |
| 76 | +mpl.figure.prototype.push_to_output = function(remove_interactive) { |
| 77 | + // Turn the data on the canvas into data in the output cell. |
| 78 | + var width = this.canvas.width/mpl.ratio |
| 79 | + var dataURL = this.canvas.toDataURL(); |
| 80 | + this.cell_info[1]['text/html'] = '<img src="' + dataURL + '" width="' + width + '">'; |
| 81 | +} |
| 82 | + |
| 83 | +mpl.figure.prototype.updated_canvas_event = function() { |
| 84 | + // Tell IPython that the notebook contents must change. |
| 85 | + IPython.notebook.set_dirty(true); |
| 86 | + this.send_message("ack", {}); |
| 87 | + var fig = this; |
| 88 | + // Wait a second, then push the new image to the DOM so |
| 89 | + // that it is saved nicely (might be nice to debounce this). |
| 90 | + setTimeout(function () { fig.push_to_output() }, 1000); |
| 91 | +} |
| 92 | + |
| 93 | +mpl.figure.prototype._init_toolbar = function() { |
| 94 | + var fig = this; |
| 95 | + |
| 96 | + var nav_element = $('<div/>') |
| 97 | + nav_element.attr('style', 'width: 100%'); |
| 98 | + this.root.append(nav_element); |
| 99 | + |
| 100 | + // Define a callback function for later on. |
| 101 | + function toolbar_event(event) { |
| 102 | + return fig.toolbar_button_onclick(event['data']); |
69 | 103 | } |
70 | | - |
71 | | - mpl.figure.prototype.updated_canvas_event = function() { |
72 | | - // Tell IPython that the notebook contents must change. |
73 | | - IPython.notebook.set_dirty(true); |
74 | | - this.send_message("ack", {}); |
| 104 | + function toolbar_mouse_event(event) { |
| 105 | + return fig.toolbar_button_onmouseover(event['data']); |
75 | 106 | } |
76 | 107 |
|
77 | | - mpl.figure.prototype._init_toolbar = function() { |
78 | | - var fig = this; |
79 | | - |
80 | | - var nav_element = $('<div/>') |
81 | | - nav_element.attr('style', 'width: 100%'); |
82 | | - this.root.append(nav_element); |
83 | | - |
84 | | - // Define a callback function for later on. |
85 | | - function toolbar_event(event) { |
86 | | - return fig.toolbar_button_onclick(event['data']); |
87 | | - } |
88 | | - function toolbar_mouse_event(event) { |
89 | | - return fig.toolbar_button_onmouseover(event['data']); |
90 | | - } |
| 108 | + for(var toolbar_ind in mpl.toolbar_items){ |
| 109 | + var name = mpl.toolbar_items[toolbar_ind][0]; |
| 110 | + var tooltip = mpl.toolbar_items[toolbar_ind][1]; |
| 111 | + var image = mpl.toolbar_items[toolbar_ind][2]; |
| 112 | + var method_name = mpl.toolbar_items[toolbar_ind][3]; |
91 | 113 |
|
92 | | - for(var toolbar_ind in mpl.toolbar_items){ |
93 | | - var name = mpl.toolbar_items[toolbar_ind][0]; |
94 | | - var tooltip = mpl.toolbar_items[toolbar_ind][1]; |
95 | | - var image = mpl.toolbar_items[toolbar_ind][2]; |
96 | | - var method_name = mpl.toolbar_items[toolbar_ind][3]; |
| 114 | + if (!name) { continue; }; |
97 | 115 |
|
98 | | - if (!name) { continue; }; |
99 | | - |
100 | | - var button = $('<button class="btn btn-default" href="#" title="' + name + '"><i class="fa ' + image + ' fa-lg"></i></button>'); |
101 | | - button.click(method_name, toolbar_event); |
102 | | - button.mouseover(tooltip, toolbar_mouse_event); |
103 | | - nav_element.append(button); |
104 | | - } |
105 | | - |
106 | | - // Add the status bar. |
107 | | - var status_bar = $('<span class="mpl-message" style="text-align:right; float: right;"/>'); |
108 | | - nav_element.append(status_bar); |
109 | | - this.message = status_bar[0]; |
110 | | - |
111 | | - // Add the close button to the window. |
112 | | - var buttongrp = $('<div class="btn-group inline pull-right"></div>'); |
113 | | - var button = $('<button class="btn btn-mini btn-primary" href="#" title="Stop Interaction"><i class="fa fa-power-off icon-remove icon-large"></i></button>'); |
114 | | - button.click(function (evt) { fig.handle_close(fig, {}); } ); |
115 | | - button.mouseover('Stop Interaction', toolbar_mouse_event); |
116 | | - buttongrp.append(button); |
117 | | - var titlebar = this.root.find($('.ui-dialog-titlebar')); |
118 | | - titlebar.prepend(buttongrp); |
| 116 | + var button = $('<button class="btn btn-default" href="#" title="' + name + '"><i class="fa ' + image + ' fa-lg"></i></button>'); |
| 117 | + button.click(method_name, toolbar_event); |
| 118 | + button.mouseover(tooltip, toolbar_mouse_event); |
| 119 | + nav_element.append(button); |
119 | 120 | } |
120 | 121 |
|
121 | | - mpl.figure.prototype._root_extra_style = function(el){ |
122 | | - var fig = this |
123 | | - el.on("remove", function(){ |
124 | | - fig.close_ws(fig, {}); |
125 | | - }); |
126 | | - } |
127 | | - |
128 | | - mpl.figure.prototype._canvas_extra_style = function(el){ |
129 | | - // this is important to make the div 'focusable |
130 | | - el.attr('tabindex', 0) |
131 | | - // reach out to IPython and tell the keyboard manager to turn it's self |
132 | | - // off when our div gets focus |
| 122 | + // Add the status bar. |
| 123 | + var status_bar = $('<span class="mpl-message" style="text-align:right; float: right;"/>'); |
| 124 | + nav_element.append(status_bar); |
| 125 | + this.message = status_bar[0]; |
| 126 | + |
| 127 | + // Add the close button to the window. |
| 128 | + var buttongrp = $('<div class="btn-group inline pull-right"></div>'); |
| 129 | + var button = $('<button class="btn btn-mini btn-primary" href="#" title="Stop Interaction"><i class="fa fa-power-off icon-remove icon-large"></i></button>'); |
| 130 | + button.click(function (evt) { fig.handle_close(fig, {}); } ); |
| 131 | + button.mouseover('Stop Interaction', toolbar_mouse_event); |
| 132 | + buttongrp.append(button); |
| 133 | + var titlebar = this.root.find($('.ui-dialog-titlebar')); |
| 134 | + titlebar.prepend(buttongrp); |
| 135 | +} |
| 136 | + |
| 137 | +mpl.figure.prototype._root_extra_style = function(el){ |
| 138 | + var fig = this |
| 139 | + el.on("remove", function(){ |
| 140 | + fig.close_ws(fig, {}); |
| 141 | + }); |
| 142 | +} |
133 | 143 |
|
134 | | - // location in version 3 |
135 | | - if (IPython.notebook.keyboard_manager) { |
136 | | - IPython.notebook.keyboard_manager.register_events(el); |
137 | | - } |
138 | | - else { |
139 | | - // location in version 2 |
140 | | - IPython.keyboard_manager.register_events(el); |
141 | | - } |
| 144 | +mpl.figure.prototype._canvas_extra_style = function(el){ |
| 145 | + // this is important to make the div 'focusable |
| 146 | + el.attr('tabindex', 0) |
| 147 | + // reach out to IPython and tell the keyboard manager to turn it's self |
| 148 | + // off when our div gets focus |
142 | 149 |
|
| 150 | + // location in version 3 |
| 151 | + if (IPython.notebook.keyboard_manager) { |
| 152 | + IPython.notebook.keyboard_manager.register_events(el); |
143 | 153 | } |
144 | | - |
145 | | - mpl.figure.prototype._key_event_extra = function(event, name) { |
146 | | - var manager = IPython.notebook.keyboard_manager; |
147 | | - if (!manager) |
148 | | - manager = IPython.keyboard_manager; |
149 | | - |
150 | | - // Check for shift+enter |
151 | | - if (event.shiftKey && event.which == 13) { |
152 | | - this.canvas_div.blur(); |
153 | | - event.shiftKey = false; |
154 | | - // select the cell after this one |
155 | | - var index = IPython.notebook.find_cell_index(this.cell_info[0]); |
156 | | - IPython.notebook.select(index + 1); } |
| 154 | + else { |
| 155 | + // location in version 2 |
| 156 | + IPython.keyboard_manager.register_events(el); |
157 | 157 | } |
158 | 158 |
|
159 | | - mpl.figure.prototype.handle_save = function(fig, msg) { |
160 | | - fig.ondownload(fig, null); |
| 159 | +} |
| 160 | + |
| 161 | +mpl.figure.prototype._key_event_extra = function(event, name) { |
| 162 | + var manager = IPython.notebook.keyboard_manager; |
| 163 | + if (!manager) |
| 164 | + manager = IPython.keyboard_manager; |
| 165 | + |
| 166 | + // Check for shift+enter |
| 167 | + if (event.shiftKey && event.which == 13) { |
| 168 | + this.canvas_div.blur(); |
| 169 | + event.shiftKey = false; |
| 170 | + // Send a "J" for go to next cell |
| 171 | + event.which = 74; |
| 172 | + event.keyCode = 74; |
| 173 | + manager.command_mode(); |
| 174 | + manager.handle_keydown(event); |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +mpl.figure.prototype.handle_save = function(fig, msg) { |
| 179 | + fig.ondownload(fig, null); |
| 180 | +} |
| 181 | + |
| 182 | + |
| 183 | +mpl.find_output_cell = function(html_output) { |
| 184 | + // Return the cell and output element which can be found *uniquely* in the notebook. |
| 185 | + // Note - this is a bit hacky, but it is done because the "notebook_saving.Notebook" |
| 186 | + // IPython event is triggered only after the cells have been serialised, which for |
| 187 | + // our purposes (turning an active figure into a static one), is too late. |
| 188 | + var cells = IPython.notebook.get_cells(); |
| 189 | + var ncells = cells.length; |
| 190 | + for (var i=0; i<ncells; i++) { |
| 191 | + var cell = cells[i]; |
| 192 | + if (cell.cell_type === 'code'){ |
| 193 | + for (var j=0; j<cell.output_area.outputs.length; j++) { |
| 194 | + var data = cell.output_area.outputs[j]; |
| 195 | + if (data.data) { |
| 196 | + // IPython >= 3 moved mimebundle to data attribute of output |
| 197 | + data = data.data; |
| 198 | + } |
| 199 | + if (data['text/html'] == html_output) { |
| 200 | + return [cell, data, j]; |
| 201 | + } |
| 202 | + } |
| 203 | + } |
161 | 204 | } |
| 205 | +} |
162 | 206 |
|
163 | | - return {MPLCanvasView: MPLCanvasView} |
164 | | -}); |
| 207 | +// Register the function which deals with the matplotlib target/channel. |
| 208 | +// The kernel may be null if the page has been refreshed. |
| 209 | +if (IPython.notebook.kernel != null) { |
| 210 | + IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm); |
| 211 | +} |
0 commit comments