Daily Tips - How to use AI to accelerate building dash app?

Hi there,

How are you doing with the dash online course? Today I’m gonna bring you an example of using GPT to help you quickly build an app. If you are like me and have no patience for the dense and dense doc that keeps popping up, and you can’t wait to try new components. Or you are just new to dash and have no idea how to get started with its rich ecology.

This thing started when I was planning to implement a network diagram and found that I didn’t know much about dash-cytoscape. Guess what? The official document is already very long, but still not that specific. It keeps asking me to go to the official website of Cytoscape to take a look. So, I asked GPT a question, do you know dash-cytoscape?

Let’s talk about my example. To achieve visualization, we first have to have a data model.

: Plz help to build a multi-level distribution model using networkx, as close to reality as possible. It needs to contain two-level distribution and more than 50 customers.

Of course, you can also adjust it to facilitate comparison with the actual case you face.

: There may be inventory exchanges between distributors. And plz give me the code.

import networkx as nx
import matplotlib.pyplot as plt
import random

# Create a directed graph
G = nx.DiGraph()

# Add the central distributor
central_distributor = 'CentralDistributor'
G.add_node(central_distributor, level=0, type='distributor')

# Add regional distributors
num_regional_distributors = 10
regional_distributors = [f'RegionalDistributor_{i}' for i in range(1, num_regional_distributors + 1)]
for distributor in regional_distributors:
    G.add_node(distributor, level=1, type='distributor')
    G.add_edge(central_distributor, distributor)

# Add customers
num_customers = 50
customers = [f'Customer_{i}' for i in range(1, num_customers + 1)]
for i, customer in enumerate(customers):
    region_index = i % num_regional_distributors
    regional_distributor = regional_distributors[region_index]
    G.add_node(customer, level=2, type='customer')
    G.add_edge(regional_distributor, customer)

# Add inventory exchange edges between regional distributors
for i in range(num_regional_distributors):
    for j in range(i + 1, num_regional_distributors):
        if random.random() < 0.2:  # 20% chance of having an exchange edge
            G.add_edge(regional_distributors[i], regional_distributors[j], type='exchange')
            G.add_edge(regional_distributors[j], regional_distributors[i], type='exchange')

# Draw the graph
pos = nx.spring_layout(G, seed=42)  # Seed for reproducible layout
plt.figure(figsize=(12, 12))
nx.draw(G, pos, with_labels=True, node_color='lightblue', edge_color='gray', node_size=2000, font_size=10, font_weight='bold', arrows=True)
edge_labels = nx.get_edge_attributes(G, 'type')
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
plt.title('Multi-Level Distribution Model')
plt.show()

Then you can start doing visualizations with cytoscape.

: I’d like to use dash-cytoscape.

import dash
import dash_cytoscape as cyto
from dash import html
import random

# Create a Dash app
app = dash.Dash(__name__)

# Define nodes and edges
nodes = [
    {'data': {'id': 'CentralDistributor', 'label': 'Central Distributor', 'level': 0, 'type': 'distributor'}},
]

# Add regional distributors
num_regional_distributors = 10
for i in range(1, num_regional_distributors + 1):
    nodes.append({'data': {'id': f'RegionalDistributor_{i}', 'label': f'Regional Distributor {i}', 'level': 1, 'type': 'distributor'}})

# Add customers
num_customers = 50
for i in range(1, num_customers + 1):
    nodes.append({'data': {'id': f'Customer_{i}', 'label': f'Customer {i}', 'level': 2, 'type': 'customer'}})

# Define edges
edges = [{'data': {'source': 'CentralDistributor', 'target': f'RegionalDistributor_{i}'}} for i in range(1, num_regional_distributors + 1)]

# Add edges between regional distributors
for i in range(num_regional_distributors):
    for j in range(i + 1, num_regional_distributors):
        if random.random() < 0.2:  # 20% chance of having an exchange edge
            edges.append({'data': {'source': f'RegionalDistributor_{i + 1}', 'target': f'RegionalDistributor_{j + 1}', 'type': 'exchange'}})
            edges.append({'data': {'source': f'RegionalDistributor_{j + 1}', 'target': f'RegionalDistributor_{i + 1}', 'type': 'exchange'}})

