Update Plotly parallel coordinates plot in Panel?

Hello!

I’m trying to make a Plotly parallel coordinates plot in Panel, such that the user can interactively select which coordinates (aka dimensions) to display using a check box group.

Below is an example of what I’m attempting to do. The user should be able to select two (or more) of coordinates ‘A’, ‘B’, ‘C’, and ‘D’, and the parallel coordinates plot should update to display only the selected coordinates.

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

def make_plot(plotdf, a_start, a_end, cols):
    
    # create list of all possible dimensions for parallel coords plot
    all_dimensions = list([
            dict(constraintrange = [a_start, a_end],
                 label = 'A', values = plotdf['var 1'].values),
            dict(label = 'B', values = plotdf['var 2'].values),
            dict(label = 'C', values = plotdf['var 3'].values),
            dict(label = 'D', values = plotdf['var 3'].values)
            ])
    
    # create a list of user-selected dimensions for parallel coords plot
    selected_dimensions = []
    for i, dim in enumerate(all_dimensions):
        if dim['label'] in cols:
            selected_dimensions.append(all_dimensions[i])
    
    fig = go.Figure(data=
    go.Parcoords(
        line_color='blue',
        dimensions=selected_dimensions
        )
    )
       
    return fig


# data
d = {'var 1': [42, 17, 33, 49, 51, 72], 
     'var 2': [9, 17, 32, 43, 22, 12], 
     'var 3': [3, 12, 10, 4, 52, 42], 
     'var 4': [31, 1, 13, 42, 12, 32]}

df = pd.DataFrame(data=d) 

# widgets
pn.extension('plotly')
a_start_widget = pn.widgets.IntSlider(name='A start', start=0, end=100, step=1, value=30)
a_end_widget = pn.widgets.IntSlider(name='A end', start=0, end=100, step=1, value=50)
columns_widget = pn.widgets.CheckBoxGroup(name='columns', value=['A', 'C'], options=['A', 'B', 'C', 'D'])

# plot
interactive = pn.bind(make_plot, plotdf=df, a_start=a_start_widget, a_end=a_end_widget, cols=columns_widget)
plot = pn.Column(a_start_widget, a_end_widget, columns_widget, interactive)
plot

It sort of works. It starts off with ‘A’ and ‘C’ selected. If you select ‘B’, it will add ‘B’ to the plot. Good so far… However, unselecting ‘B’ does not remove it from the plot, unless you then select ‘D’, and vice versa.

And, after selecting all 4 coordinates (‘A’, ‘B’, ‘C’, and ‘D’), deselecting any of them does not remove them from the plot.

Finally, after selecting all 4 coordinates and deselecting any of them, the sliders for ‘A start’ and ‘A end’ stop working until you select all 4 check boxes again.

I am new to Panel and Plotly, so I’m sure I’m doing something wrong… Any advice would be greatly appreciated!

2 Likes

UPDATE: I think I figured it out. It works if I pass a list to the ‘values’ attribute of ‘dimensions’ instead of a numpy array.

To make it work, change these original lines:

# create list of all possible dimensions for parallel coords plot
all_dimensions = list([
        dict(constraintrange = [a_start, a_end],
             label = 'A', values = plotdf['var 1'].values),
        dict(label = 'B', values = plotdf['var 2'].values),
        dict(label = 'C', values = plotdf['var 3'].values),
        dict(label = 'D', values = plotdf['var 3'].values)
        ])

To this (i.e., pass lists to ‘values’ instead of numpy arrays):

# create list of all possible dimensions for parallel coords plot
all_dimensions = list([
        dict(constraintrange = [a_start, a_end],
             label = 'A', values = list(plotdf['var 1'])),
        dict(label = 'B', values = list(plotdf['var 2'])),
        dict(label = 'C', values = list(plotdf['var 3'])),
        dict(label = 'D', values = list(plotdf['var 4']))   # fixed typo by changing 'var 3' to 'var 4'
        ])

This seems strange to me because, from Plotly’s perspective, ‘values’ can accept a list, numpy array, Pandas series, and some other data types as well: https://plotly.com/python/reference/parcoords/#parcoords-dimensions-items-dimension-values

Does anyone know why passing a numpy array to ‘values’ instead of a list makes the Panel plot buggy? It’s also buggy if a Panda series is passed to ‘value’, even though that’s also valid Plotly code.

1 Like

Try this code, works perfect with me

import pandas as pd
import panel as pn
import plotly.graph_objects as go
import param

class PlotlyGraph(param.Parameterized):

    slider1 = param.Integer(default=30, bounds=(0, 100), label="A start")
    slider2 = param.Integer(default=50, bounds=(0, 100), label="A end")
    checkbox = param.ListSelector(
        default=['A', 'C'], objects=['A', 'B', 'C', 'D'], label="columns")

    def __init__(self, **params):
        super().__init__(**params)

        d = {'var 1': [42, 17, 33, 49, 51, 72],
             'var 2': [9, 17, 32, 43, 22, 12],
             'var 3': [3, 12, 10, 4, 52, 42],
             'var 4': [31, 1, 13, 42, 12, 32]}

        self.df = pd.DataFrame(data=d)

    @param.depends('slider1', 'slider2', 'checkbox')
    def make_plot(self):

        all_dimensions = list([

            dict(constraintrange=[self.slider1, self.slider2],
                 label='A', values=self.df['var 1'].values),
            dict(label='B', values=self.df['var 2'].values),
            dict(label='C', values=self.df['var 3'].values),
            dict(label='D', values=self.df['var 3'].values)
        ])

        selected_dimensions = []

        for i, dim in enumerate(all_dimensions):
            if dim['label'] in self.checkbox:
                selected_dimensions.append(all_dimensions[i])

        fig = go.Figure(data=go.Parcoords(
            line_color='blue',
            dimensions=selected_dimensions
        )
        )
        return pn.pane.Plotly(fig, config={"modeBarButtonsToRemove": ["sendDataToCloud"],
                                           "displaylogo": False})

def main():
    pn.extension('plotly')
    plotlygraph = PlotlyGraph()
    plot = pn.Column(plotlygraph.param.slider1,
                     plotlygraph.param.slider2,
                     pn.Param(plotlygraph.param.checkbox, widgets={
                              'checkbox': pn.widgets.CheckBoxGroup}),
                     plotlygraph.make_plot)
    return plot

pn.serve(
    {
        'PlotlyGraph': main
    }
)

to run the code, type in the terminal: python [name of this file].py

1 Like