Lasso deselect from inside a function

#1

Hi there,

I am trying to use the lasso to select data on one plot and show in in the other plot via indices and vice versa, however I cannot make it work since the data of the first plot stays selected. Does anyone know how to call a lasso (or range) deselect from a function?

here an attemp to make it work:


import numpy as np
import plotly.graph_objs as go
import ipywidgets as widgets
from plotly.colors import DEFAULT_PLOTLY_COLORS

x = np.linspace(0,6,100)
y = np.sin(x)

data = [go.Scattergl(x=x, y=y, mode='markers', name = 'All nodes', selected=dict(marker=dict(color=DEFAULT_PLOTLY_COLORS[1]))),
        go.Scattergl(mode='markers', name='selected nodes')]

layout = dict(title=dict(text = 'plot1'), dragmode='lasso')
fig = go.FigureWidget(data=data, layout= layout)

data2 = [go.Scattergl(x=y, y=x, mode='markers', name = 'All nodes', selected=dict(marker=dict(color=DEFAULT_PLOTLY_COLORS[1]))),
        go.Scattergl(mode='markers', name='selected nodes')]

layout2 = dict(title=dict(text = 'plot2'), dragmode='lasso')
fig2 = go.FigureWidget(data=data2, layout= layout2)

def selection_fn(trace, points, selector):
    # !!!! here call deselect lasso from plot2
    fig.data[1].x = [None]
    fig.data[1].y = [None]
    fig.layout.xaxis.autorange = True
    fig2.data[1].x = y[points.point_inds]
    fig2.data[1].y = x[points.point_inds]
    
    
def selection_fn2(trace, points, selector):
    # !!!! here call deselect lasso from plot1
    fig2.data[1].x = [None]
    fig2.data[1].y = [None]
    fig.data[1].x = x[points.point_inds]
    fig.data[1].y = y[points.point_inds]


fig.data[0].on_selection(selection_fn)
fig2.data[0].on_selection(selection_fn2)
display(widgets.HBox([fig,fig2]))

I made this as an alternative but it was not really what i aimed to do:

import numpy as np
import plotly.graph_objs as go
import ipywidgets as widgets
from plotly.colors import DEFAULT_PLOTLY_COLORS

x = np.linspace(0,6,100)
y = np.sin(x)

data = [go.Scattergl(x=x, y=y, mode='markers', name = 'All nodes', marker=dict(color=np.array([DEFAULT_PLOTLY_COLORS[0]]*len(x))),
                     selected=dict(marker=dict(color=DEFAULT_PLOTLY_COLORS[1])))]

layout = dict(title=dict(text = 'plot1'), dragmode='lasso')
fig = go.FigureWidget(data=data, layout= layout)

data2 = [go.Scattergl(x=y, y=x, mode='markers', name = 'All nodes', marker=dict(color=np.array([DEFAULT_PLOTLY_COLORS[0]]*len(x))),
                      selected=dict(marker=dict(color=DEFAULT_PLOTLY_COLORS[2])))]

layout2 = dict(title=dict(text = 'plot2'), dragmode='lasso')
fig2 = go.FigureWidget(data=data2, layout= layout2)

def selection_fn(trace, points, selector):
    col = np.array([DEFAULT_PLOTLY_COLORS[0]]*len(x))
    col[points.point_inds] = np.array([DEFAULT_PLOTLY_COLORS[1]]*len(points.point_inds))
    fig2.data[0].marker.color = col
    
def selection_fn2(trace, points, selector):
    col = np.array([DEFAULT_PLOTLY_COLORS[0]]*len(x))
    col[points.point_inds] = np.array([DEFAULT_PLOTLY_COLORS[2]]*len(points.point_inds))
    fig.data[0].marker.color = col

fig.data[0].on_selection(selection_fn)
fig2.data[0].on_selection(selection_fn2)
display(widgets.HBox([fig,fig2]))
#2

Hi @Alexboiboi,

You can use the scatter.selectedpoints property to control which points are selected programmatically. Here’s an example:

import numpy as np
import plotly.graph_objs as go
import ipywidgets as widgets
from plotly.colors import DEFAULT_PLOTLY_COLORS

x = np.linspace(0,6,100)
y = np.sin(x)

data = [go.Scattergl(x=x, y=y, mode='markers', name = 'All nodes',
                     selected=dict(marker=dict(color=DEFAULT_PLOTLY_COLORS[1])))]

layout = dict(title=dict(text = 'plot1'), dragmode='lasso')
fig = go.FigureWidget(data=data, layout= layout)

