Transforming Neo4j Output (in JSON form) for Generating a D3 Tree Visualization

Exploring options to visualize a Hierarchy using a D3 Tree along with additional field details. Currently, unsure how to obtain the necessary JSON for the D3 Tree graph directly from the Java neo4j APIs. Our setup involves Embedded Neo4j and Web-Service layered on top of Neo4j.

In search of guidance and sample Java code that can generate the desired JSON format for the relationship:

Below is an example of the JSON structure and D3 implementation.

Domain Model:

(Switch)-[:CONNECTED_TO]->(Switch)

By utilizing the provided JSON Format (refer below), aiming to create a D3 Tree as shown:

var json = 
     {
  "name": "Start",
  "display" : "Entry",
  "parent": "null",
  "children": [
    {
      "name": "Swith-1",
      "display": "United States of America Entry Swith",
      "parent": "Start",
      "children": [
        {
          "name": "Swith-1.1",
        "display": "IL Entry Swith",
          "parent": "Swith-1",
           "children": [
        {
          "name": "Swith-1.1.1",
        "display": "Chicago Entry Swith",
          "parent": "Swith-1.1"
        },
        {
          "name": "Swith-1.1.2",
        "display": "Springfield Entry Swith",
          "parent": "Swith-1.1"
        }
      ] 
        },
        {
          "name": "Swith-1.2",
        "display": "CA Entry Swith",
          "parent": "Swith-1"
        }
      ]
    },
    {
      "name": "Swith-2",
      "display": "External gateways",
      "parent": "Start"
    }
  ]
};

var width = 700;
var height = 650;
var maxLabel = 150;
var duration = 500;
var radius = 5;
    
var i = 0;
var root;

var tree = d3.layout.tree()
    .size([height, width]);

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.y, d.x]; });

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
        .append("g")
        .attr("transform", "translate(" + maxLabel + ",0)");

root = json;
root.x0 = height / 2;
root.y0 = 0;

root.children.forEach(collapse);

function update(source) 
{
    // Compute the new tree layout.
    var nodes = tree.nodes(root).reverse();
    var links = tree.links(nodes);

    // Normalize for fixed-depth.
    nodes.forEach(function(d) { d.y = d.depth * maxLabel; });

    // Update the nodes…
    var node = svg.selectAll("g.node")
        .data(nodes, function(d){ 
            return d.id || (d.id = ++i); 
        });

    // Enter any new nodes at the parent's previous position.
    var nodeEnter = node.enter()
        .append("g")
        .attr("class", "node")
        .attr("transform", function(d){ return "translate(" + source.y0 + "," + source.x0 + ")"; })
        .on("click", click);

    nodeEnter.append("circle")
        .attr("r", 0)
        .style("fill", function(d){ 
            return d._children ? "lightsteelblue" : "white"; 
        });

    nodeEnter.append("text")
        .attr("x", function(d){ 
            var spacing = computeRadius(d) + 5;
            return d.children || d._children ? -spacing : spacing; 
        })
        .attr("dy", "3")
        .attr("text-anchor", function(d){ return d.children || d._children ? "end" : "start"; })
        .text(function(d){ return d.name; })
        .style("fill-opacity", 0);

    // Transition nodes to their new position.
    var nodeUpdate = node.transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

    nodeUpdate.select("circle")
        .attr("r", function(d){ return computeRadius(d); })
        .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

    nodeUpdate.select("text").style("fill-opacity", 1);

    // Transition exiting nodes to the parent's new position.
    var nodeExit = node.exit().transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
        .remove();

    nodeExit.select("circle").attr("r", 0);
    nodeExit.select("text").style("fill-opacity", 0);

    // Update the links…
    var link = svg.selectAll("path.link")
        .data(links, function(d){ return d.target.id; });

    // Enter any new links at the parent's previous position.
    link.enter().insert("path", "g")
        .attr("class", "link")
        .attr("d", function(d){
            var o = {x: source.x0, y: source.y0};
            return diagonal({source: o, target: o});
        });

    // Transition links to their new position.
    link.transition()
        .duration(duration)
        .attr("d", diagonal);

    // Transition exiting nodes to the parent's new position.
    link.exit().transition()
        .duration(duration)
        .attr("d", function(d){
            var o = {x: source.x, y: source.y};
            return diagonal({source: o, target: o});
        })
        .remove();

    // Stash the old positions for transition.
    nodes.forEach(function(d){
        d.x0 = d.x;
        d.y0 = d.y;
    });
}

function computeRadius(d)
{
    if(d.children || d._children) return radius + (radius * nbEndNodes(d) / 10);
    else return radius;
}

function nbEndNodes(n)
{
    nb = 0;    
    if(n.children){
        n.children.forEach(function(c){ 
            nb += nbEndNodes(c); 
        });
    }
    else if(n._children){
        n._children.forEach(function(c){ 
            nb += nbEndNodes(c); 
        });
    }
    else nb++;
    
    return nb;
}

