diff --git a/doc/conf.py b/doc/conf.py index e336fd075253..5a68653d38bc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -114,7 +114,7 @@ # Plot directive configuration # ---------------------------- -plot_formats = [('png', 80), ('hires.png', 200), ('pdf', 50)] +plot_formats = [('svg', 72), ('png', 80)] # Subdirectories in 'examples/' directory of package and titles for gallery mpl_example_sections = ( diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index fa0a42f17c99..dec202cdef9e 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1703,6 +1703,8 @@ def add_image(self, image): Returns the image. """ self._set_artist_props(image) + if not image.get_label(): + image.set_label('_image%d' % len(self.images)) self.images.append(image) image._remove_method = lambda h: self.images.remove(h) return image diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index d1207b1e88b1..fc24fe186f1d 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -587,7 +587,7 @@ def _init_toolbar(self): if figureoptions is not None: a = self.addAction(self._icon("qt4_editor_options.png"), 'Customize', self.edit_parameters) - a.setToolTip('Edit curves line and axes parameters') + a.setToolTip('Edit axis, curve and image parameters') self.buttons = {} @@ -619,23 +619,12 @@ def edit_parameters(self): else: titles = [] for axes in allaxes: - title = axes.get_title() - ylabel = axes.get_ylabel() - label = axes.get_label() - if title: - fmt = "%(title)s" - if ylabel: - fmt += ": %(ylabel)s" - fmt += " (%(axes_repr)s)" - elif ylabel: - fmt = "%(axes_repr)s (%(ylabel)s)" - elif label: - fmt = "%(axes_repr)s (%(label)s)" - else: - fmt = "%(axes_repr)s" - titles.append(fmt % dict(title=title, - ylabel=ylabel, label=label, - axes_repr=repr(axes))) + name = (axes.get_title() or + " - ".join(filter(None, [axes.get_xlabel(), + axes.get_ylabel()])) or + "".format( + type(axes).__name__, id(axes))) + titles.append(name) item, ok = QtWidgets.QInputDialog.getItem( self.parent, 'Customize', 'Select axes:', titles, 0, False) if ok: diff --git a/lib/matplotlib/backends/qt_editor/figureoptions.py b/lib/matplotlib/backends/qt_editor/figureoptions.py index f2fc9e115efc..566c06d8bfb5 100644 --- a/lib/matplotlib/backends/qt_editor/figureoptions.py +++ b/lib/matplotlib/backends/qt_editor/figureoptions.py @@ -16,7 +16,7 @@ import matplotlib.backends.qt_editor.formlayout as formlayout from matplotlib.backends.qt_compat import QtGui -from matplotlib import markers +from matplotlib import cm, markers from matplotlib.colors import colorConverter, rgb2hex @@ -43,8 +43,6 @@ def figure_edit(axes, parent=None): """Edit matplotlib figure options""" sep = (None, None) # separator - has_curve = len(axes.get_lines()) > 0 - # Get / General xmin, xmax = map(float, axes.get_xlim()) ymin, ymax = map(float, axes.get_ylim()) @@ -69,79 +67,103 @@ def figure_edit(axes, parent=None): xunits = axes.xaxis.get_units() yunits = axes.yaxis.get_units() - if has_curve: - # 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), ...]`. - """ - # 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.keys()) - for label in curvelabels: - line = linedict[label] - color = rgb2hex(colorConverter.to_rgb(line.get_color())) - ec = rgb2hex(colorConverter.to_rgb(line.get_markeredgecolor())) - fc = rgb2hex(colorConverter.to_rgb(line.get_markerfacecolor())) - curvedata = [ - ('Label', label), - sep, - (None, 'Line'), - ('Line Style', prepare_data(LINESTYLES, line.get_linestyle())), - ('Draw Style', prepare_data(DRAWSTYLES, line.get_drawstyle())), - ('Width', line.get_linewidth()), - ('Color', color), - sep, - (None, 'Marker'), - ('Style', prepare_data(MARKERS, line.get_marker())), - ('Size', line.get_markersize()), - ('Facecolor', fc), - ('Edgecolor', ec)] - curves.append([curvedata, label, ""]) - - # make sure that there is at least one displayed curve - has_curve = bool(curves) + # 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), ...]`. + """ + # 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.keys()) + for label in curvelabels: + line = linedict[label] + color = rgb2hex(colorConverter.to_rgb(line.get_color())) + ec = rgb2hex(colorConverter.to_rgb(line.get_markeredgecolor())) + fc = rgb2hex(colorConverter.to_rgb(line.get_markerfacecolor())) + curvedata = [ + ('Label', label), + sep, + (None, 'Line'), + ('Line Style', prepare_data(LINESTYLES, line.get_linestyle())), + ('Draw Style', prepare_data(DRAWSTYLES, line.get_drawstyle())), + ('Width', line.get_linewidth()), + ('Color', color), + sep, + (None, 'Marker'), + ('Style', prepare_data(MARKERS, line.get_marker())), + ('Size', line.get_markersize()), + ('Facecolor', fc), + ('Edgecolor', 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) + 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: + cmaps = [(cmap, cmap.name)] + cmaps + imagedata = [ + ('Label', label), + ('Colormap', [cmap.name] + cmaps) + ] + images.append([imagedata, label, ""]) + # Is there an image displayed? + has_image = bool(images) datalist = [(general, "Axes", "")] - if has_curve: + if curves: datalist.append((curves, "Curves", "")) + if images: + datalist.append((images, "Images", "")) def apply_callback(data): """This function will be called to apply changes""" - if has_curve: - general, curves = data - else: - general, = data + 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, xmin, xmax, xlabel, xscale, ymin, ymax, ylabel, yscale, \ - generate_legend = general + (title, xmin, xmax, xlabel, xscale, ymin, ymax, ylabel, yscale, + generate_legend) = general axes.set_xscale(xscale) axes.set_yscale(yscale) axes.set_title(title) @@ -158,26 +180,30 @@ def apply_callback(data): axes.xaxis._update_axisinfo() axes.yaxis._update_axisinfo() - if has_curve: - # 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) - line.set_color(color) - if marker is not 'none': - line.set_marker(marker) - line.set_markersize(markersize) - line.set_markerfacecolor(markerfacecolor) - line.set_markeredgecolor(markeredgecolor) + # 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) + line.set_color(color) + 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 = image_settings + image.set_label(label) + image.set_cmap(cm.get_cmap(cmap)) # re-generate legend, if checkbox is checked - if generate_legend: draggable = None ncol = None diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 866c636b206b..3401ac707cd5 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -499,11 +499,14 @@ def draw(self, renderer): gc.set_foreground(self._edgecolor, isRGBA=True) - lw = self._linewidth - if self._edgecolor[3] == 0: - lw = 0 - gc.set_linewidth(lw) - gc.set_linestyle(self._linestyle) + if (self._edgecolor[3] == 0 or + self._linestyle in {'none', 'None', ' ', '', None}): + # (some?) renderers expect this as no-edge signal + gc.set_linewidth(0.0) + else: + gc.set_linewidth(self._linewidth) + gc.set_linestyle(self._linestyle) + gc.set_capstyle(self._capstyle) gc.set_joinstyle(self._joinstyle) diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index f86cca7ebab1..adeff3ef441b 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -377,7 +377,7 @@ def remove_coding(text): {% endif %} {% for img in images %} - .. figure:: {{ build_dir }}/{{ img.basename }}.png + .. figure:: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }} {% for option in options -%} {{ option }} {% endfor %} @@ -537,17 +537,7 @@ def clear_state(plot_rcparams, close=True): matplotlib.rcParams.update(plot_rcparams) -def render_figures(code, code_path, output_dir, output_base, context, - function_name, config, context_reset=False, - close_figs=False): - """ - Run a pyplot script and save the low and high res PNGs and a PDF - in *output_dir*. - - Save the images under *output_dir* with file names derived from - *output_base* - """ - # -- Parse format list +def get_plot_formats(config): default_dpi = {'png': 80, 'hires.png': 200, 'pdf': 200} formats = [] plot_formats = config.plot_formats @@ -559,14 +549,27 @@ def render_figures(code, code_path, output_dir, output_base, context, for fmt in plot_formats: if isinstance(fmt, six.string_types): if ':' in fmt: - suffix,dpi = fmt.split(':') + suffix, dpi = fmt.split(':') formats.append((str(suffix), int(dpi))) else: formats.append((fmt, default_dpi.get(fmt, 80))) - elif type(fmt) in (tuple, list) and len(fmt)==2: + elif type(fmt) in (tuple, list) and len(fmt) == 2: formats.append((str(fmt[0]), int(fmt[1]))) else: raise PlotError('invalid image format "%r" in plot_formats' % fmt) + return formats + + +def render_figures(code, code_path, output_dir, output_base, context, + function_name, config, context_reset=False, + close_figs=False): + """ + Run a pyplot script and save the images in *output_dir*. + + Save the images under *output_dir* with file names derived from + *output_base* + """ + formats = get_plot_formats(config) # -- Try to determine if all images already exist @@ -666,6 +669,9 @@ def run(arguments, content, options, state_machine, state, lineno): config = document.settings.env.config nofigs = 'nofigs' in options + formats = get_plot_formats(config) + default_fmt = formats[0][0] + options.setdefault('include-source', config.plot_include_source) keep_context = 'context' in options context_opt = None if not keep_context else options['context'] @@ -814,6 +820,7 @@ def run(arguments, content, options, state_machine, state, lineno): result = format_template( config.plot_template or TEMPLATE, + default_fmt=default_fmt, dest_dir=dest_dir_link, build_dir=build_dir_link, source_link=src_link, diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py index f9de913bfaf3..eac6568f0492 100644 --- a/lib/matplotlib/textpath.py +++ b/lib/matplotlib/textpath.py @@ -343,7 +343,7 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, 1094995778)]: try: font.select_charmap(charmap_code) - except ValueError: + except (ValueError, RuntimeError): pass else: break diff --git a/setup.py b/setup.py index 3ca8a2f4d2df..43fef759d6ea 100644 --- a/setup.py +++ b/setup.py @@ -150,6 +150,7 @@ def run(self): cmdclass['test'] = NoopTestCommand cmdclass['build_ext'] = BuildExtraLibraries + # patch bdist_wheel for a bug on windows # https://bitbucket.org/pypa/wheel/issues/91/cannot-create-a-file-when-that-file if os.name == 'nt':