# Connect customers to regional distributors
for i in range(1, num_customers + 1):
    region_index = (i - 1) % num_regional_distributors + 1
    edges.append({'data': {'source': f'RegionalDistributor_{region_index}', 'target': f'Customer_{i}'}})

# Define styles
styles = {
    'node': {
        'distributor': {'background-color': '#74b9ff', 'shape': 'rectangle'},
        'customer': {'background-color': '#55efc4', 'shape': 'ellipse'}
    },
    'edge': {
        'exchange': {'line-color': '#fdcb6e', 'target-arrow-color': '#fdcb6e', 'target-arrow-shape': 'triangle'}
    }
}

# Define layout
layout = {
    'name': 'cose',
    'animate': True,
}

# Create the Dash app layout
app.layout = html.Div([
    cyto.Cytoscape(
        id='cytoscape',
        elements=nodes + edges,
        style={'width': '100%', 'height': '800px'},
        layout=layout,
        stylesheet=[
            {'selector': f'node[type = "distributor"]',
             'style': styles['node']['distributor']},
            {'selector': f'node[type = "customer"]',
             'style': styles['node']['customer']},
            {'selector': f'edge[type = "exchange"]',
             'style': styles['edge']['exchange']}
        ]
    )
])

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)

Next is the crucial, you can adjust it by talking to GPT and figure out how this component works. From my personal experience, this is more efficient than reading the documentation.

: What if I want the flow between distributors to be represented by a dotted line?

import dash
import dash_cytoscape as cyto
from dash import html
import networkx as nx
import random

# Create a networkx directed graph
G = nx.DiGraph()

# Add nodes with volume attribute
G.add_node('CentralDistributor', type='distributor', level=0, volume=1000)
num_regional_distributors = 10
for i in range(1, num_regional_distributors + 1):
    G.add_node(f'RegionalDistributor_{i}', type='distributor', level=1, volume=random.randint(500, 1500))

num_customers = 50
for i in range(1, num_customers + 1):
    G.add_node(f'Customer_{i}', type='customer', level=2, volume=random.randint(100, 500))

# Add edges with flow size attribute
for i in range(1, num_regional_distributors + 1):
    G.add_edge('CentralDistributor', f'RegionalDistributor_{i}', flow_size=random.randint(10, 100))

for i in range(num_regional_distributors):
    for j in range(i + 1, num_regional_distributors):
        if random.random() < 0.2:  # 20% chance of having an exchange edge
            flow_size = random.randint(5, 50)
            G.add_edge(f'RegionalDistributor_{i + 1}', f'RegionalDistributor_{j + 1}', flow_size=flow_size, type='exchange')
            G.add_edge(f'RegionalDistributor_{j + 1}', f'RegionalDistributor_{i + 1}', flow_size=flow_size, type='exchange')

for i in range(1, num_customers + 1):
    region_index = (i - 1) % num_regional_distributors + 1
    G.add_edge(f'RegionalDistributor_{region_index}', f'Customer_{i}', flow_size=random.randint(5, 30))

# Convert networkx graph to dash-cytoscape format
def nx_to_cytoscape(G):
    nodes = [{'data': {'id': node, 'label': G.nodes[node].get('label', node), 'type': G.nodes[node].get('type', 'unknown'), 'volume': G.nodes[node].get('volume', 100)}}
             for node in G.nodes]
    edges = [{'data': {'source': u, 'target': v, 'type': G.edges[u, v].get('type', 'default'), 'flow_size': G.edges[u, v].get('flow_size', 10)}}
             for u, v in G.edges]
    return nodes, edges

nodes, edges = nx_to_cytoscape(G)

# Define the stylesheet with scaled node sizes
stylesheet = [
    {'selector': 'node[type = "distributor"]',
     'style': {
         'background-color': '#74b9ff',
         'shape': 'rectangle',
         'width': 'mapData(volume, 0, 1500, 20, 50)',  # Scale node sizes
         'height': 'mapData(volume, 0, 1500, 20, 50)'  # Scale node sizes
     }},
    {'selector': 'node[type = "customer"]',
     'style': {
         'background-color': '#55efc4',
         'shape': 'ellipse',
         'width': 'mapData(volume, 0, 500, 10, 30)',  # Scale node sizes
         'height': 'mapData(volume, 0, 500, 10, 30)'  # Scale node sizes
     }},
    {'selector': 'edge[type = "exchange"]',
     'style': {
         'line-color': '#fdcb6e',
         'line-style': 'dotted',
         'line-width': 'mapData(flow_size, 0, 50, 1, 5)',  # Scale edge thickness
         'target-arrow-color': '#fdcb6e',
         'target-arrow-shape': 'triangle'
     }},
    {'selector': 'edge:not([type])',
     'style': {
         'line-color': '#b2bec3',
         'line-width': 'mapData(flow_size, 0, 100, 1, 5)',  # Scale edge thickness
         'target-arrow-color': '#b2bec3',
         'target-arrow-shape': 'triangle'
     }}
]

