[Share] Visdcc - a dash core components for vis.js network

I created a dash core components for plotting vis.js network,

it has few functions currently, I will keep updating if other function need to be used :slightly_smiling_face:

9 Likes

Wow, this is really cool!

1 Like

Update an new function: Animate or move the camera: :grinning: :grinning:

Thanks for your great work. Your visdcc works like a charm when it serves only one individual dash app. I have multiple dash app and I put them together with file structure as below.

File structure:
- app.py
- index.py
- apps
   |-- __init__.py
   |-- app1.py
   |-- app2.py

app.py

import dash

app = dash.Dash()
server = app.server
app.config.supress_callback_exceptions = True

app.css.config.serve_locally = True

external_css = ["static/base.css",
                "static/custom.css"]
for css in external_css:
    app.css.append_css({"external_url": css})

index.py

from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

from app import app
from apps import app1, app2



app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])


@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/':
         return app1.layout
    elif pathname == '/apps/app2':
         return app2.layout
    else:
        return '404'
        
if __name__ == '__main__':
    app.run_server(debug=True)

app1

from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc

from app import app

layout = html.Div([
    html.H3('App 1'),
    dcc.Dropdown(
        id='app-1-dropdown',
        options=[
            {'label': 'App 1 - {}'.format(i), 'value': i} for i in [
                'NYC', 'MTL', 'LA'
            ]
        ]
    ),
    html.Div(id='app-1-display-value'),
    dcc.Link('Go to App 2', href='/apps/app2')
],className='container')


@app.callback(
    Output('app-1-display-value', 'children'),
    [Input('app-1-dropdown', 'value')])
def display_value(value):
    return 'You have selected "{}"'.format(value)

app2

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, Event, State
import visdcc

from app import app

layout = html.Div([
      visdcc.Network(id = 'net', 
                     options = dict(height= '600px', width= '100%')),
      dcc.Input(id = 'label',
                placeholder = 'Enter a label ...',
                type = 'text',
                value = ''  ),
      html.Br(),html.Br(),
      dcc.RadioItems(id = 'color',
                     options=[{'label': 'Red'  , 'value': '#ff0000'},
                              {'label': 'Green', 'value': '#00ff00'},
                              {'label': 'Blue' , 'value': '#0000ff'} ],
                     value='Red'  )             
])

@app.callback(
    Output('net', 'data'),
    [Input('label', 'value')])
def myfun(x):
    data ={'nodes':[{'id': 1, 'label':    x    , 'color':'#00ffff'},
                    {'id': 2, 'label': 'Node 2'},
                    {'id': 4, 'label': 'Node 4'},
                    {'id': 5, 'label': 'Node 5'},
                    {'id': 6, 'label': 'Node 6'}                    ],
           'edges':[{'id':'1-3', 'from': 1, 'to': 3},
                    {'id':'1-2', 'from': 1, 'to': 2} ]
           }
    return data

@app.callback(
    Output('net', 'options'),
    [Input('color', 'value')])
def myfun(x):
    return {'nodes':{'color': x}}

So the problem I encountered is the app2 with visdcc works great as individual dash app but wont work as file structure above. It shows blank on the screen and shows no error. When I remove visdcc.Network(id = 'net', options = dict(height= '600px', width= '100%')),, the content is showing up again. That is why I think there may be a problem related to visdcc when constructing multiple dash apps.

I have posted the same thing to your github. Does anyone encounter the same problem?

:thinking: i don’t know what the difference between multiple app and single app on visdcc, the problem may be on your css , or file structure … and so on.

Thanks tho.
This issue happened to someone who used custom component with Multi-Page Apps before.
Custom component with Multi-Page Apps

The reason for this type of error is (from @chriddyp) :

A little context: When Dash serves the page, it crawls the app.layout to see which component libraries are being used (e.g. dash_core_components). Then, with this list of unique component libraries, it serves the necessary JS and CSS bundles that are distributed with those component libraries.

In this case, we’re serving dash_table_experiments on a separate page, as the response of a callback. Dash only sees dash_html_components and dash_core_components in the app.layout and so it doesn’t serve the necessary JS and CSS bundles that are required for the dash-table-components component that is rendered in the future.

So the solution for fixing this kind of problem when constructing multiple pages with visdcc (in this case) is adds the following code to the Index.py :

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content'),
    html.Div(visdcc.Network(id='net',
             options = dict(height= '600px', 
                                    width= '100%',
                                    physics={'barnesHut': {'avoidOverlap': 0}},         
                                    )),style={'display': 'none'})
])

