I am trying to populate nodes' data in a graph, asynchronously.
How to ensure that data fetched asyncrosly is actually bound to the graph, and rendered when ready?
First, you render the graph structure, node and links. Second, you render data as nodes' properties, when data is ready. New node can by dynamically added by interacting on parents' nodes, and don't want to wait for completion of node's properties.
Please note I am using Vivagraph.js library.graph
is an object created with the library, addLinks()
and getNode()
are its function properties - see the demo at Vivagraph demo, I am using that as a draft of my attempts.
The issue I experience is that nodes are rendered in the graph as soon as they are added - addNode() either addLinks(node1, node2) functions -, while node's properties fetched asynchronously - getNode(node).property = updatedValue - result undefined.
EDITED - Simplified code based on comment
Below I include a working mockup version, based on tutorial provided by @Anvaka, the author of this (awesome) library.
My goal is to render the graph immediately, enabling interaction, and update data while it is being fetched from third parties.
// attempt 1: fetch data async
var fetchInfo = function (graph, nodeId) {
var root = 'http://jsonplaceholder.typicode.com';
$.ajax({
url: root + '/photos/' + nodeId,
method: 'GET'
}).then(function (data) {
graph.getNode(nodeId).data = data.thumbnailUrl;
console.log(graph.getNode(nodeId));
});
};
// attempt 2: defer ajax directly
var fetchInfo_2 = function (graph, nodeId) {
var root = 'http://jsonplaceholder.typicode.com';
return $.ajax({
url: root + '/photos/' + nodeId,
method: 'GET'
});
};
function main() {
// As in previous steps, we create a basic structure of a graph:
var graph = Viva.Graph.graph();
graph.addLink(1, 2);
fetchInfo(graph, 1); // updated data is undefined when graph is rendered
fetchInfo(graph, 2); // updated data is undefined when graph is rendered
/* trying a different outcome by deferring whole ajax
graph.getNode(1).data = fetchInfo_2(1).done(function(data) {
data.thumbnailUrl;
}); // the whole object is deferred but cannot fetch data
graph.getNode(2).data = fetchInfo_2(2).done(function(data) {
data.thumbnailUrl;
}); // the whole object is deferred but cannot fetch data
*/
var graphics = Viva.Graph.View.svgGraphics(),
nodeSize = 24,
addRelatedNodes = function (nodeId, isOn) {
for (var i = 0; i < 6; ++i) {
var child = Math.floor((Math.random() * 150) + nodeId);
// I add children and update data from external sources
graph.addLink(nodeId, child);
fetchInfo(graph, child);
}
};
// dynamically add nodes on mouse interaction
graphics.node(function (node) {
var ui = Viva.Graph.svg('image')
.attr('width', nodeSize)
.attr('height', nodeSize)
.link(node.data);
console.log('rendered', node.id, node.data);
$(ui).hover(function () {
// nodes are rendered; nodes' data is undefined
addRelatedNodes(node.id);
});
return ui;
}).placeNode(function (nodeUI, pos) {
nodeUI.attr('x', pos.x - nodeSize / 2).attr('y', pos.y - nodeSize / 2);
});
graphics.link(function (link) {
return Viva.Graph.svg('path')
.attr('stroke', 'gray');
}).placeLink(function (linkUI, fromPos, toPos) {
var data = 'M' + fromPos.x + ',' + fromPos.y +
'L' + toPos.x + ',' + toPos.y;
linkUI.attr("d", data);
})
var renderer = Viva.Graph.View.renderer(graph, {
graphics: graphics
});
renderer.run();
}
main();
svg {
width: 100%;
height: 100%;
}
<script src="https://rawgit.com/anvaka/VivaGraphJS/master/dist/vivagraph.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
As I said in the comments, any work you want to do as a result of an asynchronous process must be done in the callback. This includes any rendering work.
For jQuery's deferreds (like the result of an Ajax call), the callback is defined through .then
or .done
, enabling you to detach the act of fetching a resource from working with that resource.
Setting the image source is rendering work, so you must do it in the callback. Below is a function that fetches images and returns the deferred result and the node
callback function uses that to do its own piece of work.
function fetchInfo(id) {
var root = 'http://jsonplaceholder.typicode.com';
return $.getJSON(root + '/photos/' + id);
}
function main() {
var graph = Viva.Graph.graph(),
graphics = Viva.Graph.View.svgGraphics(),
nodeSize = 24,
addRelatedNodes = function (nodeId, count) {
var childId, i;
for (i = 0; i < count; ++i) {
childId = Math.floor(Math.random() * 150) + nodeId;
graph.addLink(nodeId, childId);
}
};
graphics
.node(function (node) {
var ui = Viva.Graph.svg('image')
.attr('width', nodeSize)
.attr('height', nodeSize)
.link('http://www.ajaxload.info/images/exemples/24.gif');
$(ui).dblclick(function () {
addRelatedNodes(node.id, 6);
});
// render when ready
fetchInfo(node.id).done(function (data) {
ui.link(data.thumbnailUrl);
});
return ui;
})
.placeNode(function (nodeUI, pos) {
nodeUI.attr('x', pos.x - nodeSize / 2).attr('y', pos.y - nodeSize / 2);
})
.link(function (link) {
return Viva.Graph.svg('path')
.attr('stroke', 'gray');
})
.placeLink(function (linkUI, fromPos, toPos) {
var data =
'M' + fromPos.x + ',' + fromPos.y +
'L' + toPos.x + ',' + toPos.y;
linkUI.attr("d", data);
});
graph.addLink(1, 2);
Viva.Graph.View.renderer(graph, {
graphics: graphics
}).run();
}
main();
svg {
width: 100%;
height: 100%;
}
img {
cursor: pointer;
}
<script src="https://rawgit.com/anvaka/VivaGraphJS/master/dist/vivagraph.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
Sadly, it does not seem to run in the StackSnippets, but it works over on jsFiddle: http://jsfiddle.net/tL9992ua/