2

I am looking to an algorithm or layout to be implemented in D3JS to render a graph of a "general" lattice, a lattice that is not (necessarily) binomial.

An example of the kind of result I would like to obtain is:

enter image description here

I want to visualize the lattice as a hierarchy from the bottom to the top, similar to a tree-styled structure.

I tried to adapt a force layout, but this worked badly, especially because it was hard to locate the levels of the hierarchy properly.

I don't have any constraint about the form of the input, that it can be the more appropriate for the procedure.

John Paul
  • 12,196
  • 6
  • 55
  • 75
enridaga
  • 631
  • 4
  • 9

1 Answers1

3

You could do something like this, but I'm not sure how useful it would be. It makes use of the cola.js library.

// Sample data set
        var json = {
            nodes: [
                    { "name": 'Beverage' },
                    { "name": 'nonAlcoholic' },
                    { "name": 'sparkling' },
                    { "name": 'alcoholic' },
                    { "name": 'hot' },
                    { "name": 'caffeinic' },
                    { "name": 'MineralWater' },
                    { "name": 'madeFromGrain' },
                    { "name": 'madeFromGrapes' },
                    { "name": 'HerbTea' },
                    { "name": 'Coffee' },
                    { "name": 'Cola' },
                    { "name": 'Beer' },
                    { "name": 'Wine' },
                    { "name": 'Champagne' },
                    { "name": '' }
                   ],
            links: [
                    { "source": 0, "target": 1},
                    { "source": 0, "target": 2},
                    { "source": 0, "target": 3},
                    { "source": 1, "target": 4},
                    { "source": 1, "target": 5},
                    { "source": 1, "target": 6},
                    { "source": 2, "target": 6},
                    { "source": 2, "target": 7},
                    { "source": 2, "target": 14},
                    { "source": 3, "target": 7},
                    { "source": 3, "target": 8},
                    { "source": 4, "target": 9},
                    { "source": 4, "target": 10},
                    { "source": 5, "target": 10},
                    { "source": 5, "target": 11},
                    { "source": 6, "target": 11},
                    { "source": 7, "target": 12},
                    { "source": 8, "target": 13},
                    { "source": 8, "target": 13},
                    { "source": 13, "target": 14},
                    { "source": 10, "target": 15},
                    { "source": 11, "target": 15},
                    { "source": 12, "target": 15},
                    { "source": 14, "target": 15},
                   ]
        };
  
        var width = 800, height = 600, n = 10000;
  
        var svg = d3.select('#vis').append('svg').attr('width', width).attr('height', height);

        var force = cola.d3adaptor()
                        .linkDistance(80)
                        .size([width, height])
                        .nodes(json.nodes)
                        .links(json.links)
                        .flowLayout("y", 25)
                        .on("tick", tick)
                        .start();
  
        var node = svg.selectAll("circle.node")
                       .data(json.nodes)
                     .enter().append("circle")
                       .attr("class", "node")
                       .attr("r", 5);

        var text = svg.selectAll("text.label")
                       .data(json.nodes)
                     .enter().append("text")
                       .attr("class", "label")
                       .text(function(d) { return d.name; });

        var link = svg.selectAll("line.link")
                       .data(json.links)
                     .enter().append("line")
                       .attr("class", "link");

        function tick() {
            link.attr("x1", function(d) { return d.source.x; })
                .attr("y1", function(d) { return d.source.y; })
                .attr("x2", function(d) { return d.target.x; })
                .attr("y2", function(d) { return d.target.y; });

            node.attr("cx", function(d) { return d.x; })
                .attr("cy", function(d) { return d.y; });

            text.attr("x", function(d) { return d.x + 8; })
                .attr("y", function(d) { return d.y; });
        }

        function hover(d) {
            switch(d3.event.type) {
                case "mouseover": var tip = svg.append("g")
                                               .attr("class", "tip")
                                               .attr("transform", "translate(" + d3.event.x + "," + d3.event.y + ")");
                                  var rect = tip.append("rect")
                                                .attr("fill", "white");
                                  var text = tip.append("text")
                                                .text(d.name);
                                  var bounds = text.node().getBBox();
                                  rect.attr("x", bounds.x)
                                      .attr("y", bounds.y)
                                      .attr("width", bounds.width)
                                      .attr("height", bounds.height);
                                  
                                  break;
                case "mouseout":  svg.selectAll("g.tip")
                                      .remove();
                                  break;
                default: break;
            }
        }
        text {
            font: 10px sans-serif;
        }

        line {
            stroke: #000;
            stroke-width: 1.5px;
        }

        circle {
            stroke: #fff;
            stroke-width: 1.5px;
        }
<div id="vis"></div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://marvl.infotech.monash.edu/webcola/cola.v3.min.js"></script>

Using cola.js you can specify additional constraints to assist with laying things out correctly. As it stands, the only constraint that I've placed on it is a flowLayout parameter. The good thing about this is the layout is repeatable and achieving a similar result with a d3 force layout may be quite difficult (although, in fairness, it's not really intended to be used that way).

