docs: def _plot2d(plotfunc):
      """
      Decorator for common 2d plotting logic
  
      Also adds the 2d plot method to class _PlotMethods
      """
      commondoc = """
      Parameters
      ----------
      darray : DataArray
          Must be 2 dimensional, unless creating faceted plots
      x : string, optional
          Coordinate for x axis. If None use darray.dims[1]
      y : string, optional
          Coordinate for y axis. If None use darray.dims[0]
      figsize : tuple, optional
          A tuple (width, height) of the figure in inches.
          Mutually exclusive with ``size`` and ``ax``.
      aspect : scalar, optional
          Aspect ratio of plot, so that ``aspect * size`` gives the width in
          inches. Only used if a ``size`` is provided.
      size : scalar, optional
          If provided, create a new figure for the plot with the given size.
          Height (in inches) of each plot. See also: ``aspect``.
      ax : matplotlib axes object, optional
          Axis on which to plot this figure. By default, use the current axis.
          Mutually exclusive with ``size`` and ``figsize``.
      row : string, optional
          If passed, make row faceted plots on this dimension name
      col : string, optional
          If passed, make column faceted plots on this dimension name
      col_wrap : integer, optional
          Use together with ``col`` to wrap faceted plots
      xscale, yscale : 'linear', 'symlog', 'log', 'logit', optional
          Specifies scaling for the x- and y-axes respectively
      xticks, yticks : Specify tick locations for x- and y-axes
      xlim, ylim : Specify x- and y-axes limits
      xincrease : None, True, or False, optional
          Should the values on the x axes be increasing from left to right?
          if None, use the default for the matplotlib function.
      yincrease : None, True, or False, optional
          Should the values on the y axes be increasing from top to bottom?
          if None, use the default for the matplotlib function.
      add_colorbar : Boolean, optional
          Adds colorbar to axis
      add_labels : Boolean, optional
          Use xarray metadata to label axes
      norm : ``matplotlib.colors.Normalize`` instance, optional
          If the ``norm`` has vmin or vmax specified, the corresponding kwarg
          must be None.
      vmin, vmax : floats, optional
          Values to anchor the colormap, otherwise they are inferred from the
          data and other keyword arguments. When a diverging dataset is inferred,
          setting one of these values will fix the other by symmetry around
          ``center``. Setting both values prevents use of a diverging colormap.
          If discrete levels are provided as an explicit list, both of these
          values are ignored.
      cmap : matplotlib colormap name or object, optional
          The mapping from data values to color space. If not provided, this
          will be either be ``viridis`` (if the function infers a sequential
          dataset) or ``RdBu_r`` (if the function infers a diverging dataset).
          When `Seaborn` is installed, ``cmap`` may also be a `seaborn`
          color palette. If ``cmap`` is seaborn color palette and the plot type
          is not ``contour`` or ``contourf``, ``levels`` must also be specified.
      colors : discrete colors to plot, optional
          A single color or a list of colors. If the plot type is not ``contour``
          or ``contourf``, the ``levels`` argument is required.
      center : float, optional
          The value at which to center the colormap. Passing this value implies
          use of a diverging colormap. Setting it to ``False`` prevents use of a
          diverging colormap.
      robust : bool, optional
          If True and ``vmin`` or ``vmax`` are absent, the colormap range is
          computed with 2nd and 98th percentiles instead of the extreme values.
      extend : {'neither', 'both', 'min', 'max'}, optional
          How to draw arrows extending the colorbar beyond its limits. If not
          provided, extend is inferred from vmin, vmax and the data limits.
      levels : int or list-like object, optional
          Split the colormap (cmap) into discrete color intervals. If an integer
          is provided, "nice" levels are chosen based on the data range: this can
          imply that the final number of levels is not exactly the expected one.
          Setting ``vmin`` and/or ``vmax`` with ``levels=N`` is equivalent to
          setting ``levels=np.linspace(vmin, vmax, N)``.
      infer_intervals : bool, optional
          Only applies to pcolormesh. If True, the coordinate intervals are
          passed to pcolormesh. If False, the original coordinates are used
          (this can be useful for certain map projections). The default is to
          always infer intervals, unless the mesh is irregular and plotted on
          a map projection.
      subplot_kws : dict, optional
          Dictionary of keyword arguments for matplotlib subplots. Only applies
          to FacetGrid plotting.
      cbar_ax : matplotlib Axes, optional
          Axes in which to draw the colorbar.
      cbar_kwargs : dict, optional
          Dictionary of keyword arguments to pass to the colorbar.
      **kwargs : optional
          Additional arguments to wrapped matplotlib function
  
      Returns
      -------
      artist :
          The same type of primitive artist that the wrapped matplotlib
          function returns
      """
  
      # Build on the original docstring
      plotfunc.__doc__ = '%s\n%s' % (plotfunc.__doc__, commondoc)
  
      @functools.wraps(plotfunc)
      def newplotfunc(darray, x=None, y=None, figsize=None, size=None,
                      aspect=None, ax=None, row=None, col=None,
                      col_wrap=None, xincrease=True, yincrease=True,
                      add_colorbar=None, add_labels=True, vmin=None, vmax=None,
                      cmap=None, center=None, robust=False, extend=None,
                      levels=None, infer_intervals=None, colors=None,
                      subplot_kws=None, cbar_ax=None, cbar_kwargs=None,
                      xscale=None, yscale=None, xticks=None, yticks=None,
                      xlim=None, ylim=None, norm=None, **kwargs):
          # All 2d plots in xarray share this function signature.
          # Method signature below should be consistent.
  
          # Decide on a default for the colorbar before facetgrids
          if add_colorbar is None:
              add_colorbar = plotfunc.__name__ != 'contour'
          imshow_rgb = (
              plotfunc.__name__ == 'imshow' and
              darray.ndim == (3 + (row is not None) + (col is not None)))
          if imshow_rgb:
              # Don't add a colorbar when showing an image with explicit colors
              add_colorbar = False
              # Matplotlib does not support normalising RGB data, so do it here.
              # See eg. https://github.com/matplotlib/matplotlib/pull/10220
              if robust or vmax is not None or vmin is not None:
                  darray = _rescale_imshow_rgb(darray, vmin, vmax, robust)
                  vmin, vmax, robust = None, None, False
  
          # Handle facetgrids first
          if row or col:
              allargs = locals().copy()
              allargs.pop('imshow_rgb')
              allargs.update(allargs.pop('kwargs'))
              allargs.pop('darray')
              # Need the decorated plotting function
              allargs['plotfunc'] = globals()[plotfunc.__name__]
              return _easy_facetgrid(darray, kind='dataarray', **allargs)
  
          plt = import_matplotlib_pyplot()
  
          rgb = kwargs.pop('rgb', None)
          if rgb is not None and plotfunc.__name__ != 'imshow':
              raise ValueError('The "rgb" keyword is only valid for imshow()')
          elif rgb is not None and not imshow_rgb:
              raise ValueError('The "rgb" keyword is only valid for imshow()'
                               'with a three-dimensional array (per facet)')
  
          xlab, ylab = _infer_xy_labels(
              darray=darray, x=x, y=y, imshow=imshow_rgb, rgb=rgb)
  
          # better to pass the ndarrays directly to plotting functions
          xval = darray[xlab].values
          yval = darray[ylab].values
  
          # check if we need to broadcast one dimension
          if xval.ndim < yval.ndim:
              xval = np.broadcast_to(xval, yval.shape)
  
          if yval.ndim < xval.ndim:
              yval = np.broadcast_to(yval, xval.shape)
  
          # May need to transpose for correct x, y labels
          # xlab may be the name of a coord, we have to check for dim names
          if imshow_rgb:
              # For RGB[A] images, matplotlib requires the color dimension
              # to be last.  In Xarray the order should be unimportant, so
              # we transpose to (y, x, color) to make this work.
              yx_dims = (ylab, xlab)
              dims = yx_dims + tuple(d for d in darray.dims if d not in yx_dims)
              if dims != darray.dims:
                  darray = darray.transpose(*dims)
          elif darray[xlab].dims[-1] == darray.dims[0]:
              darray = darray.transpose()
  
          # Pass the data as a masked ndarray too
          zval = darray.to_masked_array(copy=False)
  
          # Replace pd.Intervals if contained in xval or yval.
          xplt, xlab_extra = _resolve_intervals_2dplot(xval, plotfunc.__name__)
          yplt, ylab_extra = _resolve_intervals_2dplot(yval, plotfunc.__name__)
  
          _ensure_plottable(xplt, yplt)
  
          cmap_params, cbar_kwargs = _process_cmap_cbar_kwargs(
              plotfunc, locals(), zval.data)
  
          if 'contour' in plotfunc.__name__:
              # extend is a keyword argument only for contour and contourf, but
              # passing it to the colorbar is sufficient for imshow and
              # pcolormesh
              kwargs['extend'] = cmap_params['extend']
              kwargs['levels'] = cmap_params['levels']
              # if colors == a single color, matplotlib draws dashed negative
              # contours. we lose this feature if we pass cmap and not colors
              if isinstance(colors, str):
                  cmap_params['cmap'] = None
                  kwargs['colors'] = colors
  
          if 'pcolormesh' == plotfunc.__name__:
              kwargs['infer_intervals'] = infer_intervals
  
          if 'imshow' == plotfunc.__name__ and isinstance(aspect, str):
              # forbid usage of mpl strings
              raise ValueError("plt.imshow's `aspect` kwarg is not available "
                               "in xarray")
  
          ax = get_axis(figsize, size, aspect, ax)
          primitive = plotfunc(xplt, yplt, zval, ax=ax, cmap=cmap_params['cmap'],
                               vmin=cmap_params['vmin'],
                               vmax=cmap_params['vmax'],
                               norm=cmap_params['norm'],
                               **kwargs)
  
          # Label the plot with metadata
          if add_labels:
              ax.set_xlabel(label_from_attrs(darray[xlab], xlab_extra))
              ax.set_ylabel(label_from_attrs(darray[ylab], ylab_extra))
              ax.set_title(darray._title_for_slice())
  
          if add_colorbar:
              if add_labels and 'label' not in cbar_kwargs:
                  cbar_kwargs['label'] = label_from_attrs(darray)
              cbar = _add_colorbar(primitive, ax, cbar_ax, cbar_kwargs,
                                   cmap_params)
  
          elif (cbar_ax is not None or cbar_kwargs):
              # inform the user about keywords which aren't used
              raise ValueError("cbar_ax and cbar_kwargs can't be used with "
                               "add_colorbar=False.")
  
          # origin kwarg overrides yincrease
          if 'origin' in kwargs:
              yincrease = None
  
          _update_axes(ax, xincrease, yincrease, xscale, yscale,
                       xticks, yticks, xlim, ylim)
  
          # Rotate dates on xlabels
          # Do this without calling autofmt_xdate so that x-axes ticks
          # on other subplots (if any) are not deleted.
          # https://stackoverflow.com/questions/17430105/autofmt-xdate-deletes-x-axis-labels-of-all-subplots
          if np.issubdtype(xplt.dtype, np.datetime64):
              for xlabels in ax.get_xticklabels():
                  xlabels.set_rotation(30)
                  xlabels.set_ha('right')
  
          return primitive
  
      # For use as DataArray.plot.plotmethod
      @functools.wraps(newplotfunc)
      def plotmethod(_PlotMethods_obj, x=None, y=None, figsize=None, size=None,
                     aspect=None, ax=None, row=None, col=None, col_wrap=None,
                     xincrease=True, yincrease=True, add_colorbar=None,
                     add_labels=True, vmin=None, vmax=None, cmap=None,
                     colors=None, center=None, robust=False, extend=None,
                     levels=None, infer_intervals=None, subplot_kws=None,
                     cbar_ax=None, cbar_kwargs=None,
                     xscale=None, yscale=None, xticks=None, yticks=None,
                     xlim=None, ylim=None, norm=None, **kwargs):
          """
          The method should have the same signature as the function.
  
          This just makes the method work on Plotmethods objects,
          and passes all the other arguments straight through.
          """
          allargs = locals()
          allargs['darray'] = _PlotMethods_obj._da
          allargs.update(kwargs)
          for arg in ['_PlotMethods_obj', 'newplotfunc', 'kwargs']:
              del allargs[arg]
          return newplotfunc(**allargs)
  
      # Add to class _PlotMethods
      setattr(_PlotMethods, plotmethod.__name__, plotmethod)
  
      return newplotfunc