Exemplo n.º 1
0
 def subplots_adjust(self, *args):
     """
     Adjusts spacings.
     """
     dimThing = args[0] if args else self.fig.get_window_extent()
     fWidth, fHeight = [getattr(dimThing, x) for x in ('width', 'height')]
     self.adj.updateFigSize(fWidth, fHeight)
     if self._figTitle:
         kw = {
             'm': 10,
             'fontsize': self.fontsize('title', 14),
             'alpha': 1.0,
             'fDims': (fWidth, fHeight),
         }
         ax = self.fig.get_axes()[0]
         if self.tbmTitle: self.tbmTitle.remove()
         self.tbmTitle = TextBoxMaker(self.fig, **kw)("N", self._figTitle)
         titleObj = self.tbmTitle.tList[0]
     else:
         self.tbmTitle = titleObj = None
     kw = self.adj(self._universal_xlabel, titleObj)
     try:
         self.fig.subplots_adjust(**kw)
     except ValueError as e:
         if self.verbose:
             print(
                 (sub("WARNING: ValueError '{}' doing subplots_adjust({})",
                      e.message,
                      ", ".join([sub("{}={}", x, kw[x]) for x in kw]))))
     self.updateAnnotations()
Exemplo n.º 2
0
    def dims(self, textObj, size=None):
        """
        Returns the dimensions of the supplied text object in pixels.

        If there's no renderer yet, estimates the dimensions based on
        font size and DPI.

        If you supply a string as the I{textObj}, you must also
        specify the font I{size}.
        """
        if size is None:
            # Size not specified
            if isinstance(textObj, str):
                raise ValueError("You must specify size of text in a string")
            # Compute size from text object specified (not rendered) size
            size = self.DPI * textObj.get_size() / 72
        if size in self.fontsizeMap:
            # Specified by name
            size = self.fontsizeMap[size]
        elif isinstance(size, int):
            # Specified as an int
            size = float(size)
        elif not isinstance(size, float):
            # Bogus size specification!
            raise ValueError(sub("Unknown font size {}", size))
        return self.realistic_dims(textObj, size)
Exemplo n.º 3
0
    def plotVectors(self):
        """
        Here is where I finally plot my X, Y vector pairs.

        B{TODO:} Support yscale, last seen in commit #e20e6c15. Or
        maybe don't bother.
        """
        for k, pair in enumerate(self.pairs):
            kw = {} if pair.fmt else self.p.doKeywords(k, pair.kw)
            plotter = self.pickPlotter(pair.call, kw)
            # Finally, the actual plotting call
            args = [pair.X, pair.Y]
            if pair.fmt: args.append(pair.fmt)
            self.lineInfo[0].extend(plotter(*args, **kw))
            # Add legend, if selected
            legend = self.p.opts['legend']
            if k < len(legend):
                # We have a legend for this subplot
                legend = legend[k]
            elif self.p.opts['autolegend']:
                # We don't have a legend for the subplot, but
                # autolegend is enabled, so make one up
                legend = pair.Yname
                if not legend: legend = sub("#{:d}", k + 1)
            else:
                # We have gone past the last defined legend, or none
                # have been defined
                continue
            self.lineInfo[1].append(legend)
            if self.p.opts['useLabels']: self.addLegend(k, legend)
Exemplo n.º 4
0
 def avoid(self, obj):
     """
     Call to have annotations avoid the region defined by any supplied
     Matplotlib obj having a C{get_window_extent} method.
     """
     if not hasattr(obj, 'get_window_extent'):
         raise TypeError(
             sub("Supplied object {} has no 'get_window_extent' method!",
                 obj))
     self.avoided.add(obj)
Exemplo n.º 5
0
 def setDims(self, k, name, dims):
     """
     For subplot I{k} (index starts at zero), sets the X and Y
     dimensions of an object with the specified I{name} to the
     supplied sequence I{dims}.
     """
     dims = tuple(dims)
     if self.debug:
         print(sub("DIMS {:d}: {} <-- {}", k, name, dims))
     self.sp_dicts.setdefault(k, {})[name] = dims
