cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.

Neovis_simplified

Hi all,
it's a log time I use Javascript+Neo4j+NeoVis+visjs to display my Hobby-Ontology for calculating machines.
Since my whole script is a bit cluttered, I thought that a simplified version would serve as an explanation how a simplifed NeoVis could be used together with some visjs code. For reason of compatibility, I had to tweak the code in order to cirumvent the trust problem when using neo4j+s schema. You can call the following HTML as a file in your browser. It is calling a sandbox, which can easily adapted to another neo4j-db.
The only interaction is a HOLD on a node which displays 10 connected:

<!DOCTYPE html>
<html>
<head>
        <title>simple Vis Network + Neo4j with Movie data; author:WJI </title>
	<!-- code from visjs,cdr or github/neo4j /neovis -->
    <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
	<script src="https://unpkg.com/neo4j-driver"></script>
	<style type="text/css">
	            #mynetwork {
                float:left;
                width: 99%;
                height: 600px;
                border: 1px solid lightgray;
		.box{
			inline-size: 150px;
			word-break: break-all;
			overflow-wrap: break-word;
		}
            }
	</style>
</head>
<body onload="init_simplified()">
        <div id="mynetwork"></div>
<center><footer><i> &copy; W.J.Irler</i> based on:<a href="https://github.com/neo4j-contrib/neovis.js/">NeoVis</a> uses:<a href="https://visjs.org/">Visjs</a> and <a href="https://neo4j.com/">Neo4j</a></footer></center>
<script  type="text/javascript" >
//global viz,network,data,config;
			var events; // for events.js
			var viz;
/*needed*/	var network;   /// network object
/*needed*/	var container = document.getElementById("mynetwork");
			var data;
			var config_neo4j,config_vis;
            function init_simplified() {  /// at the beginning
			config_neo4j={
                container_id: "mynetwork",
                server_url: "neo4j+s://03420f736f8556652e7e7babc6291e43.neo4jsandbox.com:7687",
                server_user: "neo4j",
				server_password: "masts-drug-detection",
                initial_cypher: "MATCH (n) return n limit 3"
			}
				events = new EventController();	//see events.js !!
            viz = new NeoVis_simplified({
				container_id: config_neo4j.container_id,
				server_database: config_neo4j.server_database,
				server_user: config_neo4j.server_user,
				server_password: config_neo4j.server_password,
			});   // must be without trust!
console.log("dummy viz=NeoVis_simplified(...) created without trust and driver");
			config_vis={
					nodes: {
					  shape: "ellipse",
					  font: {
						  size:16,
						  strokeWidth: 5
					  },
					  size: 50,
						widthConstraint: {
							maximum: 300
						},
						heightConstraint: {
							minimum: 20
						},
						mass: 2,
					  scaling: {
						min: 40,
						max: 60,
						label: {
							enabled: false
						}
					  },
						labelHighlightBold: true,
						margin: 5,
						brokenImage: "./WJI2.png",
						shapeProperties:{useImageSize: false} 
					},
				    edges: {
					    arrows: {
							to: {
								enabled: true,
								type: 'arrow',
								scaleFactor: 0.25
							}
						},
						arrowStrikethrough: true,
						color: {
							  opacity: 0.5
						},
						font: {
							  align: 'middle'
						}
					}
				}
console.log("dummy initial vis network");			
				let data = {nodes: new vis.DataSet([{id:0,label:"root",raw:{} }]),
							edges: new vis.DataSet([])
					};
			viz._data = data;			
			viz._network = new vis.Network(container, data, config_vis);
// only now driver!
			viz._driver= neo4j.driver(config_neo4j.server_url,
			neo4j.auth.basic(config_neo4j.server_user, config_neo4j.server_password),{}
			);
console.log("driver set after new new vis.Network");
            viz.registerOnEvent('completed', render_completed_simplified);

console.log("renderWithCypher");
				viz.renderWithCypher(config_neo4j.initial_cypher);
console.log("rendered");
           }   ///init_simplified
       function render_completed_simplified(param){   /// called by event in neovis
console.log("-----------render completed with", (param.record_count)+ " records------------text: "+param.text); 
			if(param.record_count===0){
alert("no result!");
				return;
			}

		  try{
		    let nn =viz._data.nodes.length;
			let ne =viz._data.edges.length;
console.log("completed with nodes:"+nn+", edges:"+ne);
		  }catch(e){
console.log("render_completed 1st ERROR "+e);
		  }
		  try{
			network=viz._network;
			viz.nodes.forEach(item => {
				let node={};
			if(item.raw.properties){
				node=item.raw.properties;
				node.id=item.id;
				node.label=item.raw.properties.name ||  item.raw.properties.title;
				node.title=item.raw.properties.plot || node.label;
				node.labels=item.raw.labels;
				node.connections=(node.connections && node.connections.low) ? node.connections.low : 0;
				node.group=(node.group &&node.group.low) ? node.group.low : 0;
			}else{		
				node=item;
			}
			 if(!(node.shape==="image" || node.shape==="database")){
				  node.shape="ellipse";
			 }
			 network.body.data.nodes.getDataSet().update(node);
			});
			
			viz._data.nodes.getDataSet().forEach(edge => {
				edge.arrows={to:{enabled: true,
								type: 'arrow',
								scaleFactor: 0.25}
							};
			 network.body.data.edges.getDataSet().update(edge);
			});
			on_handlers_simplified(network);

		  }catch(e){
console.log("render_completed 2nd ERROR "+e);
		  }
		}	///render_completed
		
       function on_handlers_simplified(network){   /// called by event in neovis
network.on("hold", function (params) {
			  if(params.nodes.length>0){
						hold_simplified(params);
			  }
});
		}	///on_handlers_simplified
	function hold_simplified(params){   /// by on.hold
     try{
        params.event.stopPropagation();
        params.event.preventDefault();
    }catch(e){}
      let nodeId = params.nodes[0] || 0;
			renderWithCypher_simplified(nodeId);
  }   ///hold_simplified
	function renderWithCypher_simplified(nodeId){
		let query_simplified;  //display query
            query_simplified = "match (n) where id(n)="+nodeId+" with n";
            query_simplified += " optional match (n)-[r]-(m)";
            query_simplified += " return n,r,m order by toInteger(id(m)*rand()) limit 10";
console.log("cypher-query:"+query_simplified);
				viz.renderWithCypher(query_simplified);
	}   ///renderWithCypher_simplified
	

//events.js for Browser (from NeoVis, without exports as for node.js):
/*export*/ const CompletionEvent = 'completed';
			 const ClickNodeEvent = 'clickNode';
			 const ClickEdgeEvent = 'clickEdge';
			 const ErrorEvent = 'error';
           
/*export*/ class EventController {

	constructor() {
		this._handlers = {
			[CompletionEvent]: [],
            [ErrorEvent]: [],
            [ClickNodeEvent]: [],
            [ClickEdgeEvent]: [],
		};
	}

	/**
	 *
	 * @param {string} eventType - Type of the event to be handled
	 * @param {callback} handler - Handler to manage the event
	 */
	register(eventType, handler) {
//console.log("register: "+eventType);
		if (this._handlers[eventType] === undefined) {
			throw new Error('Unknown event: ' + eventType);
		}

		this._handlers[eventType].push(handler);
	}

	/**
	 *
	 * @param {string} eventType - Type of the event generated
	 * @param {object} values - Values associated to the event
	 */
	generateEvent(eventType, values) {
//console.log("generateEvent: "+eventType);
		if (this._handlers[eventType] === undefined) {
			throw new Error('Unknown event: ' + eventType);
		}

		for (const handler of this._handlers[eventType]) {
			handler(values);
		}
	}
}

/// neoVis simplified(WJI)
  class NeoVis_simplified {
	_nodes = {};
	_edges = {};
	_data = {};
	_network = null;
	_events = new EventController();

	/**
	 * Get current vis nodes from the graph
	 */
	get nodes() {
		return this._data.nodes;
	}

	/**
	 * Get current vis edges from the graph
	 */
	get edges() {
		return this._data.edges;
	}

	/**
	 *
	 * @constructor
	 * @param {object} config - configures the visualization and Neo4j server connection
	 *  {
	 *    container:
	 *    server_url:
	 *    server_password?:
	 *    server_username?:
	 *    server_database?:
	 *
	 */
	constructor(config) {
		this._init(config);

		this._consoleLog(config);
	}

	_consoleLog(message, level = 'log') {
		if (level !== 'log' || this._config.console_debug) {
			// eslint-disable-next-line no-console
			console[level](message);
		}
	}

	_init(config) {
		this._config = config;
		this._encrypted = config.encrypted;
		this._trust = config.trust;
		this._driver = this._driver && neo4j.driver(  /// create perhaps neo4jVis without driver
			config.server_url,
			neo4j.auth.basic(config.server_user, config.server_password),
			{
				encrypted: this._encrypted,
				trust: this._trust,
				maxConnectionPoolSize: 100,
				connectionAcquisitionTimeout: 10000,
			}
		);
		this._database = config.server_database;
		this._query = config.initial_cypher;
		this._container = document.getElementById(config.container_id);
	}

	_addNode(node) {
		this._nodes[node.id] = node;
	}

	_addEdge(edge) {
		this._edges[edge.id] = edge;
	}

	/**
	 * Build node object for vis from a neo4j Node
	 * FIXME: use config
	 * FIXME: move to private api
	 * @param neo4jNode
	 * @returns {{}}
	 */
	async buildNodeVisObject(neo4jNode) {
		let node = {};

		node.id = neo4jNode.identity.toInt();
		node.raw = neo4jNode;

		return node;
	}

	/**
	 * Build edge object for vis from a neo4j Relationship
	 * @param r
	 * @returns {{}}
	 */
	buildEdgeVisObject(r) {

		let edge = {};
		edge.id = r.identity.toInt();
		edge.from = r.start.toInt();
		edge.to = r.end.toInt();
		edge.raw = r;

		edge.label = r.type;
		return edge;
	}

	// public API

	render(query) {

		// connect to Neo4j instance
		// run query
		let recordCount = 0;
		const _query = query || this._query;
		let session = this._driver.session(this._database && { database: this._database });
		const dataBuildPromises = [];
		session
			.run(_query, { limit: 30 })
			.subscribe({
				onNext: (record) => {
					recordCount++;

					this._consoleLog('CLASS NAME');
					this._consoleLog(record && record.constructor.name);
					this._consoleLog(record);

					const dataPromises = Object.values(record.toObject()).map(async (v) => {
						this._consoleLog('Constructor:');
						this._consoleLog(v && v.constructor.name);
						if (v instanceof neo4j.types.Node) {
							let node = await this.buildNodeVisObject(v);
							try {
								this._addNode(node);
							} catch (e) {
								this._consoleLog(e, 'error');
							}

						} else if (v instanceof neo4j.types.Relationship) {
							let edge = this.buildEdgeVisObject(v);
							this._addEdge(edge);

						} else if (v instanceof neo4j.types.Path) {
							this._consoleLog('PATH');
							this._consoleLog(v);
							let startNode = await this.buildNodeVisObject(v.start);
							let endNode = await this.buildNodeVisObject(v.end);

							this._addNode(startNode);
							this._addNode(endNode);

							for (let obj of v.segments) {
								this._addNode(await this.buildNodeVisObject(obj.start));
								this._addNode(await this.buildNodeVisObject(obj.end));
								this._addEdge(this.buildEdgeVisObject(obj.relationship));
							}

						} else if (v instanceof Array) {
							for (let obj of v) {
								this._consoleLog('Array element constructor:');
								this._consoleLog(obj && obj.constructor.name);
								if (obj instanceof neo4j.types.Node) {
									let node = await this.buildNodeVisObject(obj);
									this._addNode(node);

								} else if (obj instanceof neo4j.types.Relationship) {
									let edge = this.buildEdgeVisObject(obj);

									this._addEdge(edge);
								}
							}
						}
					});
					dataBuildPromises.push(Promise.all(dataPromises));
				},
				onCompleted: async () => {
					await Promise.all(dataBuildPromises);
					session.close();

					if (this._network && this._network.body.data.nodes.length > 0) {
						this._data.nodes.update(Object.values(this._nodes));
						this._data.edges.update(Object.values(this._edges));
					} else {
						let options = {
							nodes: {
								//shape: 'dot',
								font: {
									size: 26,
									strokeWidth: 7
								},
								scaling: {
								}
							},
							edges: {
								arrows: {
									to: { enabled: this._config.arrows || false } // FIXME: handle default value
								},
								length: 200
							},
							layout: {
								improvedLayout: false,
								hierarchical: {
									enabled: this._config.hierarchical || false,
									sortMethod: this._config.hierarchical_sort_method || 'hubsize'
								}
							},
							physics: { // TODO: adaptive physics settings based on size of graph rendered
								// enabled: true,
								// timestep: 0.5,
								// stabilization: {
								//     iterations: 10
								// }

								adaptiveTimestep: true,
								// barnesHut: {
								//     gravitationalConstant: -8000,
								//     springConstant: 0.04,
								//     springLength: 95
								// },
								stabilization: {
									iterations: 200,
									fit: true
								}
							}
						};
						
						this._config.options=this._config.options || options;  ///WJI  ?? not possible
						
						const container = this._container;
						this._data = {
							nodes: new vis.DataSet(Object.values(this._nodes)),
							edges: new vis.DataSet(Object.values(this._edges))
						};

						this._consoleLog(this._data.nodes);
						this._consoleLog(this._data.edges);

						this._network = new vis.Network(container, this._data, this._config.options);
					}
					this._consoleLog('completed');
					setTimeout(
						() => {
							this._network.stopSimulation();
						},
						10000
					);
					this._events.generateEvent(CompletionEvent, { record_count: recordCount });

					let neo4jVis = this;
					this._network.on('click', function (params) {
						if (params.nodes.length > 0) {
							let nodeId = this.getNodeAt(params.pointer.DOM);
							neo4jVis._events.generateEvent(ClickNodeEvent, { nodeId: nodeId, node: neo4jVis._nodes[nodeId] });
						} else if (params.edges.length > 0) {
							let edgeId = this.getEdgeAt(params.pointer.DOM);
							neo4jVis._events.generateEvent(ClickEdgeEvent, { edgeId: edgeId, edge: neo4jVis._edges[edgeId] });
						}
					});
				},
				onError: (error) => {
					this._consoleLog(error, 'error');
					this._events.generateEvent(ErrorEvent, { error_msg: error });
				}
			});
	}

	/**
	 * Clear the data for the visualization
	 */
	clearNetwork() {
		this._neo4jNodes = {};
		this._neo4jEdges = {};
		this._nodes = {};
		this._edges = {};
	  if(this._network)	/// clear only if present (WJI)
		this._network.setData([]);
	}


	/**
	 *
	 * @param {string} eventType Event type to be handled
	 * @param {callback} handler Handler to manage the event
	 */
	registerOnEvent(eventType, handler) {
		this._events.register(eventType, handler);
	}


	/**
	 * Reset the config object and reload data
	 * @param config
	 */
	reinit(config) {
		this._init(config);
		this.render();
	}

	/**
	 * Fetch live data form the server and reload the visualization
	 */
	reload() {
		this.clearNetwork();
		this.render();
	}

	/**
	 * Stabilize the visualization
	 */
	stabilize() {
		this._network.stopSimulation();
		this._consoleLog('Calling stopSimulation');
	}

	/**
	 * Execute an arbitrary Cypher query and re-render the visualization
	 * @param query
	 */
	renderWithCypher(query) {
		// this._config.initial_cypher = query;
		this.clearNetwork();
		this._query = query;
		this.render();
	}

	/**
	 * Execute an arbitrary Cypher query and update the current visualization, retaning current nodes
	 * This function will not change the original query given by renderWithCypher or the inital cypher.
	 * @param query 
	 */
	updateWithCypher(query) {
		this.render(query);
	}

}	

</script>
</body>
<html>

hava a look on editions, because the sandbox expires often, and I try to change sometimes the URL. Otherwise put your own URL.

1 ACCEPTED SOLUTION

Hallo interested,
my sandbox expired, I've created a longer lasting Aura db, please change:
server_url: "neo4j+s://a621e2a8.databases.neo4j.io:7687",
server_password: "M6IhoLvEa9sqx2jb_6DO9tFyFaZu4KJOjiuzRlTOM4g",
enjoy
WJI

View solution in original post

1 REPLY 1

Hallo interested,
my sandbox expired, I've created a longer lasting Aura db, please change:
server_url: "neo4j+s://a621e2a8.databases.neo4j.io:7687",
server_password: "M6IhoLvEa9sqx2jb_6DO9tFyFaZu4KJOjiuzRlTOM4g",
enjoy
WJI

Nodes 2022
Nodes
NODES 2022, Neo4j Online Education Summit

All the sessions of the conference are now available online