Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active February 15, 2019 06:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nitaku/7891d5fe93150c9faeb7 to your computer and use it in GitHub Desktop.
Save nitaku/7891d5fe93150c9faeb7 to your computer and use it in GitHub Desktop.
Fuzzy graph II

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.

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)
.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;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Fuzzy graph II</title>
<link rel="stylesheet" href="index.css">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://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>
// 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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment