@@ -27,6 +27,10 @@ var MPLCanvasModel = widgets.DOMWidgetModel.extend({
2727 _message : '' ,
2828 _cursor : 'pointer' ,
2929 _image_mode : 'full' ,
30+ _rubberband_x : 0 ,
31+ _rubberband_y : 0 ,
32+ _rubberband_width : 0 ,
33+ _rubberband_height : 0 ,
3034 } ) ;
3135 } ,
3236
@@ -41,6 +45,8 @@ var MPLCanvasModel = widgets.DOMWidgetModel.extend({
4145 this . offscreen_context . msBackingStorePixelRatio ||
4246 this . offscreen_context . oBackingStorePixelRatio || 1 ;
4347
48+ this . requested_size = null ;
49+ this . resize_requested = false ;
4450 this . ratio = ( window . devicePixelRatio || 1 ) / backingStore ;
4551 this . _init_image ( ) ;
4652
@@ -84,18 +90,37 @@ var MPLCanvasModel = widgets.DOMWidgetModel.extend({
8490
8591 handle_resize : function ( msg ) {
8692 var size = msg [ 'size' ] ;
93+
8794 this . resize_canvas ( size [ 0 ] , size [ 1 ] ) ;
95+ this . offscreen_context . drawImage ( this . image , 0 , 0 ) ;
96+
8897 this . _for_each_view ( function ( view ) {
8998 view . resize_canvas ( size [ 0 ] , size [ 1 ] ) ;
9099 } ) ;
100+
91101 this . send_message ( 'refresh' ) ;
102+
103+ this . resize_requested = false ;
104+ if ( this . requested_size !== null ) {
105+ // Requesting saved resize
106+ this . resize ( this . requested_size [ 0 ] , this . requested_size [ 1 ] ) ;
107+ this . requested_size = null ;
108+ }
109+ } ,
110+
111+ resize : function ( width , height ) {
112+ if ( this . resize_requested ) {
113+ // If a resize was already requested, save the requested size for later
114+ this . requested_size = [ width , height ] ;
115+ } else {
116+ this . resize_requested = true ;
117+ this . send_message ( 'resize' , { 'width' : width , 'height' : height } ) ;
118+ }
92119 } ,
93120
94121 resize_canvas : function ( width , height ) {
95122 this . offscreen_canvas . setAttribute ( 'width' , width * this . ratio ) ;
96123 this . offscreen_canvas . setAttribute ( 'height' , height * this . ratio ) ;
97- this . offscreen_canvas . style . width = width + 'px' ;
98- this . offscreen_canvas . style . height = height + 'px' ;
99124 } ,
100125
101126 handle_rubberband : function ( msg ) {
@@ -107,16 +132,15 @@ var MPLCanvasModel = widgets.DOMWidgetModel.extend({
107132 y0 = Math . floor ( y0 ) + 0.5 ;
108133 x1 = Math . floor ( x1 ) + 0.5 ;
109134 y1 = Math . floor ( y1 ) + 0.5 ;
110- var min_x = Math . min ( x0 , x1 ) ;
111- var min_y = Math . min ( y0 , y1 ) ;
112- var width = Math . abs ( x1 - x0 ) ;
113- var height = Math . abs ( y1 - y0 ) ;
114135
115- this . _for_each_view ( function ( view ) {
116- view . rubberband_context . clearRect (
117- 0 , 0 , view . rubberband_canvas . width , view . rubberband_canvas . height ) ;
136+ this . set ( '_rubberband_x' , Math . min ( x0 , x1 ) ) ;
137+ this . set ( '_rubberband_y' , Math . min ( y0 , y1 ) ) ;
138+ this . set ( '_rubberband_width' , Math . abs ( x1 - x0 ) ) ;
139+ this . set ( '_rubberband_height' , Math . abs ( y1 - y0 ) ) ;
140+ this . save_changes ( ) ;
118141
119- view . rubberband_context . strokeRect ( min_x , min_y , width , height ) ;
142+ this . _for_each_view ( function ( view ) {
143+ view . update_canvas ( ) ;
120144 } ) ;
121145 } ,
122146
@@ -177,7 +201,7 @@ var MPLCanvasModel = widgets.DOMWidgetModel.extend({
177201 that . offscreen_context . drawImage ( that . image , 0 , 0 ) ;
178202
179203 that . _for_each_view ( function ( view ) {
180- view . context . drawImage ( that . offscreen_canvas , 0 , 0 ) ;
204+ view . update_canvas ( ) ;
181205 } ) ;
182206 } ;
183207 } ,
@@ -189,6 +213,10 @@ var MPLCanvasModel = widgets.DOMWidgetModel.extend({
189213 } ) ;
190214 }
191215 } ,
216+
217+ remove : function ( ) {
218+ this . send_message ( 'closing' ) ;
219+ }
192220} , {
193221 serializers : _ . extend ( {
194222 toolbar : { deserialize : widgets . unpack_models }
@@ -199,17 +227,23 @@ var MPLCanvasView = widgets.DOMWidgetView.extend({
199227 render : function ( ) {
200228 this . canvas = undefined ;
201229 this . context = undefined ;
202- this . rubberband_canvas = undefined ;
203- this . rubberband_context = undefined ;
230+ this . top_canvas = undefined ;
231+ this . top_context = undefined ;
232+ this . resizing = false ;
233+ this . resize_handle_size = 20 ;
204234
205235 this . figure = document . createElement ( 'div' ) ;
206- this . figure . addEventListener ( 'remove' , this . close . bind ( this ) ) ;
207236 this . figure . classList = 'jupyter-matplotlib-figure jupyter-widgets widget-container widget-box widget-vbox' ;
208237
209238 this . _init_header ( ) ;
210239 this . _init_canvas ( ) ;
211240 this . _init_footer ( ) ;
212241
242+ this . _resize_event = this . resize_event . bind ( this ) ;
243+ this . _stop_resize_event = this . stop_resize_event . bind ( this ) ;
244+ window . addEventListener ( 'mousemove' , this . _resize_event ) ;
245+ window . addEventListener ( 'mouseup' , this . _stop_resize_event ) ;
246+
213247 this . waiting = false ;
214248
215249 var that = this ;
@@ -320,41 +354,82 @@ var MPLCanvasView = widgets.DOMWidgetView.extend({
320354
321355 this . context = canvas . getContext ( '2d' ) ;
322356
323- var rubberband_canvas = this . rubberband_canvas = document . createElement ( 'canvas' ) ;
324- rubberband_canvas . style . display = 'block' ;
325- rubberband_canvas . style . position = 'absolute' ;
326- rubberband_canvas . style . left = 0 ;
327- rubberband_canvas . style . top = 0 ;
328- rubberband_canvas . style . zIndex = 1 ;
357+ var top_canvas = this . top_canvas = document . createElement ( 'canvas' ) ;
358+ top_canvas . style . display = 'block' ;
359+ top_canvas . style . position = 'absolute' ;
360+ top_canvas . style . left = 0 ;
361+ top_canvas . style . top = 0 ;
362+ top_canvas . style . zIndex = 1 ;
329363
330- rubberband_canvas . addEventListener ( 'mousedown' , this . mouse_event ( 'button_press' ) ) ;
331- rubberband_canvas . addEventListener ( 'mouseup' , this . mouse_event ( 'button_release' ) ) ;
332- rubberband_canvas . addEventListener ( 'mousemove' , this . mouse_event ( 'motion_notify' ) ) ;
364+ top_canvas . addEventListener ( 'mousedown' , this . mouse_event ( 'button_press' ) ) ;
365+ top_canvas . addEventListener ( 'mouseup' , this . mouse_event ( 'button_release' ) ) ;
366+ top_canvas . addEventListener ( 'mousemove' , this . mouse_event ( 'motion_notify' ) ) ;
333367
334- rubberband_canvas . addEventListener ( 'mouseenter' , this . mouse_event ( 'figure_enter' ) ) ;
335- rubberband_canvas . addEventListener ( 'mouseleave' , this . mouse_event ( 'figure_leave' ) ) ;
368+ top_canvas . addEventListener ( 'mouseenter' , this . mouse_event ( 'figure_enter' ) ) ;
369+ top_canvas . addEventListener ( 'mouseleave' , this . mouse_event ( 'figure_leave' ) ) ;
336370
337- rubberband_canvas . addEventListener ( 'wheel' , this . mouse_event ( 'scroll' ) ) ;
371+ top_canvas . addEventListener ( 'wheel' , this . mouse_event ( 'scroll' ) ) ;
338372
339373 canvas_div . appendChild ( canvas ) ;
340- canvas_div . appendChild ( rubberband_canvas ) ;
374+ canvas_div . appendChild ( top_canvas ) ;
341375
342- this . rubberband_context = rubberband_canvas . getContext ( '2d' ) ;
343- this . rubberband_context . strokeStyle = '#000000 ' ;
376+ this . top_context = top_canvas . getContext ( '2d' ) ;
377+ this . top_context . strokeStyle = 'rgba(0, 0, 0, 255) ' ;
344378
345379 // Disable right mouse context menu.
346- this . rubberband_canvas . addEventListener ( 'contextmenu' , function ( e ) {
380+ this . top_canvas . addEventListener ( 'contextmenu' , function ( e ) {
347381 event . preventDefault ( ) ;
348382 event . stopPropagation ( ) ;
349383 return false ;
350384 } ) ;
351385
352386 this . resize_canvas ( this . model . get ( '_width' ) , this . model . get ( '_height' ) ) ;
387+ this . update_canvas ( ) ;
388+ } ,
389+
390+ update_canvas : function ( ) {
391+ if ( this . canvas . width == 0 || this . canvas . height == 0 ) {
392+ return ;
393+ }
394+
395+ this . context . clearRect ( 0 , 0 , this . canvas . width , this . canvas . height ) ;
353396 this . context . drawImage ( this . model . offscreen_canvas , 0 , 0 ) ;
397+
398+ this . top_context . clearRect ( 0 , 0 , this . top_canvas . width , this . top_canvas . height ) ;
399+
400+ // Draw rubberband
401+ if ( this . model . get ( '_rubberband_width' ) != 0 && this . model . get ( '_rubberband_height' ) != 0 ) {
402+ this . top_context . strokeRect (
403+ this . model . get ( '_rubberband_x' ) , this . model . get ( '_rubberband_y' ) ,
404+ this . model . get ( '_rubberband_width' ) , this . model . get ( '_rubberband_height' )
405+ ) ;
406+ }
407+
408+ // Draw resize handle
409+ this . top_context . save ( ) ;
410+
411+ var gradient = this . top_context . createLinearGradient (
412+ this . top_canvas . width - this . resize_handle_size / 3 , this . top_canvas . height - this . resize_handle_size / 3 ,
413+ this . top_canvas . width - this . resize_handle_size / 4 , this . top_canvas . height - this . resize_handle_size / 4
414+ ) ;
415+ gradient . addColorStop ( 0 , 'rgba(0, 0, 0, 0)' ) ;
416+ gradient . addColorStop ( 1 , 'rgba(0, 0, 0, 255)' ) ;
417+
418+ this . top_context . fillStyle = gradient ;
419+
420+ this . top_context . globalAlpha = 0.3 ;
421+ this . top_context . beginPath ( ) ;
422+ this . top_context . moveTo ( this . top_canvas . width , this . top_canvas . height ) ;
423+ this . top_context . lineTo ( this . top_canvas . width , this . top_canvas . height - this . resize_handle_size ) ;
424+ this . top_context . lineTo ( this . top_canvas . width - this . resize_handle_size , this . top_canvas . height ) ;
425+ this . top_context . closePath ( ) ;
426+ this . top_context . fill ( ) ;
427+
428+ this . top_context . restore ( ) ;
354429 } ,
355430
356431 _update_cursor : function ( ) {
357- this . rubberband_canvas . style . cursor = this . model . get ( '_cursor' ) ;
432+ this . top_canvas . style . cursor = this . model . get ( '_cursor' ) ;
358433 } ,
359434
360435 _init_footer : function ( ) {
@@ -376,11 +451,13 @@ var MPLCanvasView = widgets.DOMWidgetView.extend({
376451 this . canvas . style . width = width + 'px' ;
377452 this . canvas . style . height = height + 'px' ;
378453
379- this . rubberband_canvas . setAttribute ( 'width' , width ) ;
380- this . rubberband_canvas . setAttribute ( 'height' , height ) ;
454+ this . top_canvas . setAttribute ( 'width' , width ) ;
455+ this . top_canvas . setAttribute ( 'height' , height ) ;
381456
382457 this . canvas_div . style . width = width + 'px' ;
383458 this . canvas_div . style . height = height + 'px' ;
459+
460+ this . update_canvas ( ) ;
384461 } ,
385462
386463 mouse_event : function ( name ) {
@@ -399,8 +476,25 @@ var MPLCanvasView = widgets.DOMWidgetView.extend({
399476 }
400477
401478 if ( name === 'button_press' ) {
402- that . canvas . focus ( ) ;
403- that . canvas_div . focus ( ) ;
479+ // If clicking on the resize handle
480+ if ( canvas_pos . x >= that . top_canvas . width - that . resize_handle_size &&
481+ canvas_pos . y >= that . top_canvas . height - that . resize_handle_size ) {
482+ that . resizing = true ;
483+ return ;
484+ } else {
485+ that . canvas . focus ( ) ;
486+ that . canvas_div . focus ( ) ;
487+ }
488+ }
489+
490+ if ( name === 'motion_notify' ) {
491+ // If the mouse is on the handle, change the cursor style
492+ if ( canvas_pos . x >= that . top_canvas . width - that . resize_handle_size &&
493+ canvas_pos . y >= that . top_canvas . height - that . resize_handle_size ) {
494+ that . top_canvas . style . cursor = 'nw-resize' ;
495+ } else {
496+ that . top_canvas . style . cursor = that . model . get ( '_cursor' ) ;
497+ }
404498 }
405499
406500 // Rate-limit the position text updates so that we don't overwhelm the
@@ -421,10 +515,21 @@ var MPLCanvasView = widgets.DOMWidgetView.extend({
421515 * to control all of the cursor setting manually through the
422516 * 'cursor' event from matplotlib */
423517 event . preventDefault ( ) ;
424- return false ;
425518 } ;
426519 } ,
427520
521+ resize_event : function ( event ) {
522+ if ( this . resizing ) {
523+ var new_size = utils . get_mouse_position ( event , this . top_canvas ) ;
524+
525+ this . model . resize ( new_size . x , new_size . y ) ;
526+ }
527+ } ,
528+
529+ stop_resize_event : function ( ) {
530+ this . resizing = false ;
531+ } ,
532+
428533 key_event : function ( name ) {
429534 var that = this ;
430535 return function ( event ) {
@@ -458,9 +563,9 @@ var MPLCanvasView = widgets.DOMWidgetView.extend({
458563 } ;
459564 } ,
460565
461- close : function ( ) {
462- this . send_message ( 'closing' ) ;
463- this . trigger ( 'close' ) ;
566+ remove : function ( ) {
567+ window . removeEventListener ( 'mousemove' , this . _resize_event ) ;
568+ window . removeEventListener ( 'mouseup' , this . _stop_resize_event ) ;
464569 }
465570} ) ;
466571
0 commit comments