function click(d)
{
    if (d.children){
        d._children = d.children;
        d.children = null;
    } 
    else{
        d.children = d._children;
        d._children = null;
    }
    update(d);
}

function collapse(d){
    if (d.children){
        d._children = d.children;
        d._children.forEach(collapse);
        d.children = null;
    }
}

update(root);
html{
    font: 10px sans-serif;
}

svg{
    border: 1px solid silver;
}

.node{
    cursor: pointer;
}

.node circle{
    stroke: steelblue;
    stroke-width: 1.5px;
}

.link{
    fill: none;
    stroke: lightgray;
    stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id=tree></div>

Answer №1

In my experience with node.js, the process should be similar in Java as both use the same CIPHER query syntax.

You can view an example of the final outcome here:

To access the nodes and links, refer to my source code:
https://github.com/paradite/Graphpedia/blob/master/models/term.js

Specifically look at the functions

Term.prototype.getOutgoingAndOthers
and Term.get (which utilizes the neo4j npm package, but you have access to the source code)

The cipher query for retrieving the outgoing links for a node is:

//Construct neo4j raw query
var query = [
    'MATCH (term:Term), (other:Term)',
    'OPTIONAL MATCH (term) -[relall]-> (other)',
    'WHERE ID(term) = {termId}',
    'RETURN other, COUNT(relall), type(relall)',
    // COUNT(rel) is a workaround for 1 or 0, where 1 indicates there is an outgoing relationship.
].join('\n')

For converting them into d3.js objects suitable for utilization in a tree layout, check out:
https://github.com/paradite/Graphpedia/blob/master/routes/terms.js#L108
Refer to the exports.show function.

Answer №2

After a bit of struggle, I've managed to figure it out. Here's the raw code that needs some cleaning up:

Main Logic....

@GET
@javax.ws.rs.Path("/getTree/{name}/")
@Produces({ "application/json" })
public Response getTree(@PathParam("name") final String name, @Context final GraphDatabaseService db)
        throws IOException {

    System.out.println(" Attribute Name To SearchByName: " + name);

    HashMap<Long, TreeNode> treeNodeMap = new HashMap<Long, TreeNode>();

    TreeNode rootNode = null;

    String attrubute = "name";

    try (Transaction tx = db.beginTx()) {
        final Node swtch = db.findNode(Labels.Switch, attrubute, name);
        if (swtch != null) {
            TraversalDescription td = db.traversalDescription().depthFirst()
                    .expand(PathExpanders.forTypeAndDirection(RelationshipTypes.CONNECTED_TO, Direction.OUTGOING));

            for (Path directoryPath : td.traverse(swtch)) 
            {
                Iterable<Relationship> connectedTos = directoryPath.endNode().getRelationships(Direction.OUTGOING,RelationshipTypes.CONNECTED_TO);

                if (connectedTos != null) 
                {
                    for(Relationship connectedTo : connectedTos)
                    {
                        //For the Current Relationship
                        //get the start node as parent
                        Node parentNode = connectedTo.getStartNode();
                        Long parentNodeId = parentNode.getId();
                        TreeNode parentTreeNode = treeNodeMap.get(parentNodeId);

                        if(parentTreeNode == null)
                        {
                            ////Populate the Parent Details
                            parentTreeNode = new TreeNode(parentNode.getProperty("name", "NoName").toString());
                            if(rootNode == null)
                                rootNode = parentTreeNode;
                            //Add to the linear HashMap for subsequent searches
                            treeNodeMap.put(parentNodeId, parentTreeNode);
                        }

                        //For the Current Relationship get the end node Children
                        Node childNode = connectedTo.getEndNode();
                        Long childNodeId = childNode.getId();
                        TreeNode childTreeNode = treeNodeMap.get(childNodeId);

                        if(childTreeNode == null)
                        {
                            childTreeNode = new TreeNode(childNode.getProperty("name", "NoName").toString());
                            treeNodeMap.put(childNodeId, childTreeNode);
                            parentTreeNode.setChildren(childTreeNode);
                        }
                    }
            }
        }
        tx.success();
    }
    }
    /*
     System.out.println("JSON: " + objectMapper.writeValueAsString(rootNode));
     System.out.println("LinearHashMap: " + objectMapper.writeValueAsString(treeNodeMap ));
     */

    return Response.ok().entity(objectMapper.writeValueAsString(rootNode)).build();

}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Implement a feature that adds a circle element when an image is clicked in a React application

I am attempting to create a program that allows users to add circles to an image by clicking on it. Essentially, when the user clicks at coordinates (x,y), a circle with a radius of 10 will appear at that location. I am exploring ways to implement meta-pro ...

Splitting JSON files according to the number of rows

UPDATE: I am in the process of converting my CSV file, for instance, 19-01-2018.csv which contains around 1000 rows to a JSON file named 19-01-2018.json One of the requirements is that the JSON files that are created should be split into chunks of 30 rows ...

Storing and accessing nested JSON arrays in Laravel 6: A guide

I am currently facing an issue with retrieving JSON data stored in a MySQL database. Here is how my migration is set up: Schema::create('items', function (Blueprint $table) { $table->bigIncrements('id'); $table->s ...

Is it possible that Entity Framework and Linq are struggling to deserialize the object in question?

I am working with 3 classes: public class Widget { public int Id {get; set;} public string Name {get; set;} public List<WidgetConfig> Configs {get; set;} public LabelConfig LabelConfig {get; set;} } public class WidgetConfig { ...

The Typescript error message states: "Unable to access 'add' property of null"

I am trying to implement a role command in discord.js v13.6, but I am facing an issue where it cannot read the line related to adding a role. The error message displayed is Typescript: Cannot read properties of null (reading "add"). How can I resolve thi ...

The parameter passed to the parser as an argument of [object Object] was found to be an invalid GraphQL DocumentNode. It is recommended to utilize 'graphql-tag' or an alternative approach to properly convert the parameter

Whenever I attempt to utilize useQuery from @apollo/client, the following error crops up: "Invariant Violation: Argument of [object Object] passed to parser was not a valid GraphQL DocumentNode. You may need to use 'graphql-tag' or another ...

eliminate the script and HTML comment from a designated division

Here is the default code that I need to modify using JavaScript to remove scripts, comments, and some text specifically from this div: I came across this script that removes all scripts, but I only want to remove specific ones from this div: $('scri ...

Encountering an error when attempting to access undefined property while using a method as a callback

Exploring OOP and angular is new to me. I am currently trying to implement a reusable table with pagination that triggers an API request when the page changes (pagination within the table component). The issue arises when I attempt to access my method usi ...

Guide to incorporating d.ts file for enhancing intellisense in VS Code using a method akin to the NPM approach

In my nodejs project (in JS), I find myself relying heavily on node global variables. Despite receiving warnings against using globals, everything works well except for one thing: The lack of intellisense for globals. Every time I need to use a global fu ...

I can't figure out why I'm receiving undefined even though all the variables are populated with the necessary

I have been working on a project that involves implementing email and password authentication using Firebase. However, I encountered an error when the user submits their credentials: FirebaseError: Firebase: Error (auth/admin-restricted-operation). at ...

Angular JS plugin that locates image links within plain text and transforms them into HTML <img> tags using JavaScript

I am faced with a situation in which I need to show images if the chat messages contain image links, but currently I am only displaying the links as text. One idea I had was to check for the lastIndexOf('.') and extract the file extension to mat ...

JavaScript Closures can utilize a shared global setting object or be passed as an argument to individual functions

Looking for advice on the use of a global "settings object" in relation to JavaScript closures. Should I access it from within functions in the global scope or pass the object every time a function requires access? To provide context, here's a mockup ...

"Angular encountering an error with converting a string to a date due to

I have a date stored as a string in the format '2022-07-15T09:29:24.958922Z[GMT]'. When I try to convert it to a date object, I am getting an error saying it is an invalid date. Can anyone help me with this issue? const myDate = response; / ...

Fluctuating issues with deserialization in REST WCF Service on Windows Communication Foundation in .NET version 4.5

After upgrading our project to Visual Studio 2012 and targeting the .NET framework 4.5 instead of 4.0, we have been encountering intermittent serialization exception issues when trying to return data from our service methods. To debug the problem, we have ...

The base64 string is not correctly displaying the image in the JSON format

I am trying to upload an image to a server using a JSON POST webservice. I have encoded the image to base 64 string, but for some reason, it is not being posted on the server along with other data. The webservice seems to be working fine as the image uploa ...

The PHP code fails to output the JSON-formatted array

I'm facing an issue with a php script that is supposed to display multiple datasets in a json encoded string, but instead, the page appears blank. <?php $con = mysqli_connect("SERVER", "USER", "PASSWORD", "DATABASE"); if(!$sql = "SELECT news.titl ...

Merge information from various sources using ajax

Currently, I have a single ajax request that retrieves data from an API and uses it to generate a table. Now, I'm looking to modify the code so that it can retrieve data from two different URLs and merge them into the same table (retTable). Below is ...

Searching for the top 10 documents with the highest value in a specific field using mongoose

I am working with a mongoose model that includes a field called played_frequency. How can I retrieve the first 10 documents from all those available, based on the highest value in the play frequency? Here is the interface of my model: export interface ITr ...

Hiding icons in a jquery datatable's table

I am currently developing an inline editing feature, and I would like to display a green checkmark in a column cell to indicate that the record has been updated. However, I need to hide it initially. This is how it looks at the moment: https://i.sstatic. ...

Is it a mistake to gather errors together and send them to the promise resolve in JavaScript?

When processing a list in a loop that runs asynchronously and returns a promise, exiting processing on exception is not desired. Instead, the errors are aggregated and passed to the resolve callback in an outer finally block. I am curious if this approach ...