diff --git a/external-deps/spyder-kernels/.gitrepo b/external-deps/spyder-kernels/.gitrepo
index 8289cc5780e..6211a979227 100644
--- a/external-deps/spyder-kernels/.gitrepo
+++ b/external-deps/spyder-kernels/.gitrepo
@@ -4,9 +4,9 @@
; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
;
[subrepo]
- remote = https://github.com/spyder-ide/spyder-kernels.git
- branch = master
- commit = 34a6d634383f27c61cd92172be94c1b344bc5cf1
- parent = f80c897ac6aafb65c421da50a63e2092f32ff7e2
+ remote = https://github.com/PierreRaybaut/spyder-kernels.git
+ branch = improving_guiqwt_integration_kernel
+ commit = 6c5418b2866bcd558bd9c4227ff7584b601bed28
+ parent = 2b75726b538443ca0140cfa6ed02ab75c5bb8318
method = merge
cmdver = 0.4.5
diff --git a/external-deps/spyder-kernels/spyder_kernels/console/start.py b/external-deps/spyder-kernels/spyder_kernels/console/start.py
index f5e4b122b0c..0dae58077ff 100644
--- a/external-deps/spyder-kernels/spyder_kernels/console/start.py
+++ b/external-deps/spyder-kernels/spyder_kernels/console/start.py
@@ -255,12 +255,20 @@ def varexp(line):
ip = get_ipython() #analysis:ignore
funcname, name = line.split()
try:
- import guiqwt.pyplot as pyplot
- except:
- import matplotlib.pyplot as pyplot
- pyplot.figure();
- getattr(pyplot, funcname[2:])(ip.kernel._get_current_namespace()[name])
- pyplot.show()
+ data = ip.kernel._get_current_namespace()[name]
+ except KeyError:
+ print("\033[41mVariable %s does not exist in current namespace.\033[0m" % name)
+ plotlib = os.environ.get('SPY_VAREXP_PLOTLIB')
+ try:
+ pyplot = __import__(plotlib + '.pyplot', globals(), locals(), [], 0).pyplot
+ pyplot.figure()
+ getattr(pyplot, funcname[2:])(data)
+ pyplot.show()
+ except (ImportError, ModuleNotFoundError):
+ print("\033[41mPlease install %s or select an available plotting library "
+ "in Variable Explorer preferences.\033[0m" % plotlib)
+ except Exception:
+ ip.showtraceback(sys.exc_info())
def main():
diff --git a/spyder/config/main.py b/spyder/config/main.py
index cd06e14ae92..1b20e2e0661 100644
--- a/spyder/config/main.py
+++ b/spyder/config/main.py
@@ -21,6 +21,7 @@
from spyder.config.appearance import APPEARANCE
from spyder.plugins.editor.utils.findtasks import TASKS_PATTERN
from spyder.utils.introspection.module_completion import PREFERRED_MODULES
+from spyder.plugins.variableexplorer.plotlib import DEFAULT_PLOTLIB
# =============================================================================
@@ -176,7 +177,8 @@
'truncate': True,
'minmax': False,
'show_callable_attributes': True,
- 'show_special_attributes': False
+ 'show_special_attributes': False,
+ 'plotlib': DEFAULT_PLOTLIB,
}),
('debugger',
{
diff --git a/spyder/plugins/ipythonconsole/utils/kernelspec.py b/spyder/plugins/ipythonconsole/utils/kernelspec.py
index ae6ced09d71..af290dd25a8 100644
--- a/spyder/plugins/ipythonconsole/utils/kernelspec.py
+++ b/spyder/plugins/ipythonconsole/utils/kernelspec.py
@@ -208,6 +208,7 @@ def env(self):
'SPY_GREEDY_O': self.get_conf('greedy_completer'),
'SPY_JEDI_O': self.get_conf('jedi_completer'),
'SPY_SYMPY_O': self.get_conf('symbolic_math'),
+ 'SPY_VAREXP_PLOTLIB': self.get_conf('plotlib', section='variable_explorer'),
'SPY_TESTING': running_under_pytest() or get_safe_mode(),
'SPY_HIDE_CMD': self.get_conf('hide_cmd_windows'),
'SPY_PYTHONPATH': pypath
diff --git a/spyder/plugins/plots/widgets/figurebrowser.py b/spyder/plugins/plots/widgets/figurebrowser.py
index c619feb193e..74bda0626e2 100644
--- a/spyder/plugins/plots/widgets/figurebrowser.py
+++ b/spyder/plugins/plots/widgets/figurebrowser.py
@@ -772,8 +772,6 @@ def add_thumbnail(self, fig, fmt):
parent=self, background_color=self.background_color)
thumbnail.canvas.load_figure(fig, fmt)
thumbnail.sig_canvas_clicked.connect(self.set_current_thumbnail)
- thumbnail.sig_remove_figure_requested.connect(self.remove_thumbnail)
- thumbnail.sig_save_figure_requested.connect(self.save_figure_as)
thumbnail.sig_context_menu_requested.connect(
lambda point: self.show_context_menu(point, thumbnail))
self._thumbnails.append(thumbnail)
@@ -796,8 +794,6 @@ def remove_all_thumbnails(self):
"""Remove all thumbnails."""
for thumbnail in self._thumbnails:
thumbnail.sig_canvas_clicked.disconnect()
- thumbnail.sig_remove_figure_requested.disconnect()
- thumbnail.sig_save_figure_requested.disconnect()
self.layout().removeWidget(thumbnail)
thumbnail.setParent(None)
thumbnail.hide()
@@ -815,8 +811,6 @@ def remove_thumbnail(self, thumbnail):
# Disconnect signals
try:
thumbnail.sig_canvas_clicked.disconnect()
- thumbnail.sig_remove_figure_requested.disconnect()
- thumbnail.sig_save_figure_requested.disconnect()
except TypeError:
pass
@@ -945,29 +939,6 @@ class FigureThumbnail(QWidget):
The clicked figure thumbnail.
"""
- sig_remove_figure_requested = Signal(object)
- """
- This signal is emitted to request the removal of a figure thumbnail.
-
- Parameters
- ----------
- figure_thumbnail: spyder.plugins.plots.widget.figurebrowser.FigureThumbnail
- The figure thumbnail to remove.
- """
-
- sig_save_figure_requested = Signal(object, str)
- """
- This signal is emitted to request the saving of a figure thumbnail.
-
- Parameters
- ----------
- figure_thumbnail: spyder.plugins.plots.widget.figurebrowser.FigureThumbnail
- The figure thumbnail to save.
- format: str
- The image format to use when saving the image. One of "image/png",
- "image/jpeg" and "image/svg+xml".
- """
-
sig_context_menu_requested = Signal(QPoint)
"""
This signal is emitted to request a context menu.
diff --git a/spyder/plugins/variableexplorer/confpage.py b/spyder/plugins/variableexplorer/confpage.py
index a2e7a8d0ecd..e40c2eefd2b 100644
--- a/spyder/plugins/variableexplorer/confpage.py
+++ b/spyder/plugins/variableexplorer/confpage.py
@@ -7,15 +7,17 @@
"""Variable Explorer Plugin Configuration Page."""
# Third party imports
-from qtpy.QtWidgets import QGroupBox, QVBoxLayout
+from qtpy.QtWidgets import QGroupBox, QVBoxLayout, QLabel
# Local imports
from spyder.config.base import _
from spyder.api.preferences import PluginConfigPage
+from spyder.plugins.variableexplorer import plotlib
class VariableExplorerConfigPage(PluginConfigPage):
def setup_page(self):
+ # Filter Group
filter_group = QGroupBox(_("Filter"))
filter_data = [
('exclude_private', _("Exclude private references")),
@@ -27,20 +29,40 @@ def setup_page(self):
]
filter_boxes = [self.create_checkbox(text, option)
for option, text in filter_data]
-
- display_group = QGroupBox(_("Display"))
- display_data = [('minmax', _("Show arrays min/max"), '')]
- display_boxes = [self.create_checkbox(text, option, tip=tip)
- for option, text, tip in display_data]
-
filter_layout = QVBoxLayout()
for box in filter_boxes:
filter_layout.addWidget(box)
filter_group.setLayout(filter_layout)
+ # Display Group
+ display_group = QGroupBox(_("Display"))
+ display_data = [("minmax", _("Show arrays min/max"), "")]
+ display_boxes = [
+ self.create_checkbox(text, option, tip=tip)
+ for option, text, tip in display_data
+ ]
display_layout = QVBoxLayout()
for box in display_boxes:
display_layout.addWidget(box)
+ plotlib_opt = self.create_combobox(
+ _("Plotting library:") + " ",
+ zip(plotlib.SUPPORTED_PLOTLIBS, plotlib.SUPPORTED_PLOTLIBS),
+ "plotlib",
+ default=plotlib.DEFAULT_PLOTLIB,
+ tip=_(
+ "Default library used for data plotting of NumPy arrays "
+ "(curve, histogram, image).
Regarding the "
+ "%varexp magic command, this option will be "
+ "applied the next time a console is opened."
+ ),
+ )
+ display_layout.addWidget(plotlib_opt)
+ if not plotlib.AVAILABLE_PLOTLIBS:
+ msg = "%s" % plotlib.REQ_ERROR_MSG[:-1]
+ msg += " " + _("for enabling data plotting from Spyder IDE process.")
+ plotlib_msg = QLabel(msg)
+ plotlib_msg.setWordWrap(True)
+ display_layout.addWidget(plotlib_msg)
display_group.setLayout(display_layout)
vlayout = QVBoxLayout()
diff --git a/spyder/plugins/variableexplorer/plotlib.py b/spyder/plugins/variableexplorer/plotlib.py
new file mode 100644
index 00000000000..1274ffcab76
--- /dev/null
+++ b/spyder/plugins/variableexplorer/plotlib.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+
+"""
+Variable Explorer Plugin plotting library management.
+"""
+
+# Standard library imports
+import importlib
+
+# Local imports
+from spyder.config.base import _
+
+
+# Defining compatible plotting libraries
+SUPPORTED_PLOTLIBS = ("matplotlib", "guiqwt")
+
+# Default library is the first one of the list
+DEFAULT_PLOTLIB = SUPPORTED_PLOTLIBS[0]
+
+
+def is_package_installed(modname):
+ """Check if package is installed **without importing it**
+
+ Note: As Spyder won't start if matplotlib has been imported too early,
+ we do not use `utils.programs.is_module_installed` here because
+ it imports module to check if it's installed.
+ """
+ return importlib.util.find_spec(modname) is not None
+
+
+def get_available_plotlibs():
+ """Return list of available plotting libraries"""
+ return [name for name in SUPPORTED_PLOTLIBS if is_package_installed(name)]
+
+
+def get_requirement_error_message():
+ """Return html error message when no library is available"""
+ txt = ", ".join(["%s" % name for name in SUPPORTED_PLOTLIBS])
+ return _("Please install a compatible plotting library (%s).") % txt
+
+
+AVAILABLE_PLOTLIBS = get_available_plotlibs()
+REQ_ERROR_MSG = get_requirement_error_message()
diff --git a/spyder/pyplot.py b/spyder/pyplot.py
deleted file mode 100644
index 6890e731def..00000000000
--- a/spyder/pyplot.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Copyright (c) 2009- Spyder Project Contributors
-#
-# Distributed under the terms of the MIT License
-# (see spyder/__init__.py for details)
-# -----------------------------------------------------------------------------
-
-
-"""
-Import guiqwt's pyplot module or matplotlib's pyplot.
-"""
-
-
-try:
- from guiqwt.pyplot import *
-except Exception:
- from matplotlib.pyplot import *
diff --git a/spyder/widgets/collectionseditor.py b/spyder/widgets/collectionseditor.py
index 5b8e0a76a37..4fd06d4c9f7 100644
--- a/spyder/widgets/collectionseditor.py
+++ b/spyder/widgets/collectionseditor.py
@@ -60,6 +60,7 @@
from spyder.plugins.variableexplorer.widgets.importwizard import ImportWizard
from spyder.widgets.helperwidgets import CustomSortFilterProxy
from spyder.plugins.variableexplorer.widgets.basedialog import BaseDialog
+from spyder.plugins.variableexplorer.plotlib import REQ_ERROR_MSG
from spyder.utils.palette import SpyderPalette
from spyder.utils.stylesheet import PANES_TOOLBAR_STYLESHEET
@@ -1132,57 +1133,45 @@ def view_item(self):
index = index.child(index.row(), 3)
self.delegate.createEditor(self, None, index, object_explorer=True)
- def __prepare_plot(self):
- try:
- import guiqwt.pyplot #analysis:ignore
- return True
- except:
- try:
- if 'matplotlib' not in sys.modules:
- import matplotlib # noqa
- return True
- except Exception:
- QMessageBox.warning(self, _("Import error"),
- _("Please install matplotlib"
- " or guiqwt."))
-
def plot_item(self, funcname):
"""Plot item"""
index = self.currentIndex()
- if self.__prepare_plot():
- if self.proxy_model:
- key = self.source_model.get_key(
- self.proxy_model.mapToSource(index))
- else:
- key = self.source_model.get_key(index)
- try:
- self.plot(key, funcname)
- except (ValueError, TypeError) as error:
- QMessageBox.critical(self, _( "Plot"),
- _("Unable to plot data."
- "
Error message:
%s"
- ) % str(error))
+ if self.proxy_model:
+ key = self.source_model.get_key(
+ self.proxy_model.mapToSource(index))
+ else:
+ key = self.source_model.get_key(index)
+ try:
+ self.plot(key, funcname)
+ except (ValueError, TypeError) as error:
+ QMessageBox.critical(
+ self,
+ _( "Plot"),
+ _("Unable to plot data.
"
+ "The error message was:
%s") % str(error)
+ )
@Slot()
def imshow_item(self):
"""Imshow item"""
index = self.currentIndex()
- if self.__prepare_plot():
- if self.proxy_model:
- key = self.source_model.get_key(
- self.proxy_model.mapToSource(index))
+ if self.proxy_model:
+ key = self.source_model.get_key(
+ self.proxy_model.mapToSource(index))
+ else:
+ key = self.source_model.get_key(index)
+ try:
+ if self.is_image(key):
+ self.show_image(key)
else:
- key = self.source_model.get_key(index)
- try:
- if self.is_image(key):
- self.show_image(key)
- else:
- self.imshow(key)
- except (ValueError, TypeError) as error:
- QMessageBox.critical(self, _( "Plot"),
- _("Unable to show image."
- "
Error message:
%s"
- ) % str(error))
+ self.imshow(key)
+ except (ValueError, TypeError) as error:
+ QMessageBox.critical(
+ self,
+ _( "Plot"),
+ _("Unable to show image.
"
+ "The error message was:
%s") % str(error)
+ )
@Slot()
def save_array(self):
@@ -1383,21 +1372,38 @@ def oedit(self, key):
oedit)
oedit(data[key])
+ def __get_pyplot(self):
+ """
+ Return default plotting library `pyplot` package if available.
+
+ The default plotting library is an option defined in the Variable
+ Explorer plugin scope, e.g. "maplotlib" or "guiqwt".
+ """
+ libname = self.get_conf('plotlib', section='variable_explorer')
+ try:
+ return __import__(libname + '.pyplot',
+ globals(), locals(), [], 0).pyplot
+ except Exception:
+ msg = _("Unable to plot data using %s library.") % libname
+ QMessageBox.critical(self, _("Error"), msg + "
" + REQ_ERROR_MSG)
+
def plot(self, key, funcname):
"""Plot item"""
data = self.source_model.get_data()
- import spyder.pyplot as plt
- plt.figure()
- getattr(plt, funcname)(data[key])
- plt.show()
+ plt = self.__get_pyplot()
+ if plt is not None:
+ plt.figure()
+ getattr(plt, funcname)(data[key])
+ plt.show()
def imshow(self, key):
"""Show item's image"""
data = self.source_model.get_data()
- import spyder.pyplot as plt
- plt.figure()
- plt.imshow(data[key])
- plt.show()
+ plt = self.__get_pyplot()
+ if plt is not None:
+ plt.figure()
+ plt.imshow(data[key])
+ plt.show()
def show_image(self, key):
"""Show image (item is a PIL image)"""