|
| 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