dash-app-gallery icon indicating copy to clipboard operation
dash-app-gallery copied to clipboard

Adding Parallel Coordinates app

Open Coding-with-Adam opened this issue 3 years ago • 4 comments

Hi @IcToxi , That parellel coordinates app you proposed is really good. Thanks for sharing. Can you please use the final app code below that I modified to create a pull request and add that app to the gallery.

I would prefer being more explicit with constraintrange so it's easier for beginners to understand what's happening. Thus, instead of:

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

I modified it to:

    fig.update_traces(dimensions=list([
        dict(constraintrange=[row[0] - row[0] / 100000, row[0]]),
        dict(constraintrange=[row[1] - row[1] / 100000, row[1]]),
        dict(constraintrange=[row[2] - row[2] / 100000, row[2]]),
        dict(constraintrange=[row[3] - row[3] / 100000, row[3]]),
        dict(constraintrange=[row[4] - row[4] / 100000, row[4]])
    ]))

Final app code:

from dash import Dash, html, dcc, dash_table, 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 := dash_table.DataTable(
            df.to_dict("records"),
            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()
    )

    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,
    )
    fig.update_traces(dimensions=list([
        dict(constraintrange=[row[0] - row[0] / 100000, row[0]]),
        dict(constraintrange=[row[1] - row[1] / 100000, row[1]]),
        dict(constraintrange=[row[2] - row[2] / 100000, row[2]]),
        dict(constraintrange=[row[3] - row[3] / 100000, row[3]]),
        dict(constraintrange=[row[4] - row[4] / 100000, row[4]])
    ]))

    return fig


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

Coding-with-Adam avatar May 07 '22 16:05 Coding-with-Adam

Actually, I don't think this is more clear. How about adding a comment to the code to say why this is needed? And/or a link to the docs for more info?

fig.update_traces(dimensions=list([
        dict(constraintrange=[row[0] - row[0] / 100000, row[0]]),
        dict(constraintrange=[row[1] - row[1] / 100000, row[1]]),
        dict(constraintrange=[row[2] - row[2] / 100000, row[2]]),
        dict(constraintrange=[row[3] - row[3] / 100000, row[3]]),
        dict(constraintrange=[row[4] - row[4] / 100000, row[4]])
    ]))

AnnMarieW avatar May 07 '22 16:05 AnnMarieW

Good point. Maybe something like this:

from dash import Dash, html, dcc, dash_table, 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 := dash_table.DataTable(
            df.to_dict("records"),
            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()
    )

    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,
    )

# Use `constraintrange` to filter a certain trace. Read more in docs: 
# https://plotly.com/python/reference/parcoords/#parcoords-dimensions

    fig.update_traces(dimensions=list([
        dict(constraintrange=[row[0] - row[0] / 100000, row[0]]),
        dict(constraintrange=[row[1] - row[1] / 100000, row[1]]),
        dict(constraintrange=[row[2] - row[2] / 100000, row[2]]),
        dict(constraintrange=[row[3] - row[3] / 100000, row[3]]),
        dict(constraintrange=[row[4] - row[4] / 100000, row[4]])
    ]))

    return fig


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

Coding-with-Adam avatar May 07 '22 17:05 Coding-with-Adam

The thing is, the documentation tells me that there can be <= but it's actually <. I also don't think it's clear here, so that's why I discuss it first.

IcToxi avatar May 07 '22 17:05 IcToxi

Yes, I think you're right. It has to be <.

Coding-with-Adam avatar May 07 '22 17:05 Coding-with-Adam

Closing this since it's related to the PR that was closed.

Coding-with-Adam avatar May 31 '24 18:05 Coding-with-Adam