anvaka / VivaGraphJS

Graph drawing library for JavaScript
Other
3.75k stars 425 forks source link

Recreating the entire scene dynamically without page reload? #57

Closed simov closed 9 years ago

simov commented 10 years ago

Hi, thanks for this great library! I have a question about a topic that's not covered in the examples.

I'm trying to recreate the entire scene dynamically without reloading the page. Here is what I do:

var graph = Viva.Graph.graph();
var graphics = Viva.Graph.View.svgGraphics();
var renderer = null;

// initialization code follows
// mostly code from your examples
// I'm adding some nodes, creating custom node appearance
// adding some jquery event handlers
// and running the render

After that on some user interaction when new JSON arrived I'm making this:

if (renderer) renderer.dispose();
graphics.release();
graph.clear();
// the same initialization code follows

Although this technique works it has some odd side effects. After refresh on dragging the scene it start to have some offset and moves really fast. The worst is that the animation is active for only a few seconds after that if I drag a node, the rest of the scene is not moving like in the constantLayout.html example.

What is the proper way to recreate the entire scene dynamically?

Thanks

danpaulsmith commented 9 years ago

This question still stands. How do I render a new graph using the same setup? Effectively a "wipe" of the current renderer, listeners, events etc.

joerodrig commented 9 years ago

@danpaulsmith that depends on how you implement your graph(ie. Are your event listeners implemented via the renderer every time a node / link is created or are you using an MVC such as angular to manage event listeners/UI events while the vivagraph instance merely handles default node/link creation) . I can try and help you a bit more if you have a code example

danpaulsmith commented 9 years ago

The only events I have are the WebglInputEvents, which are setup in the "set up" phase and some keydown/keyup from the multiselect demo.

I have a drawMap() function which I want to call to multiple times, each time, wiping the canvas and drawing a totally new graph. I'm trying to call renderer.dispose, but it throws an error (TypeError: publicEvents.removeAllListeners is not a function).

var App = {};

function drawMap(results) {

    if (App.renderer) {
        App.renderer.dispose();
        App.graphics.release();
        App.graph.clear();
    }

    App.results = results;

    var graph = App.graph = Viva.Graph.serializer().loadFromJSON(results);

    var layout = Viva.Graph.Layout.forceDirected(graph, {
        springLength: 100,
        springCoeff: 0.0005,
        dragCoeff: 0.02,
        gravity: 0
    });

    var graphicsOptions = {
      clearColor: true, // we want to avoid rendering artifacts
      clearColorValue: { // use black color to erase background
        r: 0,
        g: 0,
        b: 0,
        a: 1
      }
    };
    var graphics = App.graphics = Viva.Graph.View.webglGraphics(graphicsOptions);
    graph.forEachNode(function(node) {
        layout.setNodePosition(node.id, node.data.x, node.data.y);
    });
    graphics.node(function (node) {
        return Viva.Graph.View.webglSquare(node.data.size, node.data.color);
    });

    var renderer = App.renderer = Viva.Graph.View.renderer(graph, {
        layout: layout,
        graphics: graphics,
        renderLinks: false,
        container: document.getElementById('graph-container')
    });

    var multiSelectOverlay;

    var events = Viva.Graph.webglInputEvents(graphics, graph);
    events.mouseEnter(function (node) {
        detailsElement.innerHTML = node.data.title;
        document.body.style.cursor = 'pointer';
    }).mouseLeave(function (node) {
        detailsElement.innerHTML = '';
        document.body.style.cursor = 'auto';
    }).dblClick(function (node) {
    }).click(function (node) {
        App.selectNodes([node.id]);
    });

    renderGraph();

    function renderGraph() {

        renderer.run();
        // Final bit: most likely graph will take more space than available
        // screen. Let's zoom out to fit it into the view:
        var graphRect = layout.getGraphRect();
        var graphSize = Math.min(graphRect.x2 - graphRect.x1, graphRect.y2 - graphRect.y1);
        var screenSize = Math.min(document.body.clientWidth, document.body.clientHeight);
        var desiredScale = screenSize / graphSize;
        zoomOut(desiredScale, 1);

        App.rendererGraphics = renderer.getGraphics();

        renderer.pause();
        renderer.reset();

        function zoomOut(desiredScale, currentScale) {
            // zoom API in vivagraph 0.5.x is silly. There is no way to pass transform
            // directly. Maybe it will be fixed in future, for now this is the best I could do:
            if (desiredScale < currentScale) {
                currentScale = renderer.zoomOut();
                setTimeout(function() {
                    zoomOut(desiredScale, currentScale);
                }, 16);
            }
        }
    }

    document.addEventListener('keydown', function(e) {
        if (e.which === 16 && !multiSelectOverlay) { // shift key
            multiSelectOverlay = startMultiSelect(graph, renderer, layout);
        }
    });
    document.addEventListener('keyup', function(e) {
        if (e.which === 16 && multiSelectOverlay) {
            multiSelectOverlay.destroy();
            multiSelectOverlay = null;
        }
    });
}
danpaulsmith commented 9 years ago

