【问题标题】:Matplotlib NavigationToolbar: Advanced figure options?Matplotlib NavigationToolbar:高级图形选项?
【发布时间】:2019-04-05 13:26:52
【问题描述】:

我在 GUI 应用程序中使用 matplotlib 和 PyQt5。要绘制我的数据,我使用“FigureCanvasQTAgg”并添加“NavigationToolbar2QT”以便能够修改和保存我的绘图。它有效,但我想知道是否有更多先进的工具栏,例如允许更改标题和/或标签的字体大小?这是我使用的atm:

import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar

self.figure = plt.figure()
self.ax = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas)

可用的“图形选项”如下所示:

我正在寻找的选项是:

  • 标题的字体大小
  • 轴标签字体大小
  • 位置、字体大小、样式等图例选项

可能我不是第一个寻找这些选项的人,所以我猜有人已经编写了这样一个高级工具栏,但我找不到任何东西,并认为在我尝试自己编写代码之前值得在这里询问并且(可能)浪费大量时间。

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    图形选项qt对话框定义在 https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backends/qt_editor/figureoptions.py

    您可以将该代码复制到一个新文件中,例如myfigureoptions.py,然后进行您想要的更改。然后将其修改为原始版本。

    以下将添加一个标题字体大小字段。

    # Copyright © 2009 Pierre Raybaut
    # Licensed under the terms of the MIT License
    # see the mpl licenses directory for a copy of the license
    # Modified to add a title fontsize 
    
    """Module that provides a GUI-based editor for matplotlib's figure options."""
    
    import os.path
    import re
    
    import matplotlib
    from matplotlib import cm, colors as mcolors, markers, image as mimage
    import matplotlib.backends.qt_editor.formlayout as formlayout
    from matplotlib.backends.qt_compat import QtGui
    
    
    
    def get_icon(name):
        basedir = os.path.join(matplotlib.rcParams['datapath'], 'images')
        return QtGui.QIcon(os.path.join(basedir, name))
    
    
    LINESTYLES = {'-': 'Solid',
                  '--': 'Dashed',
                  '-.': 'DashDot',
                  ':': 'Dotted',
                  'None': 'None',
                  }
    
    DRAWSTYLES = {
        'default': 'Default',
        'steps-pre': 'Steps (Pre)', 'steps': 'Steps (Pre)',
        'steps-mid': 'Steps (Mid)',
        'steps-post': 'Steps (Post)'}
    
    MARKERS = markers.MarkerStyle.markers
    
    
    def figure_edit(axes, parent=None):
        """Edit matplotlib figure options"""
        sep = (None, None)  # separator
    
        # Get / General
        # Cast to builtin floats as they have nicer reprs.
        xmin, xmax = map(float, axes.get_xlim())
        ymin, ymax = map(float, axes.get_ylim())
        general = [('Title', axes.get_title()),
                   ('Title Fontsize', axes.title.get_fontsize()),  # <------------- HERE
                   sep,
                   (None, "<b>X-Axis</b>"),
                   ('Left', xmin), ('Right', xmax),
                   ('Label', axes.get_xlabel()),
                   ('Scale', [axes.get_xscale(), 'linear', 'log', 'logit']),
                   sep,
                   (None, "<b>Y-Axis</b>"),
                   ('Bottom', ymin), ('Top', ymax),
                   ('Label', axes.get_ylabel()),
                   ('Scale', [axes.get_yscale(), 'linear', 'log', 'logit']),
                   sep,
                   ('(Re-)Generate automatic legend', False),
                   ]
    
        # Save the unit data
        xconverter = axes.xaxis.converter
        yconverter = axes.yaxis.converter
        xunits = axes.xaxis.get_units()
        yunits = axes.yaxis.get_units()
    
        # Sorting for default labels (_lineXXX, _imageXXX).
        def cmp_key(label):
            match = re.match(r"(_line|_image)(\d+)", label)
            if match:
                return match.group(1), int(match.group(2))
            else:
                return label, 0
    
        # Get / Curves
        linedict = {}
        for line in axes.get_lines():
            label = line.get_label()
            if label == '_nolegend_':
                continue
            linedict[label] = line
        curves = []
    
        def prepare_data(d, init):
            """Prepare entry for FormLayout.
    
            `d` is a mapping of shorthands to style names (a single style may
            have multiple shorthands, in particular the shorthands `None`,
            `"None"`, `"none"` and `""` are synonyms); `init` is one shorthand
            of the initial style.
    
            This function returns an list suitable for initializing a
            FormLayout combobox, namely `[initial_name, (shorthand,
            style_name), (shorthand, style_name), ...]`.
            """
            if init not in d:
                d = {**d, init: str(init)}
            # Drop duplicate shorthands from dict (by overwriting them during
            # the dict comprehension).
            name2short = {name: short for short, name in d.items()}
            # Convert back to {shorthand: name}.
            short2name = {short: name for name, short in name2short.items()}
            # Find the kept shorthand for the style specified by init.
            canonical_init = name2short[d[init]]
            # Sort by representation and prepend the initial value.
            return ([canonical_init] +
                    sorted(short2name.items(),
                           key=lambda short_and_name: short_and_name[1]))
    
        curvelabels = sorted(linedict, key=cmp_key)
        for label in curvelabels:
            line = linedict[label]
            color = mcolors.to_hex(
                mcolors.to_rgba(line.get_color(), line.get_alpha()),
                keep_alpha=True)
            ec = mcolors.to_hex(
                mcolors.to_rgba(line.get_markeredgecolor(), line.get_alpha()),
                keep_alpha=True)
            fc = mcolors.to_hex(
                mcolors.to_rgba(line.get_markerfacecolor(), line.get_alpha()),
                keep_alpha=True)
            curvedata = [
                ('Label', label),
                sep,
                (None, '<b>Line</b>'),
                ('Line style', prepare_data(LINESTYLES, line.get_linestyle())),
                ('Draw style', prepare_data(DRAWSTYLES, line.get_drawstyle())),
                ('Width', line.get_linewidth()),
                ('Color (RGBA)', color),
                sep,
                (None, '<b>Marker</b>'),
                ('Style', prepare_data(MARKERS, line.get_marker())),
                ('Size', line.get_markersize()),
                ('Face color (RGBA)', fc),
                ('Edge color (RGBA)', ec)]
            curves.append([curvedata, label, ""])
        # Is there a curve displayed?
        has_curve = bool(curves)
    
        # Get / Images
        imagedict = {}
        for image in axes.get_images():
            label = image.get_label()
            if label == '_nolegend_':
                continue
            imagedict[label] = image
        imagelabels = sorted(imagedict, key=cmp_key)
        images = []
        cmaps = [(cmap, name) for name, cmap in sorted(cm.cmap_d.items())]
        for label in imagelabels:
            image = imagedict[label]
            cmap = image.get_cmap()
            if cmap not in cm.cmap_d.values():
                cmaps = [(cmap, cmap.name)] + cmaps
            low, high = image.get_clim()
            imagedata = [
                ('Label', label),
                ('Colormap', [cmap.name] + cmaps),
                ('Min. value', low),
                ('Max. value', high),
                ('Interpolation',
                 [image.get_interpolation()]
                 + [(name, name) for name in sorted(mimage.interpolations_names)])]
            images.append([imagedata, label, ""])
        # Is there an image displayed?
        has_image = bool(images)
    
        datalist = [(general, "Axes", "")]
        if curves:
            datalist.append((curves, "Curves", ""))
        if images:
            datalist.append((images, "Images", ""))
    
        def apply_callback(data):
            """This function will be called to apply changes"""
            orig_xlim = axes.get_xlim()
            orig_ylim = axes.get_ylim()
    
            general = data.pop(0)
            curves = data.pop(0) if has_curve else []
            images = data.pop(0) if has_image else []
            if data:
                raise ValueError("Unexpected field")
            # Set / General
            (title, titlefontsize, xmin, xmax, xlabel, xscale,    # <------------- HERE
             ymin, ymax, ylabel, yscale, generate_legend) = general 
    
            if axes.get_xscale() != xscale:
                axes.set_xscale(xscale)
            if axes.get_yscale() != yscale:
                axes.set_yscale(yscale)
    
            axes.set_title(title)
            axes.title.set_fontsize(titlefontsize)                # <------------- HERE
            axes.set_xlim(xmin, xmax)
            axes.set_xlabel(xlabel)
            axes.set_ylim(ymin, ymax)
            axes.set_ylabel(ylabel)
    
            # Restore the unit data
            axes.xaxis.converter = xconverter
            axes.yaxis.converter = yconverter
            axes.xaxis.set_units(xunits)
            axes.yaxis.set_units(yunits)
            axes.xaxis._update_axisinfo()
            axes.yaxis._update_axisinfo()
    
            # Set / Curves
            for index, curve in enumerate(curves):
                line = linedict[curvelabels[index]]
                (label, linestyle, drawstyle, linewidth, color, marker, markersize,
                 markerfacecolor, markeredgecolor) = curve
                line.set_label(label)
                line.set_linestyle(linestyle)
                line.set_drawstyle(drawstyle)
                line.set_linewidth(linewidth)
                rgba = mcolors.to_rgba(color)
                line.set_alpha(None)
                line.set_color(rgba)
                if marker is not 'none':
                    line.set_marker(marker)
                    line.set_markersize(markersize)
                    line.set_markerfacecolor(markerfacecolor)
                    line.set_markeredgecolor(markeredgecolor)
    
            # Set / Images
            for index, image_settings in enumerate(images):
                image = imagedict[imagelabels[index]]
                label, cmap, low, high, interpolation = image_settings
                image.set_label(label)
                image.set_cmap(cm.get_cmap(cmap))
                image.set_clim(*sorted([low, high]))
                image.set_interpolation(interpolation)
    
            # re-generate legend, if checkbox is checked
            if generate_legend:
                draggable = None
                ncol = 1
                if axes.legend_ is not None:
                    old_legend = axes.get_legend()
                    draggable = old_legend._draggable is not None
                    ncol = old_legend._ncol
                new_legend = axes.legend(ncol=ncol)
                if new_legend:
                    new_legend.set_draggable(draggable)
    
            # Redraw
            figure = axes.get_figure()
            figure.canvas.draw()
            if not (axes.get_xlim() == orig_xlim and axes.get_ylim() == orig_ylim):
                figure.canvas.toolbar.push_current()
    
        data = formlayout.fedit(datalist, title="Figure options", parent=parent,
                                icon=get_icon('qt4_editor_options.svg'),
                                apply=apply_callback)
        if data is not None:
            apply_callback(data)
    
    # Monkey-patch original figureoptions    
    from matplotlib.backends.qt_editor import figureoptions       # <------------- HERE
    figureoptions.figure_edit = figure_edit
    

    把它当作

    import matplotlib.pyplot as plt
    import myfigureoptions
    
    fig, ax = plt.subplots()
    ax.plot([1,2])
    ax.set_title("My Title")
    
    plt.show()
    

    单击图形选项对话框时,您现在有一个标题字体大小字段。

    【讨论】:

    • 感谢您的回答和代码。我在我的 GUI 中实现了它,它工作正常。但是,我仍然希望之前有人编写过此类高级选项,并且不会立即接受您的回答。
    【解决方案2】:

    根据 ImportanceOfBeingErnes 的回答,我开始以我的应用程序需要的方式修改 matplotlib 图形选项。我删除了 X 轴和 Y 轴的左/右和上/下限制,因为我已经在我的 GUI 中实现了这些选项,但是我为各种图例选项添加了另一个选项卡。这是他现在的图形选项的样子:

    这是当前版本的代码(我不得不访问一些私有变量,因为我找不到相应的get-functions,而且变量命名可能不是最好的。请随时修改我的代码) :

    # Copyright © 2009 Pierre Raybaut
    # Licensed under the terms of the MIT License
    # see the mpl licenses directory for a copy of the license
    # Modified to add a title fontsize
    
    """Module that provides a GUI-based editor for matplotlib's figure options."""
    
    import os.path
    import re
    
    import matplotlib
    from matplotlib import cm, colors as mcolors, markers, image as mimage
    import matplotlib.backends.qt_editor.formlayout as formlayout
    from matplotlib.backends.qt_compat import QtGui
    
    
    def get_icon(name):
        basedir = os.path.join(matplotlib.rcParams['datapath'], 'images')
        return QtGui.QIcon(os.path.join(basedir, name))
    
    
    LINESTYLES = {'-': 'Solid',
                  '--': 'Dashed',
                  '-.': 'DashDot',
                  ':': 'Dotted',
                  'None': 'None',
                  }
    
    DRAWSTYLES = {
        'default': 'Default',
        'steps-pre': 'Steps (Pre)', 'steps': 'Steps (Pre)',
        'steps-mid': 'Steps (Mid)',
        'steps-post': 'Steps (Post)'}
    
    MARKERS = markers.MarkerStyle.markers
    
    
    def figure_edit(axes, parent=None):
        """Edit matplotlib figure options"""
        sep = (None, None)  # separator
    
        # Get / General
        # Cast to builtin floats as they have nicer reprs.
        xmin, xmax = map(float, axes.get_xlim())
        ymin, ymax = map(float, axes.get_ylim())
        if 'labelsize' in axes.xaxis._major_tick_kw:
            _ticksize = int(axes.xaxis._major_tick_kw['labelsize'])
        else:
            _ticksize = 15
        general = [(None, "<b>Figure Title</b>"),
                   ('Title', axes.get_title()),
                   ('Font Size', int(axes.title.get_fontsize())),
                   sep,
                   (None, "<b>Axes settings</b>"),
                   ('Label Size', int(axes.xaxis.label.get_fontsize())),
                   ('Tick Size', _ticksize),
                   ('Show grid', axes.xaxis._gridOnMajor),
                   sep,
                   (None, "<b>X-Axis</b>"),
                   ('Label', axes.get_xlabel()),
                   ('Scale', [axes.get_xscale(), 'linear', 'log', 'logit']),
                   sep,
                   (None, "<b>Y-Axis</b>"),
                   ('Label', axes.get_ylabel()),
                   ('Scale', [axes.get_yscale(), 'linear', 'log', 'logit'])
                   ]
    
        if axes.legend_ is not None:
            old_legend = axes.get_legend()
            _draggable = old_legend._draggable is not None
            _ncol = old_legend._ncol
            _fontsize = int(old_legend._fontsize)
            _frameon = old_legend._drawFrame
            _shadow = old_legend.shadow
            _fancybox = type(old_legend.legendPatch.get_boxstyle()) == matplotlib.patches.BoxStyle.Round
            _framealpha = old_legend.get_frame().get_alpha()
        else:
            _draggable = False
            _ncol = 1
            _fontsize = 15
            _frameon = True
            _shadow = True
            _fancybox = True
            _framealpha = 0.5
    
        legend = [('Draggable', _draggable),
                  ('columns', _ncol),
                  ('Font Size', _fontsize),
                  ('Frame', _frameon),
                  ('Shadow', _shadow),
                  ('FancyBox', _fancybox),
                  ('Alpha', _framealpha)
                  ]
    
        # Save the unit data
        xconverter = axes.xaxis.converter
        yconverter = axes.yaxis.converter
        xunits = axes.xaxis.get_units()
        yunits = axes.yaxis.get_units()
    
        # Sorting for default labels (_lineXXX, _imageXXX).
        def cmp_key(label):
            match = re.match(r"(_line|_image)(\d+)", label)
            if match:
                return match.group(1), int(match.group(2))
            else:
                return label, 0
    
        # Get / Curves
        linedict = {}
        for line in axes.get_lines():
            label = line.get_label()
            if label == '_nolegend_':
                continue
            linedict[label] = line
        curves = []
    
        def prepare_data(d, init):
            """Prepare entry for FormLayout.
    
            `d` is a mapping of shorthands to style names (a single style may
            have multiple shorthands, in particular the shorthands `None`,
            `"None"`, `"none"` and `""` are synonyms); `init` is one shorthand
            of the initial style.
    
            This function returns an list suitable for initializing a
            FormLayout combobox, namely `[initial_name, (shorthand,
            style_name), (shorthand, style_name), ...]`.
            """
            if init not in d:
                d = {**d, init: str(init)}
            # Drop duplicate shorthands from dict (by overwriting them during
            # the dict comprehension).
            name2short = {name: short for short, name in d.items()}
            # Convert back to {shorthand: name}.
            short2name = {short: name for name, short in name2short.items()}
            # Find the kept shorthand for the style specified by init.
            canonical_init = name2short[d[init]]
            # Sort by representation and prepend the initial value.
            return ([canonical_init] +
                    sorted(short2name.items(),
                           key=lambda short_and_name: short_and_name[1]))
    
        curvelabels = sorted(linedict, key=cmp_key)
        for label in curvelabels:
            line = linedict[label]
            color = mcolors.to_hex(
                mcolors.to_rgba(line.get_color(), line.get_alpha()),
                keep_alpha=True)
            ec = mcolors.to_hex(
                mcolors.to_rgba(line.get_markeredgecolor(), line.get_alpha()),
                keep_alpha=True)
            fc = mcolors.to_hex(
                mcolors.to_rgba(line.get_markerfacecolor(), line.get_alpha()),
                keep_alpha=True)
            curvedata = [
                ('Label', label),
                sep,
                (None, '<b>Line</b>'),
                ('Line style', prepare_data(LINESTYLES, line.get_linestyle())),
                ('Draw style', prepare_data(DRAWSTYLES, line.get_drawstyle())),
                ('Width', line.get_linewidth()),
                ('Color (RGBA)', color),
                sep,
                (None, '<b>Marker</b>'),
                ('Style', prepare_data(MARKERS, line.get_marker())),
                ('Size', line.get_markersize()),
                ('Face color (RGBA)', fc),
                ('Edge color (RGBA)', ec)]
            curves.append([curvedata, label, ""])
        # Is there a curve displayed?
        has_curve = bool(curves)
    
        # Get / Images
        imagedict = {}
        for image in axes.get_images():
            label = image.get_label()
            if label == '_nolegend_':
                continue
            imagedict[label] = image
        imagelabels = sorted(imagedict, key=cmp_key)
        images = []
        cmaps = [(cmap, name) for name, cmap in sorted(cm.cmap_d.items())]
        for label in imagelabels:
            image = imagedict[label]
            cmap = image.get_cmap()
            if cmap not in cm.cmap_d.values():
                cmaps = [(cmap, cmap.name)] + cmaps
            low, high = image.get_clim()
            imagedata = [
                ('Label', label),
                ('Colormap', [cmap.name] + cmaps),
                ('Min. value', low),
                ('Max. value', high),
                ('Interpolation',
                 [image.get_interpolation()]
                 + [(name, name) for name in sorted(mimage.interpolations_names)])]
            images.append([imagedata, label, ""])
        # Is there an image displayed?
        has_image = bool(images)
    
        datalist = [(general, "Axes", ""), (legend, "Legend", "")]
        if curves:
            datalist.append((curves, "Curves", ""))
        if images:
            datalist.append((images, "Images", ""))
    
        def apply_callback(data):
            """This function will be called to apply changes"""
    
            general = data.pop(0)
            legend = data.pop(0)
            curves = data.pop(0) if has_curve else []
            images = data.pop(0) if has_image else []
            if data:
                raise ValueError("Unexpected field")
    
            # Set / General
            (title, titlesize, labelsize, ticksize, grid, xlabel, xscale,
             ylabel, yscale) = general
    
            if axes.get_xscale() != xscale:
                axes.set_xscale(xscale)
            if axes.get_yscale() != yscale:
                axes.set_yscale(yscale)
    
            axes.set_title(title)
            axes.title.set_fontsize(titlesize)
    
            axes.set_xlabel(xlabel)
            axes.xaxis.label.set_size(labelsize)
            axes.xaxis.set_tick_params(labelsize=ticksize)
    
            axes.set_ylabel(ylabel)
            axes.yaxis.label.set_size(labelsize)
            axes.yaxis.set_tick_params(labelsize=ticksize)
    
            axes.grid(grid)
    
            # Restore the unit data
            axes.xaxis.converter = xconverter
            axes.yaxis.converter = yconverter
            axes.xaxis.set_units(xunits)
            axes.yaxis.set_units(yunits)
            axes.xaxis._update_axisinfo()
            axes.yaxis._update_axisinfo()
    
            # Set / Legend
            (leg_draggable, leg_ncol, leg_fontsize, leg_frameon, leg_shadow,
             leg_fancybox, leg_framealpha, ) = legend
    
            new_legend = axes.legend(ncol=leg_ncol,
                                     fontsize=float(leg_fontsize),
                                     frameon=leg_frameon,
                                     shadow=leg_shadow,
                                     framealpha=leg_framealpha,
                                     fancybox=leg_fancybox)
            new_legend.set_draggable(leg_draggable)
    
            # Set / Curves
            for index, curve in enumerate(curves):
                line = linedict[curvelabels[index]]
                (label, linestyle, drawstyle, linewidth, color, marker, markersize,
                 markerfacecolor, markeredgecolor) = curve
                line.set_label(label)
                line.set_linestyle(linestyle)
                line.set_drawstyle(drawstyle)
                line.set_linewidth(linewidth)
                rgba = mcolors.to_rgba(color)
                line.set_alpha(None)
                line.set_color(rgba)
                if marker is not 'none':
                    line.set_marker(marker)
                    line.set_markersize(markersize)
                    line.set_markerfacecolor(markerfacecolor)
                    line.set_markeredgecolor(markeredgecolor)
    
            # Set / Images
            for index, image_settings in enumerate(images):
                image = imagedict[imagelabels[index]]
                label, cmap, low, high, interpolation = image_settings
                image.set_label(label)
                image.set_cmap(cm.get_cmap(cmap))
                image.set_clim(*sorted([low, high]))
                image.set_interpolation(interpolation)
    
            # Redraw
            figure = axes.get_figure()
            figure.canvas.draw()
    
        data = formlayout.fedit(datalist, title="Figure options", parent=parent,
                                icon=get_icon('qt4_editor_options.svg'),
                                apply=apply_callback)
        if data is not None:
            apply_callback(data)
    
    
    # Monkey-patch original figureoptions
    from matplotlib.backends.qt_editor import figureoptions
    figureoptions.figure_edit = figure_edit
    

    【讨论】:

      猜你喜欢
      • 2013-11-23
      • 2018-11-21
      • 2019-11-14
      • 2018-07-20
      • 1970-01-01
      • 2020-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多