Bring Drag & Drop to Dash with Dashboard Engine. 💫 Learn how at our next webinar!

Linking and brushing between parallel coordinate and scatterplot

Hi guys,

I am working on a solution to link the parallel coordinate and scatterplot together. For example, brush the points in scatter and the points are highlighted accordingly in parallel coordinate.

I am trying to use the framework below:

import plotly.express as px
df = px.data.iris()
figScatter = px.scatter(df, x="sepal_width", y="sepal_length", color="species", marginal_y="rug", marginal_x="histogram")
figParallel = px.parallel_coordinates(df, color="species_id", labels={"species_id": "Species",
                  "sepal_width": "Sepal Width", "sepal_length": "Sepal Length",
                  "petal_width": "Petal Width", "petal_length": "Petal Length", },
                    color_continuous_scale=px.colors.diverging.Tealrose, color_continuous_midpoint=2)

from ipywidgets import widgets
fig1 = go.FigureWidget(figParallel)
fig2 = go.FigureWidget(figScatter)

def update_highlight(trace, points, selector):
    print("highlight")
fig1.data[0].on_selection(update_highlight)

My problem is that the callback events for parallel coordinate doesnt trigger while interacting the parallel coordinate, I am wondering which callback function I should use for parallel coordinate?

I have tried fig1.data[0].on_selection and fig1.data[0].on_click, but I can not see the “hightlight” output.

Thanks a lot

anyone has ideas about this issue? thanks

Did you ever find a solution. I am trying to figure out how to get a list of the selected lines/rows from the PC graph.

1 Like

Hi @Fantasyfans2012, @phylroy,

this is a way to make it work, at least from parallel coordinates to scatter (not bidirectional):

import plotly.express as px
import plotly.graph_objects as go
import ipywidgets as widgets
import numpy as np

df = px.data.iris()
figScatter_inputs = dict(x="sepal_width", y="sepal_length", color="species", marginal_y="rug", marginal_x="histogram") 
figScatter = px.scatter(df, **figScatter_inputs)
labels = {"species_id": "Species",
    "sepal_width": "Sepal Width", "sepal_length": "Sepal Length",
    "petal_width": "Petal Width", "petal_length": "Petal Length"}
labelsrev = {v:k for k,v in labels.items()}
figParallel = px.parallel_coordinates(df, color="species_id", labels=labels,
    color_continuous_scale=px.colors.diverging.Tealrose, color_continuous_midpoint=2)

fig1 = go.FigureWidget(figParallel)
fig2 = go.FigureWidget(figScatter)

def update_highlight(dimension, constraintrange):
    df_filt = df.copy()
    for d in fig1.data[0].dimensions:
        if d.constraintrange is not None:
            crs = np.array(d.constraintrange)
            if crs.ndim==1:
                crs  = [crs]
            masks=[]
            for cr in crs:
                key = labelsrev[d.label]
                masks.append(df_filt[key].between(*cr))
            df_filt = df_filt[np.logical_or.reduce(masks)]
    fig2.data = []
    if df_filt.index.empty:
        fig2.layout.annotations = [dict(text='filtering results in empty dataframe', showarrow=False)]
    else:
        fig2.layout.annotations = []
        pxfig = px.scatter(df_filt, **figScatter_inputs)
        fig2.add_traces(pxfig.data)
        fig2.layout = pxfig.layout
    
for d in fig1.data[0].dimensions:
    d.on_change(update_highlight, 'constraintrange')
display(widgets.HBox([fig1, fig2]))

hope it helps, Alex-

This works perfect! I was able to use this logic to add a table view using the same methodology. Thanks a lot! You made my day!