Call back between plots

Hi guys. I just started learning panel to create dashboard-like visualizations. Also, still learning about correct places to post questions, so I apologize in advance if this is not quite in the right place.

I am working on a simple example with plotly where a display a few lines. I would like to click (select) one of these lines and update another plot with the distribution of the y values of the line.

  1. I have a text box that stores the ID of the line selected. Currently the distribution plot is not being updated and I am not sure what I am still missing there.

  2. Also have a slider that lets me select how many lines I want to display. This is currently not working… again, not sure why.

Please see below my code, Any help is very much appreciated.
-m

import numpy as np
import panel as pn
import pandas as pd
import plotly.graph_objects as go

pn.extension('plotly')

data = {}
for i in range(10):
    xo = 5 * np.random.rand()
    xf = 5 + 5 * np.random.rand()
    x = np.linspace(xo, xf, int(50 + 50*np.random.rand()))
    e = np.random.rand()
    y = x**(1+e) * np.cos(x) + 4*(np.random.rand(len(x)) - 0.5)
    data[i] = {
        'x': x.tolist(),
        'y': y.tolist()
    }

selected_line = 0
nlines = pn.widgets.IntSlider(name='Number of lines', value=2, start=1, end=9)
textbx = pn.widgets.TextInput(name='Selected line:', value=str(selected_line))

@pn.depends(nlines.param.value)
def get_plot(nlines: int=2):
    fig = go.Figure()
    for i in range(nlines):
        fig.add_trace(
            go.Scatter(x=data[i]['x'], y=data[i]['y'], mode='lines', name=f'line-{i}'))
    return fig

@pn.depends(textbx.value)
def get_distribution(lineid: str='0'):
    fig = go.Figure()
    fig.add_trace(
        go.Histogram(x=data[int(lineid)]['y'])
    )
    return fig

timeseries = pn.pane.Plotly(get_plot())
distribution = pn.pane.Plotly(get_distribution())

@pn.depends(timeseries.param.click_data, watch=True)
def print_hello_world(click_data):
    global selected_line
    textbx.value = str(click_data['points'][0]['curveNumber'])
    
pn.Column(pn.Row(nlines, timeseries), pn.Row(textbx, distribution))
1 Like

Hi @vianamp

Welcome to the community. The main reason why your app is not working is that you provide the results of the get_plot and get_distribution functions as arguments instead of the functions themselves. But you would need to restructure a bit to get it working. The below works.

Happy coding.

import numpy as np
import panel as pn
import pandas as pd
import plotly.graph_objects as go

pn.extension('plotly')

data = {}
for i in range(10):
    xo = 5 * np.random.rand()
    xf = 5 + 5 * np.random.rand()
    x = np.linspace(xo, xf, int(50 + 50*np.random.rand()))
    e = np.random.rand()
    y = x**(1+e) * np.cos(x) + 4*(np.random.rand(len(x)) - 0.5)
    data[i] = {
        'x': x.tolist(),
        'y': y.tolist()
    }

nlines = pn.widgets.IntSlider(name='Number of lines', value=2, start=1, end=9)
timeseries = pn.pane.Plotly()

@pn.depends(nlines=nlines.param.value, watch=True)
def get_plot(nlines):
    print("get_plot", nlines)
    fig = go.Figure()
    for i in range(nlines):
        fig.add_trace(
            go.Scatter(x=data[i]['x'], y=data[i]['y'], mode='lines', name=f'line-{i}'))
    timeseries.object = fig
get_plot(nlines.value)

textbx = pn.widgets.TextInput(name='Selected line:', value=str(0))

@pn.depends(textbx.param.value)
def get_distribution(lineid: str='0'):
    fig = go.Figure()
    fig.add_trace(
        go.Histogram(x=data[int(lineid)]['y'])
    )
    return fig

@pn.depends(timeseries.param.click_data, watch=True)
def print_hello_world(click_data):
    textbx.value = str(click_data['points'][0]['curveNumber'])

pn.Column(
    pn.Row(nlines, timeseries),
    pn.Row(textbx, get_distribution)
).servable()
2 Likes

Hi @Marc. Thank you very much for the reply. I slightly changed the code and below is the current version of it. My next question is, how to get rid of the text box and still have the second plot updated when the first plot is clicked? My idea was to use a global variable to store the selected line ID, but so far I had no success.

import numpy as np
import panel as pn
import pandas as pd
import plotly.graph_objects as go

pn.extension('plotly')

data = {}
for i in range(10):
    xo = 5 * np.random.rand()
    xf = 5 + 5 * np.random.rand()
    x = np.linspace(xo, xf, int(50 + 50*np.random.rand()))
    e = np.random.rand()
    y = x**(1+e) * np.cos(x) + 4*(np.random.rand(len(x)) - 0.5)
    data[i] = {
        'x': x.tolist(),
        'y': y.tolist()
    }

nlines = pn.widgets.IntSlider(name='Number of lines', value=2, start=1, end=9)
timeseries = pn.pane.Plotly()
textbx = pn.widgets.TextInput(name='Selected line:', value=str(0))
distribution = pn.pane.Plotly()

@pn.depends(nlines=nlines.param.value, watch=True)
def get_plot(nlines: int):
    fig = go.Figure()
    for i in range(nlines):
        fig.add_trace(
            go.Scatter(x=data[i]['x'], y=data[i]['y'], mode='lines', name=f'line-{i}'))
    timeseries.object = fig

@pn.depends(textbx.param.value, watch=True)
def get_distribution(lineid: str):
    fig = go.Figure()
    fig.add_trace(
        go.Histogram(x=data[int(lineid)]['y'])
    )
    distribution.object = fig

@pn.depends(timeseries.param.click_data, watch=True)
def print_hello_world(click_data):
    textbx.value = str(click_data['points'][0]['curveNumber'])

get_plot(nlines.value)
get_distribution(textbx.value)
pn.Column(
    pn.Row(nlines, timeseries),
    pn.Row(textbx, distribution)
).servable()