Dynamic Plotting based on selections in cross selector

Hello Everyone, I used the above code inspiration from Is there a way to create a Dynamic Layout Based on user input? - #3 by Marc to create the following.

I have created the option to select the dataset I want to view, the dropdown to select what I would like to have on my x-axis and cross-select filter to select what signals I would like to plot on y-axis. I am stuck exploring two things

  1. I would like to make multiple plots stacked below each other based on my selections of signals for the filtered options on the right i.e. Whatever I select on the right, should stack a new plot and plot it in that object. Alternatively, I thought it would be easier if I could “add” new empty plot, select that plot window and then populate what I select. This facilitates what I want to plot and where. If I do this, I would probably not need the option of “add” or “remove” plot option.
  2. Add a vertical line moving cursor that indicates the “y” values as the Vline moves/hovers over the plot. Similar to Stumpy Timeseries Analysis
import numpy as np
import pandas as pd
import panel as pn
pn.extension(sizing_mode="stretch_width")
#pd.options.plotting.backend = "holoviews"



data1 = pd.DataFrame({"C" : np.random.randint(low=1, high=100, size=10),
                     "D"  : np.random.normal(0.0, 1.0, size=10)
                     })
data2 = pd.DataFrame({"C" : np.random.randint(low=1, high=300, size=10),
                     "D"  : np.random.normal(0.0, 1.0, size=10)
                     })

trace_datasets = {'data_id1':data1, 'dataid2':data2}



cols_X = list(data1.columns)
cols_Y =  list(data1.columns)

widget_x = pn.widgets.Select(name='x', options= cols_X)
#widget_y = pn.widgets.MultiSelect(name='y', options= cols_Y) #add default signals to plot

#not using widget_y. instead using cross_selector to filter options and plot what we want 
cross_selector = pn.widgets.CrossSelector(name='y', options=list(data1.columns))


plot_type = pn.widgets.Select(name="Plot Type", value="line", options=["line", "scatter"])

dataset_selector = pn.widgets.Select(name="Select Data",  options= list(trace_datasets.keys()))


@pn.depends(dataset_selector) 
def get_plot_analyze(cols_X, cols_Y, plot_type ='line',dataset_selector = dataset_selector.value):

    if plot_type == "line":

        curve_ds_analyze = trace_datasets[dataset_selector].hvplot.line(x=cols_X, y=cols_Y, width = 500)

        return curve_ds_analyze

    elif plot_type == "scatter":

        scatter_ds_analyse = trace_datasets[dataset_selector].hvplot.scatter(x=cols_X, y=cols_Y,width = 500) 

        return  scatter_ds_analyse
    
    
@pn.depends(widget_x, cross_selector, plot_type, dataset_selector)
def get_plot(cols_X = widget_x.value, cols_Y=cross_selector.value, plot_type = 'line',dataset_selector = dataset_selector.value):

    return get_plot_analyze(cols_X, cols_Y, plot_type, dataset_selector)

# Accordion, Column, Row, Tabs, WidgetBox
container = pn.Column()

@pn.depends(widget_x, cross_selector, plot_type, dataset_selector)
def add_plot(*events):
    container.append(get_plot(cols_X=widget_x.value, cols_Y=cross_selector.value, plot_type = 'line',dataset_selector = dataset_selector.value))
add_plot()

add_plot_button = pn.widgets.Button(name="ADD PLOT", button_type="primary")
add_plot_button.on_click(add_plot)

def remove_plot(*events):
    if len(container)>0:
        container.pop(len(container)-1)

remove_plot_button = pn.widgets.Button(name="REMOVE PLOT", button_type="success")
remove_plot_button.on_click(remove_plot)


#pn.Row(pn.Column(pn.WidgetBox(dataset_selector, widget_x, widget_y, plot_type,add_plot_button, remove_plot_button),width=200), get_plot, container)
pn.Column(pn.WidgetBox(
                'Controls',
                dataset_selector, widget_x, cross_selector, plot_type,add_plot_button, remove_plot_button,
                width=1000,
                sizing_mode='fixed',),get_plot, container)

Maybe someone can help me with ideas :slight_smile: I managed to do plotting based on my selection in cross selector. I still have two issues

  1. When I make a new selection in cross selector, I get a plot but also the plot for previous selection which is repetitive.
  2. I see that when I select something from dropdown widgets (“x” or “Select Data”), I get a new plot instead of updating the data in the current plot.