To add to this, if I comment out the line publicEvents.removeAllListeners(); in vivagraph.js, then I am able to re-render a new graph using the same setup function (drawGraph).

anvaka commented 9 years ago

In theory graph.clear() should completely reset the graph. Then adding new nodes/edges should reuse the same renderer/layout settings. If this doesn't work then we need to find where it's broken.

danpaulsmith commented 9 years ago

Doesn't seem to work for me, the visualisation freezes. Here's a simple example:

What do I need to do after using graph.clear() in order to draw a new graph?

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>VivaGraphs WebGL Mouse Input test page</title>
  <style type='text/css'>
  body, #graph-container {
    height: 100%;
    width: 100%;
    position: absolute;
    overflow: hidden;
    margin: 0px;
  }
  #loadNew {
    margin: 50px;
    position: fixed;
    left: 0;
    top: 0;
    z-index: 99999;
  }
  </style>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  <script src="https://raw.githubusercontent.com/anvaka/VivaGraphJS/master/dist/vivagraph.min.js"></script>
  <script type='text/javascript'>
  var App = {};

  function onLoad() {
    App.graphGenerator = Viva.Graph.generator();
    App.graph = App.graphGenerator.grid(50, 10);
    App.layout = Viva.Graph.Layout.forceDirected(App.graph);
    App.graphics = Viva.Graph.View.webglGraphics();
    App.renderer = Viva.Graph.View.renderer(App.graph, {
      layout: App.layout,
      graphics: App.graphics,
      container: document.getElementById('graph-container')
    });
    App.renderer.run();
  }

  function loadNew() {
    App.graph.clear();
    App.graph = App.graphGenerator.grid(10, 10);
    // ... ?
  }

  $(document).ready(function() {
    $('button#loadNew').click(function() {
      console.log('loadNew', App);
      loadNew();
    });
  });
  </script>
</head>

<body onload="onLoad()">
  <div id="graph-container"></div>
  <div class="graph-overlay"></div>
  <button id="loadNew">Load new graph</button>
</body>

</html>
anvaka commented 9 years ago

Oh, but you are changing the reference to the graph in this line App.graph = App.graphGenerator.grid(10, 10);. Try copy graph instead:

copyGraph(App.graphGenerator.grid(10, 10), App.graph);

function copyGraph(from, to) {
  to.beginUpdate();
  from.forEachLink(copyLink);
  to.endUpdate();
  function copyLink(link) {
    to.addLink(link.fromId, link.toId);
  }
}

Here is a demo: http://jsbin.com/poduteguyu/1/edit?html,output

danpaulsmith commented 9 years ago

Ah okay. My graphs have 50,000 nodes which have their positions precomputed, I thought there must be a way to draw the graph without having to add each node individually. So I'm using loadJSON(). Is the only way to draw a new graph to beginUpdate(), add each node, endUpdate()?

On 14 Feb 2015, at 20:47, Andrei Kashcha notifications@github.com wrote:

Oh, but you are changing the reference to the graph in this line App.graph = App.graphGenerator.grid(10, 10);. Try copy graph instead:

copyGraph(App.graphGenerator.grid(10, 10), App.graph);

function copyGraph(from, to) { to.beginUpdate(); from.forEachLink(copyLink); to.endUpdate(); function copyLink(link) { to.addLink(link.fromId, link.toId); } } Here is a demo: http://jsbin.com/poduteguyu/1/edit?html,output

— Reply to this email directly or view it on GitHub.