Parallel coordinates Plot: Selecting just one line

Hi, I’ve created a Parallel Coordinates Plot, and was wondering if it is possible to highlight/brush just one line in the data by selecting the value from a table.

My table is connected to the data that is shown on my PCP, and whenever I click the table I get the value of the row back, I wish to highlight this row in the PCP, is it possible?

FYI

Hi! Thanks for the suggestion, I don’t see how it’s the same that I am doing.

I was hoping it was possible to select a row from the table and then color that line (or all other lines) to highlight that particular row on the PCP.

Just reverse the direction of the callback.
fine. @celia is on the road.

Hi again,
I can’t make the same solution work for me.

The input and format I get from clicking a row is:
{ 'row': 0, 'petal_length': 4.7, 'petal_width': 1.2, 'sepal_length': 6.1, 'sepal_width': 2.8}

So I use this input in my update_parallel_coordinates_plot:

@app.callback(
    Output("parallel-coordinates-plot", "figure"),
    [Input("data_set", "data"),
     Input("get_class_dropdown", "value"),
     Input("row", "data")],
    )
def update_parallel_coordinates_plot(df, class_input, clicked_cell):
    """
    Callback that updates the parallel coordinates plot
    :param df:           dataframe
    :param class_input:  class input
    :param clicked_cell: clicked row from table
    :return:             figure
    """
    df_shap = pd.DataFrame(df).copy()

    ylimits = get_limits(subset_columns(df_shap))
    df = get_df_class(df_shap, class_input)

    fig = go.Figure(data=go.Parcoords(
        line=dict(color=df['y_pred']),
        dimensions=list(get_dimensions(subset_columns(df), ylimits))))

    fig.update_traces(labelangle=45)

    # If clicked:
        # then update the figure giving that one point a particular color

    return fig

But I simply cannot figure out how to change the color of that particular row. I was thinking of adding the state of the figure, and using the:

'line': {'color': [1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}, 'type': 'parcoords'}],

and select the item in the array corresponding to the row value and change the color that way. But I have not had success with that approach yet.

Also, I’m not sure what this means? :smiley:

@celia is on the road.

:point_up:She always has the perfect solution.

from dash import Dash, html, dcc
from dash.dash_table import DataTable
from dash.dependencies import Output, Input, State
from dash.exceptions import PreventUpdate
import plotly.express as px

df = px.data.iris()
fig = 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)

app = Dash(__name__)

app.layout = html.Div([
    my_graph := dcc.Graph(figure=fig), my_table :=
    DataTable(df.to_dict('records'), [{
        "name": i,
        "id": i
    } for i in df.columns],
              row_selectable='single')
])


@app.callback(Output(my_graph, 'figure'), Input(my_table, 'selected_rows'),
              State(my_graph, 'figure'))
def pick(r, f):
    if r is None:
        raise PreventUpdate

    row = df[[
        "sepal_length", "sepal_width", "petal_length", "petal_width",
        "species_id"
    ]].loc[r[0]].to_list()

    for i, v in enumerate(f.get('data')[0].get('dimensions')):
        v.update({'constraintrange': [row[i] - row[i] / 100000, row[i]]})

    return f


if __name__ == "__main__":
    app.run_server(debug=True)
from dash import Dash, html, dcc
from dash.dash_table import DataTable
from dash.dependencies import Output, Input, State
from dash.exceptions import PreventUpdate
import plotly.express as px

df = px.data.iris()

app = Dash(__name__)

app.layout = html.Div([
    my_graph := html.Div(), my_table := DataTable(df.to_dict('records'),
                                                  [{
                                                      "name": i,
                                                      "id": i
                                                  } for i in df.columns],
                                                  row_selectable='single')
])


