VISJS: save manipulated data to json

I am using vis.js to create network graphs on a web page.

My need is to store manipulated graphs to a database in JSON format, exporting the json from the network graph.

I did not find any documentation about it, is it feasible to export vis.js network with manipulated data for storage (in either JSON or in a form convertable into JSON)?

Answers:

Answer

As for data, here's my hacky way to extract it for storage (I'll try to cut off code bits irrelevant to the question):

// get nodes and edges
var nodes = network.body.data.nodes._data; // contains id, label, x,y, custom per-node options and doesn't contain options from options.nodes; presumably contains option values set when network was created, not current ones (it is so for x,y)
// network.body.nodes[id].nodeOptions shows options from options.nodes but not custom per-node options (same for edges and network.body.edges[id].edgeOptions)
// network.body.nodes contain much more stuff (x,y, default stuff)
//# look for a suitable getter
var edges = network.body.data.edges._data; // map; for edges to/from? certain node use network.getConnectedNodes(id)
// network.body.data.edges._data is a hash of { id: , from: , to: }

// get node positions
var positions = network.getPositions(),
    nodeIds = Object.keys(nodes);

// get data describing nodes, edges and options for storage
var storedEdges = [], storedEdge, storedNodes = [], storedNode;
var indexIds = {}, idIndex = 1, end;

for(var nodeId in nodes) {
    // nodes[nodeId].x is the initial value, positions[nodeId].x is the current one
    if(positions[nodeId]) { // undefined for hidden
        nodes[nodeId].x = positions[nodeId].x;
        nodes[nodeId].y = positions[nodeId].y;
    }
    storedNode = copyObjectProperties(nodes[nodeId]);

    // don't store id unless that breaks connected edges
    if(!network.getConnectedEdges(nodeId).length)
        storedNode.id = undefined;
    // substitute generated ids with no semantics with simple indices
    if(/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/.exec(storedNode.id)) {

        while(nodes[idIndex])
            idIndex++;
        indexIds[storedNode.id] = idIndex; // remember the given index
        storedNode.id = idIndex; // substitute with an index
        idIndex++;
    }

    storedNodes.push(storedNode);
}
for(var edgeId in edges) {
    storedEdge = copyObjectProperties(edges[edgeId]);
    storedEdge.id = undefined; // then strip id

    // change from/to in accord to the substitution above (for nodes' ids)
    for(end of ["from","to"])
        storedEdge[end] = indexIds[storedEdge[end]] || storedEdge[end];

    storedEdges.push(storedEdge);
}

dataAndOptions = {
    data: { nodes: storedNodes, edges: storedEdges },
    options: storedOptions
};

var dataAndOptionsText = JSON.stringify(dataAndOptions,"",4)
    .replace(/ {4}/gm,"\t").replace(/},\n\t\t\t{/gm,"},{");

and helper definition:

// helper for storing options
var copyObjectProperties = function(obj) {
    return JSON.parse(JSON.stringify(obj));
};

For more context, see my plugin for TiddlyWiki Classic (saveDataAndOptions method). It's not the latest version, but I'll update it at some point.

As for network options (if they were changed), I haven't figured a nice way yet.

Answer

According to Visjs documents, the method you might want is storePositions()

this is the the function description:

When using the vis.DataSet to load your nodes into the network, this method will put the X and Y positions of all nodes into that dataset. If you're loading your nodes from a database and have this dynamically coupled with the DataSet, you can use this to stablize your network once, then save the positions in that database through the DataSet so the next time you load the nodes, stabilization will be near instantaneous.

If the nodes are still moving and you're using dynamic smooth edges (which is on by default), you can use the option stabilization.onlyDynamicEdges in the physics module to improve initialization time.

This method does not support clustering. At the moment it is not possible to cache positions when using clusters since they cannot be correctly initialized from just the positions.

VisJs docs

Answer

tl;dr

Use storePositions() to load the X and Y coordinates to your dataset. You can serialize them, then just expand the nodes with the serialized coordinates when you initialize the network later.

Explanation

The vis.js Network docs on storePositions() says:

When using the vis.DataSet to load your nodes into the network, this method will put the X and Y positions of all nodes into that dataset. If you're loading your nodes from a database and have this dynamically coupled with the DataSet, you can use this to stabilize your network once, then save the positions in that database through the DataSet so the next time you load the nodes, stabilization will be near instantaneous.

Saving

You have to use vis.js's DataSet for your network.data. To "save", just call network.storePositions() so it can load the X and Y coordinates to network.data.nodes then you serialize it however you want.

Loading

You just forEach() your network.data.nodes and add in the serialized X and Y coordinates to its nodes via update().

Example

In this demo, the positions are serialized into a textarea. You can generate randomly positioned graphs (that is the default behavior), move the nodes, serialize them, optionally edit it in the textarea, then load it back.

const nodes = [
  { id: 1, label: 1 },
  { id: 2, label: 2 },
  { id: 3, label: 3 },
  { id: 4, label: 4 },
]

const edges = [
  { id: '1-2',  from: 1, to: 2 },
  { id: '1-3', from: 1, to: 3 },
  { id: '2-3', from: 2, to: 3 },
  { id: '1-4', from: 1, to: 4 },
]

const positionsElement = document.getElementById('positions')
const container = document.getElementById('graph')
const data = {
  nodes: new vis.DataSet(nodes),
  edges: new vis.DataSet(edges),
}
const options = {
  layout: {
    improvedLayout: false,
  },
  edges: {
    smooth: false,
  },
  physics: false,
}

let network = null

function initGraph(generateRandomPosition = true) {
  if (generateRandomPosition) {
    data.nodes.forEach(node => {
      data.nodes.update({ id: node.id, x: undefined, x: undefined })
    })
  }
  network = new vis.Network(container, data, options)
}

document.getElementById('generate-graph').addEventListener('click', initGraph)

document.getElementById('extract-positions').addEventListener('click', e => {
  network.storePositions()
  const nodePositions = data.nodes.map(({ id, x, y }) => ({ id, x, y }))
  positionsElement.value = JSON.stringify(nodePositions)
})

document.getElementById('load-positions').addEventListener('click', e => {
  const nodePositions = JSON.parse(positionsElement.value)
  nodePositions.forEach(nodePosition => data.nodes.update(nodePosition))
  initGraph(false)
})
#graph {
  width: 100%;
  height: 300px;
  border: 1px solid lightgray;
}

#positions {
  width: 100%;
  min-height: 60px;
}
<link href="https://visjs.github.io/vis-network/dist/vis-network.min.css" rel="stylesheet" />
<script src="https://visjs.github.io/vis-network/dist/vis-network.min.js"></script>
<div id="graph"></div>
<button id="generate-graph">Generate graph</button>
<button id="extract-positions">Extract positions</button>
<button id="load-positions">Load positions</button>
<textarea id="positions"></textarea>

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.