diff --git a/external-deps/spyder-kernels/.gitrepo b/external-deps/spyder-kernels/.gitrepo index c6045f0122b..156bcbd6d64 100644 --- a/external-deps/spyder-kernels/.gitrepo +++ b/external-deps/spyder-kernels/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/spyder-ide/spyder-kernels.git branch = master - commit = a24faa7ae3083f4247709481627aa97bf2a6efc0 - parent = 3a083a611f2aa96cf3d7975d5af8a228948abc9a + commit = 66576a716f1dc0282ff3dc26ba5ce7c43ede852c + parent = 5fca910e11f69b1dc83f8d249c3a28b1b99ca3d6 method = merge - cmdver = 0.4.3 + cmdver = 0.4.6 diff --git a/external-deps/spyder-kernels/spyder_kernels/console/kernel.py b/external-deps/spyder-kernels/spyder_kernels/console/kernel.py index 0585077a75c..56902ee10f2 100644 --- a/external-deps/spyder-kernels/spyder_kernels/console/kernel.py +++ b/external-deps/spyder-kernels/spyder_kernels/console/kernel.py @@ -557,6 +557,8 @@ def set_matplotlib_conf(self, conf): resolution_n = 'pylab/inline/resolution' width_n = 'pylab/inline/width' height_n = 'pylab/inline/height' + fontsize_n = 'pylab/inline/fontsize' + bottom_n = 'pylab/inline/bottom' bbox_inches_n = 'pylab/inline/bbox_inches' inline_backend = 'inline' @@ -581,6 +583,15 @@ def set_matplotlib_conf(self, conf): (conf[width_n], conf[height_n]) ) + if fontsize_n in conf: + self._set_mpl_inline_rc_config('font.size', conf[fontsize_n]) + + if bottom_n in conf: + self._set_mpl_inline_rc_config( + 'figure.subplot.bottom', + conf[bottom_n] + ) + if bbox_inches_n in conf: self.set_mpl_inline_bbox_inches(conf[bbox_inches_n]) diff --git a/spyder/config/main.py b/spyder/config/main.py index 98dcc64bc35..7270e4d294e 100644 --- a/spyder/config/main.py +++ b/spyder/config/main.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + # # Copyright © Spyder Project Contributors # Licensed under the terms of the MIT License @@ -151,6 +152,8 @@ 'pylab/inline/resolution': 72, 'pylab/inline/width': 6, 'pylab/inline/height': 4, + 'pylab/inline/fontsize': 10.0, + 'pylab/inline/bottom': 0.11, 'pylab/inline/bbox_inches': True, 'startup/run_lines': '', 'startup/use_run_file': False, diff --git a/spyder/plugins/ipythonconsole/confpage.py b/spyder/plugins/ipythonconsole/confpage.py index aea60f46620..93aad79088c 100644 --- a/spyder/plugins/ipythonconsole/confpage.py +++ b/spyder/plugins/ipythonconsole/confpage.py @@ -171,6 +171,26 @@ def setup_page(self): _("Height:")+" ", " "+_("inches"), 'pylab/inline/height', min_=1, max_=20, step=1, tip=_("Default is 4")) + fontsize_spin = self.create_spinbox( + _("Font size:") + " ", + " " + _("points"), + 'pylab/inline/fontsize', + min_=5, + max_=48, + step=1.0, + tip=_("Default is 10") + ) + bottom_spin = self.create_spinbox( + _("Bottom edge:") + " ", + " " + _("of figure height"), + 'pylab/inline/bottom', + min_=0, + max_=0.3, + step=0.01, + tip=_("The position of the bottom edge of the subplots,\nas a " + "fraction of the figure height.\nThe default is 0.11.") + ) + bottom_spin.spinbox.setDecimals(2) bbox_inches_box = newcb( _("Use a tight layout for inline plots"), 'pylab/inline/bbox_inches', @@ -185,16 +205,16 @@ def setup_page(self): inline_layout = QGridLayout() inline_layout.addWidget(format_box.label, 1, 0) inline_layout.addWidget(format_box.combobox, 1, 1) - inline_layout.addWidget(resolution_spin.plabel, 2, 0) - inline_layout.addWidget(resolution_spin.spinbox, 2, 1) - inline_layout.addWidget(resolution_spin.slabel, 2, 2) - inline_layout.addWidget(width_spin.plabel, 3, 0) - inline_layout.addWidget(width_spin.spinbox, 3, 1) - inline_layout.addWidget(width_spin.slabel, 3, 2) - inline_layout.addWidget(height_spin.plabel, 4, 0) - inline_layout.addWidget(height_spin.spinbox, 4, 1) - inline_layout.addWidget(height_spin.slabel, 4, 2) - inline_layout.addWidget(bbox_inches_box, 5, 0, 1, 4) + + spinboxes = [resolution_spin, width_spin, height_spin, + fontsize_spin, bottom_spin] + for counter, spinbox in enumerate(spinboxes): + inline_layout.addWidget(spinbox.plabel, counter + 2, 0) + inline_layout.addWidget(spinbox.spinbox, counter + 2, 1) + inline_layout.addWidget(spinbox.slabel, counter + 2, 2) + inline_layout.addWidget(spinbox.help_label, counter + 2, 3) + + inline_layout.addWidget(bbox_inches_box, len(spinboxes) + 2, 0, 1, 4) inline_h_layout = QHBoxLayout() inline_h_layout.addLayout(inline_layout) diff --git a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py index 796dc3509da..6dafdfafec1 100644 --- a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py +++ b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py @@ -17,6 +17,7 @@ import shutil import sys from textwrap import dedent +from unittest.mock import patch # Third party imports from ipykernel._version import __version__ as ipykernel_version @@ -1914,6 +1915,21 @@ def test_restart_intertactive_backend(ipyconsole, qtbot): assert bool(os.environ.get('BACKEND_REQUIRE_RESTART')) +def test_mpl_conf(ipyconsole, qtbot): + """ + Test that after setting matplotlib-related config options, the member + function send_mpl_backend of the shellwidget is called with the new value. + """ + main_widget = ipyconsole.get_widget() + client = main_widget.get_current_client() + with patch.object(client.shellwidget, 'send_mpl_backend') as mock: + main_widget.set_conf('pylab/inline/fontsize', 20.5) + mock.assert_called_once_with({'pylab/inline/fontsize': 20.5}) + with patch.object(client.shellwidget, 'send_mpl_backend') as mock: + main_widget.set_conf('pylab/inline/bottom', 0.314) + mock.assert_called_once_with({'pylab/inline/bottom': 0.314}) + + @flaky(max_runs=3) @pytest.mark.no_web_widgets def test_no_infowidget(ipyconsole): diff --git a/spyder/plugins/ipythonconsole/widgets/main_widget.py b/spyder/plugins/ipythonconsole/widgets/main_widget.py index b362ce31926..0bb6165a4ab 100644 --- a/spyder/plugins/ipythonconsole/widgets/main_widget.py +++ b/spyder/plugins/ipythonconsole/widgets/main_widget.py @@ -871,6 +871,7 @@ def change_clients_autocall(self, value): 'pylab', 'pylab/backend', 'pylab/autoload', 'pylab/inline/figure_format', 'pylab/inline/resolution', 'pylab/inline/width', 'pylab/inline/height', + 'pylab/inline/fontsize', 'pylab/inline/bottom', 'pylab/inline/bbox_inches']) def change_possible_restart_and_mpl_conf(self, option, value): """ diff --git a/spyder/plugins/ipythonconsole/widgets/shell.py b/spyder/plugins/ipythonconsole/widgets/shell.py index c59b26ef32d..772ac499dd2 100644 --- a/spyder/plugins/ipythonconsole/widgets/shell.py +++ b/spyder/plugins/ipythonconsole/widgets/shell.py @@ -564,6 +564,8 @@ def send_mpl_backend(self, option=None): resolution_n = 'pylab/inline/resolution' width_n = 'pylab/inline/width' height_n = 'pylab/inline/height' + fontsize_n = 'pylab/inline/fontsize' + bottom_n = 'pylab/inline/bottom' bbox_inches_n = 'pylab/inline/bbox_inches' backend_o = self.get_conf(pylab_backend_n) @@ -591,6 +593,22 @@ def send_mpl_backend(self, option=None): if height_o is not None: matplotlib_conf[height_n] = height_o + # Font size + fontsize_o = float(self.get_conf(fontsize_n)) + if ( + fontsize_o is not None + and (option is None or fontsize_n in option) + ): + matplotlib_conf[fontsize_n] = fontsize_o + + # Bottom part + bottom_o = float(self.get_conf(bottom_n)) + if ( + bottom_o is not None + and (option is None or bottom_n in option) + ): + matplotlib_conf[bottom_n] = bottom_o + # Print figure kwargs bbox_inches_o = self.get_conf(bbox_inches_n) if option is None or bbox_inches_n in option: diff --git a/spyder/plugins/variableexplorer/widgets/namespacebrowser.py b/spyder/plugins/variableexplorer/widgets/namespacebrowser.py index 0e2d0695bf6..61795fd2d60 100644 --- a/spyder/plugins/variableexplorer/widgets/namespacebrowser.py +++ b/spyder/plugins/variableexplorer/widgets/namespacebrowser.py @@ -479,6 +479,13 @@ def plot_in_plots_plugin(self, data, funcname): import spyder.pyplot as plt from IPython.core.pylabtools import print_figure + try: + from matplotlib import rc_context + except ImportError: + # Ignore fontsize and bottom options if guiqwt is used + # as plotting library + from contextlib import nullcontext as rc_context + if self.get_conf('pylab/inline/figure_format', section='ipython_console') == 1: figure_format = 'svg' @@ -498,10 +505,23 @@ def plot_in_plots_plugin(self, data, funcname): else: bbox_inches = None - fig, ax = plt.subplots(figsize=(width, height)) - getattr(ax, funcname)(data) - image = print_figure(fig, fmt=figure_format, bbox_inches=bbox_inches, - dpi=resolution) + matplotlib_rc = { + 'font.size': self.get_conf('pylab/inline/fontsize', + section='ipython_console'), + 'figure.subplot.bottom': self.get_conf('pylab/inline/bottom', + section='ipython_console') + } + + with rc_context(matplotlib_rc): + fig, ax = plt.subplots(figsize=(width, height)) + getattr(ax, funcname)(data) + image = print_figure( + fig, + fmt=figure_format, + bbox_inches=bbox_inches, + dpi=resolution + ) + if figure_format == 'svg': image = image.encode() self.sig_show_figure_requested.emit(image, mime_type, self.shellwidget) diff --git a/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py b/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py index 71b33421eb4..3881605926f 100644 --- a/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py +++ b/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py @@ -231,6 +231,37 @@ def test_namespacebrowser_plot_with_mute_inline_plotting_true( assert blocker.args == expected_args +def test_namespacebrowser_plot_options(namespacebrowser): + """ + Test that font.size and figure.subplot.bottom in matplotlib.rcParams are + set to the values from the Spyder preferences when plotting. + """ + def check_rc(*args): + from matplotlib import rcParams + assert rcParams['font.size'] == 20.5 + assert rcParams['figure.subplot.bottom'] == 0.314 + + namespacebrowser.set_conf('mute_inline_plotting', True, section='plots') + namespacebrowser.plots_plugin_enabled = True + namespacebrowser.set_conf( + 'pylab/inline/fontsize', 20.5, section='ipython_console' + ) + namespacebrowser.set_conf( + 'pylab/inline/bottom', 0.314, section='ipython_console' + ) + + mock_figure = Mock() + mock_axis = Mock() + mock_png = b'fake png' + + with patch('spyder.pyplot.subplots', + return_value=(mock_figure, mock_axis)), \ + patch('IPython.core.pylabtools.print_figure', + return_value=mock_png), \ + patch.object(mock_axis, 'plot', check_rc): + namespacebrowser.plot([4, 2], 'plot') + + def test_namespacebrowser_plot_with_mute_inline_plotting_false(namespacebrowser): """ Test that plotting a list from the namespace browser shows a plot if