2
2
3
3
module Matplotlib
4
4
module IRuby
5
+ module HookExtension
6
+ def self . extended ( obj )
7
+ @event_registry ||= { }
8
+ @event_registry [ obj ] = { }
9
+ end
10
+
11
+ def self . register_event ( target , event , hook )
12
+ @event_registry [ target ] [ event ] ||= [ ]
13
+ @event_registry [ target ] [ event ] << hook
14
+ end
15
+
16
+ def register_event ( event , hook = nil , &block )
17
+ HookExtension . register_event ( self , event , [ hook , block ] . compact )
18
+ end
19
+
20
+ def self . unregister_event ( target , event , hook )
21
+ return unless @event_registry [ target ]
22
+ return unless @event_registry [ target ] [ event ]
23
+ @event_registry [ target ] [ event ] . delete ( hook )
24
+ end
25
+
26
+ def unregister_event ( event , hook )
27
+ HookExtension . unregister_event ( self , event , hook )
28
+ end
29
+
30
+ def self . trigger_event ( target , event )
31
+ return unless @event_registry [ target ] [ event ]
32
+ @event_registry [ target ] [ event ] . each do |hooks |
33
+ hooks . to_a . each do |hook |
34
+ hook . call if hook
35
+ end
36
+ end
37
+ rescue Exception
38
+ $stderr. puts "Error occurred in triggerred event: target=#{ target } event=#{ event } " , $!. to_s , *$!. backtrace
39
+ end
40
+
41
+ def trigger_event ( event )
42
+ HookExtension . trigger_event ( self , event )
43
+ end
44
+
45
+ def execute_request ( msg )
46
+ code = msg [ :content ] [ 'code' ]
47
+ @execution_count += 1 if msg [ :content ] [ 'store_history' ]
48
+ @session . send ( :publish , :execute_input , code : code , execution_count : @execution_count )
49
+
50
+ trigger_event ( :pre_execute )
51
+
52
+ content = {
53
+ status : :ok ,
54
+ payload : [ ] ,
55
+ user_expressions : { } ,
56
+ execution_count : @execution_count
57
+ }
58
+ result = nil
59
+ begin
60
+ result = @backend . eval ( code , msg [ :content ] [ 'store_history' ] )
61
+ rescue SystemExit
62
+ content [ :payload ] << { source : :ask_exit }
63
+ rescue Exception => e
64
+ content = error_message ( e )
65
+ @session . send ( :publish , :error , content )
66
+ end
67
+
68
+ trigger_event ( :post_execute )
69
+
70
+ @session . send ( :reply , :execute_reply , content )
71
+ @session . send ( :publish , :execute_result ,
72
+ data : ::IRuby ::Display . display ( result ) ,
73
+ metadata : { } ,
74
+ execution_count : @execution_count ) unless result . nil? || msg [ :content ] [ 'silent' ]
75
+ end
76
+ end
77
+
5
78
AGG_FORMATS = {
6
79
"image/png" => "png" ,
7
80
"application/pdf" => "pdf" ,
8
81
"application/eps" => "eps" ,
9
82
"image/eps" => "eps" ,
10
83
"application/postscript" => "ps" ,
11
84
"image/svg+xml" => "svg"
12
- }
85
+ } . freeze
13
86
14
87
module Helper
15
88
def register_formats
@@ -31,7 +104,152 @@ def register_formats
31
104
class << self
32
105
# NOTE: This method is translated from `IPython.core.activate_matplotlib` function.
33
106
def activate ( gui = :inline )
107
+ enable_matplotlib ( gui )
108
+ end
109
+
110
+ GUI_BACKEND_MAP = {
111
+ tk : :TkAgg ,
112
+ gtk : :GTKAgg ,
113
+ gtk3 : :GTK3Agg ,
114
+ wx : :WXAgg ,
115
+ qt : :Qt4Agg ,
116
+ qt4 : :Qt4Agg ,
117
+ qt5 : :Qt5Agg ,
118
+ osx : :MacOSX ,
119
+ nbagg : :nbAgg ,
120
+ notebook : :nbAgg ,
121
+ agg : :agg ,
122
+ inline : 'module://ruby.matplotlib.backend_inline' ,
123
+ } . freeze
124
+
125
+ BACKEND_GUI_MAP = Hash [ GUI_BACKEND_MAP . select { |k , v | v } ] . freeze
126
+
127
+ private_constant :GUI_BACKEND_MAP , :BACKEND_GUI_MAP
128
+
129
+ def available_gui_names
130
+ GUI_BACKEND_MAP . keys
131
+ end
132
+
133
+ private
134
+
135
+ # This method is based on IPython.core.interactiveshell.InteractiveShell.enable_matplotlib function.
136
+ def enable_matplotlib ( gui = nil )
137
+ gui , backend = find_gui_and_backend ( gui , @gui_select )
138
+
139
+ if gui != :inline
140
+ if @gui_select . nil?
141
+ @gui_select = gui
142
+ elsif gui != @gui_select
143
+ $stderr. puts "Warning: Cannot change to a different GUI toolkit: #{ gui } . Using #{ @gui_select } instead."
144
+ gui , backend = find_gui_and_backend ( @gui_select )
145
+ end
146
+ end
147
+
148
+ activate_matplotlib ( backend )
149
+ configure_inline_support ( backend )
150
+ # self.enable_gui(gui)
151
+ # register matplotlib-aware execution runner for ExecutionMagics
152
+
153
+ [ gui , backend ]
154
+ end
155
+
156
+ # Given a gui string return the gui and matplotlib backend.
157
+ # This method is based on IPython.core.pylabtools.find_gui_and_backend function.
158
+ #
159
+ # @param [String, Symbol, nil] gui can be one of (:tk, :gtk, :wx, :qt, :qt4, :inline, :agg).
160
+ # @param [String, Symbol, nil] gui_select can be one of (:tk, :gtk, :wx, :qt, :qt4, :inline, :agg).
161
+ #
162
+ # @return A pair of (gui, backend) where backend is one of (:TkAgg, :GTKAgg, :WXAgg, :Qt4Agg, :agg).
163
+ def find_gui_and_backend ( gui = nil , gui_select = nil )
164
+ gui = gui . to_sym if gui . kind_of? String
165
+
166
+ if gui && gui != :auto
167
+ # select backend based on requested gui
168
+ backend = GUI_BACKEND_MAP [ gui ]
169
+ gui = nil if gui == :agg
170
+ return [ gui , backend ]
171
+ end
172
+
173
+ backend = Matplotlib . rcParamsOrig [ 'backend' ] &.to_sym
174
+ gui = BACKEND_GUI_MAP [ backend ]
175
+
176
+ # If we have already had a gui active, we need it and inline are the ones allowed.
177
+ if gui_select && gui != gui_select
178
+ gui = gui_select
179
+ backend = backend [ gui ]
180
+ end
181
+
182
+ [ gui , backend ]
183
+ end
184
+
185
+ # Activate the given backend and set interactive to true.
186
+ # This method is based on IPython.core.pylabtools.activate_matplotlib function.
187
+ #
188
+ # @param [String, Symbol] backend a name of matplotlib backend
189
+ def activate_matplotlib ( backend )
190
+ require 'matplotlib'
191
+ Matplotlib . interactive ( true )
192
+
193
+ backend = backend . to_s
194
+ Matplotlib . rcParams [ 'backend' ] = backend
195
+
34
196
require 'matplotlib/pyplot'
197
+ Matplotlib ::Pyplot . switch_backend ( backend )
198
+
199
+ # TODO: should support wrapping python function
200
+ # plt = Matplotlib::Pyplot
201
+ # plt.__pyobj__.show._needmain = false
202
+ # plt.__pyobj__.draw_if_interactive = flag_calls(plt.__pyobj__.draw_if_interactive)
203
+ end
204
+
205
+ # This method is based on IPython.core.pylabtools.configure_inline_support function.
206
+ #
207
+ # @param shell an instance of IRuby shell
208
+ # @param backend a name of matplotlib backend
209
+ def configure_inline_support ( backend )
210
+ # Temporally monky-patching IRuby kernel to enable flushing and closing figures.
211
+ # TODO: Make this feature a pull-request for sciruby/iruby.
212
+ kernel = ::IRuby ::Kernel . instance
213
+ kernel . extend HookExtension
214
+ if backend == GUI_BACKEND_MAP [ :inline ]
215
+ kernel . register_event ( :post_execute , method ( :flush_figures ) )
216
+ # TODO: save original rcParams and overwrite rcParams with IRuby-specific configuration
217
+ new_backend_name = :inline
218
+ else
219
+ kernel . unregister_event ( :post_execute , method ( :flush_figures ) )
220
+ # TODO: restore saved original rcParams
221
+ new_backend_name = :not_inline
222
+ end
223
+ if new_backend_name != @current_backend
224
+ # TODO: select figure formats
225
+ @current_backend = new_backend_name
226
+ end
227
+ end
228
+
229
+ # This method is based on ipykernel.pylab.backend_inline.flush_figures function.
230
+ def flush_figures
231
+ # TODO: I want to allow users to turn on/off automatic figure closing.
232
+ show_figures ( true )
233
+ end
234
+
235
+ # This method is based on ipykernel.pylab.backend_inline.show function.
236
+ #
237
+ # @param [true, false] close If true, a `plt.close('all')` call is automatically issued after sending all the figures.
238
+ def show_figures ( close = false )
239
+ _pylab_helpers = PyCall . import_module ( 'matplotlib._pylab_helpers' )
240
+ gcf = PyCall . getattr ( _pylab_helpers , :Gcf )
241
+ kernel = ::IRuby ::Kernel . instance
242
+ gcf . get_all_fig_managers . ( ) . each do |fig_manager |
243
+ data = ::IRuby ::Display . display ( fig_manager . canvas . figure )
244
+ kernel . session . send ( :publish , :execute_result ,
245
+ data : data ,
246
+ metadata : { } ,
247
+ execution_count : kernel . instance_variable_get ( :@execution_count ) )
248
+ end
249
+ ensure
250
+ unless gcf . get_all_fig_managers . ( ) . none?
251
+ Matplotlib ::Pyplot . close ( 'all' )
252
+ end
35
253
end
36
254
end
37
255
end
0 commit comments