# Create the Dash app layout
app = dash.Dash(__name__)

app.layout = html.Div([
    cyto.Cytoscape(
        id='cytoscape',
        elements=nodes + edges,
        style={'width': '100%', 'height': '800px'},
        layout={'name': 'cose', 'animate': True},
        stylesheet=stylesheet
    )
])

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)

I believe you have seen that the selector is the key to changing the graph and understand how it works. Let’s make it a little interactive.

: I’d like to add two buttons to highlight customers or distributors.

import dash
import dash_cytoscape as cyto
from dash import html, dcc
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate
import networkx as nx
import random

# Create a networkx directed graph
G = nx.DiGraph()

# Define colors for each regional distributor
colors = {
    f'RegionalDistributor_{i}': f'#{random.randint(0, 0xFFFFFF):06x}'  # Generate random color
    for i in range(1, 11)
}

# Add nodes with volume attribute
G.add_node('CentralDistributor', type='distributor', level=0, volume=1000)
num_regional_distributors = 10
for i in range(1, num_regional_distributors + 1):
    G.add_node(f'RegionalDistributor_{i}', type='distributor', level=1, volume=random.randint(500, 1500))

num_customers = 50
for i in range(1, num_customers + 1):
    G.add_node(f'Customer_{i}', type='customer', level=2, volume=random.randint(100, 500))

# Add edges with flow size attribute and color based on the source distributor
for i in range(1, num_regional_distributors + 1):
    G.add_edge('CentralDistributor', f'RegionalDistributor_{i}', flow_size=random.randint(10, 100), color=colors[f'RegionalDistributor_{i}'])

for i in range(num_regional_distributors):
    for j in range(i + 1, num_regional_distributors):
        if random.random() < 0.2:  # 20% chance of having an exchange edge
            flow_size = random.randint(5, 50)
            color = colors[f'RegionalDistributor_{i + 1}']
            G.add_edge(f'RegionalDistributor_{i + 1}', f'RegionalDistributor_{j + 1}', flow_size=flow_size, type='exchange', color=color)
            G.add_edge(f'RegionalDistributor_{j + 1}', f'RegionalDistributor_{i + 1}', flow_size=flow_size, type='exchange', color=color)

for i in range(1, num_customers + 1):
    region_index = (i - 1) % num_regional_distributors + 1
    G.add_edge(f'RegionalDistributor_{region_index}', f'Customer_{i}', flow_size=random.randint(5, 30), color=colors[f'RegionalDistributor_{region_index}'])

# Convert networkx graph to dash-cytoscape format
def nx_to_cytoscape(G):
    nodes = [{'data': {'id': node, 'label': G.nodes[node].get('label', node), 'type': G.nodes[node].get('type', 'unknown'), 'volume': G.nodes[node].get('volume', 100)}}
             for node in G.nodes]
    
    edges = [{'data': {'source': u, 'target': v, 'type': G.edges[u, v].get('type', 'default'), 'flow_size': G.edges[u, v].get('flow_size', 10), 'color': G.edges[u, v].get('color', '#b2bec3')}}
             for u, v in G.edges]
    
    return nodes, edges

nodes, edges = nx_to_cytoscape(G)