:wink:

1 Like

Great ! :crazy_face:

@jimmybow wow! really nice component for network viz! one short Q, How can I add a hyperlink to a given node? Is there a way to do this in the component w/o messing up with JS?

I only found this on the visjs side:

And I couldn’t find any example on the visdcc side.

Thanks!

it’s not support click event now, but you can use ‘selection’ prop to get selected nodes, and design it just like the click event, the code will like…

@app.callback(
    Output(...),
    [Input('net', 'selection')])
def myfun(x): 
    if len(x['nodes']) > 0 : 
        ...
    return ...

:slightly_smiling_face:

@jimmybow I am still a bit behind my project because I got stuck in a much more trivial task:

When I do this code I do not see the graph, no idea why:

""" ETL Flow diagram for Reference Match against supplier names """


import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, Event, State
import visdcc


app = dash.Dash()


app.layout = html.Div([
  visdcc.Network(
      id = 'net',
      options = dict(
          height= '1100px',
          width= '100%',
          layout={
              'hierarchical': {
                  'hierarchical.enabled':'true'
              }
          }
      )
  ),
  dcc.RadioItems(id='color',
                 options=[{'label': 'Red'  , 'value': '#ff0000'},
                          {'label': 'Green', 'value': '#00ff00'},
                          {'label': 'Blue' , 'value': '#0000ff'}],
                 value='Red')
])


