Repository: scverse/scanpy

Issue:
Argument tab completion for sc.pl.umap-like functions?
It'd be nice if some level of tab completion of argument names could be maintained for functions like `sc.pl.umap`. This came up recently in #455, and I find myself frequently misspelling (mostly mis-pluralizing) argument names like `color/ colors` and `gene_name/ gene_names`. I've given this a shot using `functools.wraps`, but no luck yet.

Any ideas on if we could do this @flying-sheep?


Discussion:
I think to preserve the signature like that we could easily do a subset of what `update_wrapper` (and therefore `wraps`) does:

```py
def wraps_plot_scatter(wrapper):
    wrapper.__annotations__ = {
        k: v for k, v in plot_scatter.__annotations__.items()
        if k != 'basis'
    }
    wrapper.__wrapped__ = plot_scatter
    return wrapper

@wraps_plot_scatter
def umap(adata, **kwargs):
    """..."""
    return plot_scatter(adata, basis='umap', **kwargs)
```

but first: what did you try and why did it fail?
Here's pretty much what I've tried:

```python
import scanpy as sc
from functools import wraps, partial

pca = wraps(sc.pl.scatter)(partial(sc.pl.scatter, basis="pca"))
pca( #tried tab completion here
```

Tab completion shows:

<img width="597" alt="image" src="https://user-images.githubusercontent.com/8238804/54574990-8e796f80-4a46-11e9-8ad4-ae5aeead9604.png">

As opposed to:

<img width="598" alt="image" src="https://user-images.githubusercontent.com/8238804/54580161-fdf95a00-4a5a-11e9-9b51-8eeec17babfc.png">


Hmm, doesn’t seem to work with `functools.partial`. It works if I do almost the same, but with a lambda:

```py
import scanpy as sc
from functools import wraps, partial

pca = wraps(sc.pl.scatter)(lambda *args, **kw: sc.pl.scatter(*args, basis="pca", **kw))
```

But I think the custom solution above is better anyway! `wraps` is if you really want to pose as the wrapped function, while we only want to inherit its signature.
Just wanted to mention that this would add much convenience. Since Fidel rewrote the plotting API, I saw that it was the right thing to do to reduce all the code in the almost-identical function headers, but  I was missing the autocomplete, which had obviously disappeared.

Resolving patch:
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -71,6 +71,7 @@
     leidenalg=('https://leidenalg.readthedocs.io/en/latest/', None),
     louvain=('https://louvain-igraph.readthedocs.io/en/latest/', None),
     matplotlib=('https://matplotlib.org/', None),
+    networkx=('https://networkx.github.io/documentation/networkx-1.10/', None),
     numpy=('https://docs.scipy.org/doc/numpy/', None),
     pandas=('http://pandas.pydata.org/pandas-docs/stable/', None),
     python=('https://docs.python.org/3', None),
