This is part two to one of our previous blogs where we showed you how to build an LDAP based Org Chart using Python and Neo4j. In the previous blog, we visualized with the Neo4j Browser and YFiles for Jupyter Notebooks. While visualization is fun and sometimes challenging, it is very unlikely that you would want to post a Jupyter Notebook for “user” consumption.
In short, we will now take our prototype to a production version of the visualization. We are going to build a visualization using HTML, CSS, JavaScript, the Neo4j JavaScript Driver, and a graph visualization library called “NeoVis.Js”. NeoVis.js incorporates the Neo4J JavaScript driver and the extremely popular Vis.Js JavaScript library. Our end goal is to create a visual that looks like the image below.
If you would like to follow along, the code can be found in our GitLab repo below.
Neo4j Database
The data we will use in this visualization will come from the Neo4j database we created in the previous blog. We will add some additional properties such as “Team” nodes and team membership. To augment the data and make the visuals more useful to the “User.” The Cypher Query Language (“CQL”) commands are below and will be executed via Neo4j Browser this time versus via Python.
CQL for Neo4j Data Augmentation to support visuals |
// Create properties in the Neo4j database to support our visuals. Such as size and hover text. // These commands update the “MANAGES” relationship. MATCH p=()-[r:MANAGES]->() set r.size=10 MATCH p=()-[r:MANAGES]->() set r.hover=type(r) // These commands update the “IS_EMPLOYEE_OF” relationship. MATCH p=()-[r:IS_EMPLOYEE_OF]->() set r.size=10 MATCH p=()-[r:IS_EMPLOYEE_OF]->() set r.hover=type(r) // This command allows us to capture the Nodes ID as a string for use in the visuals. MATCH (n:Person) set n.id_number=id(n) // This command allows us to map “User Friendly” team names based on the “PersonManager” property. match(n:Person) where (n.PersonManager_Name='Mike Jones') set n.Team='Sales'; match(n:Person) where (n.PersonManager_Name='Mike Martin') set n.Team='Engineering'; match(n:Person) where (n.PersonManager_Name='Archie Kerry') set n.Team='Accounting'; // This command creates the actual teams as Nodes (Sales, Engineering, Accounting), // we can then create the relationship of the Team property to a Team Node. merge(t:Team{TeamName:'Sales'}); merge(t:Team{TeamName:'Engineering'}); merge(t:Team{TeamName:'Accounting'}); //This command sets the “CompanyName” property for each “Team” Node. MATCH (n:Team) set n.CompanyName='Faker Inc.' // Now we can create a cartesian and map “Person” nodes to teams as “MEMBER_OF” match(n:Person),(t:Team) where (n.Team=t.TeamName) merge(n)-[:MEMBER_OF]-(t) // Now we can create a cartesian and map “Team” nodes to “Company” nodes as “PART_OF” match(c:Company),(t:Team) where (t.CompanyName=c.CompanyName) merge(t)-[:PART_OF]->(c) // Since we created two new relationship types, we will then add the “size” and “hover” properties // like we did in the syntax earlier for “MANAGES” and “EMPLOYEE_OF”. MATCH p=()-[r:MEMBER_OF]->() set r.size=10; MATCH p=()-[r:MEMBER_OF]->() set r.hover=type(r); MATCH p=()-[r:PART_OF]->() set r.size=10; MATCH p=()-[r:PART_OF]->() set r.hover=type(r); |
HTML
With the database updated to support our graphics, (which you can modify as you choose) we can begin to construct the HTML components. Luckily for us this is simple. The code below is used to build the basic HTML and the all-important “DIV” object to host our graph visualization. The table below is our starter code. The code is commented.
The first bolded block is just a placeholder for our “CSS” styling on the page. We will use CSS to modify the appearance of items in the “VIZ” “DIV” object. With the goal of using icons instead of basic shapes, such as circles, squares, or triangles, we need to create a link to the “FontAwesome” CSS. We also need to add a link to the CDN hosting the “Neovis.js” library. All the JavaScript we need for our application of these libraries will be contained in the “Visualizing_Neo4j_OrgChart_Hierarchical.js” file stored in the “js” directory of our application. All the rest of the syntax is purely HTML content required for rending HTML and my comments.
<!doctype html> <html> <head> <title>Visualizing_Neo4j_OrgChart_Hierarchical</title> <!--These are placeholders for our style tags --> <style> </style> <!--This is the link to the style sheet we will use to include "icons" in our graph visual --> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.11.1/css/all.css"/> <!--This is the link to the Neovis.js JavaScript library via CDN --> <script src="https://unpkg.com/neovis.js@2.0.2"></script> <!--This JS file will contain all our JavaScript used to support our graph visual --> <!--Separating the JS from CSS and HTML helps to keep the code cleaner --> <script src="js/Visualizing_Neo4j_OrgChart_Hierarchical.js"></script> </head> <!—This is the JavaScript function we will call when the page loads to render the graph --> <body onload="draw()"> <!--This is the container target for our graph visual --> <!--The graph will render in this container --> <div id="viz"></div> </body> </html> |
CSS
For styling we will add the following to the HTML content above. The new code should look like the syntax in the table below. This syntax will set the height, width, color, font, and alignment of our graph visual.
<!doctype html> <html> <head> <title>Visualizing_Neo4j_OrgChart_Hierarchical</title> <!--These are placeholders for our style tags --> <style> width: 1200px; height: 600px; border: 10px solid lightgray; font: 22pt arial; justify-content: center; </style> <!--This is the link to the style sheet we will use to include "icons" in our graph visual --> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.11.1/css/all.css"/> <!--This is the link to the Neovis.js JavaScript library via CDN --> <script src="https://unpkg.com/neovis.js@2.0.2"></script> <!--This JS file will contain all our JavaScript used to support our graph visual --> <!--Separating the JS from CSS and HTML helps to keep the code cleaner --> <script src="js/Visualizing_Neo4j_OrgChart_Hierarchical.js"></script> </head> <!—This is the JavaScript function we will call when the page loads to render the graph --> <body onload="draw()"> <!--This is the container target for our graph visual --> <!--The graph will render in this container --> <div id="viz"></div> </body> </html> |
CSS
The JavaScript we will build is the “meat and potatoes” of the implementation. With this code we can control how our graph is converted from this:
To this:
To get started we need to create a file called “Visualizing_Neo4j_OrgChart_Hierarchical.js” and save it to a directory called “js” in the root of your project folder. All the JavaScript is commented below for simplicity. If you want to explore your options further, there will be reference links below. If you get stuck or want to customize even further, you can always contact one of our account managers.
In the code below we create the “draw()” function that will be called when the HTML loads in the browser. The code below should be modified to reflect your environment and credentials for connecting the HTML and JavaScript to your Neo4j database. In the code you will see that we define a few components of the graph. We will start with the overall “config.” The “config” contains all the settings we need to configure to render the graph. This includes the container used to render the graph visual, the Neo4j connection and authentication information. The “config” also contains the “visConfig;” this is where we specify the physics for repulsion, the nodes, the edges, and overall layout.
We then move on to the node labels, relationships and finally the CQL query to run to return the graph for rendering. Once all the settings are configured, we can then instantiate the “NeoVis” object and provide the “config” as input. The last step is to render the graph visual with the “viz.render()” method. For posterity and basic testing, we write this data to the console.
let viz; function draw() { const config = { containerId: "viz", neo4j: { serverUrl: "bolt://127.0.0.1:7687", serverUser: "read_only", serverPassword: "read_only123" }, visConfig: { nodes: { }, edges: { length: 100, arrows: { to: {enabled: false} }, }, physics: { hierarchicalRepulsion: { avoidOverlap: 1 }, solver: "repulsion", repulsion: { nodeDistance: 1000 } }, layout: { improvedLayout: true, randomSeed: 420, hierarchical: { enabled: true, direction: 'DU', sortMethod: 'directed', nodeSpacing: 1000, treeSpacing: 20, levelSeparation: 250, } }, }, labels: { Person: { label: "PersonGivenName", group: 'Team', caption: true, [NeoVis.NEOVIS_ADVANCED_CONFIG]: { cypher: { value: "MATCH (n) WHERE id(n) = $id RETURN n.size" }, function: { title: NeoVis.objectToTitleHtml, title: (props) => NeoVis.objectToTitleHtml(props, ["Team","PersonDisplayName", "PersonTelephoneNumber", "PersonMail", "PersonManager_Name","PersonCN"]) }, static: { shape: 'icon', icon: { face: "'Font Awesome 5 Free'", weight: "900", // Font Awesome 5 doesn't work properly unless bold. code: "\uf007", size: 50, color: "rgba(84,81,81,0.75)", }, }, } }, Company: { label: "CompanyName", caption: true, [NeoVis.NEOVIS_ADVANCED_CONFIG]: { cypher: { value: "MATCH (n) WHERE id(n) = $id RETURN n.size" }, function: { title: NeoVis.objectToTitleHtml, title: (props) => NeoVis.objectToTitleHtml(props, ["PersonDisplayName", "PersonTelephoneNumber", "PersonMail", "PersonManager_Name"]) }, static: { shape: "icon", icon: { face: "'Font Awesome 5 Free'", weight: "bold", // Font Awesome 5 doesn't work properly unless bold. code: "\uf1ad", size: 100, color: "rgb(11,27,131)", }, }, } }, Team: { label: "TeamName", caption: true, [NeoVis.NEOVIS_ADVANCED_CONFIG]: { cypher: { value: "MATCH (n) WHERE id(n) = $id RETURN n.size" }, function: { title: NeoVis.objectToTitleHtml, title: (props) => NeoVis.objectToTitleHtml(props, ["TeamName"]) }, static: { shape: "icon", icon: { face: "'Font Awesome 5 Free'", weight: "900", // Font Awesome 5 doesn't work properly unless bold. code: "\uf0c0", size: 75, color: "#f57842", }, }, } } }, relationships: { "MANAGES": { caption: true, label: "name", group: "community", [NeoVis.NEOVIS_ADVANCED_CONFIG]: { cypher: { value: "MATCH (n) WHERE id(n) = $id RETURN n.size" }, function: { title: NeoVis.objectToTitleHtml, title: (props) => NeoVis.objectToTitleHtml(props, ["hover"]) }, } }, "IS_EMPLOYEE_OF": { caption: true, label: "name", group: "community", [NeoVis.NEOVIS_ADVANCED_CONFIG]: { cypher: { value: "MATCH (n) WHERE id(n) = $id RETURN n.size" }, function: { title: NeoVis.objectToTitleHtml, title: (props) => NeoVis.objectToTitleHtml(props, ["hover"]) }, } }, "PART_OF": { caption: true, label: "name", group: "community", [NeoVis.NEOVIS_ADVANCED_CONFIG]: { cypher: { value: "MATCH (n) WHERE id(n) = $id RETURN n.size" }, function: { title: NeoVis.objectToTitleHtml, title: (props) => NeoVis.objectToTitleHtml(props, ["hover"]) }, } }, "MEMBER_OF": { caption: true, label: "name", group: "community", [NeoVis.NEOVIS_ADVANCED_CONFIG]: { cypher: { value: "MATCH (n) WHERE id(n) = $id RETURN n.size" }, function: { title: NeoVis.objectToTitleHtml, title: (props) => NeoVis.objectToTitleHtml(props, ["hover"]) }, } } }, initialCypher: "match(p)-[r]-(c) return p,r,c limit 500" }; viz = new NeoVis.default(config); viz.render(); console.log(viz); } |
If you followed along with the code examples above, you should now see the following results when loading your HTML file into your browsers.
Summary
We take pride in creating visualizations for intricate data structures. If you found this blog helpful or require assistance in developing your own graph visualization, whether it's for a more advanced or comprehensive solution, please don't hesitate to reach out to our account management team. We would be glad to explore how we can assist you in presenting your data in impactful and meaningful ways.
Reference Links
Neovis.js: https://github.com/neo4j-contrib/neovis.js
FontAwesome: https://fontawesome.com/
Vis.js: https://visjs.org/
Comments