import numpy as np
import pandas as pd
import panel as pn
pn.extension(sizing_mode="stretch_width")
#pd.options.plotting.backend = "holoviews"

data1 = pd.DataFrame({"C" : np.random.randint(low=1, high=100, size=10),
                     "D"  : np.random.normal(0.0, 1.0, size=10)
                     })
data2 = pd.DataFrame({"C" : np.random.randint(low=1, high=300, size=10),
                     "D"  : np.random.normal(0.0, 1.0, size=10)
                     })

trace_datasets = {'data_id1':data1, 'dataid2':data2}



cols_X = list(data1.columns)
cols_Y =  list(data1.columns)

widget_x = pn.widgets.Select(name='x', options= cols_X)
#widget_y = pn.widgets.MultiSelect(name='y', options= cols_Y) #add default signals to plot

cross_selector = pn.widgets.CrossSelector(name='y', options=cols_Y)

plot_type = pn.widgets.Select(name="Plot Type", value="line", options=["line", "scatter"])

dataset_selector = pn.widgets.Select(name="Select Run Data",  options= list(trace_datasets.keys()))

frame_slider = pn.widgets.IntSlider(name="Time", value=0, start=0, end=999)

# @pn.depends(dataset_selector) 
# def get_plot_analyze(cols_X, cols_Y, plot_type ='line',dataset_selector = dataset_selector.value):
      
#     if plot_type == "line":
#         if cols_Y:
            
#             curve_ds_analyze = trace_dict[dataset_selector].hvplot.line(x=cols_X, y=cols_Y,width=1000, grid=True)
#         else:
#             curve_ds_analyze = hv.Curve([])

#         return curve_ds_analyze

#     elif plot_type == "scatter":

#         scatter_ds_analyse = trace_dict[dataset_selector].hvplot.scatter(x=cols_X, y=cols_Y,width=1000,grid=True) 

#         return  scatter_ds_analyse

container2 = pn.Column()

@pn.depends(widget_x, cross_selector, plot_type, dataset_selector, watch=True)
def get_plot_analyze(cols_X, cols_Y, plot_type ='line',dataset_selector = dataset_selector.value):
    for selections in cross_selector.value:
        if plot_type=='line':
            if cols_Y:
                
                line_plot=container2.append(trace_datasets[dataset_selector].hvplot.line(x=cols_X,y=selections,grid=True))
                
            else:
                line_plot=container2.append(hv.Curve([]))
        
        elif plot_type=='scatter':
            scatter_plot=container2.append(trace_datasets[dataset_selector].hvplot.scatter(x=cols_X,y=selections,grid=True))
    
    
@pn.depends(widget_x, cross_selector, plot_type, dataset_selector)
def get_plot(cols_X=widget_x.value, cols_Y=cross_selector.value, plot_type = 'line',dataset_selector = dataset_selector.value):

    return get_plot_analyze(cols_X = widget_x.value, cols_Y=cross_selector.value, plot_type = 'line',dataset_selector = dataset_selector.value)

# Accordion, Column, Row, Tabs, WidgetBox
#container = pn.Column()

# @pn.depends(widget_x, cross_selector, plot_type, dataset_selector)
# def add_plot(*events):
#     container.append(get_plot)
#     #container.objects.hvplot.line(x=cols_X, y=cols_Y,width=1000, grid=True)
# add_plot()


# add_plot_button = pn.widgets.Button(name="ADD PLOT", button_type="primary")
# add_plot_button.on_click(add_plot)

def remove_plot(*events):
    if len(container2)>0:
        container2.pop(len(container)-1)

remove_plot_button = pn.widgets.Button(name="REMOVE PLOT", button_type="success")
remove_plot_button.on_click(remove_plot)