diff --git a/scanpy/plotting/_docs.py b/scanpy/plotting/_docs.py
--- a/scanpy/plotting/_docs.py
+++ b/scanpy/plotting/_docs.py
@@ -3,91 +3,97 @@
 
 
 doc_adata_color_etc = """\
-adata : :class:`~anndata.AnnData`
+adata
     Annotated data matrix.
-color : string or list of strings, optional (default: `None`)
+color
     Keys for annotations of observations/cells or variables/genes, e.g.,
     `'ann1'` or `['ann1', 'ann2']`.
-gene_symbols : string, optional (default: `None`)
+gene_symbols
     Column name in `.var` DataFrame that stores gene symbols. By default `var_names` 
     refer to the index column of the `.var` DataFrame. Setting this option allows
     alternative names to be used.
-use_raw : `bool`, optional (default: `None`)
+use_raw
     Use `.raw` attribute of `adata` for coloring with gene expression. If
     `None`, uses `.raw` if present.\
 """
 
 
 doc_edges_arrows = """\
-edges : `bool`, optional (default: `False`)
+edges
     Show edges.
-edges_width : `float`, optional (default: 0.1)
+edges_width
     Width of edges.
-edges_color : matplotlib color, optional (default: 'grey')
-    Color of edges.
-arrows : `bool`, optional (default: `False`)
-    Show arrows (requires to run :func:`~scanpy.api.tl.rna_velocity` before).\
+edges_color : matplotlib color(s), optional (default: 'grey')
+    Color of edges. See :func:`~networkx.drawing.nx_pylab.draw_networkx_edges`.
+arrows
+    Show arrows (requires to run :func:`~scanpy.api.tl.rna_velocity` before).
+arrows_kwds
+    Passed to :func:`~matplotlib.axes.Axes.quiver`\
 """
 
 
 doc_scatter_bulk = """\
-sort_order : `bool`, optional (default: `True`)
+sort_order
     For continuous annotations used as color parameter, plot data points
     with higher values on top of others.
-groups : `str`, optional (default: `all groups`)
+groups
     Restrict to a few categories in categorical observation annotation.
-components : `str` or list of `str`, optional (default: '1,2')
+    The default is not to restrict to any groups.
+components
     For instance, `['1,2', '2,3']`. To plot all available components use
     `components='all'`.
 projection : {'2d', '3d'}, optional (default: '2d')
     Projection of plot.
-legend_loc : `str`, optional (default: 'right margin')
+legend_loc
     Location of legend, either 'on data', 'right margin' or valid keywords for
     `matplotlib.legend`.
-legend_fontsize : `int`, optional (default: `None`)
+legend_fontsize
     Legend font size.
 legend_fontweight : {'normal', 'bold', ...}, optional (default: `None`)
     Legend font weight. Defaults to 'bold' if `legend_loc == 'on data'`,
     otherwise to 'normal'. Available are `['light', 'normal', 'medium',
     'semibold', 'bold', 'heavy', 'black']`.
-size : `float` (default: `None`)
+size
     Point size. If `None`, is automatically computed.
-color_map : `matplotlib.colors.Colormap` or `str`, optional (default: None)
+color_map
     Color map to use for continous variables. Anything that works for `cmap`
     argument of `pyplot.scatter` should work here (e.g. `"magma"`, `"viridis"`,
     `mpl.cm.cividis`). If `None` value of `mpl.rcParams["image.cmap"]` is used.
-palette : `str`, list of `str`, or `Cycler` optional (default: `None`)
+palette
     Colors to use for plotting categorical annotation groups. The palette can be
     a valid `matplotlib.pyplot.colormap` name like `'Set2'` or `'tab20'`, a list
     of colors like `['red', '#ccdd11', (0.1, 0.2, 1)]` or a Cycler object. If
     `None`, `mpl.rcParams["axes.prop_cycle"]` is used unless the categorical
     variable already has colors stored in `adata.uns["{var}_colors"]`. If
     provided, values of `adata.uns["{var}_colors"]` will be set by this palette.
-frameon : `bool`, optional (default: `None`)
+frameon
     Draw a frame around the scatter plot. Defaults to value set in
     :func:`~scanpy.api.tl.set_figure_params`, defaults to `True`.
-ncols : `int` (default: 4)
+ncols
     Number of panels per row.
-wspace : `float` (default: 0.1)
+wspace
     Adjust the width of the space between multiple panels.
-hspace : `float` (default: 0.25)
+hspace
     Adjust the height of the space between multiple panels.
-title : `str` or list of `str`, optional (default: `None`)
-    Provide title for panels either as, e.g. `['title1', 'title2', ...]`.
+title
+    Provide title for panels either as string or list of strings,
+    e.g. `['title1', 'title2', ...]`.
 kwargs : further keyword arguments, optional
-    Arguments to pass to `matplotlib.pyplot.scatter`, for instance: the maximum
-    and minimum values (e.g. `vmin=-2, vmax=5`).\
+    Arguments to pass to :func:`matplotlib.pyplot.scatter`,
+    for instance: the maximum and minimum values (e.g. `vmin=-2, vmax=5`).\
 """
 
 
 doc_show_save_ax = """\
-show : `bool`, optional (default: `None`)
+show
      Show the plot, do not return axis.
-save : `bool` or `str`, optional (default: `None`)
+save
     If `True` or a `str`, save the figure. A string is appended to the default
     filename. Infer the filetype if ending on {'.pdf', '.png', '.svg'}.
-ax : `matplotlib.Axes`, optional (default: `None`)
-    A matplotlib axes object. Only works if plotting a single component.\
+ax
+    A matplotlib axes object. Only works if plotting a single component.
+return_fig
+    Return the matplotlib figure.\
 """
 
 
