tgdwyer / WebCola

Javascript constraint-based graph layout
http://marvl.infotech.monash.edu/webcola/
MIT License
2.03k stars 258 forks source link

Groups with fixed positions #180

Open p-pant opened 8 years ago

p-pant commented 8 years ago

Hello, I have a case where groups represent actual floorplan positions and the nodes in the groups must reside within that region. I've tried to assign a min/max X & Y position to each node based on it's group's dimensions, and then adjusted the tick function as:

        // snap nodes to stay within group boundaries
        nodes.attr("x", function(d) { 
                    return d.x = Math.max(d.min_x + pad,  
                                          Math.min(d.max_x - d.width + pad, 
                                                   d.x - d.width/2 + pad))
             })
             .attr("y", function(d) { 
                    return d.y = Math.max(d.min_y + pad, 
                                          Math.min(d.max_y - d.height + pad, 
                                                   d.y - d.height/2 + pad)); 
             })

This does keep the nodes in the right region, but in dense graphs it causes nodes to overlap over each other even if avoidOverlaps is true.

If I use plain D3, this strategy works well since the node repulsion seems to keep nodes separated. However, I want to use Cola since I also have situations where the grouping is free-form (i.e. Cola can determine the group bounds as normal) and creating groups in D3 is not that simple at all (convex hulls don't look that good and it's hard to keep them non-overlapping).

Is there a better way to achieve fixed group positioning with Cola?

p-pant commented 8 years ago

Also, for regular unconstrained grouped layouts (as in all the grouping examples that are available), Cola allows the nodes to be dragged off the SVG boundary. I tried using the same technique as above, with each node's min/max set to the SVG size, and it doesn't behave properly even for medium sized networks. If Cola tries to lay out the network such that the nodes should be outside the boundary, this code pins the nodes at the edges, but the groups seem to float away sometimes and no longer wrap the nodes.

It seems that we need a proper node min/max constraint mechanism that the core layout engine can use, instead of snapping the nodes as shown above.

tgdwyer commented 8 years ago

Sorry for the slow reply. Your second assessment is right. Group boundaries could be fixed but it really needs to be handled by the constraint solver. To do this we need to thready some additional arguments through the API. It's on my todo list but I'm a bit slammed at the moment. Will post back here when it's done (but it might take a while).

p-pant commented 8 years ago

Thanks Tim. I'll keep an eye on this page. If you can give me some pointers on where to start looking, I can play around with the code a bit. Currently, it looks a bit imposing since I'm pretty new with Javascript :-)

I should say that other than this issue, things are working really well for my little application!

tgdwyer commented 8 years ago

Of course I would be thrilled if you did it :-).

the projection call from Layout.start is where the fun of setting up group bounding boxes and keeping them separated happens. You'll see inside the projection function that dummy variables are created for the group bounds and these are given weights and ideal positions. If given enormous weights and arbitrary ideal positions they will go precisely where they are told.

Sorry, that's only a very high level description but perhaps enough to get you started. Have a go and feel free to ask if you get stuck.

p-pant commented 8 years ago

I tried a couple of different ideas, but it didn't work as I expected. I'll wait till you manage to get to this. I'll attach a couple of simplified testcases to this ticket.

On a separate note, I added a zoom/pan event to the tick() handler so that the entire network is always visible/centered. This eliminated the irritating behavior of the network to float off-screen sometimes. Here's the code snippet:

        // One last thing: resize and pan the graph such that it is always visible in the SVG viewport.
        // G is the SVG container for all the elements
        var bbox    = G.node().getBBox();  

        // Calculate the scale ratio:
        // - don't expand if the graph already fits
        // - add an extra 10% space at the edges
        var scaling = Math.min(svgWidth/bbox.width, svgHeight/bbox.height, 1.0)/1.1;

        // Remember that the scaling is performed first *which moves the top-left 
        // corner of G* (since every point in G moves away from or towards the origin). 
        // Need to take this into account when computing the translation vector!
        var new_x   = 0.5*(svgWidth - bbox.width*scaling),
            new_y   = 0.5*(svgHeight- bbox.height*scaling),
            move_x  = new_x - bbox.x*scaling;
            move_y  = new_y - bbox.y*scaling;

        // Do the transformation
        G.attr("transform", "translate("+move_x+","+move_y+")scale("+scaling+")");
        zoom.translate([move_x,move_y]).scale(scaling);

Pankaj

andrewc111 commented 7 years ago

Hi, I would like to add this is also needed for my scenarios. Has any progress been made by anyone on this functionality? I would imagine that having fixed group positions with force directed node layout would be very useful in many applications that require a more structure visualisation. Many thanks Andrew

den1k commented 6 years ago

I'd also love using this to render multiple graphs in rows that can be merged (and should animate)