# Define the stylesheet with dynamic colors for edges and appropriate width for edges
default_stylesheet = [
    {'selector': 'node[type = "distributor"]',
     'style': {
         'background-color': '#74b9ff',  # Blue color for distributors
         'shape': 'rectangle',
         'width': 'mapData(volume, 0, 1500, 20, 50)',  # Scale node sizes
         'height': 'mapData(volume, 0, 1500, 20, 50)'  # Scale node sizes
     }},
    {'selector': 'node[type = "customer"]',
     'style': {
         'background-color': '#55efc4',  # Light green color for customers
         'shape': 'ellipse',
         'width': 'mapData(volume, 0, 500, 10, 30)',  # Scale node sizes
         'height': 'mapData(volume, 0, 500, 10, 30)'  # Scale node sizes
     }},
    {'selector': 'edge[type = "exchange"]',
     'style': {
         'line-style': 'dotted',
         'line-width': 'mapData(flow_size, 0, 50, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }},
    {'selector': 'edge:not([type])',
     'style': {
         'line-width': 'mapData(flow_size, 0, 100, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }}
]

highlight_customers_stylesheet = [
    {'selector': 'node[type = "distributor"]',
     'style': {
         'background-color': '#74b9ff',  # Blue color for distributors
         'shape': 'rectangle',
         'width': 'mapData(volume, 0, 1500, 20, 50)',  # Scale node sizes
         'height': 'mapData(volume, 0, 1500, 20, 50)'  # Scale node sizes
     }},
    {'selector': 'node[type = "customer"]',
     'style': {
         'background-color': '#fd79a8',  # Highlighted color for customers
         'border-color': '#d63031',
         'border-width': '3px',
         'shape': 'ellipse',
         'width': 'mapData(volume, 0, 500, 10, 30)',  # Scale node sizes
         'height': 'mapData(volume, 0, 500, 10, 30)'  # Scale node sizes
     }},
    {'selector': 'edge[type = "exchange"]',
     'style': {
         'line-style': 'dotted',
         'line-width': 'mapData(flow_size, 0, 50, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }},
    {'selector': 'edge:not([type])',
     'style': {
         'line-width': 'mapData(flow_size, 0, 100, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }}
]

highlight_distributors_stylesheet = [
    {'selector': 'node[type = "distributor"]',
     'style': {
         'background-color': '#e17055',  # Highlighted color for distributors
         'border-color': '#d63031',
         'border-width': '3px',
         'shape': 'rectangle',
         'width': 'mapData(volume, 0, 1500, 20, 50)',  # Scale node sizes
         'height': 'mapData(volume, 0, 1500, 20, 50)'  # Scale node sizes
     }},
    {'selector': 'node[type = "customer"]',
     'style': {
         'background-color': '#55efc4',  # Light green color for customers
         'shape': 'ellipse',
         'width': 'mapData(volume, 0, 500, 10, 30)',  # Scale node sizes
         'height': 'mapData(volume, 0, 500, 10, 30)'  # Scale node sizes
     }},
    {'selector': 'edge[type = "exchange"]',
     'style': {
         'line-style': 'dotted',
         'line-width': 'mapData(flow_size, 0, 50, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }},
    {'selector': 'edge:not([type])',
     'style': {
         'line-width': 'mapData(flow_size, 0, 100, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }}
]

# Create the Dash app layout
app = dash.Dash(__name__)

app.layout = html.Div([
    html.Button('Highlight Customers', id='highlight-customers', n_clicks=0),
    html.Button('Highlight Distributors', id='highlight-distributors', n_clicks=0),
    cyto.Cytoscape(
        id='cytoscape',
        elements=nodes + edges,
        style={'width': '100%', 'height': '800px'},
        layout={'name': 'cose', 'animate': True},
        stylesheet=default_stylesheet
    )
])

# Define callback to update the stylesheet using dash.ctx
@app.callback(
    Output('cytoscape', 'stylesheet'),
    [Input('highlight-customers', 'n_clicks'),
     Input('highlight-distributors', 'n_clicks')]
)
def update_stylesheet(customers_clicks, distributors_clicks):
    ctx = dash.ctx  # Get the current context
    if not ctx.triggered:
        raise PreventUpdate
    
    triggered_input = ctx.triggered[0]['prop_id'].split('.')[0]
    
    if triggered_input == 'highlight-distributors':
        return highlight_distributors_stylesheet
    elif triggered_input == 'highlight-customers':
        return highlight_customers_stylesheet
    else:
        return default_stylesheet

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)

: Show something when I click on one node or edge.

import dash
import dash_cytoscape as cyto
from dash import html, dcc
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate
import networkx as nx
import random

# Create a networkx directed graph
G = nx.DiGraph()

# Define colors for each regional distributor
colors = {
    f'RegionalDistributor_{i}': f'#{random.randint(0, 0xFFFFFF):06x}'  # Generate random color
    for i in range(1, 11)
}

# Add nodes with volume attribute
G.add_node('CentralDistributor', type='distributor', level=0, volume=1000)
num_regional_distributors = 10
for i in range(1, num_regional_distributors + 1):
    G.add_node(f'RegionalDistributor_{i}', type='distributor', level=1, volume=random.randint(500, 1500))

num_customers = 50
for i in range(1, num_customers + 1):
    G.add_node(f'Customer_{i}', type='customer', level=2, volume=random.randint(100, 500))

# Add edges with flow size attribute and color based on the source distributor
for i in range(1, num_regional_distributors + 1):
    G.add_edge('CentralDistributor', f'RegionalDistributor_{i}', flow_size=random.randint(10, 100), color=colors[f'RegionalDistributor_{i}'])

for i in range(num_regional_distributors):
    for j in range(i + 1, num_regional_distributors):
        if random.random() < 0.2:  # 20% chance of having an exchange edge
            flow_size = random.randint(5, 50)
            color = colors[f'RegionalDistributor_{i + 1}']
            G.add_edge(f'RegionalDistributor_{i + 1}', f'RegionalDistributor_{j + 1}', flow_size=flow_size, type='exchange', color=color)
            G.add_edge(f'RegionalDistributor_{j + 1}', f'RegionalDistributor_{i + 1}', flow_size=flow_size, type='exchange', color=color)

for i in range(1, num_customers + 1):
    region_index = (i - 1) % num_regional_distributors + 1
    G.add_edge(f'RegionalDistributor_{region_index}', f'Customer_{i}', flow_size=random.randint(5, 30), color=colors[f'RegionalDistributor_{region_index}'])

# Convert networkx graph to dash-cytoscape format
def nx_to_cytoscape(G):
    nodes = [{'data': {'id': node, 'label': G.nodes[node].get('label', node), 'type': G.nodes[node].get('type', 'unknown'), 'volume': G.nodes[node].get('volume', 100)}}
             for node in G.nodes]
    
    edges = [{'data': {'source': u, 'target': v, 'type': G.edges[u, v].get('type', 'default'), 'flow_size': G.edges[u, v].get('flow_size', 10), 'color': G.edges[u, v].get('color', '#b2bec3')}}
             for u, v in G.edges]
    
    return nodes, edges

nodes, edges = nx_to_cytoscape(G)

# Define the stylesheet with dynamic colors for edges and appropriate width for edges
default_stylesheet = [
    {'selector': 'node[type = "distributor"]',
     'style': {
         'background-color': '#74b9ff',  # Blue color for distributors
         'shape': 'rectangle',
         'width': 'mapData(volume, 0, 1500, 20, 50)',  # Scale node sizes
         'height': 'mapData(volume, 0, 1500, 20, 50)'  # Scale node sizes
     }},
    {'selector': 'node[type = "customer"]',
     'style': {
         'background-color': '#55efc4',  # Light green color for customers
         'shape': 'ellipse',
         'width': 'mapData(volume, 0, 500, 10, 30)',  # Scale node sizes
         'height': 'mapData(volume, 0, 500, 10, 30)'  # Scale node sizes
     }},
    {'selector': 'edge[type = "exchange"]',
     'style': {
         'line-style': 'dotted',
         'line-width': 'mapData(flow_size, 0, 50, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }},
    {'selector': 'edge:not([type])',
     'style': {
         'line-width': 'mapData(flow_size, 0, 100, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }}
]

highlight_customers_stylesheet = [
    {'selector': 'node[type = "distributor"]',
     'style': {
         'background-color': '#74b9ff',  # Blue color for distributors
         'shape': 'rectangle',
         'width': 'mapData(volume, 0, 1500, 20, 50)',  # Scale node sizes
         'height': 'mapData(volume, 0, 1500, 20, 50)'  # Scale node sizes
     }},
    {'selector': 'node[type = "customer"]',
     'style': {
         'background-color': '#fd79a8',  # Highlighted color for customers
         'border-color': '#d63031',
         'border-width': '3px',
         'shape': 'ellipse',
         'width': 'mapData(volume, 0, 500, 10, 30)',  # Scale node sizes
         'height': 'mapData(volume, 0, 500, 10, 30)'  # Scale node sizes
     }},
    {'selector': 'edge[type = "exchange"]',
     'style': {
         'line-style': 'dotted',
         'line-width': 'mapData(flow_size, 0, 50, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }},
    {'selector': 'edge:not([type])',
     'style': {
         'line-width': 'mapData(flow_size, 0, 100, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }}
]

highlight_distributors_stylesheet = [
    {'selector': 'node[type = "distributor"]',
     'style': {
         'background-color': '#e17055',  # Highlighted color for distributors
         'border-color': '#d63031',
         'border-width': '3px',
         'shape': 'rectangle',
         'width': 'mapData(volume, 0, 1500, 20, 50)',  # Scale node sizes
         'height': 'mapData(volume, 0, 1500, 20, 50)'  # Scale node sizes
     }},
    {'selector': 'node[type = "customer"]',
     'style': {
         'background-color': '#55efc4',  # Light green color for customers
         'shape': 'ellipse',
         'width': 'mapData(volume, 0, 500, 10, 30)',  # Scale node sizes
         'height': 'mapData(volume, 0, 500, 10, 30)'  # Scale node sizes
     }},
    {'selector': 'edge[type = "exchange"]',
     'style': {
         'line-style': 'dotted',
         'line-width': 'mapData(flow_size, 0, 50, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }},
    {'selector': 'edge:not([type])',
     'style': {
         'line-width': 'mapData(flow_size, 0, 100, 1, 5)',  # Scale edge thickness
         'line-color': 'data(color)',  # Use dynamic color from edge data
         'target-arrow-color': 'data(color)',
         'target-arrow-shape': 'triangle'
     }}
]

# Create the Dash app layout
app = dash.Dash(__name__)

app.layout = html.Div([
    html.Button('Highlight Customers', id='highlight-customers', n_clicks=0),
    html.Button('Highlight Distributors', id='highlight-distributors', n_clicks=0),
    html.Div(id='click-info', style={'padding': '10px', 'border': '1px solid black'}),
    cyto.Cytoscape(
        id='cytoscape',
        elements=nodes + edges,
        style={'width': '100%', 'height': '800px'},
        layout={'name': 'cose', 'animate': True},
        stylesheet=default_stylesheet
    )
])

# Define callback to update the stylesheet
@app.callback(
    Output('cytoscape', 'stylesheet'),
    [Input('highlight-customers', 'n_clicks'),
     Input('highlight-distributors', 'n_clicks')]
)
def update_stylesheet(customers_clicks, distributors_clicks):
    ctx = dash.ctx  # Get the current context
    if not ctx.triggered:
        raise PreventUpdate
    
    triggered_input = ctx.triggered[0]['prop_id'].split('.')[0]
    
    if triggered_input == 'highlight-distributors':
        return highlight_distributors_stylesheet
    elif triggered_input == 'highlight-customers':
        return highlight_customers_stylesheet
    else:
        return default_stylesheet

# Define callback to display information about clicked node or edge
@app.callback(
    Output('click-info', 'children'),
    [Input('cytoscape', 'tapNodeData'),
     Input('cytoscape', 'tapEdgeData')]
)
def display_click_info(node_data, edge_data):
    if node_data:
        return f'Clicked Node: {node_data["id"]}, Type: {node_data["type"]}, Volume: {node_data.get("volume", "N/A")}'
    elif edge_data:
        return f'Clicked Edge: {edge_data["source"]} -> {edge_data["target"]}, Flow Size: {edge_data["flow_size"]}, Color: {edge_data["color"]}'
    else:
        return 'Click on a node or edge to see details.'

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)

Hope this helps you. XD

Keywords: GPT, Cytoscape, Network Graph

Other Daily Tips series:
Daily Tips - If I have a Bunch of Triggers
Daily Tips - Write the Simplest Client-side Callback

3 Likes

It’s amazing how much one can learn using GPT!
How long from start to finish did it take you, @stu , to create the final graph?

about 20 mins. I omitted something and didn’t post them, you know AI can’t feedback perfect answers every time. but don’t worry, you can correct it or feed it traceback info, and GPT can correct its own mistakes.

1 Like