Exemplo n.º 6
0
 def __init__(self, ax, pairs, **kw):
     self.ax = ax
     self.annotations = []
     self.pos = PositionEvaluator(ax, pairs, self.annotations)
     for name in kw:
         setattr(self, name, kw[name])
     self.boxprops = self._boxprops.copy()
     self.boxprops['boxstyle'] = sub("round,pad={:0.3f}",
                                     self._paddingForSize())
     self.db = DebugBoxer(self.ax) if self.verbose else None
Exemplo n.º 7
0
    def addAnnotations(self):
        """
        Creates an L{Annotator} for my annotations to this subplot and
        then populates it.

        Twinned C{Axes} objects in a single subplot are not yet
        supported (and may never be), so the annotator object is
        constructed with my single C{Axes} object and operates only
        with that. That does B{not} mean that only a single vector can
        be annotated within one subplot. Twinning is an unusual use
        case and multiple vectors typically share the same C{Axes}
        object. An annotation can point its arrow to any X,Y
        coordinate in the subplot, whether that is on a plotted curve
        or not.

        This method only adds the annotations, plopping them right on
        top of the data points of interest. You need to call
        L{updateAnnotations} to have them intelligently repositioned
        after the subplot has been completed and its final dimensions
        are established, or (B{TODO}) if it is resized and the
        annotations need repositioning.
        """
        self.p.plt.draw()
        annotator = self.get_annotator()
        for k, text, kVector, is_yValue in self.p.opts['annotations']:
            X, Y = self.pairs[kVector].getXY()
            if not isinstance(k, (int, np.int64)):
                if is_yValue:
                    k = np.argmin(np.abs(Y - k))
                else:
                    k = np.searchsorted(X, k)
            if k < 0 or k >= len(X):
                continue
            x = X[k]
            y = Y[k]
            if isinstance(text, int):
                text = sub("{:d}", text)
            elif isinstance(text, float):
                text = sub("{:.2f}", text)
            # Annotator does not yet support twinned axes, nor are
            # they yet used
            annotator.add(x, y, text)
Exemplo n.º 8
0
 def getDims(self, k, name):
     """
     For subplot I{k}, returns the dimension of the object with the
     specified I{name} or C{None} if no such dimension has been
     set.
     """
     if k not in self.sp_dicts: return
     value = self.sp_dicts[k].get(name, None)
     if self.debug:
         print(sub("DIMS {:d}: {} -> {}", k, name, value))
     return value
Exemplo n.º 9
0
    def show(self, windowTitle=None, fh=None, filePath=None, noShow=False):
        """
        Call this to show the figure with suplots after the last call to
        my instance.
        
        If I have a non-C{None} I{fc} attribute (which must reference
        an instance of Qt's C{FigureCanvas}, then the FigureCanvas is
        drawn instead of PyPlot doing a window show.

        You can supply an open file-like object for PNG data to be
        written to (instead of a Matplotlib Figure being displayed)
        with the I{fh} keyword. (It's up to you to close the file
        object.)

        Or, with the I{filePath} keyword, you can specify the file
        path of a PNG file for me to create or overwrite. (That
        overrides any I{filePath} you set in the constructor.)
        """
        try:
            self.fig.tight_layout()
        except ValueError as e:
            if self.verbose:
                proto = "WARNING: ValueError '{}' doing tight_layout "+\
                        "on {:.5g} x {:.5g} figure"
                print((sub(proto, e.message, self.width, self.height)))
        self.subplots_adjust()
        # Calling plt.draw massively slows things down when generating
        # plot images on Rpi. And without it, the (un-annotated) plot
        # still updates!
        if False and self.annotators:
            # This is not actually run, see above comment
            self.plt.draw()
            for annotator in list(self.annotators.values()):
                if self.verbose: annotator.setVerbose()
                annotator.update()
        if fh is None:
            if not filePath:
                filePath = self.filePath
            if filePath:
                fh = open(filePath, 'wb+')
        if fh is None:
            self.plt.draw()
            if windowTitle: self.fig.canvas.set_window_title(windowTitle)
            if self.fc is not None: self.fc.draw()
            elif not noShow: self.plt.show()
        else:
            self.fig.savefig(fh, format='png')
            self.plt.close()
            if filePath is not None:
                # Only close a file handle I opened myself
                fh.close()
        if not noShow: self.clear()
