3

Is there a way to only show visible values in the legend in plotly express? So for example if this was my original scatter plot: enter image description here

and I zoom in on a few points like so: enter image description here

Is there a way to only show the legend values for the points that are currently visible, rather than showing all legend values?

Code for reference:

fig_2d = px.scatter(
    proj_2D, x=0, y=1,
    color=topic_nums.astype(str), labels={'color': 'topic'}, 
    #color_continuous_scale="spectral"
)

fig_2d.update_traces(marker=dict(size=5),
                selector=dict(mode='markers'))

fig_2d.update_layout(
    autosize=False,
    width=1000,
    height=800,
    )
fig_2d.show()

where proj_2D is a 2D matrix

peskysam
  • 33
  • 4
  • 2
    Please remember that Stack Overflow is not your favourite Python forum, but rather a question and answer site for all programming related questions. Thus, always include the tag of the language you are programming in, that way other users familiar with that language can more easily find your question. Take the [tour] and read up on [ask] to get more information on how this site works, then [edit] the question with the relevant tags. – Adriaan Mar 13 '23 at 11:13
  • @peskysam Is [Dash](https://dash.plotly.com/) an option? – vestland Mar 13 '23 at 14:06
  • @vestland I hadn't considered Dash because I thought it was more of an app framework. Would I be able to use Dash in a notebook to do this? – peskysam Mar 13 '23 at 15:47
  • 2
    @peskysam there is `JupyterDash` which allows you to build and view a Dash app in a Jupyter notebook. my guess is that @vestland is asking is because callbacks are supported in Dash, and this would allow us to determine the points that are currently visible in the figure (and update the legend accordingly with only the traces that contain those points) – Derek O Mar 13 '23 at 18:02
  • @DerekO ok thanks I'll have a look into that. – peskysam Mar 14 '23 at 09:23
  • @peskysam if a JupyterDash based solution would fit your use case, I'd be happy to write up an example for you – Derek O Mar 14 '23 at 14:14
  • 1
    @DerekO if you have the time that would be great! – peskysam Mar 14 '23 at 16:04

1 Answers1

2

You can create a JupyterDash app, and use a callback to read in the layout data from zoom events, then update the figure accordingly. In order to reset the default (with all of the points and traces in the legend), you can click Zoom Out tooltip in the figure.

import json
import numpy as np
import pandas as pd
import plotly.express as px
from jupyter_dash import JupyterDash
from dash import dcc, html, Input, Output

# Sample Data
np.random.seed(42)

proj_2D = pd.DataFrame({
    'x': np.random.uniform(low=0.0, high=1.0, size=100),
    'y': np.random.uniform(low=0.0, high=1.0, size=100),
    'topic': list(range(1,11))*10
})

proj_2D['topic'] = proj_2D['topic'].astype(str)

fig_2d = px.scatter(
    proj_2D, x='x', y='y',
    color='topic'
)

fig_2d.update_traces(marker=dict(size=5),
                selector=dict(mode='markers'))

fig_2d.update_layout(
    autosize=False,
    width=1000,
    height=800,
)

# Build App
app = JupyterDash(__name__, prevent_initial_callbacks=True)
app.layout = html.Div([
    html.H1("JupyterDash Scatter Zoom Update"),
    dcc.Graph(id='scatter-fig', figure=fig_2d),
])

@app.callback(
    Output('scatter-fig', 'figure'),
    Input('scatter-fig', 'relayoutData'),
)
def display_relayout_data(relayoutData):
    
    xaxis_min, xaxis_max = relayoutData["xaxis.range[0]"], relayoutData["xaxis.range[1]"]
    yaxis_min, yaxis_max = relayoutData["yaxis.range[0]"], relayoutData["yaxis.range[1]"]
    
    ## subset data
    proj_2D_new = proj_2D[
        proj_2D['x'].between(xaxis_min, xaxis_max) & 
        proj_2D['y'].between(yaxis_min, yaxis_max)
    ]

    fig_2d = px.scatter(
        proj_2D_new, x='x', y='y',
        color='topic'
    )
    
    fig_2d.update_traces(marker=dict(size=5),
                selector=dict(mode='markers'))

    fig_2d.update_layout(
        autosize=False,
        width=1000,
        height=800,
        )
    
    return fig_2d

# Run app and display result inline in the notebook
app.run_server(mode='inline', debug=True, port=8000)

enter image description here

Derek O
  • 16,770
  • 4
  • 24
  • 43
  • Thanks for that. Can I ask how is the display_relayout_data function being called? Is that the callback function? – peskysam Mar 15 '23 at 11:00
  • 1
    @peskysam yes, the `@app.callback` decorator ensures that `display_relayout_data` is a callback function, and is called once the app starts running – Derek O Mar 15 '23 at 14:29