diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 554abe8d6677..ce0ddbfe4216 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -2075,3 +2075,26 @@ def _check_and_log_subprocess(command, logger, **kwargs): .format(command, exc.output.decode('utf-8'))) logger.debug(report) return report + + +def _print_unit(st, dpi=None): + """ + Convert from a string with a number and units into inches + """ + print_units = {'cm': 2.54, 'pt': 72.0, 'mm': 25.4, 'in': 1.0} + if len(st) > 2: + num = float(st[:-2]) + else: + num = 1 + unit = st[-2:] + # special case "px" + if unit == 'px': + if dpi is not None: + return num / dpi + else: + raise ValueError('px units need dpi to be set') + elif unit in print_units: + return num / print_units[unit] + else: + # let the parent handle the errors + return st diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 819525482cb7..9a0212f46722 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -344,14 +344,17 @@ def __init__(self, if frameon is None: frameon = rcParams['figure.frameon'] - if not np.isfinite(figsize).all(): - raise ValueError('figure size must be finite not ' - '{}'.format(figsize)) - self.bbox_inches = Bbox.from_bounds(0, 0, *figsize) - + self.canvas = None + self._suptitle = None self.dpi_scale_trans = Affine2D().scale(dpi, dpi) # do not use property as it will trigger self._dpi = dpi + + # init the bbox to dummy values: + self.bbox_inches = Bbox.from_bounds(0, 0, 1, 1) + # set properly. + self.set_size(figsize) + self.bbox = TransformedBbox(self.bbox_inches, self.dpi_scale_trans) self.frameon = frameon @@ -364,9 +367,6 @@ def __init__(self, self._set_artist_props(self.patch) self.patch.set_antialiased(False) - self.canvas = None - self._suptitle = None - if subplotpars is None: subplotpars = SubplotParams() @@ -391,6 +391,14 @@ def __init__(self, # list of child gridspecs for this figure self._gridspecs = [] + def _parse_figsize(self, figsize): + if len(figsize) == 3: + unit = figsize[-1] + figsize = figsize[:2] + conv = cbook._print_unit(unit, dpi=self._dpi) + figsize = [f * conv for f in figsize] + return figsize + # TODO: I'd like to dynamically add the _repr_html_ method # to the figure in the right context, but then IPython doesn't # use it, for some reason. @@ -862,19 +870,85 @@ def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, self.stale = True return im + def set_size(self, w, h=None, units=None, forward=True): + """ + Set the figure size + + Parameters + ---------- + + w: float, string, or two-tuple of either. + + If a two-tuple, (width, height) of the figure. + + If a float, width of figure in inches. + + If a string, its a float followed by a 2-character suffix + of either 'in', 'cm', 'mm', 'pt' (inches, centimeters, + millimeters, or points). i.e. ``w='4.6cm'``. + + h: float or string + As above, if a float it is the hieght of the figure in inches, + or a string decoded as above. + + units : string + If specified, will be applied to floats in (w, h). If + either (w, h) are strings, the units they specify are used + for that dimension. Allows ``fig.set_size(12, 15, units='cm')`` + + forward : bool, default True + Forward to the window so the canvas size is updated; + e.g., you can resize the figure window from the shell + + See Also + -------- + matplotlib.Figure.set_size_inches + + """ + if h is None: + figsize = self._parse_figsize(w) + w, h = figsize + + conv = 1 # default units are inches. + if isinstance(units, str): + conv = cbook._print_unit(units, dpi=self._dpi) + if isinstance(conv, str): + raise ValueError('Could not convert units str {} to ' + 'inches'.format(conv)) + + if isinstance(w, str): + w = cbook._print_unit(w, dpi=self._dpi) + if isinstance(w, str): + raise ValueError('Could not convert str {} to ' + 'inches'.format(w)) + else: + # convert using units arg + w = w * conv + + if isinstance(h, str): + h = cbook._print_unit(h, dpi=self._dpi) + if isinstance(h, str): + raise ValueError('Could not convert str {} to ' + 'inches'.format(h)) + else: + # convert using units arg + h = h * conv + # all done: set... + self.set_size_inches(w, h, forward=forward) + def set_size_inches(self, w, h=None, forward=True): - """Set the figure size in inches. + """Set the figure size. Call signatures:: fig.set_size_inches(w, h) # OR fig.set_size_inches((w, h)) - optional kwarg *forward=True* will cause the canvas size to be + Optional kwarg *forward=True* will cause the canvas size to be automatically updated; e.g., you can resize the figure window from the shell - ACCEPTS: a (w, h) tuple with w, h in inches + ACCEPTS: a (w, h) tuple with w, h in inches. See Also -------- @@ -883,8 +957,10 @@ def set_size_inches(self, w, h=None, forward=True): # the width and height have been passed in as a tuple to the first # argument, so unpack them + if h is None: w, h = w + if not all(np.isfinite(_) for _ in (w, h)): raise ValueError('figure size must be finite not ' '({}, {})'.format(w, h))