352 lines
10 KiB
JavaScript
352 lines
10 KiB
JavaScript
// Copyright 2014 The Cayley Authors. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
function Network() {
|
|
var height = 800;
|
|
var width = 800;
|
|
|
|
// Things in the first force graph.
|
|
var nodesG = null;
|
|
var linksG = null;
|
|
var linkNodesG = null;
|
|
|
|
var node = null;
|
|
var linknode = null;
|
|
var link = null;
|
|
|
|
var curLinksData = null;
|
|
var curNodesData = null;
|
|
|
|
|
|
// Things in the second force graph
|
|
var tag_nodesG = null;
|
|
var tag_linksG = null;
|
|
|
|
var tag_node = null;
|
|
var tag_link = null;
|
|
|
|
var curTagLinksData = null;
|
|
var curTagNodesData = null;
|
|
|
|
|
|
var allData = null;
|
|
|
|
var dragstart = function(d) {
|
|
d3.select(this).classed("fixed", d.fixed = true);
|
|
}
|
|
|
|
//our force directed layout
|
|
var force = d3.layout.force()
|
|
var force_drag = force.drag().on("dragstart", dragstart)
|
|
var tag_force = d3.layout.force()
|
|
|
|
//color function used to color nodes
|
|
var baseNodeColors = d3.scale.category20()
|
|
var linkNodeColors = function(d) {
|
|
return d3.rgb("#555").toString()
|
|
}
|
|
var strokeFor = function(d) {
|
|
return d3.rgb("#000").brighter().toString()
|
|
}
|
|
|
|
var rotationTransformForLinkNode = function (d) {
|
|
var center_x = (d.link.source.x + d.link.target.x) / 2
|
|
var center_y = (d.link.source.y + d.link.target.y) / 2
|
|
var dx = d.link.target.x - d.link.source.x
|
|
var dy = d.link.target.y - d.link.source.y
|
|
var rotation_radians = Math.atan2(dy,dx)
|
|
var rotation_degrees = rotation_radians * (180 / Math.PI)
|
|
return "rotate(" + rotation_degrees + ", " + center_x + ", " + center_y + ")"
|
|
|
|
}
|
|
|
|
var updateLink = function() {
|
|
this.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})
|
|
}
|
|
|
|
var forceTick = function(e) {
|
|
tag_force.start()
|
|
node
|
|
.attr("cx", function(d) {return d.x})
|
|
.attr("cy", function(d) {return d.y})
|
|
|
|
linknode.each(function(d) {
|
|
d.x = (d.link.source.x + d.link.target.x) / 2
|
|
d.y = (d.link.source.y + d.link.target.y) / 2
|
|
})
|
|
|
|
linknode
|
|
.attr("cx", function(d) {return d.x })
|
|
.attr("cy", function(d) {return d.y })
|
|
.attr("transform", rotationTransformForLinkNode)
|
|
|
|
link.call(updateLink);
|
|
|
|
tagForceTick(e)
|
|
}
|
|
|
|
var tagForceTick = function(e) {
|
|
tag_node.each(function(d) {
|
|
if(d.is_tag === false) {
|
|
d.x = d.node.x;
|
|
d.y = d.node.y;
|
|
} else {
|
|
var b = this.childNodes[1].getBBox();
|
|
var diffX = d.x - d.node.x;
|
|
var diffY = d.y - d.node.y;
|
|
|
|
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
|
|
|
|
var shiftX = b.width * (diffX - dist) / (dist * 2);
|
|
shiftX = Math.max(-b.width, Math.min(0, shiftX));
|
|
var shiftY = 5;
|
|
this.childNodes[1].setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
|
|
}
|
|
});
|
|
tag_node
|
|
.attr("transform", function(d) {
|
|
return "translate(" + d.x + "," + d.y + ")";
|
|
});
|
|
|
|
tag_link.call(updateLink);
|
|
}
|
|
|
|
var setupData = function (data) {
|
|
data.nodes.forEach(function (n) {
|
|
n.x = randomnumber=Math.floor(Math.random()*width)
|
|
n.y = randomnumber=Math.floor(Math.random()*height)
|
|
n.radius = 10
|
|
});
|
|
|
|
var nodesMap = mapNodes(data.nodes)
|
|
|
|
data.links.forEach(function (l) {
|
|
l.source = nodesMap.get(l.source)
|
|
l.target = nodesMap.get(l.target)
|
|
nodesMap.get(l.link_node).link = l
|
|
})
|
|
|
|
data.tag_links = []
|
|
data.tag_nodes = []
|
|
var tag_id_counter = 0
|
|
|
|
data.nodes.forEach(function (n) {
|
|
if (n.tags !== undefined) {
|
|
n.tags.forEach( function (tag) {
|
|
var tag_node = {}
|
|
tag_node.id = "tag" + tag_id_counter
|
|
tag_node.tag = tag
|
|
tag_node.x = n.x
|
|
tag_node.y = n.y
|
|
tag_node.is_tag = true
|
|
tag_node.is_value = false
|
|
tag_node.node = n
|
|
tag_node.radius = 0
|
|
tag_id_counter += 1;
|
|
var fake_node = {}
|
|
fake_node.id = "tag" + tag_id_counter
|
|
fake_node.x = n.x
|
|
fake_node.y = n.y
|
|
fake_node.node = n
|
|
fake_node.radius = 0
|
|
fake_node.is_tag = false
|
|
tag_id_counter += 1;
|
|
var tag_link = {}
|
|
tag_link.source = fake_node
|
|
tag_link.target = tag_node
|
|
data.tag_nodes.push(tag_node)
|
|
data.tag_nodes.push(fake_node)
|
|
data.tag_links.push(tag_link)
|
|
})
|
|
}
|
|
if (n.values !== undefined) {
|
|
n.values.forEach( function (value) {
|
|
var tag_node = {}
|
|
tag_node.id = "tag" + tag_id_counter
|
|
tag_node.tag = value
|
|
tag_node.x = n.x
|
|
tag_node.y = n.y
|
|
tag_node.is_tag = true
|
|
tag_node.is_value = true
|
|
tag_node.node = n
|
|
tag_node.radius = 0
|
|
tag_id_counter += 1;
|
|
var fake_node = {}
|
|
fake_node.id = "tag" + tag_id_counter
|
|
fake_node.x = n.x
|
|
fake_node.y = n.y
|
|
fake_node.node = n
|
|
fake_node.radius = 0
|
|
fake_node.is_tag = false
|
|
tag_id_counter += 1;
|
|
var tag_link = {}
|
|
tag_link.source = fake_node
|
|
tag_link.target = tag_node
|
|
data.tag_nodes.push(tag_node)
|
|
data.tag_nodes.push(fake_node)
|
|
data.tag_links.push(tag_link)
|
|
})
|
|
}
|
|
})
|
|
|
|
return data;
|
|
}
|
|
|
|
var mapNodes = function (nodes) {
|
|
var nodesMap = d3.map()
|
|
nodes.forEach(function (n) {
|
|
nodesMap.set(n.id, n)
|
|
})
|
|
return nodesMap
|
|
}
|
|
|
|
var network = function (selection, data) {
|
|
allData = setupData(data)
|
|
var vis = d3.select(selection).append("svg")
|
|
.attr("width", width)
|
|
.attr("height", height)
|
|
|
|
vis.append("defs").append("marker")
|
|
.attr("id", "arrowhead")
|
|
.attr("refX", 6 + 3) // shift?
|
|
.attr("refY", 2)
|
|
.attr("markerWidth", 6)
|
|
.attr("markerHeight", 4)
|
|
.attr("orient", "auto")
|
|
.append("path")
|
|
.attr("d", "M 0,0 V 4 L6,2 Z");
|
|
|
|
linksG = vis.append("g").attr("id", "links")
|
|
nodesG = vis.append("g").attr("id", "nodes")
|
|
linkNodesG = vis.append("g").attr("id", "link-nodes")
|
|
|
|
tagNodesG = vis.append("g").attr("id", "tag_nodes")
|
|
tagLinksG = vis.append("g").attr("id", "tag_links")
|
|
|
|
force.size([width, height])
|
|
force.on("tick", forceTick)
|
|
.charge(-200)
|
|
.linkDistance(100);
|
|
|
|
tag_force
|
|
.gravity(0)
|
|
.linkDistance(25)
|
|
.linkStrength(20)
|
|
.charge(-100)
|
|
.size([width, height])
|
|
|
|
tag_force.start()
|
|
|
|
//perform rendering and start force layout
|
|
update()
|
|
|
|
}
|
|
|
|
|
|
|
|
var update = function () {
|
|
allNodes = allData.nodes;
|
|
curLinksData = allData.links;
|
|
curNodesData = $.grep(allData.nodes, function(n) { return n.is_link_node === false; })
|
|
curLinkNodesData = $.grep(allData.nodes, function(n) { return n.is_link_node; })
|
|
|
|
curTagLinksData = allData.tag_links
|
|
curTagNodesData = allData.tag_nodes
|
|
|
|
force.nodes(allNodes);
|
|
node = nodesG.selectAll("circle.node")
|
|
.data(curNodesData, function(d) {return d.id})
|
|
|
|
node.enter().append("circle")
|
|
.attr("class", "node")
|
|
.attr("cx", function(d) {return d.x})
|
|
.attr("cy", function(d) {return d.y})
|
|
.attr("r", function(d) {return d.radius;})
|
|
.style("fill", function(d) {return baseNodeColors(d.id);})
|
|
.style("stroke", function(d) {return strokeFor(d);})
|
|
.style("stroke-width", 1.0)
|
|
.call(force_drag)
|
|
|
|
node.exit().remove();
|
|
|
|
linknode = linkNodesG.selectAll("ellipse.node")
|
|
.data(curLinkNodesData, function(d) { return d.id })
|
|
|
|
linknode.enter().append("ellipse")
|
|
.attr("class", "node")
|
|
.attr("cx", function(d) {return d.x})
|
|
.attr("cy", function(d) {return d.y})
|
|
.attr("rx", function(d) {return d.radius;})
|
|
.attr("ry", function(d) {return d.radius / 2;})
|
|
.style("fill", function(d) {return linkNodeColors(d.id);})
|
|
.style("stroke", function(d) {return strokeFor(d);})
|
|
.style("stroke-width", 1.0);
|
|
|
|
linknode.exit().remove();
|
|
|
|
|
|
force.links(curLinksData);
|
|
|
|
link = linksG.selectAll("line.link")
|
|
.data(curLinksData, function(d) { return d.source.id + "_" + d.target.id});
|
|
|
|
link.enter().append("line")
|
|
.attr("class", "link")
|
|
.attr("stroke", "#222")
|
|
.attr("stroke-opacity", 1.0)
|
|
.attr("marker-end", "url(#arrowhead)")
|
|
.style("stroke-width", 2.0)
|
|
.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});
|
|
|
|
link.exit().remove();
|
|
force.start();
|
|
|
|
tag_force.nodes(curTagNodesData);
|
|
tag_node = nodesG.selectAll("g.tag_node")
|
|
.data(curTagNodesData, function(d) { return d.id })
|
|
var tag_g = tag_node.enter().append("g").attr("class", "tag_node")
|
|
tag_g.append("svg:circle").attr("r", 0).style("fill", "#FFF");
|
|
tag_g.append("svg:text")
|
|
.text(function(d) { return d.is_tag ? d.tag : "" })
|
|
.style("fill", "#555")
|
|
.style("font-family", function(d) { return d.is_value ? "Courier" : "Arial"})
|
|
.style("font-size", 12);
|
|
|
|
tag_force.links(curTagLinksData);
|
|
tag_link = linksG.selectAll("line.tag_link")
|
|
.data(curTagLinksData, function(d) { return d.source.id + "_" + d.target.id});
|
|
|
|
tag_link.enter().append("line")
|
|
.attr("class", "tag_link")
|
|
.attr("stroke", "#ddd")
|
|
.attr("stroke-opacity", 0.5)
|
|
.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});
|
|
|
|
tag_link.exit().remove();
|
|
tag_force.start()
|
|
}
|
|
|
|
return network
|
|
};
|
|
|