lib/matplotlib/iruby.rb in matplotlib-0.1.0.alpha.20170302 vs lib/matplotlib/iruby.rb in matplotlib-0.1.0.alpha.20170307
- old
+ new
@@ -1,17 +1,90 @@
require 'pycall'
module Matplotlib
module IRuby
+ module HookExtension
+ def self.extended(obj)
+ @event_registry ||= {}
+ @event_registry[obj] = {}
+ end
+
+ def self.register_event(target, event, hook)
+ @event_registry[target][event] ||= []
+ @event_registry[target][event] << hook
+ end
+
+ def register_event(event, hook=nil, &block)
+ HookExtension.register_event(self, event, [hook, block].compact)
+ end
+
+ def self.unregister_event(target, event, hook)
+ return unless @event_registry[target]
+ return unless @event_registry[target][event]
+ @event_registry[target][event].delete(hook)
+ end
+
+ def unregister_event(event, hook)
+ HookExtension.unregister_event(self, event, hook)
+ end
+
+ def self.trigger_event(target, event)
+ return unless @event_registry[target][event]
+ @event_registry[target][event].each do |hooks|
+ hooks.to_a.each do |hook|
+ hook.call if hook
+ end
+ end
+ rescue Exception
+ $stderr.puts "Error occurred in triggerred event: target=#{target} event=#{event}", $!.to_s, *$!.backtrace
+ end
+
+ def trigger_event(event)
+ HookExtension.trigger_event(self, event)
+ end
+
+ def execute_request(msg)
+ code = msg[:content]['code']
+ @execution_count += 1 if msg[:content]['store_history']
+ @session.send(:publish, :execute_input, code: code, execution_count: @execution_count)
+
+ trigger_event(:pre_execute)
+
+ content = {
+ status: :ok,
+ payload: [],
+ user_expressions: {},
+ execution_count: @execution_count
+ }
+ result = nil
+ begin
+ result = @backend.eval(code, msg[:content]['store_history'])
+ rescue SystemExit
+ content[:payload] << { source: :ask_exit }
+ rescue Exception => e
+ content = error_message(e)
+ @session.send(:publish, :error, content)
+ end
+
+ trigger_event(:post_execute)
+
+ @session.send(:reply, :execute_reply, content)
+ @session.send(:publish, :execute_result,
+ data: ::IRuby::Display.display(result),
+ metadata: {},
+ execution_count: @execution_count) unless result.nil? || msg[:content]['silent']
+ end
+ end
+
AGG_FORMATS = {
"image/png" => "png",
"application/pdf" => "pdf",
"application/eps" => "eps",
"image/eps" => "eps",
"application/postscript" => "ps",
"image/svg+xml" => "svg"
- }
+ }.freeze
module Helper
def register_formats
bytes_io = PyCall.import_module('io').BytesIO
type { Figure }
@@ -29,10 +102,155 @@
end
class << self
# NOTE: This method is translated from `IPython.core.activate_matplotlib` function.
def activate(gui=:inline)
+ enable_matplotlib(gui)
+ end
+
+ GUI_BACKEND_MAP = {
+ tk: :TkAgg,
+ gtk: :GTKAgg,
+ gtk3: :GTK3Agg,
+ wx: :WXAgg,
+ qt: :Qt4Agg,
+ qt4: :Qt4Agg,
+ qt5: :Qt5Agg,
+ osx: :MacOSX,
+ nbagg: :nbAgg,
+ notebook: :nbAgg,
+ agg: :agg,
+ inline: 'module://ruby.matplotlib.backend_inline',
+ }.freeze
+
+ BACKEND_GUI_MAP = Hash[GUI_BACKEND_MAP.select {|k, v| v }].freeze
+
+ private_constant :GUI_BACKEND_MAP, :BACKEND_GUI_MAP
+
+ def available_gui_names
+ GUI_BACKEND_MAP.keys
+ end
+
+ private
+
+ # This method is based on IPython.core.interactiveshell.InteractiveShell.enable_matplotlib function.
+ def enable_matplotlib(gui=nil)
+ gui, backend = find_gui_and_backend(gui, @gui_select)
+
+ if gui != :inline
+ if @gui_select.nil?
+ @gui_select = gui
+ elsif gui != @gui_select
+ $stderr.puts "Warning: Cannot change to a different GUI toolkit: #{gui}. Using #{@gui_select} instead."
+ gui, backend = find_gui_and_backend(@gui_select)
+ end
+ end
+
+ activate_matplotlib(backend)
+ configure_inline_support(backend)
+ # self.enable_gui(gui)
+ # register matplotlib-aware execution runner for ExecutionMagics
+
+ [gui, backend]
+ end
+
+ # Given a gui string return the gui and matplotlib backend.
+ # This method is based on IPython.core.pylabtools.find_gui_and_backend function.
+ #
+ # @param [String, Symbol, nil] gui can be one of (:tk, :gtk, :wx, :qt, :qt4, :inline, :agg).
+ # @param [String, Symbol, nil] gui_select can be one of (:tk, :gtk, :wx, :qt, :qt4, :inline, :agg).
+ #
+ # @return A pair of (gui, backend) where backend is one of (:TkAgg, :GTKAgg, :WXAgg, :Qt4Agg, :agg).
+ def find_gui_and_backend(gui=nil, gui_select=nil)
+ gui = gui.to_sym if gui.kind_of? String
+
+ if gui && gui != :auto
+ # select backend based on requested gui
+ backend = GUI_BACKEND_MAP[gui]
+ gui = nil if gui == :agg
+ return [gui, backend]
+ end
+
+ backend = Matplotlib.rcParamsOrig['backend']&.to_sym
+ gui = BACKEND_GUI_MAP[backend]
+
+ # If we have already had a gui active, we need it and inline are the ones allowed.
+ if gui_select && gui != gui_select
+ gui = gui_select
+ backend = backend[gui]
+ end
+
+ [gui, backend]
+ end
+
+ # Activate the given backend and set interactive to true.
+ # This method is based on IPython.core.pylabtools.activate_matplotlib function.
+ #
+ # @param [String, Symbol] backend a name of matplotlib backend
+ def activate_matplotlib(backend)
+ require 'matplotlib'
+ Matplotlib.interactive(true)
+
+ backend = backend.to_s
+ Matplotlib.rcParams['backend'] = backend
+
require 'matplotlib/pyplot'
+ Matplotlib::Pyplot.switch_backend(backend)
+
+ # TODO: should support wrapping python function
+ # plt = Matplotlib::Pyplot
+ # plt.__pyobj__.show._needmain = false
+ # plt.__pyobj__.draw_if_interactive = flag_calls(plt.__pyobj__.draw_if_interactive)
+ end
+
+ # This method is based on IPython.core.pylabtools.configure_inline_support function.
+ #
+ # @param shell an instance of IRuby shell
+ # @param backend a name of matplotlib backend
+ def configure_inline_support(backend)
+ # Temporally monky-patching IRuby kernel to enable flushing and closing figures.
+ # TODO: Make this feature a pull-request for sciruby/iruby.
+ kernel = ::IRuby::Kernel.instance
+ kernel.extend HookExtension
+ if backend == GUI_BACKEND_MAP[:inline]
+ kernel.register_event(:post_execute, method(:flush_figures))
+ # TODO: save original rcParams and overwrite rcParams with IRuby-specific configuration
+ new_backend_name = :inline
+ else
+ kernel.unregister_event(:post_execute, method(:flush_figures))
+ # TODO: restore saved original rcParams
+ new_backend_name = :not_inline
+ end
+ if new_backend_name != @current_backend
+ # TODO: select figure formats
+ @current_backend = new_backend_name
+ end
+ end
+
+ # This method is based on ipykernel.pylab.backend_inline.flush_figures function.
+ def flush_figures
+ # TODO: I want to allow users to turn on/off automatic figure closing.
+ show_figures(true)
+ end
+
+ # This method is based on ipykernel.pylab.backend_inline.show function.
+ #
+ # @param [true, false] close If true, a `plt.close('all')` call is automatically issued after sending all the figures.
+ def show_figures(close=false)
+ _pylab_helpers = PyCall.import_module('matplotlib._pylab_helpers')
+ gcf = PyCall.getattr(_pylab_helpers, :Gcf)
+ kernel = ::IRuby::Kernel.instance
+ gcf.get_all_fig_managers.().each do |fig_manager|
+ data = ::IRuby::Display.display(fig_manager.canvas.figure)
+ kernel.session.send(:publish, :execute_result,
+ data: data,
+ metadata: {},
+ execution_count: kernel.instance_variable_get(:@execution_count))
+ end
+ ensure
+ unless gcf.get_all_fig_managers.().none?
+ Matplotlib::Pyplot.close('all')
+ end
end
end
end
end