block by nitaku 7891d5fe93150c9faeb7

Fuzzy graph II

Full Screen

A simpler approach to the visualization of fuzzy graphs (see the previous example). This time, links only show the constrained maximum degree (in light gray) and the actual degree (in dark gray). Avoiding to display the unconstrained maximum degree (i.e., 1) reduce the complexity of the visualization, at the expense of losing a reference for evaluating a link’s thickness.

index.js

// Generated by CoffeeScript 1.4.0
(function() {
  var crisp, d3cola, enter_crisp_nodes, enter_fuzzy_links, enter_fuzzy_nodes, fuzzy, graph, height, l, links, n, nodes, radius, svg, thickness, width, _i, _j, _len, _len1, _ref, _ref1;

  graph = {
    nodes: [
      {
        id: 'A',
        u: Math.random()
      }, {
        id: 'B',
        u: Math.random()
      }, {
        id: 'C',
        u: Math.random()
      }, {
        id: 'D',
        u: Math.random()
      }, {
        id: 'E',
        u: Math.random()
      }, {
        id: 'F',
        u: Math.random()
      }, {
        id: 'G',
        u: Math.random()
      }, {
        id: 'H',
        u: Math.random()
      }, {
        id: 'I',
        u: Math.random()
      }, {
        id: 'J',
        u: Math.random()
      }, {
        id: 'K',
        u: Math.random()
      }, {
        id: 'L',
        u: Math.random()
      }, {
        id: 'M',
        u: Math.random()
      }, {
        id: 'N',
        u: Math.random()
      }, {
        id: 'O',
        u: Math.random()
      }
    ],
    links: [
      {
        id: 1,
        source: 'A',
        target: 'B'
      }, {
        id: 2,
        source: 'B',
        target: 'C'
      }, {
        id: 3,
        source: 'C',
        target: 'A'
      }, {
        id: 4,
        source: 'B',
        target: 'D'
      }, {
        id: 5,
        source: 'D',
        target: 'C'
      }, {
        id: 6,
        source: 'D',
        target: 'E'
      }, {
        id: 7,
        source: 'E',
        target: 'F'
      }, {
        id: 8,
        source: 'F',
        target: 'G'
      }, {
        id: 9,
        source: 'F',
        target: 'H'
      }, {
        id: 10,
        source: 'G',
        target: 'H'
      }, {
        id: 11,
        source: 'G',
        target: 'I'
      }, {
        id: 12,
        source: 'H',
        target: 'I'
      }, {
        id: 13,
        source: 'J',
        target: 'E'
      }, {
        id: 14,
        source: 'J',
        target: 'L'
      }, {
        id: 15,
        source: 'J',
        target: 'K'
      }, {
        id: 16,
        source: 'K',
        target: 'L'
      }, {
        id: 17,
        source: 'L',
        target: 'M'
      }, {
        id: 18,
        source: 'M',
        target: 'K'
      }, {
        id: 19,
        source: 'N',
        target: 'O'
      }
    ]
  };

  /* objectify the graph
  */


  /* resolve node IDs (not optimized at all!)
  */


  _ref = graph.links;
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    l = _ref[_i];
    _ref1 = graph.nodes;
    for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
      n = _ref1[_j];
      if (l.source === n.id) {
        l.source = n;
      }
      if (l.target === n.id) {
        l.target = n;
      }
    }
    l.u = Math.min(Math.random(), l.source.u, l.target.u);
  }

  radius = d3.scale.sqrt().domain([0, 1]).range([0, 18]);

  thickness = d3.scale.linear().domain([0, 1]).range([0, 10]);

  svg = d3.select('svg');

  width = svg.node().getBoundingClientRect().width;

  height = svg.node().getBoundingClientRect().height;

  /* create a crisp and a fuzzy layer
  */


  crisp = svg.append('g');

  fuzzy = svg.append('g');

  /* create crisp nodes
  */


  nodes = crisp.selectAll('.node').data(graph.nodes, function(d) {
    return d.id;
  });

  enter_crisp_nodes = nodes.enter().append('g').attr({
    "class": 'crisp node'
  });

  enter_crisp_nodes.append('circle').attr({
    r: radius.range()[1]
  });

  enter_crisp_nodes.append('title').text(function(d) {
    return "(" + d.id + " " + (d3.format('%')(d.u)) + ")";
  });

  /* create fuzzy nodes and links
  */


  links = fuzzy.selectAll('.link').data(graph.links, function(d) {
    return d.id;
  });

  enter_fuzzy_links = links.enter().append('g').attr({
    "class": 'fuzzy link'
  });

  enter_fuzzy_links.append('line').attr({
    "class": 'max',
    'stroke-width': function(d) {
      return thickness(Math.min(d.source.u, d.target.u));
    }
  });

  enter_fuzzy_links.append('line').attr({
    "class": 'value',
    'stroke-width': function(d) {
      return thickness(d.u);
    }
  });

  enter_fuzzy_links.append('title').text(function(d) {
    return "(" + d.source.id + ")-[" + (d3.format('%')(d.u)) + "]-(" + d.target.id + ")\nMax: " + (d3.format('%')(Math.min(d.source.u, d.target.u)));
  });

  nodes = fuzzy.selectAll('.node').data(graph.nodes, function(d) {
    return d.id;
  });

  enter_fuzzy_nodes = nodes.enter().append('g').attr({
    "class": 'fuzzy node'
  });

  enter_fuzzy_nodes.append('circle').attr({
    r: function(d) {
      return radius(d.u);
    }
  });

  /* draw the label
  */


  enter_fuzzy_nodes.append('text').text(function(d) {
    return d.id;
  }).attr({
    dy: '0.8em',
    x: function(d) {
      return radius(d.u);
    },
    y: function(d) {
      return radius(d.u) / 2;
    }
  });

  /* cola layout
  */


  graph.nodes.forEach(function(v) {
    v.width = 2.5 * radius(v.u);
    return v.height = 2.5 * radius(v.u);
  });

  d3cola = cola.d3adaptor().size([width, height]).linkDistance(60).avoidOverlaps(true).nodes(graph.nodes).links(graph.links).on('tick', function() {
    /* update nodes and links
    */
    svg.selectAll('.node').attr('transform', function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });
    svg.selectAll('.crisp.link > line').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;
    });
    return svg.selectAll('.fuzzy.link > line').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;
    });
  });

  enter_crisp_nodes.call(d3cola.drag);

  d3cola.start(30, 30, 30);

}).call(this);

index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Fuzzy graph II</title>
    <link rel="stylesheet" href="index.css">
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="//marvl.infotech.monash.edu/webcola/cola.v3.min.js"></script>
  </head>
  <body>
    <svg width="960px" height="500px"></svg>
    <script src="index.js"></script>
  </body>
</html>

index.coffee

graph = {
  nodes: [
    {id: 'A', u: Math.random()},
    {id: 'B', u: Math.random()},
    {id: 'C', u: Math.random()},
    {id: 'D', u: Math.random()},
    {id: 'E', u: Math.random()},
    {id: 'F', u: Math.random()},
    {id: 'G', u: Math.random()},
    {id: 'H', u: Math.random()},
    {id: 'I', u: Math.random()},
    {id: 'J', u: Math.random()},
    {id: 'K', u: Math.random()},
    {id: 'L', u: Math.random()},
    {id: 'M', u: Math.random()},
    {id: 'N', u: Math.random()},
    {id: 'O', u: Math.random()}
  ],
  links: [
    {id:  1, source: 'A', target: 'B'},
    {id:  2, source: 'B', target: 'C'},
    {id:  3, source: 'C', target: 'A'},
    {id:  4, source: 'B', target: 'D'},
    {id:  5, source: 'D', target: 'C'},
    {id:  6, source: 'D', target: 'E'},
    {id:  7, source: 'E', target: 'F'},
    {id:  8, source: 'F', target: 'G'},
    {id:  9, source: 'F', target: 'H'},
    {id: 10, source: 'G', target: 'H'},
    {id: 11, source: 'G', target: 'I'},
    {id: 12, source: 'H', target: 'I'},
    {id: 13, source: 'J', target: 'E'},
    {id: 14, source: 'J', target: 'L'},
    {id: 15, source: 'J', target: 'K'},
    {id: 16, source: 'K', target: 'L'},
    {id: 17, source: 'L', target: 'M'},
    {id: 18, source: 'M', target: 'K'},
    {id: 19, source: 'N', target: 'O'}
  ]}


### objectify the graph ###
### resolve node IDs (not optimized at all!) ###
for l in graph.links
  for n in graph.nodes
    if l.source is n.id
      l.source = n

    if l.target is n.id
      l.target = n
      
  # link's u cannot exceed the ones of connected nodes
  l.u = Math.min(Math.random(), l.source.u, l.target.u)
  
radius = d3.scale.sqrt()
  .domain([0,1])
  .range([0,18])

thickness = d3.scale.linear()
  .domain([0,1])
  .range([0,10])
  
svg = d3.select('svg')
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height

### create a crisp and a fuzzy layer ###
crisp = svg.append('g')
fuzzy = svg.append('g')

### create crisp nodes ###

nodes = crisp.selectAll('.node')
  .data(graph.nodes, (d) -> d.id)
        
enter_crisp_nodes = nodes.enter().append('g')
  .attr
    class: 'crisp node'

enter_crisp_nodes.append('circle')
  .attr
    r: radius.range()[1]

enter_crisp_nodes.append('title')
  .text((d) -> "(#{d.id} #{d3.format('%')(d.u)})")
    
### create fuzzy nodes and links ###
links = fuzzy.selectAll('.link')
  .data(graph.links, (d) -> d.id)
  
enter_fuzzy_links = links
  .enter().append('g')
    .attr
      class: 'fuzzy link'
      
enter_fuzzy_links.append('line')
  .attr
    class: 'max'
    'stroke-width': (d) -> thickness(Math.min(d.source.u, d.target.u))
    
enter_fuzzy_links.append('line')
  .attr
    class: 'value'
    'stroke-width': (d) -> thickness(d.u)
    
enter_fuzzy_links.append('title')
  .text((d) -> "(#{d.source.id})-[#{d3.format('%')(d.u)}]-(#{d.target.id})\nMax: #{d3.format('%')(Math.min(d.source.u,d.target.u))}")

nodes = fuzzy.selectAll('.node')
  .data(graph.nodes, (d) -> d.id)
        
enter_fuzzy_nodes = nodes.enter().append('g')
  .attr
    class: 'fuzzy node'
  
enter_fuzzy_nodes.append('circle')
  .attr
    r: (d) -> radius(d.u)
    
### draw the label ###
enter_fuzzy_nodes.append('text')
  .text((d) -> d.id)
  .attr
    dy: '0.8em'
    x: (d) -> radius(d.u)
    y: (d) -> radius(d.u)/2
  
### cola layout ###
graph.nodes.forEach (v) ->
  v.width = 2.5*radius(v.u)
  v.height = 2.5*radius(v.u)

d3cola = cola.d3adaptor()
  .size([width, height])
  .linkDistance(60)
  .avoidOverlaps(true)
  .nodes(graph.nodes)
  .links(graph.links)
  .on 'tick', () ->
    ### update nodes and links  ###
    svg.selectAll('.node')
      .attr('transform', (d) -> "translate(#{d.x},#{d.y})")

    svg.selectAll('.crisp.link > line')
      .attr('x1', (d) -> d.source.x)
      .attr('y1', (d) -> d.source.y)
      .attr('x2', (d) -> d.target.x)
      .attr('y2', (d) -> d.target.y)
      
    svg.selectAll('.fuzzy.link > line')
      .attr('x1', (d) -> d.source.x)
      .attr('y1', (d) -> d.source.y)
      .attr('x2', (d) -> d.target.x)
      .attr('y2', (d) -> d.target.y)
      
enter_crisp_nodes
  .call(d3cola.drag)
  
d3cola.start(30,30,30)

index.css

.crisp.node > circle {
  fill: #DDD;
}
.fuzzy.node > circle {
  fill: #595;
  pointer-events: none;
}

.node > text {
  font-family: sans-serif;
  text-anchor: start;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  text-shadow: -2px 0 white, 0 2px white, 2px 0 white, 0 -2px white, -1px -1px white, 1px -1px white, 1px 1px white, -1px 1px white
}

.fuzzy.link .max {
  stroke: #DDD;
}
.fuzzy.link .value {
  stroke: #888;
}