@app.callback(
Output('net', 'data'),
[Input('color', 'value')]
)
def myfun(x):
data ={
    'nodes':
        [
            {'id': 1, 'label': 'chemical', 'color': 'rgb(0, 102, 204)', 'shape': 'box', 'level':'0'},
            {'id': 2, 'label': 'security_master', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level':'0'},
            {'id': 3, 'label': 'supplier', 'color': 'rgb(211,211,211)', 'shape': 'ellipse', 'level':'1'},
            {'id': 4, 'label': 'I/O', 'color': 'rgb(255, 204, 102)', 'shape': 'circle', 'level':'1'},
            {'id': 5, 'label': 'common_words', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '1'},
            {'id': 6, 'label': 'I/O', 'color': 'rgb(255, 204, 102)', 'shape': 'circle', 'level': '2'}
        ],
    'edges':
        [
            {'id':'1-3', 'from': 1, 'to': 3, 'arrows':'to'},
            {'id': '2-4', 'from': 2, 'to': 4, 'arrows': 'to'},
            {'id': '5-4', 'from': 5, 'to': 4, 'arrows': 'to'}
        ]
}
return data


if __name__ == '__main__':
app.run_server(host='0.0.0.0', port=8687, debug=True) 

But if I remove the ‘id’:‘6’ I suddenly can see the graph:

""" ETL Flow diagram for Reference Match against supplier names """


import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, Event, State
import visdcc


app = dash.Dash()


app.layout = html.Div([
      visdcc.Network(
          id = 'net',
          options = dict(
              height= '1100px',
              width= '100%',
              layout={
                  'hierarchical': {
                      'hierarchical.enabled':'true'
                  }
              }
          )
      ),
      dcc.RadioItems(id='color',
                     options=[{'label': 'Red'  , 'value': '#ff0000'},
                              {'label': 'Green', 'value': '#00ff00'},
                              {'label': 'Blue' , 'value': '#0000ff'}],
                     value='Red')
])


@app.callback(
    Output('net', 'data'),
    [Input('color', 'value')]
)
def myfun(x):
    data ={
        'nodes':
            [
                {'id': 1, 'label': 'chemical', 'color': 'rgb(0, 102, 204)', 'shape': 'box', 'level':'0'},
                {'id': 2, 'label': 'security_master', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level':'0'},
                {'id': 3, 'label': 'supplier', 'color': 'rgb(211,211,211)', 'shape': 'ellipse', 'level':'1'},
                {'id': 4, 'label': 'I/O', 'color': 'rgb(255, 204, 102)', 'shape': 'circle', 'level':'1'},
                {'id': 5, 'label': 'common_words', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '1'}
            ],
        'edges':
            [
                {'id':'1-3', 'from': 1, 'to': 3, 'arrows':'to'},
                {'id': '2-4', 'from': 2, 'to': 4, 'arrows': 'to'},
                {'id': '5-4', 'from': 5, 'to': 4, 'arrows': 'to'}
            ]
    }
    return data


if __name__ == '__main__':
    app.run_server(host='0.0.0.0', port=8687, debug=True)

Then I can see the plot:

image

Is there a restriction in number of nodes/levels? I have no idea what’s different between one code and the other besides adding a new node.

This is such an odd error that to clarify with diff, the code on the left works but the code on the right doesn’t work and I have no idea why. I also submit it as a github issue in case it’s useful to the repo too.

I guess for me the mystery continues. Trying to fix it I eliminated all the app.callback code and put the data in the visdcc.Network.

This code works and puts the nodes in the level’s of hierarchy specified:

""" ETL Flow diagram for Reference Match against supplier names """


import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, Event, State
import visdcc


app = dash.Dash()


app.layout = html.Div([
      visdcc.Network(
          id = 'net',
          options = dict(
              height= '1100px',
              width= '100%',
              layout={
                  'hierarchical': {
                      'dsfdsfsadfa': 'true'
                  }
              }
          ),
          data={
              'nodes':
                  [
                      {'id': 1, 'label': 'chemical', 'color': 'rgb(0, 102, 204)', 'shape': 'box', 'level': '0'},
                      {'id': 2, 'label': 'security_master', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '0'},
                      {'id': 3, 'label': 'supplier', 'color': 'rgb(211,211,211)', 'shape': 'ellipse', 'level': '1'},
                      {'id': 4, 'label': 'I/O', 'color': 'rgb(255, 204, 102)', 'shape': 'circle', 'level': '1'},
                      {'id': 5, 'label': 'common_words', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '1'},
                      {'id': 6, 'label': 'I/O', 'color': 'rgb(255, 204, 102)', 'shape': 'circle', 'level': '2'},
                      {'id': 7, 'label': 'oil document', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '2'},
                      {'id': 8, 'label': 'security_master_enriched', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '2'}
                  ],
              'edges':
                  [
                      {'id': '1-3', 'from': 1, 'to': 3, 'arrows': 'to'},
                      {'id': '2-4', 'from': 2, 'to': 4, 'arrows': 'to'},
                      {'id': '5-4', 'from': 5, 'to': 4, 'arrows': 'to'},
                      {'id': '3-6', 'from': 3, 'to': 6, 'arrows': 'to'},
                      {'id': '7-4', 'from': 7, 'to': 4, 'arrows': 'to'},
                      {'id': '4-8', 'from': 4, 'to': 8, 'arrows': 'to'},
                      {'id': '7-6', 'from': 7, 'to': 6, 'arrows': 'to'}
                  ]
          }
      )
])

if __name__ == '__main__':
    app.run_server(host='0.0.0.0', port=8687, debug=True)

image

However, it puts two nodes overlapping so one cannot read full text from the nodes (oil document and master_security_enriched).

To fix this I have been trying the suggestions here:
https://stackoverflow.com/questions/35431990/connected-nodes-overlapping-other-edges-or-nodes

This one works but does not put the nodes in the right levels specified:

""" ETL Flow diagram for Reference Match against supplier names """


import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, Event, State
import visdcc


app = dash.Dash()


app.layout = html.Div([
      visdcc.Network(
          id = 'net',
          options = dict(
              height= '1100px',
              width= '100%',
              layout={
                  'hierarchical': {
                      'enable': 'true'
                  }
              }
          ),
          data={
              'nodes':
                  [
                      {'id': 1, 'label': 'chemical', 'color': 'rgb(0, 102, 204)', 'shape': 'box', 'level': '0'},
                      {'id': 2, 'label': 'security_master', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '0'},
                      {'id': 3, 'label': 'supplier', 'color': 'rgb(211,211,211)', 'shape': 'ellipse', 'level': '1'},
                      {'id': 4, 'label': 'I/O', 'color': 'rgb(255, 204, 102)', 'shape': 'circle', 'level': '1'},
                      {'id': 5, 'label': 'common_words', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '1'},
                      {'id': 6, 'label': 'I/O', 'color': 'rgb(255, 204, 102)', 'shape': 'circle', 'level': '2'},
                      {'id': 7, 'label': 'oil document', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '2'},
                      {'id': 8, 'label': 'security_master_enriched', 'color': 'rgb(204, 0, 0)', 'shape': 'box', 'level': '2'}
                  ],
              'edges':
                  [
                      {'id': '1-3', 'from': 1, 'to': 3, 'arrows': 'to'},
                      {'id': '2-4', 'from': 2, 'to': 4, 'arrows': 'to'},
                      {'id': '5-4', 'from': 5, 'to': 4, 'arrows': 'to'},
                      {'id': '3-6', 'from': 3, 'to': 6, 'arrows': 'to'},
                      {'id': '7-4', 'from': 7, 'to': 4, 'arrows': 'to'},
                      {'id': '4-8', 'from': 4, 'to': 8, 'arrows': 'to'},
                      {'id': '7-6', 'from': 7, 'to': 6, 'arrows': 'to'}
                  ]
          }
      )
])

if __name__ == '__main__':
    app.run_server(host='0.0.0.0', port=8687, debug=True)

If I do ‘enabled’: ‘false’ it also doesn’t put the nodes in the right levels.

image

Now, if I add ‘nodeSpacing’:425 as suggested in here:
https://stackoverflow.com/questions/35431990/connected-nodes-overlapping-other-edges-or-nodes

it doesn’t work, i.e., no error whatsoever, just as before I cannot see any graph in the site.

At this point I am clueless as of how this componnent work and how to effectively debug since it seems to behave/missbehave in sort of a random manner… if you can shed some light on the logic of why this errrors may be happening and how to test/debug I’d be supper happy.

Thanks!

:thinking: it seem layout in options need to use this format:

layout: {
    improvedLayout:True,
    hierarchical: {
      enabled:False,
      levelSeparation: 150,
      nodeSpacing: 100,
      treeSpacing: 200,
      blockShifting: True,
      edgeMinimization: True,
      parentCentralization: True,
      direction: 'UD',        # UD, DU, LR, RL
      sortMethod: 'hubsize'   # hubsize, directed
    }
  }

the format from the document of vis.js http://visjs.org/docs/network/layout.html#,
boolean is corresponding to boolean

1 Like

Woooaw! This is great.
Does it support clustering? I trying to figure out how implement it. Any clues??

Thank you!

1 Like

yes, but it don’t have any axis and tick.
i think using scatter plot to perform clustering results is better …
anyway, you can draw complex network or tree by using visdcc.Network :yum:

You may need to use this with NetworkX, which provide clustering algorithm, and a lot more features.

1 Like

I have made a hierarchical network with huge number of nodes and want to cluster them to improve performance. Is there a way to cluster them? I’m sorry I can’t show the code but it would be very helpful if I anyone could show how to cluster the nodes (cluster by connection or zoom) using visdcc.

I’m truly grateful for your sharing of the component. However, I have trouble in changing the edge’s length. It would be highly appreciated if you could give me an example. Sincerely thanks again.

what’s the edge’s length? you can fix the node initial location so the edge’ length are fixed too

you can view the doc of vis.js http://visjs.org/docs/network/
the visdcc use the same format of json/dictionary as vis.js

you can set the options, the visdcc use the same format of json/dictionary as vis.js
you can see the vis.js example here http://visjs.org/network_examples.html

Thanks for your kindly reply. The edge’s length means I want to set the length of edges in different size. Though I set the property of length in edges,it didn’t work.Here is my code.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, Event, State
import visdcc

app = dash.Dash()

mydata ={
        'nodes':
            [
                {'id': 1, 'label': 'node1'},
                {'id': 2, 'label': 'node2'},
                {'id': 3, 'label': 'node3'}
            ],
        'edges':
            [
                {'id':'1-2', 'from': 1, 'to': 2, 'arrows':'to','length':10},
                {'id': '2-3', 'from': 2, 'to': 3, 'arrows': 'to','length':5},
                {'id': '3-1', 'from': 3, 'to': 1, 'arrows': 'to','length':1}
            ]
    }

app.layout = html.Div([
      visdcc.Network(
          id = 'net',
          data=mydata,
          options = dict(
              height= '600px',
              width= '100%',
              node=dict(shape='circle'),
              physics=False
          )
      )
])


if __name__ == '__main__':
    app.run_server(debug=False)

The following picture is my result. It seems that the length of edges is not the value I set.

Now I am confused about whether the length of edge could be set. It would be highly appreciated for your reply. Sincerely thanks.

there is no option ‘length’ for edges, you need to set the node initial location so the edge’ length will be different