diff --git a/scanpy/plotting/_tools/scatterplots.py b/scanpy/plotting/_tools/scatterplots.py
--- a/scanpy/plotting/_tools/scatterplots.py
+++ b/scanpy/plotting/_tools/scatterplots.py
@@ -1,184 +1,56 @@
-from matplotlib import pyplot as pl
-from pandas.api.types import is_categorical_dtype
+from collections import abc
+from typing import Union, Optional, Sequence, Any, Mapping
+
 import numpy as np
+from anndata import AnnData
+from cycler import Cycler
+from matplotlib.axes import Axes
+from matplotlib.figure import Figure
+from pandas.api.types import is_categorical_dtype
+from matplotlib import pyplot as pl
 from matplotlib import rcParams
-from matplotlib.colors import is_color_like
+from matplotlib.colors import is_color_like, Colormap
+
 from .. import _utils as utils
-from ...utils import sanitize_anndata, doc_params
-from ... import settings
 from .._docs import doc_adata_color_etc, doc_edges_arrows, doc_scatter_bulk, doc_show_save_ax
+from ... import settings
+from ...utils import sanitize_anndata, doc_params
 from ... import logging as logg
 
 
-@doc_params(adata_color_etc=doc_adata_color_etc, edges_arrows=doc_edges_arrows, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
-def umap(adata, **kwargs):
-    """\
-    Scatter plot in UMAP basis.
-
-    Parameters
-    ----------
-    {adata_color_etc}
-    {edges_arrows}
-    {scatter_bulk}
-    {show_save_ax}
-
-    Returns
-    -------
-    If `show==False` a `matplotlib.Axis` or a list of it.
-    """
-    return plot_scatter(adata, basis='umap', **kwargs)
-
-
-@doc_params(adata_color_etc=doc_adata_color_etc, edges_arrows=doc_edges_arrows, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
-def tsne(adata, **kwargs):
-    """\
-    Scatter plot in tSNE basis.
-
-    Parameters
-    ----------
-    {adata_color_etc}
-    {edges_arrows}
-    {scatter_bulk}
-    {show_save_ax}
-
-    Returns
-    -------
-    If `show==False` a `matplotlib.Axis` or a list of it.
-    """
-    return plot_scatter(adata, basis='tsne', **kwargs)
-
-
-@doc_params(adata_color_etc=doc_adata_color_etc, edges_arrows=doc_edges_arrows, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
-def phate(adata, **kwargs):
-    """\
-    Scatter plot in PHATE basis.
-
-    Parameters
-    ----------
-    {adata_color_etc}
-    {edges_arrows}
-    {scatter_bulk}
-    {show_save_ax}
-
-    Returns
-    -------
-    If `show==False`, a list of `matplotlib.Axis` objects. Every second element
-    corresponds to the 'right margin' drawing area for color bars and legends.
-
-    Examples
-    --------
-    >>> import scanpy.api as sc
-    >>> import phate
-    >>> data, branches = phate.tree.gen_dla(n_dim=100,
-                                            n_branch=20,
-                                            branch_length=100)
-    >>> data.shape
-    (2000, 100)
-    >>> adata = sc.AnnData(data)
-    >>> adata.obs['branches'] = branches
-    >>> sc.tl.phate(adata, k=5, a=20, t=150)
-    >>> adata.obsm['X_phate'].shape
-    (2000, 2)
-    >>> sc.pl.phate(adata,
-                    color='branches',
-                    color_map='tab20')
-    """
-    return plot_scatter(adata, basis='phate', **kwargs)
-
-
-@doc_params(adata_color_etc=doc_adata_color_etc, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
-def diffmap(adata, **kwargs):
-    """\
-    Scatter plot in Diffusion Map basis.
-
-    Parameters
-    ----------
-    {adata_color_etc}
-    {scatter_bulk}
-    {show_save_ax}
-
-    Returns
-    -------
-    If `show==False` a `matplotlib.Axis` or a list of it.
-    """
-    return plot_scatter(adata, basis='diffmap', **kwargs)
-
-
-@doc_params(adata_color_etc=doc_adata_color_etc, edges_arrows=doc_edges_arrows, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
-def draw_graph(adata, layout=None, **kwargs):
-    """\
-    Scatter plot in graph-drawing basis.
-
-    Parameters
-    ----------
-    {adata_color_etc}
-    layout : {{'fa', 'fr', 'drl', ...}}, optional (default: last computed)
-        One of the `draw_graph` layouts, see
-        :func:`~scanpy.api.tl.draw_graph`. By default, the last computed layout
-        is used.
-    {edges_arrows}
-    {scatter_bulk}
-    {show_save_ax}
-
-    Returns
-    -------
-    If `show==False` a `matplotlib.Axis` or a list of it.
-    """
-    if layout is None:
-        layout = str(adata.uns['draw_graph']['params']['layout'])
-    basis = 'draw_graph_' + layout
-    if 'X_' + basis not in adata.obsm_keys():
-        raise ValueError('Did not find {} in adata.obs. Did you compute layout {}?'
-                         .format('draw_graph_' + layout, layout))
-
-    return plot_scatter(adata, basis=basis, **kwargs)
-
-
-@doc_params(adata_color_etc=doc_adata_color_etc, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
-def pca(adata, **kwargs):
-    """\
-    Scatter plot in PCA coordinates.
-
-    {adata_color_etc}
-    {scatter_bulk}
-    {show_save_ax}
-
-    Returns
-    -------
-    If `show==False` a `matplotlib.Axis` or a list of it.
-    """
-    return plot_scatter(adata, basis='pca', **kwargs)
-
-
-def plot_scatter(adata,
-                 color=None,
-                 gene_symbols=None,
-                 use_raw=None,
-                 sort_order=True,
-                 edges=False,
-                 edges_width=0.1,
-                 edges_color='grey',
-                 arrows=False,
-                 arrows_kwds=None,
-                 basis=None,
-                 groups=None,
-                 components=None,
-                 projection='2d',
-                 color_map=None,
-                 palette=None,
-                 size=None,
-                 frameon=None,
-                 legend_fontsize=None,
-                 legend_fontweight='bold',
-                 legend_loc='right margin',
-                 ncols=4,
-                 hspace=0.25,
-                 wspace=None,
-                 title=None,
-                 show=None,
-                 save=None,
-                 ax=None, return_fig=None, **kwargs):
-
+def plot_scatter(
+    adata: AnnData,
+    basis: str,
+    *,
+    color: Union[str, Sequence[str], None] = None,
+    gene_symbols: Optional[str] = None,
+    use_raw: Optional[bool] = None,
+    sort_order: bool = True,
+    edges: bool = False,
+    edges_width: float = 0.1,
+    edges_color: Union[str, Sequence[float], Sequence[str]] = 'grey',
+    arrows: bool = False,
+    arrows_kwds: Optional[Mapping[str, Any]] = None,
+    groups: Optional[str] = None,
+    components: Union[str, Sequence[str]] = None,
+    projection: str = '2d',
+    color_map: Union[Colormap, str, None] = None,
+    palette: Union[str, Sequence[str], Cycler, None] = None,
+    size: Optional[float] = None,
+    frameon: Optional[bool] = None,
+    legend_fontsize: Optional[int] = None,
+    legend_fontweight: str = 'bold',
+    legend_loc: str = 'right margin',
+    ncols: int = 4,
+    hspace: float = 0.25,
+    wspace: Optional[float] = None,
+    title: Union[str, Sequence[str], None] = None,
+    show: Optional[bool] = None,
+    save: Union[bool, str, None] = None,
+    ax: Optional[Axes] = None,
+    return_fig: Optional[bool] = None,
+    **kwargs
+) -> Union[Figure, Axes, None]:
     sanitize_anndata(adata)
     if color_map is not None:
         kwargs['cmap'] = color_map
@@ -208,8 +80,10 @@ def plot_scatter(adata,
         #  current figure size
         wspace = 0.75 / rcParams['figure.figsize'][0] + 0.02
     if adata.raw is None and use_raw is True:
-        raise ValueError("`use_raw` is set to True but annData object does not have raw. "
-                         "Please check.")
+        raise ValueError(
+            "`use_raw` is set to True but AnnData object does not have raw. "
+            "Please check."
+        )
     # turn color into a python list
     color = [color] if isinstance(color, str) or color is None else list(color)
     if title is not None:
@@ -224,10 +98,12 @@ def plot_scatter(adata,
     # setup layout. Most of the code is for the case when multiple plots are required
     # 'color' is a list of names that want to be plotted. Eg. ['Gene1', 'louvain', 'Gene2'].
     # component_list is a list of components [[0,1], [1,2]]
-    if (isinstance(color, list) and len(color) > 1) or len(components_list) > 1:
+    if (isinstance(color, abc.Sequence) and len(color) > 1) or len(components_list) > 1:
         if ax is not None:
-            raise ValueError("When plotting multiple panels (each for a given value of 'color' "
-                             "a given ax can not be used")
+            raise ValueError(
+                "When plotting multiple panels (each for a given value of 'color') "
+                "a given ax can not be used"
+            )
         if len(components_list) == 0:
             components_list = [None]
 
@@ -243,14 +119,12 @@ def plot_scatter(adata,
                                  n_panels_y * rcParams['figure.figsize'][1]))
         left = 0.2 / n_panels_x
         bottom = 0.13 / n_panels_y
-        gs = gridspec.GridSpec(nrows=n_panels_y,
-                               ncols=n_panels_x,
-                               left=left,
-                               right=1-(n_panels_x-1)*left-0.01/n_panels_x,
-                               bottom=bottom,
-                               top=1-(n_panels_y-1)*bottom-0.1/n_panels_y,
-                               hspace=hspace,
-                               wspace=wspace)
+        gs = gridspec.GridSpec(
+            nrows=n_panels_y, ncols=n_panels_x,
+            left=left, right=1-(n_panels_x-1)*left-0.01/n_panels_x,
+            bottom=bottom, top=1-(n_panels_y-1)*bottom-0.1/n_panels_y,
+            hspace=hspace, wspace=wspace,
+        )
     else:
         if len(components_list) == 0:
             components_list = [None]
@@ -270,9 +144,11 @@ def plot_scatter(adata,
     # The plots are: [color=gene1, components=[1,2], color=gene1, components=[2,3],
     #                 color=gene2, components = [1, 2], color=gene2, components=[2,3]]
     for count, (value_to_plot, component_idx) in enumerate(itertools.product(color, idx_components)):
-        color_vector, categorical = _get_color_values(adata, value_to_plot,
-                                                      groups=groups, palette=palette,
-                                                      use_raw=use_raw, gene_symbols=gene_symbols)
+        color_vector, categorical = _get_color_values(
+            adata, value_to_plot,
+            groups=groups, palette=palette,
+            use_raw=use_raw, gene_symbols=gene_symbols,
+        )
 
         # check if higher value points should be plot on top
         if sort_order is True and value_to_plot is not None and categorical is False:
@@ -296,7 +172,6 @@ def plot_scatter(adata,
             else:
                 ax.set_title('')
         else:
-
             try:
                 ax.set_title(title[count])
             except IndexError:
@@ -309,13 +184,17 @@ def plot_scatter(adata,
 
         # make the scatter plot
         if projection == '3d':
-            cax= ax.scatter(_data_points[:, 0], _data_points[:, 1], _data_points[:, 2],
-                            marker=".", c=color_vector, rasterized=settings._vector_friendly,
-                            **kwargs)
+            cax = ax.scatter(
+                _data_points[:, 0], _data_points[:, 1], _data_points[:, 2],
+                marker=".", c=color_vector, rasterized=settings._vector_friendly,
+                **kwargs,
+            )
         else:
-            cax= ax.scatter(_data_points[:, 0], _data_points[:, 1],
-                            marker=".", c=color_vector, rasterized=settings._vector_friendly,
-                            **kwargs)
+            cax = ax.scatter(
+                _data_points[:, 0], _data_points[:, 1],
+                marker=".", c=color_vector, rasterized=settings._vector_friendly,
+                **kwargs,
+            )
 
         # remove y and x ticks
         ax.set_yticks([])
@@ -350,8 +229,10 @@ def plot_scatter(adata,
             # there is not need to plot a legend or a colorbar
             continue
 
-        _add_legend_or_colorbar(adata, ax, cax, categorical, value_to_plot, legend_loc,
-                                _data_points, legend_fontweight, legend_fontsize, groups, multi_panel)
+        _add_legend_or_colorbar(
+            adata, ax, cax, categorical, value_to_plot, legend_loc,
+            _data_points, legend_fontweight, legend_fontsize, groups, multi_panel,
+        )
 
     if return_fig is True:
         return fig
@@ -361,6 +242,174 @@ def plot_scatter(adata,
         return axs
 
 
+def _wraps_plot_scatter(wrapper):
+    annots_orig = {
+        k: v for k, v in wrapper.__annotations__.items()
+        if k not in {'adata', 'kwargs'}
+    }
+    annots_scatter = {
+        k: v for k, v in plot_scatter.__annotations__.items()
+        if k != 'basis'
+    }
+    wrapper.__annotations__ = {**annots_scatter, **annots_orig}
+    wrapper.__wrapped__ = plot_scatter
+    return wrapper
+
+
+# API
+
+
+@_wraps_plot_scatter
+@doc_params(adata_color_etc=doc_adata_color_etc, edges_arrows=doc_edges_arrows, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
+def umap(adata, **kwargs):
+    """\
+    Scatter plot in UMAP basis.
+
+    Parameters
+    ----------
+    {adata_color_etc}
+    {edges_arrows}
+    {scatter_bulk}
+    {show_save_ax}
+
+    Returns
+    -------
+    If `show==False` a `matplotlib.Axis` or a list of it.
+    """
+    return plot_scatter(adata, 'umap', **kwargs)
+
+
+@_wraps_plot_scatter
+@doc_params(adata_color_etc=doc_adata_color_etc, edges_arrows=doc_edges_arrows, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
+def tsne(adata, **kwargs):
+    """\
+    Scatter plot in tSNE basis.
+
+    Parameters
+    ----------
+    {adata_color_etc}
+    {edges_arrows}
+    {scatter_bulk}
+    {show_save_ax}
+
+    Returns
+    -------
+    If `show==False` a `matplotlib.Axis` or a list of it.
+    """
+    return plot_scatter(adata, 'tsne', **kwargs)
+
+
+@_wraps_plot_scatter
+@doc_params(adata_color_etc=doc_adata_color_etc, edges_arrows=doc_edges_arrows, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
+def phate(adata, **kwargs):
+    """\
+    Scatter plot in PHATE basis.
+
+    Parameters
+    ----------
+    {adata_color_etc}
+    {edges_arrows}
+    {scatter_bulk}
+    {show_save_ax}
+
+    Returns
+    -------
+    If `show==False`, a list of `matplotlib.Axis` objects. Every second element
+    corresponds to the 'right margin' drawing area for color bars and legends.
+
+    Examples
+    --------
+    >>> import scanpy.api as sc
+    >>> import phate
+    >>> data, branches = phate.tree.gen_dla(n_dim=100,
+                                            n_branch=20,
+                                            branch_length=100)
+    >>> data.shape
+    (2000, 100)
+    >>> adata = sc.AnnData(data)
+    >>> adata.obs['branches'] = branches
+    >>> sc.tl.phate(adata, k=5, a=20, t=150)
+    >>> adata.obsm['X_phate'].shape
+    (2000, 2)
+    >>> sc.pl.phate(adata,
+                    color='branches',
+                    color_map='tab20')
+    """
+    return plot_scatter(adata, 'phate', **kwargs)
+
+
+@_wraps_plot_scatter
+@doc_params(adata_color_etc=doc_adata_color_etc, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
+def diffmap(adata, **kwargs):
+    """\
+    Scatter plot in Diffusion Map basis.
+
+    Parameters
+    ----------
+    {adata_color_etc}
+    {scatter_bulk}
+    {show_save_ax}
+
+    Returns
+    -------
+    If `show==False` a `matplotlib.Axis` or a list of it.
+    """
+    return plot_scatter(adata, 'diffmap', **kwargs)
+
+
+@_wraps_plot_scatter
+@doc_params(adata_color_etc=doc_adata_color_etc, edges_arrows=doc_edges_arrows, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
+def draw_graph(adata, layout=None, **kwargs):
+    """\
+    Scatter plot in graph-drawing basis.
+
+    Parameters
+    ----------
+    {adata_color_etc}
+    layout : {{'fa', 'fr', 'drl', ...}}, optional (default: last computed)
+        One of the `draw_graph` layouts, see
+        :func:`~scanpy.api.tl.draw_graph`. By default, the last computed layout
+        is used.
+    {edges_arrows}
+    {scatter_bulk}
+    {show_save_ax}
+
+    Returns
+    -------
+    If `show==False` a `matplotlib.Axis` or a list of it.
+    """
+    if layout is None:
+        layout = str(adata.uns['draw_graph']['params']['layout'])
+    basis = 'draw_graph_' + layout
+    if 'X_' + basis not in adata.obsm_keys():
+        raise ValueError('Did not find {} in adata.obs. Did you compute layout {}?'
+                         .format('draw_graph_' + layout, layout))
+
+    return plot_scatter(adata, basis, **kwargs)
+
+
+@_wraps_plot_scatter
+@doc_params(adata_color_etc=doc_adata_color_etc, scatter_bulk=doc_scatter_bulk, show_save_ax=doc_show_save_ax)
+def pca(adata, **kwargs):
+    """\
+    Scatter plot in PCA coordinates.
+
+    Parameters
+    ----------
+    {adata_color_etc}
+    {scatter_bulk}
+    {show_save_ax}
+
+    Returns
+    -------
+    If `show==False` a `matplotlib.Axis` or a list of it.
+    """
+    return plot_scatter(adata, 'pca', **kwargs)
+
+
+# Helpers
+
+
 def _get_data_points(adata, basis, projection, components):
     """
     Returns the data points corresponding to the selected basis, projection and/or components.
@@ -401,7 +450,7 @@ def _get_data_points(adata, basis, projection, components):
             # eg: components='1,2'
             components_list.append([int(x.strip()) - 1 + offset for x in components.split(',')])
 
-        elif isinstance(components, list):
+        elif isinstance(components, abc.Sequence):
             if isinstance(components[0], int):
                 # components=[1,2]
                 components_list.append([int(x) - 1 + offset for x in components])
@@ -522,7 +571,7 @@ def _set_colors_for_categorical_obs(adata, value_to_plot, palette):
     else:
         # check if palette is a list and convert it to a cycler, thus
         # it doesnt matter if the list is shorter than the categories length:
-        if isinstance(palette, list):
+        if isinstance(palette, abc.Sequence):
             if len(palette) < len(categories):
                 logg.warn("Length of palette colors is smaller than the number of "
                           "categories (palette length: {}, categories length: {}. "


Test patch: