Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
09-14-2021 07:42 AM
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> © 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.
Solved! Go to Solution.
09-19-2021 02:44 AM
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
09-19-2021 02:44 AM
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
All the sessions of the conference are now available online