Skip to content

Instantly share code, notes, and snippets.

@fzyukio
Created July 28, 2018 10:05
Show Gist options
  • Save fzyukio/9fed44c01ba6bb04ce55ba4bd19270e0 to your computer and use it in GitHub Desktop.
Save fzyukio/9fed44c01ba6bb04ce55ba4bd19270e0 to your computer and use it in GitHub Desktop.
Fuzzy graph II
license: mit

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.

forked from nitaku's block: Fuzzy graph II

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