Exemplo n.º 10
0
 def pickPlotter(self, call, kw):
     """
     Returns a reference to the plotting method of my I{ax} object
     named with I{call}, modifying the supplied I{kw} dict in-place
     as needed to work with that call.
     """
     if call in PLOTTER_NAMES:
         func = getattr(self.ax, call, None)
         if func:
             for bogus in self.bogusMap.get(call, []):
                 kw.pop(bogus, None)
         return func
     raise LookupError(sub("No recognized Axes method '{}'", call))
Exemplo n.º 11
0
    def __getattr__(self, name):
        """
        You can access plotting methods and a given subplot's plotting
        options as attributes.

        If you request a plotting method, you'll get an instance of me
        with my I{_plotter} method set to I{name} first.
        """
        if name in PLOTTER_NAMES:
            self._plotter = name
            return self
        if name in self.opts:
            return self.opts[name]
        raise AttributeError(sub("No plotting option or attribute '{}'", name))
Exemplo n.º 12
0
 def __call__(self, location, proto, *args, **options):
     kw = self.kw.copy()
     kw.update(options)
     location = self.conformLocation(location)
     text = sub(proto, *args)
     fDims = kw.pop('fDims')
     margin = kw.pop('m')
     if fDims:
         dims = self.tsc.pixels2fraction(
             self.tsc.dims(text, kw['fontsize']), fDims)
         if isinstance(margin, int):
             margins = [float(margin)/x for x in fDims]
         else: margins = [margin, margin]
     else:
         dims = [0, 0]
         if isinstance(margin, int):
             raise ValueError(
                 "You must supply figure dims with integer margin")
         margins = [margin, margin]
     # Tall text boxes (many lines) get too much y-axis margin;
     # correct that.
     N_lines = text.count('\n') + 1
     if N_lines > 3:
         margins[1] = margins[1]*(4.0/(4 + N_lines))
     # Get the x, y location of the center of the box
     x, y  = self.get_XY(location, dims, margins)
     # Come up with the appropriate keywords and then do the call
     # to obj.text
     kw['horizontalalignment'], kw['verticalalignment'] = \
         self._textAlignment[location]
     if self.ax:
         kw['transform'] = self.ax.transAxes \
             if hasattr(self.ax, 'transAxes') else self.ax.transFigure
         obj = self.ax
     else: obj = self.fig
     t = obj.text(x, y, text, **kw)
     if self.DEBUG:
         rectprops = {}
         rectprops['facecolor'] = "white"
         rectprops['edgecolor'] = "red"
         t.set_bbox(rectprops)
     self.tList.append(t)
     return self
Exemplo n.º 13
0
 def __repr__(self):
     args = [int(round(x)) for x in (self.Ax, self.Ay)]
     for x in (self.x0, self.y0, self.x1, self.y1):
         args.append(int(round(x)))
     return sub("({:d}, {:d}) --> [{:d},{:d} {:d},{:d}]", *args)
Exemplo n.º 14
0
 def withSuffix(x):
     return sub("{:+.2f}{}", x, suffix)