@app.callback(Output(my_graph, 'children'), Input(my_table, 'selected_rows'))
def pick(r):

    labels = {
        "species_id": "Species",
        "sepal_width": "Sepal Width",
        "sepal_length": "Sepal Length",
        "petal_width": "Petal Width",
        "petal_length": "Petal Length",
    }

    fig = px.parallel_coordinates(
        df,
        color="species_id",
        labels=labels,
        color_continuous_scale=px.colors.diverging.Tealrose,
        color_continuous_midpoint=2)

    if r is not None:
        row = df[[
            "sepal_length", "sepal_width", "petal_length", "petal_width",
            "species_id"
        ]].loc[r[0]]
        fig.update_traces(dimensions=list([
            dict(constraintrange=[j - j / 100000, j],
                 label=labels.get(i),
                 values=df[i]) for i, j in row.to_dict().items()
        ]),
                          selector=dict(type='parcoords'))

    return dcc.Graph(figure=fig)


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

OMG, the docs has told me <=

The domain range to which the filter on the dimension is constrained. Must be an array of [fromValue, toValue] with fromValue <= toValue, or if multiselect is not disabled, you may give an array of arrays, where each inner array is [fromValue, toValue].

1 Like

Thanks for the suggestion! Tried running your example but getting this error:

dash.exceptions.IncorrectTypeException: component_id must be a string or dict, found Graph(figure=Figure({
‘data’: [{‘dimensions’: [{‘label’: ‘Sepal Length’,…

Ah, is your python version over 3.8? And Dash over 2.3?

Yarp, Python 3.9 but dash==2.0.0. :thinking:

@stu Got your example working - thanks a ton!
Here’s how I just tweaked it for the different version:

from dash import Dash, html, dcc, dash_table
from dash.dependencies import Output, Input, State
from dash.exceptions import PreventUpdate
import plotly.express as px
import dash_bootstrap_components as dbc


df = px.data.iris()
fig = 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)

app = Dash(__name__)

app.layout = html.Div([
    dbc.Row(dcc.Graph(id="my_graph", figure=fig)),
    html.Div(dash_table.DataTable(data=df.to_dict('records'), row_selectable='single',
                                  columns=[{"name": i, "id": i} for i in df.columns],
                                  id="my_table")),
])


@app.callback(Output("my_graph", 'figure'),
              Input("my_table", 'selected_rows'),
              [State("my_graph", 'figure')])
def pick(r, f):
    print(r)
    print(f)
    if r is None:
        raise PreventUpdate

    row = df[[
        "sepal_length", "sepal_width", "petal_length", "petal_width",
        "species_id"
    ]].loc[r[0]].to_list()

    for i, v in enumerate(f.get('data')[0].get('dimensions')):
        v.update({'constraintrange': [row[i] - row[i] / 100000, row[i]]})

    return f


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

Now I will try and apply it to my other project. Cheers again!

1 Like

You can also try changing it to multi-select.

1 Like

Aye, good idea! I will try that next.

Also if I want to increase the thickness of the line, what can I add to the update to do so? :slight_smile:

I don’t know. Maybe you could find something out in this below.

Thanks @stu, it seems like thickness is for another time, there seems not to be anything for PCPs yet.

Could I ask you regarding highlighting several lines? I’ve changed my table to multi-select, and I’ve taken a look at the constraintrange from the docs where it says:

And I have been attempting to apply this to my code, but it just seems to select the next line (so going from line A to line B, instead of highlighting both line A and line B).

So I thought this change would be sufficient:

    if clicked_row is not None and clicked_row != []:
        rows = []
        save_constraints = []
        prev_len = len(clicked_row) - 1
        current_len = len(clicked_row)

        if prev_len < current_len:
            for i in clicked_row:
                rows.append(subset_columns(df).loc[clicked_row[prev_len ]].to_list())

            for j, val in enumerate(rows):
                for i, v in enumerate(fig['data'][0]['dimensions']):
                    save_constraints.append([rows[j][i] - rows[j][i] / 100000, rows[j][i]])

            for k, val in enumerate(fig['data'][0]['dimensions']):
                val.update({'constraintrange': [save_constraints[k][0], save_constraints[k][1]]})

But, unfortunately not, its simply changes the highlighted constraints. Any clue how to implements multi-constraints?

You may consider taking a detailed look at the fig object returned by the front end.