I wouldn't claim that this is a great result, but short of manually determining node positions, or accepting a standard d3 force layout, this might be among the easiest and quickest methods to get something approaching what you're after.

Edit: showing how the hover was used.

// Sample data set
        var json = {
            nodes: [
                    { "name": 'Beverage' },
                    { "name": 'nonAlcoholic' },
                    { "name": 'sparkling' },
                    { "name": 'alcoholic' },
                    { "name": 'hot' },
                    { "name": 'caffeinic' },
                    { "name": 'MineralWater' },
                    { "name": 'madeFromGrain' },
                    { "name": 'madeFromGrapes' },
                    { "name": 'HerbTea' },
                    { "name": 'Coffee' },
                    { "name": 'Cola' },
                    { "name": 'Beer' },
                    { "name": 'Wine' },
                    { "name": 'Champagne' },
                    { "name": '' }
                   ],
            links: [
                    { "source": 0, "target": 1},
                    { "source": 0, "target": 2},
                    { "source": 0, "target": 3},
                    { "source": 1, "target": 4},
                    { "source": 1, "target": 5},
                    { "source": 1, "target": 6},
                    { "source": 2, "target": 6},
                    { "source": 2, "target": 7},
                    { "source": 2, "target": 14},
                    { "source": 3, "target": 7},
                    { "source": 3, "target": 8},
                    { "source": 4, "target": 9},
                    { "source": 4, "target": 10},
                    { "source": 5, "target": 10},
                    { "source": 5, "target": 11},
                    { "source": 6, "target": 11},
                    { "source": 7, "target": 12},
                    { "source": 8, "target": 13},
                    { "source": 8, "target": 13},
                    { "source": 13, "target": 14},
                    { "source": 10, "target": 15},
                    { "source": 11, "target": 15},
                    { "source": 12, "target": 15},
                    { "source": 14, "target": 15},
                   ]
        };
  
        var width = 800, height = 600, n = 10000;
  
        var svg = d3.select('#vis').append('svg').attr('width', width).attr('height', height);

        var force = cola.d3adaptor()
                        .linkDistance(80)
                        .size([width, height])
                        .nodes(json.nodes)
                        .links(json.links)
                        .flowLayout("y", 25)
                        .on("tick", tick)
                        .start();
  
        var link = svg.selectAll("line.link")
                       .data(json.links)
                     .enter().append("line")
                       .attr("class", "link");

          var node = svg.selectAll("circle.node")
                       .data(json.nodes)
                     .enter().append("circle")
                       .attr("class", "node")
                       .attr("r", 10)
                       .on("mouseover", hover)
                       .on("mouseout", hover);



        function tick() {
            link.attr("x1", function(d) { return d.source.x; })
                .attr("y1", function(d) { return d.source.y; })
                .attr("x2", function(d) { return d.target.x; })
                .attr("y2", function(d) { return d.target.y; });

            node.attr("cx", function(d) { return d.x; })
                .attr("cy", function(d) { return d.y; });


        }

        function hover(d) {
            switch(d3.event.type) {
                case "mouseover": 
                    console.log(d3.event);
                    var tip = svg.append("g")
                                               .attr("class", "tip")
                                               .attr("transform", "translate(" + d3.event.clientX + "," + d3.event.clientY + ")");
                                  var rect = tip.append("rect")
                                                .attr("fill", "white");
                                  var text = tip.append("text")
                                                .text(d.name);
                                  var bounds = text.node().getBBox();
                                  rect.attr("x", bounds.x)
                                      .attr("y", bounds.y)
                                      .attr("width", bounds.width)
                                      .attr("height", bounds.height);
                                  
                                  break;
                case "mouseout":  svg.selectAll("g.tip")
                                      .remove();
                                  break;
                default: break;
            }
        }
text {
            font: 10px sans-serif;
        }

        line {
            stroke: #000;
            stroke-width: 1.5px;
        }

        circle {
            stroke: #fff;
            stroke-width: 1.5px;
        }
<div id="vis"></div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://marvl.infotech.monash.edu/webcola/cola.v3.min.js"></script>
Ben Lyall
  • 1,976
  • 1
  • 12
  • 14
  • This is really useful. However, what is the role of function hover(d) {...}? – enridaga Feb 17 '15 at 12:34
  • Whoops... I forgot to take it out... I had a hover working on the nodes, rather than displaying the text the whole time. I have added a new snippet showing how I used the hover funciton. – Ben Lyall Feb 17 '15 at 12:58
  • I cannot make the hovering work... However this is not related to the question so it's not important. Your proposal is very valuable, however when the number of nodes increase it is harder to get the levels of the hierarchy(ies) as nodes mix up. – enridaga Feb 17 '15 at 14:10
  • The hover should work in the second snippet of my answer. When you increase the number of nodes, you'll probably need to start applying constraints on the layout. The `cola.js` web page has some information on the constraints that you can place on the positioning of nodes – Ben Lyall Feb 17 '15 at 21:47