data2 = [go.Scattergl(x=y, y=x, mode='markers', name = 'All nodes',
                      selected=dict(marker=dict(color=DEFAULT_PLOTLY_COLORS[1])))]

layout2 = dict(title=dict(text = 'plot2'), dragmode='lasso')
fig2 = go.FigureWidget(data=data2, layout= layout2)

def selection_fn(trace, points, selector):
    fig.data[0].selectedpoints = None
    fig.data[0].selectedpoints = []
    fig2.data[0].selectedpoints = points.point_inds 

    
def selection_fn2(trace, points, selector):
    fig2.data[0].selectedpoints = None    
    fig2.data[0].selectedpoints = []
    fig.data[0].selectedpoints = points.point_inds


fig.data[0].on_selection(selection_fn)
fig2.data[0].on_selection(selection_fn2)
display(widgets.HBox([fig,fig2]))

Does this do what you want? Note that I set the selectedpoints property to None and then to []. This was to work around a problem I was seeing where the selectedpoints update would only be processed the first time the callback was called. I think it has something to do with the order in which the selectedpoints property is updated and the when the on_selection callback is called.

Hope that helps!

-Jon

#3

Hi Jon, thanks a lot for your answer, this is exactly what I needed. You are the best!

I was also wondering why the selectedpoints update was only performed only once!

1 Like
#4

Great!

I also opened an issue about the selectedpoints issue at https://github.com/plotly/plotly.py/issues/1433. So hopefully we can get rid of the need to perform the None assignment.

-Jon

#5

Hi, Could I ask you how to display two diagrams in one html file as in your diagram?

#6

Hi @visual,

The easiest thing to do here is to use subplots: https://plot.ly/python/subplots/, and then save the figure to HTML with plotly.offline.plot If that doesn’t work for you, you could use an approach like I describe in How to make Html Report readable from all browsers to generate a custom HTML file with multiple figures.

Hope that helps!
-Jon

1 Like
#7

Hi Jon, thanks a lot for your answer! I am trying to make the above interactive selection, but it seemed to have failed. This is my code.

from IPython.display import display,HTML
from plotly.offline import plot
import numpy as np
import plotly.graph_objs as go
import ipywidgets as widgets
from plotly.colors import DEFAULT_PLOTLY_COLORS

x = np.linspace(0,6,100)
y = np.sin(x)

data = [go.Scattergl(x=x, y=y, mode=‘markers’, name = ‘All nodes’,
selected=dict(marker=dict(color=DEFAULT_PLOTLY_COLORS[1])))]

layout = dict(title=dict(text = ‘plot1’), dragmode=‘select’)
fig1 = go.FigureWidget(data=data, layout= layout)

data2 = [go.Scattergl(x=y, y=x, mode=‘markers’, name = ‘All nodes’,
selected=dict(marker=dict(color=DEFAULT_PLOTLY_COLORS[1])))]

layout2 = dict(title=dict(text = ‘plot2’), dragmode=‘select’)
fig2 = go.FigureWidget(data=data2, layout= layout2)

def selection_fn(trace, points, selector):
fig1.data[0].selectedpoints = None
fig1.data[0].selectedpoints = []
fig2.data[0].selectedpoints = points.point_inds

def selection_fn2(trace, points, selector):
fig2.data[0].selectedpoints = None
fig2.data[0].selectedpoints = []
fig1.data[0].selectedpoints = points.point_inds

fig1.data[0].on_selection(selection_fn)
fig2.data[0].on_selection(selection_fn2)
display(widgets.HBox([fig1,fig2]))

div1 = plot(fig1, output_type=‘div’, include_plotlyjs=True)
div2 = plot(fig2, output_type=‘div’, include_plotlyjs=True)

html = “”"
<html>
<head>
<script src=“https://cdn.plot.ly/plotly-latest.min.js”></script>
</head>
<body>
{div1}
{div2}
</body>
</html>
“”".format(div1=div1, div2=div2)

with open(‘multi_plot.html’, ‘w’) as f:
f.write(html)

#8

Hi @visual,

I didn’t realize you were intending to display two diagrams with the lasso selection callbacks in an HTML file. You won’t be able to use the FigureWidget callbacks to accomplish this because these rely on having an active Python kernel.

What you want is possible, but it’s would be a bit messy and would involve writing the callbacks in JavaScript. Here’s an example that shows the general idea: See this example for the general idea Hyperlink to markers on map.

Hope that helps clear things up,

-Jon