Search code examples
pythonplotly

Highlight certain points in Plotly through dropbown bar


I am trying to make an interactive PCA plot with Plotly. I am having some troubles in highlighting a certain marker that a user wants to see in the plot.

Instead of highlighting a marker there is a square around it.

My attempt at the code:

from sklearn.datasets import make_blobs
import pandas as pd
import plotly.express as px
X, y = make_blobs(n_samples=30, centers=3, n_features=2,
                  random_state=0)
ff = PCA(n_components=  2)
clusters = pd.DataFrame(data = ff.fit_transform(X), columns = ['PC1', 'PC2'])
clusters['target'] = y
id = [0, 4, 7]
updatemenus = [dict(buttons=list(dict(label = idd ,method = 'relayout', args = ['shapes', [dict(markers = dict(color = 'Red', size = 120), type = "markers", xref = 'x', yref = 'y', x0 = clusters.loc[idd, 'PC1'], y0=clusters.loc[idd, 'PC2'])]]) for idd in id))]
fig = px.scatter(clusters, x = 'PC1', y = 'PC2', color = 'target', hover_data = ['target'])
fig.update_layout(updatemenus = updatemenus)
fig.show()

Example

What am I doing wrong :)


Solution

  • Using shapes

    The shape type should be one of 'circle' | 'rect' | 'path' | 'line', I guess you might want to use 'circle'instead of 'markers'.

    fig = px.scatter(clusters, x='PC1', y='PC2', color='target', hover_data=['target'])
    
    size = 1/100 # ~1% of the x|y range
    rx = (clusters['PC1'].max() - clusters['PC1'].min()) * size
    ry = (clusters['PC2'].max() - clusters['PC2'].min()) * size
    
    updatemenus = [dict(
        buttons=[dict(
            label='None',
            method='relayout',
            args=['shapes', []]
        )] + [dict(
            label=idd,
            method='relayout',
            args=[{
                'xaxis.autorange': True,
                'yaxis.autorange': True,
                'shapes': [{
                    'type': 'circle',
                    'layer': 'below',
                    'xref': 'x',
                    'yref': 'y',
                    'x0': clusters.loc[idd, 'PC1'] - rx,
                    'y0': clusters.loc[idd, 'PC2'] - ry,
                    'x1': clusters.loc[idd, 'PC1'] + rx,
                    'y1': clusters.loc[idd, 'PC2'] + ry,
                    'opacity': 0.3,
                    'fillcolor': 'red',
                    'line': {
                        'color': 'red'
                    }
                }]
            }]
        ) for idd in id]
    )]
    
    fig.update_layout(updatemenus=updatemenus)
    fig.show()
    

    output


    Using a scatter trace

    You could also use a scatter trace dedicated to highlighting instead of using shapes, eg.

    fig = px.scatter(clusters, x='PC1', y='PC2', color='target', hover_data=['target'])
    
    fig.add_trace(go.Scatter(
        name='highlight',
        mode='markers',
        showlegend=False,
        hoverinfo='skip',
        marker=dict(
            size=12,
            color='red'
        )
    ))
    
    updatemenus = [dict(
        buttons=[dict(
            label='None',
            method='relayout',
            args=[{
                'x': [clusters['PC1']],
                'y': [clusters['PC2']]
            }]
        )] + [dict(
            label=idd,
            method='restyle',
            args=[{
                'x': [clusters['PC1'], [clusters.loc[idd, 'PC1']]],
                'y': [clusters['PC2'], [clusters.loc[idd, 'PC2']]]
            }]
        ) for idd in id]
    )]
    
    fig.update_layout(updatemenus=updatemenus)
    fig.show()
    

    output2