Filtering a datatable with parallel coordinates

Hello! I would like to build an app that filters a datatable based on the coordinates chosen by the user. Any tips on how to capture the event of different coordinates being chosen by user as shown in the gif below? We have tried dcc.Graph’s clickData and the figure property as the Input of the callback but nothing comes up.

pl

Example code:

import dash_bootstrap_components as dbc
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
import pandas as pd

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
df = px.data.iris()
fig = px.parallel_coordinates(df, color="species_id",
                              dimensions=['sepal_width', 'sepal_length', 'petal_width',
                                          'petal_length'],
                              color_continuous_scale=px.colors.diverging.Tealrose,
                              color_continuous_midpoint=2)

app.layout = dbc.Container([
        dcc.Graph(id="my-graph", figure=fig),
        html.Div(id='placeholder')
])

@app.callback(
    Output('placeholder', 'children'),
    Input("my-graph", "figure")
)
def udpate_table(figure):
    print(figure['data'][0])
    return ""

if __name__ == "__main__":
    app.run_server(debug=True, port=4003)

you can use

from dash import ctx

in the callback

print(ctx.triggered_id)

to evaluate what are the various properties that are getting adjusted on our graph. could be more than the click property

then, generically, you can build an intermediate via dcc.store

and have your datatable receive filter information from the dcc.store

hi @rictuar

Thank you for the suggestion. I was also trying that but to no avail.

@app.callback(
    Output('placeholder', 'children'),
    Input("my-graph", "figure")
)
def udpate_table(c):
    print(ctx.triggered_id)
    print(c)
    return ""

Any other ideas on how to capture that event displayed in the gif?

Hello @adamschroeder/@jennalenoble,

You guys are looking for the restyleData prop.

Here is a working example. :slight_smile:

import dash_bootstrap_components as dbc
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
import pandas as pd

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
dims = ['sepal_width', 'sepal_length', 'petal_width', 'petal_length']
df = px.data.iris()
fig = px.parallel_coordinates(df, color="species_id",
                              dimensions=dims,
                              color_continuous_scale=px.colors.diverging.Tealrose,
                              color_continuous_midpoint=2)

app.layout = dbc.Container([
        dcc.Graph(id="my-graph", figure=fig),
        html.Div(id='placeholder')
])

@app.callback(
    Output('placeholder', 'children'),
    Input("my-graph", "restyleData")
)
def udpate_table(data):
    if data:
        key = list(data[0].keys())[0]
        col = dims[int(key.split('[')[1].split(']')[0])]
        rng = data[0][key][0]
        if isinstance(rng[0], list):
            # if multiple choices combine df
            dff = pd.DataFrame(columns=df.columns)
            for i in rng:
                dff2 = df[df[col].between(i[0], i[1])]
                dff = pd.concat([dff,dff2])
        else:
            # if one choice
            dff = df[df[col].between(rng[0], rng[1])]
        print(dff)
    return ""

if __name__ == "__main__":
    app.run_server(debug=True, port=4003)

edited for supporting multiple choices on the same section, as well as allowing migrating to different columns.

Now… to have the data table showing properly. I’m sure you can figure out something. :wink:

1 Like

Ok… so, I just wanted to figure this out.

Here is the result:

import dash_bootstrap_components as dbc
from dash import Dash, dcc, html, Input, Output, dash_table, State, Patch
import plotly.express as px
import pandas as pd

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
dims = ['sepal_width', 'sepal_length', 'petal_width', 'petal_length']
df = px.data.iris()
fig = px.parallel_coordinates(df, color="species_id",
                              dimensions=dims,
                              color_continuous_scale=px.colors.diverging.Tealrose,
                              color_continuous_midpoint=2)

app.layout = dbc.Container([
        dcc.Graph(id="my-graph", figure=fig),
        dash_table.DataTable(id='table', columns=[{'id': i, 'name': i} for i in df.columns]),
        dcc.Store(id='activefilters', data={})
])

@app.callback(
    Output('table', 'data'),
    Input("activefilters", "data")
)
def udpate_table(data):
    if data:
        dff = df.copy()
        for col in data:
            if data[col]:
                rng = data[col][0]
                if isinstance(rng[0], list):
                    # if multiple choices combine df
                    dff3 = pd.DataFrame(columns=df.columns)
                    for i in rng:
                        dff2 = dff[dff[col].between(i[0], i[1])]
                        dff3 = pd.concat([dff3, dff2])
                    dff = dff3
                else:
                    # if one choice
                    dff = dff[dff[col].between(rng[0], rng[1])]
        return dff.to_dict('records')
    return df.to_dict('records')

@app.callback(
    Output('activefilters', 'data'),
    Input("my-graph", "restyleData")
)
def updateFilters(data):
    if data:
        key = list(data[0].keys())[0]
        col = dims[int(key.split('[')[1].split(']')[0])]
        newData = Patch()
        newData[col] = data[0][key]
        return newData
    return {}

if __name__ == "__main__":
    app.run_server(debug=True, port=4003)

This builds the filters as you go along, this is because the restyleData doesnt have all of the options, but what currently changed.

4 Likes

Thank you so much Bryan :smiley:

@jennalenoble Let me know if you need help with the implementation.

2 Likes

@jinnyzor wow that’s excellent, thank you so much!

2 Likes