We have a weird temporary global variable called sc.pl._utils._tmp_cluster_pos. We use it for storing the positions of cluster centroids (actually the centroids of any categorical variable for any sort of embedding). The weird part is that it's set in scatterplot functions (see https://github.com/theislab/scanpy/blob/master/scanpy/plotting/_anndata.py#L468 and https://github.com/theislab/scanpy/blob/master/scanpy/plotting/_tools/scatterplots.py#L809) and used only by sc.pl.paga_compare (https://github.com/theislab/scanpy/blob/master/scanpy/plotting/_tools/paga.py#L119).
First, it's not obvious where paga_compare finds centroids (it was a mystery to me until recently). Second, the current design is error-prone (see a corner case https://github.com/theislab/scanpy/issues/686). Therefore, there should be a better place to store cluster centroids :)
I'm not following the discussion about the future of AnnData, but maybe having something like adata.uns['obs_category_leiden'] and storing colors and centroids in it e.g. adata.uns['obs_category_leiden']['colors'] and adata.uns['obs_category_leiden']['centroids']['X_umap'] would be more structured.
What if we didn't store it by default, and just computed it on the fly? It doesn't seem like it would be particularly computationally expensive.
Suffering from a similar issue, in my case the on data export is not working at all. Although the positions are stored in sc.pl._utils._tmp_cluster_pos, but https://github.com/theislab/scanpy/blob/master/scanpy/plotting/_anndata.py#L471:L475 is not working and there is no pos.csv created.
I just needed to use centroids for some plotting, and found a pretty straightforward solution. This function will add labels to the centroids of each cluster, but uses adjustText so they don't overlap:
def gen_mpl_labels(adata, groupby, exclude=()):
medians = {}
for g, g_idx in adata.obs.groupby(groupby).groups.items():
if g in exclude:
continue
medians[g] = np.median(adata[g_idx].obsm["X_umap"], axis=0)
texts = [plt.text(x=x, y=y, s=k) for k, (x, y) in medians.items()]
adjust_text(texts, text_from_points=False)
@ivirshup This should be added to the embedding code
Most helpful comment
I just needed to use centroids for some plotting, and found a pretty straightforward solution. This function will add labels to the centroids of each cluster, but uses adjustText so they don't overlap: