Plotly Dash Lists of lists error when combining two sets of paths

I am trying to combine two sets of nodes(ancestors and descendants) into one to draw a graph in dash. When

I Try to do this, I receive an error about a lists of lists, but the data appears to be in the correct format, which is a list of lists, nodes ahead and nodes behind of the source node

Here is the complete code

import plotly.graph_objects as go
import networkx as nx
import dash
from dash import Dash, html, dcc, Input, Output, State

import pandas as pd


# Controls for how the graph is drawn
nodeColor = 'Pink'
nodeSize = 20
lineWidth = 2
lineColor = '#000000'

df_dict = {'From': {0: 'A', 1: 'B', 2: 'C', 3: 'A', 4: 'B', 5: 'C'},
        'To': {0: 'B', 1: 'C', 2: 'D', 3: 'A1', 4: 'B1', 5: 'C1'},
        'weight': {0: 1.0, 1: 1.0, 2: 1.0, 3: 0.5, 4: 0.5, 5: 0.5}, 'Cost': {0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 5: 2}}
df = pd.DataFrame.from_dict(df_dict)


def builddf(df):
    G = nx.from_pandas_edgelist(
        df, "From", 'To', ['weight', 'Cost'], create_using=nx.DiGraph())
    return G


def buildGraph(G):
    pos = nx.layout.spring_layout(G)
    for node in G.nodes:
        G.nodes[node]['pos'] = list(pos[node])

    # Make list of nodes for plotly
    node_x = []
    node_y = []
    text = []
    for node in G.nodes():
        x, y = G.nodes[node]['pos']
        node_x.append(x)
        node_y.append(y)
        text.append(node)

    edge_x = []
    edge_y = []
    for edge in G.edges():
        start = G.nodes[edge[0]]['pos']
        end = G.nodes[edge[1]]['pos']
        edge_x, edge_y = addEdge(
            start, end, edge_x, edge_y, .8, 'end', .04, 30, nodeSize)

    edge_trace = go.Scatter(x=edge_x, y=edge_y, line=dict(width=lineWidth, color=lineColor), hoverinfo='none', mode='lines',
                            text=text)

    node_trace = go.Scatter(x=node_x, y=node_y, text=text, mode='markers+text', hoverinfo='text',
                            marker=dict(showscale=False, color=nodeColor, size=nodeSize))

    fig = go.Figure(data=[edge_trace, node_trace],
                    layout=go.Layout(
                        showlegend=False,
                        hovermode='closest',
                        margin=dict(b=20, l=5, r=5, t=40),
                        xaxis=dict(showgrid=False, zeroline=False,
                                showticklabels=False),
                        yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))
                    )

    fig.update_layout(yaxis=dict(scaleanchor="x", scaleratio=1),
                    plot_bgcolor='rgb(255,255,255)')
    return fig


app = dash.Dash()
app.layout = html.Div([
    html.Div([
        html.H1(children='Hello Dash'),

        html.Div(children='''
        Directed Water and Orange Juice Graph.
        '''),
        html.Div(children=[

            dcc.Dropdown([{'label': 'Connected', 'value': 'con'}, {'label': 'Ascending', 'value': 'asc'},
                        {'label': 'Descending', 'value': 'desc'}], '', id='typ-dropdown'),
            html.Div(id='dd-typ-container'),
            html.Div(html.Br()),
            dcc.Dropdown(
                [{'label': 'Node A', 'value': 'A'}, {'label': 'Node B', 'value': 'B'}, {'label': 'Node C', 'value': 'C'},
                {'label': 'Node D', 'value': 'D'}, {'label': 'Node A1',
                                                    'value': 'A1'}, {'label': 'Node B1', 'value': 'B1'},
                {'label': 'Node C1', 'value': 'C1'}], '', id='node-dropdown'),
            html.Div(id='dd-out-container')

        ]),

        html.Div([
            html.H3('Water Network'),

            dcc.Graph(id='example-graph1'),

        ], className="six columns"),

        html.Div([
        ], className="six columns"),
    ], className="row")
])


@app.callback(
    Output('example-graph1', 'figure'),
    [Output('dd-out-container', 'children')],
    [Input('typ-dropdown', 'value')],
    [Input('node-dropdown', 'value')]
)
def connected_graph(contyp, value):
    ddf = df.copy()
    J = builddf(ddf)
    fig = buildGraph(J)

    if len(str(value)) > 0:
        # fig = builddf(df)
        asc = nx.ancestors(J, value)
        desc = nx.descendants(J, value)
        print('asc', asc)
        print('desc', desc)

        if contyp == 'desc':
            _desc = list(desc)
            X = 'The connected network is ' + repr(_desc)
            paths = [
                path for p in desc for path in nx.all_simple_paths(J, value, p)]
        elif contyp == 'asc':
            _asc = list(asc)
            X = 'The connected network is ' + repr(_asc)
            paths = [
                path for p in asc for path in nx.all_simple_paths(J, p, value)]

        elif contyp == 'con':
            print('con')
            paths = [
                path for p in desc for path in nx.all_simple_paths(J, value, p)]

            paths.extend(
                [path for p in asc for path in nx.all_simple_paths(J, p, value)])

            X = paths

        else:

            X = 'The length of the network is Unknown'
        Q = nx.DiGraph()
        for p in paths:
            nx.add_path(Q, p)
        figure = buildGraph(Q)
        if len(paths) > 0:
            return figure, X
        else:
            return fig, X

    else:
        X = 'The length of the network is not selected'
        return fig, X


app.run_server(debug=True)

And selecting node A and Connected, I receive the below error

The children property of a component is a list of lists, instead of just a list. Check the 
component that has the following contents, and remove one of the levels of nesting: 
[
  "A",
  "B",
  "C",
  "D"
]

Hopefully there is someone around that sees what is wrong.

Hello @wongkong,

I tried to use your code, but addEdge is not something that I can figure out how to use. Your layout loads fine, so I dont know what’s going on.

Try this:

import plotly.graph_objects as go
import networkx as nx
import dash
from dash import Dash, html, dcc, Input, Output, State

import pandas as pd


# Controls for how the graph is drawn
nodeColor = 'Pink'
nodeSize = 20
lineWidth = 2
lineColor = '#000000'

df_dict = {'From': {0: 'A', 1: 'B', 2: 'C', 3: 'A', 4: 'B', 5: 'C'},
        'To': {0: 'B', 1: 'C', 2: 'D', 3: 'A1', 4: 'B1', 5: 'C1'},
        'weight': {0: 1.0, 1: 1.0, 2: 1.0, 3: 0.5, 4: 0.5, 5: 0.5}, 'Cost': {0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 5: 2}}
df = pd.DataFrame.from_dict(df_dict)


def builddf(df):
    G = nx.from_pandas_edgelist(
        df, "From", 'To', ['weight', 'Cost'], create_using=nx.DiGraph())
    return G


def buildGraph(G):
    pos = nx.layout.spring_layout(G)
    for node in G.nodes:
        G.nodes[node]['pos'] = list(pos[node])

    # Make list of nodes for plotly
    node_x = []
    node_y = []
    text = []
    for node in G.nodes():
        x, y = G.nodes[node]['pos']
        node_x.append(x)
        node_y.append(y)
        text.append(node)

    edge_x = []
    edge_y = []
    for edge in G.edges():
        start = G.nodes[edge[0]]['pos']
        end = G.nodes[edge[1]]['pos']
        edge_x, edge_y = addEdge(
            start, end, edge_x, edge_y, .8, 'end', .04, 30, nodeSize)

    edge_trace = go.Scatter(x=edge_x, y=edge_y, line=dict(width=lineWidth, color=lineColor), hoverinfo='none', mode='lines',
                            text=text)

    node_trace = go.Scatter(x=node_x, y=node_y, text=text, mode='markers+text', hoverinfo='text',
                            marker=dict(showscale=False, color=nodeColor, size=nodeSize))

    fig = go.Figure(data=[edge_trace, node_trace],
                    layout=go.Layout(
                        showlegend=False,
                        hovermode='closest',
                        margin=dict(b=20, l=5, r=5, t=40),
                        xaxis=dict(showgrid=False, zeroline=False,
                                showticklabels=False),
                        yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))
                    )

    fig.update_layout(yaxis=dict(scaleanchor="x", scaleratio=1),
                    plot_bgcolor='rgb(255,255,255)')
    return fig


app = dash.Dash()
app.layout = html.Div([
    html.Div([
        html.H1(children='Hello Dash'),

        html.Div(children='''
        Directed Water and Orange Juice Graph.
        '''),
        html.Div(children=[

            dcc.Dropdown([{'label': 'Connected', 'value': 'con'}, {'label': 'Ascending', 'value': 'asc'},
                        {'label': 'Descending', 'value': 'desc'}], '', id='typ-dropdown'),
            html.Div(id='dd-typ-container'),
            html.Div(html.Br()),
            dcc.Dropdown(
                [{'label': 'Node A', 'value': 'A'}, {'label': 'Node B', 'value': 'B'}, {'label': 'Node C', 'value': 'C'},
                {'label': 'Node D', 'value': 'D'}, {'label': 'Node A1',
                                                    'value': 'A1'}, {'label': 'Node B1', 'value': 'B1'},
                {'label': 'Node C1', 'value': 'C1'}], '', id='node-dropdown'),
            html.Div(id='dd-out-container')

        ]),

        html.Div([
            html.H3('Water Network'),

            dcc.Graph(id='example-graph1'),

        ], className="six columns"),

        html.Div([
        ], className="six columns"),
    ], className="row")
])


@app.callback(
    Output('example-graph1', 'figure'),
    Output('dd-out-container', 'children'),
    [Input('typ-dropdown', 'value')],
    [Input('node-dropdown', 'value')],
    prevent_initial_call=True
)
def connected_graph(contyp, value):
    ddf = df.copy()
    J = builddf(ddf)
    fig = buildGraph(J)

    if len(str(value)) > 0:
        # fig = builddf(df)
        asc = nx.ancestors(J, value)
        desc = nx.descendants(J, value)
        print('asc', asc)
        print('desc', desc)

        if contyp == 'desc':
            _desc = list(desc)
            X = 'The connected network is ' + repr(_desc)
            paths = [
                path for p in desc for path in nx.all_simple_paths(J, value, p)]
        elif contyp == 'asc':
            _asc = list(asc)
            X = 'The connected network is ' + repr(_asc)
            paths = [
                path for p in asc for path in nx.all_simple_paths(J, p, value)]

        elif contyp == 'con':
            print('con')
            paths = [
                path for p in desc for path in nx.all_simple_paths(J, value, p)]

            paths.extend(
                [path for p in asc for path in nx.all_simple_paths(J, p, value)])

            X = paths

        else:

            X = 'The length of the network is Unknown'
        Q = nx.DiGraph()
        for p in paths:
            nx.add_path(Q, p)
        figure = buildGraph(Q)
        if len(paths) > 0:
            return figure, X
        else:
            return fig, X

    else:
        X = 'The length of the network is not selected'
        return fig, X


app.run_server(debug=True)

What I did, was remove the list around the second output of the callback. And prevent the initial call of the callback.

If that doesnt work, could you please let me know what the addEdge function is.

Another thing you could try is put your return values in a list. I think I had a similar issue once because when I tried to return a list and another data type. Once wrapped into a list it worked fine.

In your case, x could be a string or a list, depending on the if-else.

@jinnyzor and @AIMPED Yes, I apologize about that, I thought I had posted the github link for it. My issue only occurs for the contyp == ‘con’ portion, when combining asc and desc.

here is the link for addEdge.py
https://github.com/redransil/plotly-dirgraph/blob/master/addEdge.py