Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
02-25-2019 02:52 AM
Hi,
As i believe Neovis.js is made from vis.js graph network, i tried to implement built in interactiveButtons option in the vis options . But it is not wokring. Can you please help in implementing a physical zoom-in and zoom-out button in Neovis.js. The code snippet is -
onCompleted: function () {
session.close();
let options = {
interaction: {
navigationButtons: true,
keyboard: true
},
nodes: {
shape: 'dot',
font: {
size: 26,
strokeWidth: 7
},
scaling: {
label: {
enabled: true
}
}
},
edges: {
arrows: {
to: {enabled: self._config.arrows || false } // FIXME: handle default value
},
length: 200
},
layout: {
improvedLayout: false,
hierarchical: {
enabled: self._config.hierarchical || false,
sortMethod: self._config.hierarchical_sort_method || "hubsize"
}
},
};
//code
}
vis.js reference - http://visjs.org/docs/network/interaction.html#
Awaiting a reply.
02-25-2019 09:03 PM
Thank you. I successfully implemented a manual zoom-in, zoom-out button in the Neovis.js screen by inserting interaction option -
onCompleted: function () {
session.close();
let options = {
nodes: {
shape: 'dot',
font: {
size: 26,
strokeWidth: 7
},
scaling: {
label: {
enabled: true
}
}
},
interaction: {
hover: true,
keyboard: {
enabled: true,
bindToWindow: false
},
navigationButtons: true,
tooltipDelay: 1000000,
hideEdgesOnDrag: true,
zoomView: false
},
edges: {
arrows: {
to: {enabled: self._config.arrows || false } // FIXME: handle default value
},
length: 200
},
layout: {
improvedLayout: false,
hierarchical: {
enabled: self._config.hierarchical || false,
sortMethod: self._config.hierarchical_sort_method || "hubsize"
}
},
};
var container = self._container;
self._data = {
"nodes": new vis.DataSet(Object.values(self._nodes)),
"edges": new vis.DataSet(Object.values(self._edges))
}
console.log(self._data.nodes);
console.log(self._data.edges);
self._network = new vis.Network(container, self._data, options);
console.log("completed");
setTimeout(() => { self._network.stopSimulation(); }, 10000);
self._events.generateEvent(CompletionEvent, {record_count: recordCount});
},
onError: function (error) {
console.log(error);
}
})
};
Reference link - https://github.com/almende/vis/issues/3021
02-26-2019 01:31 AM
Perhaps you can send a pull request to neovis
02-26-2019 02:01 AM
Okay. I have implemented a spinner also inside neovis, so that till the data loads , we get a spinner asking the user to wait.
02-27-2019 08:25 PM
Hi Michael,
I am unable to push it into the neovis.js git account. I am unable to do that because -
I guess the master is empty that's why.
Please suggest.
02-28-2019 04:55 PM
You need to make a fork and do the edits in your fork and then send a pull request.
07-18-2019 10:21 PM
HI micheal,
My neovis.js code with updated zoom-in and zoom out and spinner features are given below -
'use strict';
import * as neo4j from '../vendor/neo4j-javascript-driver/lib/browser/neo4j-web.js';
import * as vis from '../vendor/vis/dist/vis-network.min.js';
import '../vendor/vis/dist/vis-network.min.css';
import { defaults } from './defaults';
import { EventController, CompletionEvent } from './events';
//self
//import {Spinner} from 'spin.js';
export default class NeoVis {
/**
*
* @constructor
* @param {object} config - configures the visualization and Neo4j server connection
* {
* container:
* server_url:
* server_password?:
* server_username?:
* labels:
*
* }
*
*/
constructor(config) {
console.log(config);
console.log(defaults);
// .encrypted
this._config = config;
this._encrypted = config.unencrypted || defaults['neo4j']['unencrypted'];
this._trust = config.trust || defaults.neo4j.trust;
this._driver = neo4j.v1.driver(config.server_url || defaults.neo4j.neo4jUri, neo4j.v1.auth.basic(config.server_user || defaults.neo4j.neo4jUser, config.server_password || defaults.neo4j.neo4jPassword), {encrypted: this._encrypted, trust: this._trust});
this._query = config.initial_cypher || defaults.neo4j.initialQuery;
this._nodes = {};
this._edges = {};
this._data = {};
this._network = null;
this._container = document.getElementById(config.container_id);
this._events = new EventController();
}
_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 n
* @returns {{}}
*/
buildNodeVisObject(n) {
var self = this;
let node = {};
let label = n.labels[0];
let captionKey = this._config && this._config.labels && this._config.labels[label] && this._config.labels[label]['caption'],
sizeKey = this._config && this._config.labels && this._config.labels[label] && this._config.labels[label]['size'],
sizeCypher = this._config && this._config.labels && this._config.labels[label] && this._config.labels[label]['sizeCypher'],
communityKey = this._config && this._config.labels && this._config.labels[label] && this._config.labels[label]['community'];
node['id'] = n.identity.toInt();
// node size
if (sizeCypher) {
// use a cypher statement to determine the size of the node
// the cypher statement will be passed a parameter {id} with the value
// of the internal node id
let session = this._driver.session();
session.run(sizeCypher, {id: neo4j.v1.int(node['id'])})
.then(function(result) {
result.records.forEach(function(record) {
record.forEach(function(v,k,r) {
if (typeof v === "number") {
self._addNode({id: node['id'], value: v});
} else if (v.constructor.name === "Integer") {
self._addNode({id: node['id'], value: v.toNumber()})
}
})
})
})
} else if (typeof sizeKey === "number") {
node['value'] = sizeKey;
} else {
let sizeProp = n.properties[sizeKey];
if (sizeProp && typeof sizeProp === "number") {
// propety value is a number, OK to use
node['value'] = sizeProp;
} else if (sizeProp && typeof sizeProp === "object" && sizeProp.constructor.name === "Integer") {
// property value might be a Neo4j Integer, check if we can call toNumber on it:
if (sizeProp.inSafeRange()) {
node['value'] = sizeProp.toNumber();
} else {
// couldn't convert to Number, use default
node['value'] = 1.0;
}
} else {
node['value'] = 1.0;
}
}
// node caption
if (typeof captionKey === "function") {
node['label'] = captionKey(n);
}
else {
node['label'] = n.properties[captionKey] || label || "";
}
// community
// behavior: color by value of community property (if set in config), then color by label
if (!communityKey) {
node['group'] = label;
} else {
try {
if (n.properties[communityKey]) {
node['group'] = n.properties[communityKey].toNumber() || label || 0; // FIXME: cast to Integer
}
else {
node['group'] = 0;
}
} catch(e) {
node['group'] = 0;
}
}
// set all properties as tooltip
node['title'] = "";
for (let key in n.properties) {
node['title'] += "<strong>" + key + ":</strong>" + " " + n.properties[key] + "<br>";
}
return node;
}
/**
* Build edge object for vis from a neo4j Relationship
* @param r
* @returns {{}}
*/
buildEdgeVisObject(r) {
let weightKey = this._config && this._config.relationships && this._config.relationships[r.type] && this._config.relationships[r.type]['thickness'],
captionKey = this._config && this._config.relationships && this._config.relationships[r.type] && this._config.relationships[r.type]['caption'];
let edge = {};
edge['id'] = r.identity.toInt();
edge['from'] = r.start.toInt();
edge['to'] = r.end.toInt();
// hover tooltip. show all properties in the format <strong>key:</strong> value
edge['title'] = "";
for (let key in r.properties) {
edge['title'] += "<strong>" + key + ":</strong>" + " " + r.properties[key] + "<br>";
}
// set relationship thickness
if (weightKey && typeof weightKey === "string") {
edge['value'] = r.properties[weightKey];
} else if (weightKey && typeof weightKey === "number") {
edge['value'] = weightKey;
} else {
edge['value'] = 1.0;
}
// set caption
if (typeof captionKey === "boolean") {
if (!captionKey) {
edge['label'] = "";
} else {
edge['label'] = r.type;
}
} else if (captionKey && typeof captionKey === "string") {
edge['label'] = r.properties[captionKey] || "";
} else {
edge['label'] = r.type;
}
return edge;
}
// public API
render() {
// connect to Neo4j instance
// run query
var opts = {
lines: 15, // The number of lines to draw
length: 20, // The length of each line
width: 12, // The line thickness
radius: 16, // The radius of the inner circle
scale: 1, // Scales overall size of the spinner
corners: 1, // Corner roundness (0..1)
color: '#456789', // CSS color or array of colors
fadeColor: 'transparent', // CSS color or array of colors
speed: 1, // Rounds per second
rotate: 0, // The rotation offset
animation: 'spinner-line-fade-more', // The CSS animation name for the lines
direction: 1, // 1: clockwise, -1: counterclockwise
zIndex: 2e9, // The z-index (defaults to 2000000000)
className: 'spinner', // The CSS class to assign to the spinner
top: '500%', // Top position relative to parent
left: '720%', // Left position relative to parent
shadow: '0 0 1px transparent', // Box-shadow for the lines
position: 'absolute' // Element positioning
};
//Spinner viz
var target = document.getElementById('viz');
var spinner = new Spinner(opts).spin(target);
//Spinner viz ends
// //----------- Machine-merger spinner ends -----------
// //============ Duplicity spinner ================
var targetCollection = document.getElementById('vizCollection');
var spinner =new Spinner(opts).spin(targetCollection);
// Spinner ends.
// var spinner = new Spinner().spin();
// target.appendChild(spinner.el);
let self = this;
let recordCount = 0;
let session = this._driver.session();
session
.run(this._query, {limit: 30})
.subscribe({
onNext: function (record) {
recordCount++;
console.log("CLASS NAME");
console.log(record.constructor.name);
console.log(record);
record.forEach(function(v, k, r) {
console.log("Constructor:");
console.log(v.constructor.name);
if (v.constructor.name === "Node") {
let node = self.buildNodeVisObject(v);
try {
self._addNode(node);
} catch(e) {
console.log(e);
}
}
else if (v.constructor.name === "Relationship") {
let edge = self.buildEdgeVisObject(v);
try {
self._addEdge(edge);
} catch(e) {
console.log(e);
}
}
else if (v.constructor.name === "Path") {
console.log("PATH");
console.log(v);
let n1 = self.buildNodeVisObject(v.start);
let n2 = self.buildNodeVisObject(v.end);
self._addNode(n1);
self._addNode(n2);
v.segments.forEach((obj) => {
self._addNode(self.buildNodeVisObject(obj.start));
self._addNode(self.buildNodeVisObject(obj.end))
self._addEdge(self.buildEdgeVisObject(obj.relationship))
});
}
else if (v.constructor.name === "Array") {
v.forEach(function(obj) {
console.log("Array element constructor:");
console.log(obj.constructor.name);
if (obj.constructor.name === "Node") {
let node = self.buildNodeVisObject(obj);
try {
self._addNode(node);
} catch(e) {
console.log(e);
}
}
else if (obj.constructor.name === "Relationship") {
let edge = self.buildEdgeVisObject(obj);
try {
self._addEdge(edge);
} catch(e) {
console.log(e);
}
}
});
}
})
},
onCompleted: function () {
session.close();
//target.appendChild(spinner.el);
let options = {
// autoResize: true,
// height: '600',
// width: '100%',
nodes: {
shape: 'dot',
font: {
size: 26,
strokeWidth: 7
},
scaling: {
label: {
enabled: true
}
}
},
interaction: {
hover: true,
keyboard: {
enabled: true,
// bindToWindow: false
bindToWindow: false
},
navigationButtons: true,
tooltipDelay: 1000000,
hideEdgesOnDrag: true,
// zoomView: false
zoomView: true
},
edges: {
arrows: {
to: { enabled: self._config.arrows || false } // FIXME: handle default value
},
length: 200
},
layout: {
improvedLayout: false,
hierarchical: {
enabled: self._config.hierarchical || false,
sortMethod: self._config.hierarchical_sort_method || "hubsize"
}
},
};
var container = self._container;
self._data = {
"nodes": new vis.DataSet(Object.values(self._nodes)),
"edges": new vis.DataSet(Object.values(self._edges))
}
console.log(self._data.nodes);
console.log(self._data.edges);
self._network = new vis.Network(container, self._data, options);
console.log("completed");
setTimeout(() => { self._network.stopSimulation(); }, 10000);
//spinner
var target = document.getElementById('viz');
var spinner =new Spinner(opts).stop(target);
//Machine-Merger spinner
//----------- Machine-merger spinner ends -----------
//============ Duplicity spinner ================
var targetCollection = document.getElementById('vizCollection');
var spinner =new Spinner(opts).stop(targetCollection);
//================ Duplicity spinner ends ===================
// -------------------- spinner ends -----------------------
self._events.generateEvent(CompletionEvent, {record_count: recordCount});
},
onError: function (error) {
console.log(error);
}
})
};
/**
* Clear the data for the visualization
*/
clearNetwork() {
this._nodes = {}
this._edges = {};
this._network.setData([]);
}
// Zoom In and Zoom Out functions.
zoomin() {
var myImg = document.getElementById("viz");
var currWidth = myImg.clientWidth;
if (currWidth == 2500) return false;
else {
myImg.style.width = (currWidth + 100) + "px";
}
}
zoomout() {
var myImg = document.getElementById("viz");
var currWidth = myImg.clientWidth;
if (currWidth == 100) return false;
else {
myImg.style.width = (currWidth - 100) + "px";
}
}
/**
*
* @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) {
};
/**
* Fetch live data form the server and reload the visualization
*/
reload() {
this.clearNetwork();
this.render();
};
/**
* Stabilize the visuzliation
*/
stabilize() {
this._network.stopSimulation();
console.log("Calling stopSimulation");
}
/**
* Execute an arbitrary Cypher query and re-render the visualization
* @param query
*/
renderWithCypher(query) {
//self._config.initial_cypher = query;
this.clearNetwork();
this._query = query;
this.render();
};
}
in index.html
<!-- spin.js library -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/spin.js/1.2.7/spin.min.js" />
All the sessions of the conference are now available online