#pn.Row(pn.Column(pn.WidgetBox(dataset_selector, widget_x, widget_y, plot_type,add_plot_button, remove_plot_button),width=200), get_plot, container)
pn.Column(pn.WidgetBox(
                'Controls',
                dataset_selector, widget_x, cross_selector, plot_type, remove_plot_button,
                width=1000,),container2)```

Hi @SVP1194

Regarding 1. The issue is in get_plot_analyze. When the cross selector value changes you append all the selected values - not only the newly added one. Either you will need to add logic to figure out what the new selection is. Or just reset container2 before you start appending.

It is difficult for me to understand what you are trying to achieve. Especially why you need the remove button?

I would implement something like the below instead.

import hvplot.pandas
import numpy as np
import pandas as pd
import panel as pn

pn.extension(sizing_mode="stretch_width")

data1 = pd.DataFrame(
    {"C": np.random.randint(low=1, high=100, size=10), "D": np.random.normal(0.0, 1.0, size=10)}
)
data2 = pd.DataFrame(
    {"C": np.random.randint(low=1, high=300, size=10), "D": np.random.normal(0.0, 1.0, size=10)}
)

trace_datasets = {"data_id1": data1, "dataid2": data2}


cols_X = list(data1.columns)
cols_Y = list(data1.columns)

widget_x = pn.widgets.Select(name="x", options=cols_X)

cross_selector = pn.widgets.CrossSelector(name="y", options=cols_Y)

plot_type = pn.widgets.Select(name="Plot Type", value="line", options=["line", "scatter"])

dataset_selector = pn.widgets.Select(name="Select Run Data", options=list(trace_datasets.keys()))

frame_slider = pn.widgets.IntSlider(name="Time", value=0, start=0, end=999)

@pn.depends(widget_x, cross_selector, plot_type, dataset_selector)
def get_plot_analyze(cols_X, cols_Y, plot_type="line", dataset_selector=dataset_selector.value):
    container2 = pn.Column()
    for selections in cross_selector.value:
        if plot_type == "line":
            if cols_Y:

                line_plot = container2.append(
                    trace_datasets[dataset_selector].hvplot.line(x=cols_X, y=selections, grid=True)
                )

            else:
                line_plot = container2.append(hv.Curve([]))

        elif plot_type == "scatter":
            scatter_plot = container2.append(
                trace_datasets[dataset_selector].hvplot.scatter(x=cols_X, y=selections, grid=True)
            )
    if len(container2)>0:
        return container2
    return "No options selected"

pn.Column(
    pn.WidgetBox(
        "Controls",
        dataset_selector,
        widget_x,
        cross_selector,
        plot_type,
        width=1000,
    ),
    get_plot_analyze,
).servable()

Hi Marc! I think your solution solved most of what I was looking for. Thanks a lot! :slight_smile: The tricky logic I am going after is to create an option (maybe a checkbox) to choose if I want to overlay all the filtered signals or to append them one below another.

Is there a way that I can choose what color I want the individual plot to be in based on my selection in a widget?

I am not sure if I am trying to do the right way. I would like to have an option to choose in the checkbox “overlay” if I want to plot all the selected signals in one plot as an overlay or do the default if unselected in the checkbox.

import hvplot.pandas
import numpy as np
import pandas as pd
import panel as pn

pn.extension(sizing_mode="stretch_width")

data1 = pd.DataFrame(
    {"C": np.random.randint(low=1, high=100, size=10), "D": np.random.normal(0.0, 1.0, size=10)}
)
data2 = pd.DataFrame(
    {"C": np.random.randint(low=1, high=300, size=10), "D": np.random.normal(0.0, 2.0, size=10)}
)

trace_datasets = {"data_id1": data1, "dataid2": data2}


cols_X = list(data1.columns)
cols_Y = list(data1.columns)

widget_x = pn.widgets.Select(name="x", options=cols_X)

cross_selector = pn.widgets.CrossSelector(name="y", options=cols_Y)

plot_type = pn.widgets.Select(name="Plot Type", value="line", options=["line", "scatter"])

dataset_selector = pn.widgets.Select(name="Select Run Data", options=list(trace_datasets.keys()))

frame_slider = pn.widgets.IntSlider(name="Time", value=0, start=20, end=150)

groupby_widget = pn.widgets.MultiSelect(name="groupby", options=cols_Y, size=10)

by_widget = pn.widgets.MultiSelect(name="by", options=cols_Y, size=10)

overlay_widget = pn.widgets.CheckBoxGroup(name='Overlay', options=["overlay"])

@pn.depends(widget_x, cross_selector, plot_type, dataset_selector, frame_slider, groupby_widget, by_widget)
def get_plot_analyze(cols_X, cols_Y, plot_type="line", 
                     dataset_selector=dataset_selector.value, frame_slider=frame_slider.param.value, groupby_widget= groupby_widget.value, by_widget=by_widget.value):
    
    if not overlay_widget.value=="overlay":
        container2 = pn.Column()
        for selections in cross_selector.value:
            if plot_type == "line":
                if cols_Y:

                    line_plot = container2.append(
                        trace_datasets[dataset_selector].hvplot.line(x=cols_X, y=selections, grid=True)*hv.VLine(frame_slider).opts(color="red")
                    )

                else:
                    line_plot = container2.append(hv.Curve([]))

            elif plot_type == "scatter":
                scatter_plot = container2.append(
                    trace_datasets[dataset_selector].hvplot.scatter(x=cols_X, y=selections, grid=True, groupby=groupby_widget,  by=by_widget)
                )
        if len(container2)>0:
            return container2
        return "No options selected"
    else:
        
        line_plot = trace_dict[dataset_selector].hvplot.line(x=cols_X, y=cross_selector.value, width=1000)
        overlay_widget.jslink(line_plot)
        
    return line_plot
#plots_pane=pn.pane.HoloViews(get_plot_analyze(cols_X, cols_Y, plot_type="line", dataset_selector=dataset_selector.value,frame=0))  


pn.Column(
    pn.WidgetBox(
        "Controls",
        dataset_selector,
        widget_x,
        cross_selector,
        pn.Row(groupby_widget,by_widget),
        plot_type,
        overlay_widget,
        frame_slider,
        width=1000,
    ),
    get_plot_analyze,
).servable()

Hi @SVP1194

I think you should depend on the overlay_widget.

@pn.depends(widget_x, cross_selector, plot_type, dataset_selector, frame_slider, groupby_widget, by_widget, overlay_widget)
def get_plot_analyze...

The full code I could not get running because trace_dict is not defined is

import hvplot.pandas
import numpy as np
import pandas as pd
import panel as pn
import holoviews as hv

pn.extension(sizing_mode="stretch_width")

data1 = pd.DataFrame(
    {"C": np.random.randint(low=1, high=100, size=10), "D": np.random.normal(0.0, 1.0, size=10)}
)
data2 = pd.DataFrame(
    {"C": np.random.randint(low=1, high=300, size=10), "D": np.random.normal(0.0, 2.0, size=10)}
)

trace_datasets = {"data_id1": data1, "dataid2": data2}


cols_X = list(data1.columns)
cols_Y = list(data1.columns)

widget_x = pn.widgets.Select(name="x", options=cols_X)

cross_selector = pn.widgets.CrossSelector(name="y", options=cols_Y)

plot_type = pn.widgets.Select(name="Plot Type", value="line", options=["line", "scatter"])

dataset_selector = pn.widgets.Select(name="Select Run Data", options=list(trace_datasets.keys()))

frame_slider = pn.widgets.IntSlider(name="Time", value=0, start=20, end=150)

groupby_widget = pn.widgets.MultiSelect(name="groupby", options=cols_Y, size=10)

by_widget = pn.widgets.MultiSelect(name="by", options=cols_Y, size=10)

overlay_widget = pn.widgets.CheckBoxGroup(name='Overlay', options=["overlay"])

@pn.depends(widget_x, cross_selector, plot_type, dataset_selector, frame_slider, groupby_widget, by_widget, overlay_widget)
def get_plot_analyze(cols_X, cols_Y, plot_type="line", 
                     dataset_selector=dataset_selector.value, frame_slider=frame_slider.param.value, groupby_widget= groupby_widget.value, by_widget=by_widget.value, overlay=overlay_widget.value):
    
    if not overlay:
        container2 = pn.Column()
        for selections in cross_selector.value:
            if plot_type == "line":
                if cols_Y:

                    line_plot = container2.append(
                        trace_datasets[dataset_selector].hvplot.line(x=cols_X, y=selections, grid=True)*hv.VLine(frame_slider).opts(color="red")
                    )

                else:
                    line_plot = container2.append(hv.Curve([]))

            elif plot_type == "scatter":
                scatter_plot = container2.append(
                    trace_datasets[dataset_selector].hvplot.scatter(x=cols_X, y=selections, grid=True, groupby=groupby_widget,  by=by_widget)
                )
        if len(container2)>0:
            return container2
        return "No options selected"
    else:
        
        line_plot = trace_dict[dataset_selector].hvplot.line(x=cols_X, y=cross_selector.value, width=1000)
        overlay_widget.jslink(line_plot)
        
    return line_plot
#plots_pane=pn.pane.HoloViews(get_plot_analyze(cols_X, cols_Y, plot_type="line", dataset_selector=dataset_selector.value,frame=0))  


pn.Column(
    pn.WidgetBox(
        "Controls",
        dataset_selector,
        widget_x,
        cross_selector,
        pn.Row(groupby_widget,by_widget),
        plot_type,
        overlay_widget,
        frame_slider,
        width=1000,
    ),
    get_plot_analyze,
).servable()

Hi @Marc, My bad! Fixed it now :slight_smile: but I still find it tricky to fix the overlay option. What happens after I add a depend on overlay_widget is that I do not get any plot when I check the box and when I uncheck the box I get whatever I have filtered in the cross selector in a grid plot style like before

import hvplot.pandas
import numpy as np
import pandas as pd
import panel as pn
import holoviews as hv

pn.extension(sizing_mode="stretch_width")

data1 = pd.DataFrame(
    {"C": np.random.randint(low=1, high=100, size=10), "D": np.random.normal(0.0, 1.0, size=10)}
)
data2 = pd.DataFrame(
    {"C": np.random.randint(low=1, high=300, size=10), "D": np.random.normal(0.0, 2.0, size=10)}
)

trace_datasets = {"data_id1": data1, "dataid2": data2}


cols_X = list(data1.columns)
cols_Y = list(data1.columns)

widget_x = pn.widgets.Select(name="x", options=cols_X)

cross_selector = pn.widgets.CrossSelector(name="y", options=cols_Y)

plot_type = pn.widgets.Select(name="Plot Type", value="line", options=["line", "scatter"])

dataset_selector = pn.widgets.Select(name="Select Run Data", options=list(trace_datasets.keys()))

frame_slider = pn.widgets.IntSlider(name="Time", value=0, start=20, end=150)

groupby_widget = pn.widgets.MultiSelect(name="groupby", options=cols_Y, size=10)

by_widget = pn.widgets.MultiSelect(name="by", options=cols_Y, size=10)

overlay_widget = pn.widgets.CheckBoxGroup(name='Overlay', options=["overlay"])

@pn.depends(widget_x, cross_selector, plot_type, dataset_selector, frame_slider, groupby_widget, by_widget, overlay_widget)
def get_plot_analyze(cols_X, cols_Y, plot_type="line", 
                     dataset_selector=dataset_selector.value, frame_slider=frame_slider.param.value, groupby_widget= groupby_widget.value, by_widget=by_widget.value, overlay=overlay_widget.value):
    
    if not overlay:
        container2 = pn.Column()
        for selections in cross_selector.value:
            if plot_type == "line":
                if cols_Y:

                    line_plot = container2.append(
                        trace_datasets[dataset_selector].hvplot.line(x=cols_X, y=selections, grid=True)*hv.VLine(frame_slider).opts(color="red")
                    )

                else:
                    line_plot = container2.append(hv.Curve([]))

            elif plot_type == "scatter":
                scatter_plot = container2.append(
                    trace_datasets[dataset_selector].hvplot.scatter(x=cols_X, y=selections, grid=True, groupby=groupby_widget,  by=by_widget)
                )
        if len(container2)>0:
            return container2
        return "No options selected"
    else:
        
        line_plot =    trace_datasets[dataset_selector].hvplot.scatter(x=cols_X, y=selections, grid=True, groupby=groupby_widget,  by=by_widget)
        overlay_widget.jslink(line_plot)
        
    return line_plot
#plots_pane=pn.pane.HoloViews(get_plot_analyze(cols_X, cols_Y, plot_type="line", dataset_selector=dataset_selector.value,frame=0))  


pn.Column(
    pn.WidgetBox(
        "Controls",
        dataset_selector,
        widget_x,
        cross_selector,
        pn.Row(groupby_widget,by_widget),
        plot_type,
        overlay_widget,
        frame_slider,
        width=1000,
    ),
    get_plot_analyze,
).servable()