Exemplo n.º 15
0
    def addCall(self, args, kw):
        """
        Parses the supplied I{args} into X, Y pairs of vectors, appending
        a L{Pair} object for each to my I{pairs} list.
        
        Pops the first arg and uses it as a container if it is a
        container (e.g., a dict, not a Numpy array). Otherwise, refers
        to the I{V} attribute of my L{Plotter} I{p} as a possible
        container. If there is indeed a container, and the remaining
        args are all strings referencing items that are in it, makes
        the remaining args into vectors.

        Each item of the names list is a string with the name of a
        vector at the same index of I{vectors}, or C{None} if that
        vector was supplied as-is and thus no name name was available.

        Called with the next subplot's local options, so need to
        temporarily switch back to options for the current subplot,
        for the duration of this call.
        """
        def likeArray(X):
            return isinstance(X, (list, tuple, np.ndarray))

        self.p.opts.usePrevLocal()
        args = list(args)
        # The 'plotter' keyword is reserved for Yampex, unrecognized
        # by Matplotlib
        call = kw.pop('plotter', self.p.opts['call'])
        # The 'legend' keyword gets co-opted, but ultimately shows up
        # in the plot like you would expect
        legend = kw.pop('legend', None)
        if legend and not isinstance(legend, (list, tuple)):
            legend = [legend]
        # Process args
        if args and self.p.V is None:
            if likeArray(args[0]):
                V = None
                OK = True
            elif len(args) < 2:
                OK = False
            else:
                V = args.pop(0)
                try:
                    OK = likeArray(V[args[0]])
                except:
                    OK = False
            if not OK:
                raise ValueError(sub(
                    "No instance-wide V container and first "+\
                    "arg {} is not a valid container of a next arg", V))
        else:
            V = self.p.V
        X0, X0_name = self.pairs.firstX()
        Xs = []
        names = []
        strings = {}
        for k, arg in enumerate(args):
            X, name, isArray = self.arrayify(V, arg)
            if isArray:
                Xs.append(X)
                # A keyword-specified legend overrides any auto-generated name set
                # otherwise
                thisLegend = None
                if legend:
                    if len(args) == 1:
                        thisLegend = legend[0]
                    elif k > 0 and k <= len(legend):
                        thisLegend = legend[k - 1]
                if thisLegend:
                    if self.p.opts['autolegend']:
                        name = thisLegend
                    else:
                        self.p.add_legend(thisLegend)
                names.append(name)
            else:
                strings[k] = X
        if len(Xs) == 1:
            kStart = 0
            # Just one vector supplied...
            if X0 is None:
                # ...and no x-axis range vector yet, so create one
                X0 = np.arange(len(Xs[0]))
                X0_name = None
            # ... but we have an x-axis range vector, so we'll just
            # re-use it
        elif Xs:
            kStart = 1
            # Use this call's x-axis vector
            X = Xs.pop(0)
            # Set time scaling based on this x-axis vector if it's the
            # first one
            if X0 is None: self._timeScaling(X)
            X0 = X
            X0_name = names.pop(0)
        # Make pairs with the x-axis vector and the remaining vector(s)
        for k, Y in enumerate(Xs):
            if X0.shape != Y.shape:
                raise ValueError(
                    sub("Shapes differ for X, Y: {} vs {}", X0.shape, Y.shape))
            pair = Pair()
            pair.call = call
            pair.X = X0
            pair.Xname = X0_name
            pair.Y = Y
            pair.Yname = names[k]
            key = k + kStart + 1
            pair.fmt = strings.pop(key) if key in strings else None
            pair.kw = kw
            self.pairs.append(pair)
        self.p.opts.useLastLocal()
Exemplo n.º 16
0
 def add_annotations(self, k, prefix):
     for kVector in (0, 1):
         text = sub("{}, {}", prefix, "upper" if kVector else "lower")
         self.sp.add_annotation(k, text, kVector=kVector)
Exemplo n.º 17
0
 def __repr__(self):
     return sub("tox={:.5g}, NA={:.5g}", self.tox, self.NA)