Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing widget for some categorical values #65

Open
ramadatta opened this issue Jan 4, 2025 · 2 comments
Open

Missing widget for some categorical values #65

ramadatta opened this issue Jan 4, 2025 · 2 comments

Comments

@ramadatta
Copy link

ramadatta commented Jan 4, 2025

Hi,

Thanks for wonderful visualization package! I am having some issue with interactive visualization.

All 3 cell_ontology_class, mouse_sex, tissue are categorical values but I am able to generate widget only for mouse_sex but not for the other 2.

import seaborn as sns
from matplotlib.colors import rgb2hex
import datamapplot as dmp

# Extract Scanpy colors for cell_ontology_class
categories = adata.obs["cell_ontology_class"].cat.categories
colors = adata.uns["cell_ontology_class_colors"]
color_mapping = dict(zip(categories, colors))

# Generate color mappings for categorical fields
mouse_sex_categories = adata.obs["mouse_sex"].unique()
mouse_sex_colors = sns.color_palette("Set2", len(mouse_sex_categories))
mouse_sex_mapping = dict(zip(mouse_sex_categories, map(rgb2hex, mouse_sex_colors)))

tissue_categories = adata.obs["tissue"].unique()
tissue_colors = sns.color_palette("tab20", len(tissue_categories))
tissue_mapping = dict(zip(tissue_categories, map(rgb2hex, tissue_colors)))


# UMAP data
umap_data = adata.obsm["X_umap"]  # UMAP coordinates
labels = adata.obs["cell_ontology_class"]  # Primary labels

# Generate hover text
hover_columns = ["cell_ontology_class", "mouse_sex", "tissue"]
hover_data = adata.obs[hover_columns].apply(
    lambda row: "\n".join([f"{col}: {row[col]}" for col in hover_columns]), axis=1
)

# Categorical data handling via colormap_rawdata and colormap_metadata
colormap_rawdata = [
    adata.obs["cell_ontology_class"],  # Primary categorical field
    adata.obs["mouse_sex"],            # Categorical
    adata.obs["tissue"],               # Categorical
]

from matplotlib import cm

colormap_metadata = [
    {
        "field": "cell_ontology_class",
        "description": "Cell Ontology Class",
        "kind": "categorical",
        "color_mapping": color_mapping,
    },
    {
        "field": "mouse_sex",
        "description": "Mouse Sex",
        "kind": "categorical",
        "color_mapping": mouse_sex_mapping,
    },
    {
        "field": "tissue",
        "description": "Tissue",
        "kind": "categorical",
        "color_mapping": tissue_mapping,
    },
]


# Create the interactive plot
dmp.create_interactive_plot(
    umap_data,
    labels,
    hover_text=hover_data,
    title="Interactive UMAP Plot",
    sub_title="Example of Direct DataMapPlot Call",
    cmap=None,  # Use default colormap or a custom one
    enable_search=True,
    label_color_map=None,  # Only needed if you want to manually map colors for `labels`
    colormap_rawdata=colormap_rawdata,
    colormap_metadata=colormap_metadata,
)

Also, when I try colormaps parameter, multiple cell_ontology_class or tissue has same color assigned and some of the labels are cutoff from the bottom.

# Create the interactive plot
dmp.create_interactive_plot(
    umap_data,
    labels,
    hover_text=hover_data,
    title="Interactive UMAP Plot",
    sub_title="Example of Direct DataMapPlot Call",
    cmap=None,  # Use default colormap or a custom one
    enable_search=True,
    label_color_map=None,  # Only needed if you want to manually map colors for `labels`
    colormaps={
        "cell_ontology_class": adata.obs["cell_ontology_class"],  # Use the correct column name
        "mouse_sex": adata.obs["mouse_sex"],  # Column name in adata.obs
        "tissue": adata.obs["tissue"],  # Column name in adata.obs
    },
)

image

May I know how to resolve these?

Many thanks in advance!

Datta

@lmcinnes
Copy link
Contributor

lmcinnes commented Jan 4, 2025

The duplication of colours with the colormaps parameter is the easiest to explain -- it tries to pick the largest colormap to fit from a set of default choices, but will fall back if it can't find one large enough. Upon falling back on a colour palette, it simply repeats colours as required as it doesn't have enough colours to cover all the categories. The solution to that is what you were doing above: providing an explicit color_mapping via the metadata.

The legend widget is currently set to only be created for categorical data with 20 categories or less. It is not uncommon for people to pass in data with very large numbers of categories (hundreds) without thinking about it, and getting a ridiculous legend that is both unreadable and far larger than the plot. I was trying to avoid this, and 20 categories seemed like about the reasonable limit. So in general it doesn't show a legend for anything with too many categories. The fact that you do get one at all with colormaps is, I believe, a bug.

Of course you seem to want a legend regardless, so perhaps it would be best if there was a way to override that behaviour and force a legend to appear (e.g. adding "force_legend":True to the metadata). That is certainly something I can implement, but it may not be an immediate fix.

If you do just want an immediate fix then there are a couple of lines in datamapplot/static/js/colormap_selector.js that you can change:

            if (((colorMap.kind === "categorical") && (colorMap.colors.length <= 20) && Object.hasOwn(colorMap, "colorMapping")) || (colorMap.kind === "continuous") || (colorMap.kind === "datetime")) {

if (((colorMap.kind === "categorical") && (colorMap.colors.length <= 20) && Object.hasOwn(colorMap, "colorMapping")) || (colorMap.kind === "continuous") || (colorMap.kind === "datetime")) {

and

            if ((colorMap.kind === "categorical") && (colorMap.colors.length <= 20) && Object.hasOwn(colorMap, "colorMapping")) {

if ((colorMap.kind === "categorical") && (colorMap.colors.length <= 20) && Object.hasOwn(colorMap, "colorMapping")) {

If you just set that 20 to a value larger than however many categories you have for your other classes it should work. Making this chance and the re-installing from the changed source should work. If you want an easier option then set minify_deps=False and you can open the resulting output html file and find those lines of javascript in the file, edit them there, and have the output you want.

I know that's a little messy, but it should provide immediate results -- otherwise you'll have to wait until I get time to implement an override option for displaying the legend.

@ramadatta
Copy link
Author

ramadatta commented Jan 4, 2025

Thank you so much @lmcinnes for such a swift reply. I will try with your above pointers. But thanks so much for such a cool package!

Edit: Just a thought, it would be nice, if we can scroll the legend. That way there is no need to make figure size bigger or make the font size of the text in the legend smaller.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants