我正在使用d3.js和jquery与PHP后端(基于yii框架)来创建动态强制有向图,以表示我们使用Nagios监视的网络上的主机和服务的当前状态。 / p>
图表显示root - >主机组 - >主机 - >服务。我已经创建了一个服务器端函数来以下列格式返回JSON对象
{
"nodes": [
{
"name": "MaaS",
"object_id": 0
},
{
"name": "Convergence",
"object_id": "531",
"colour": "#999900"
},
{
"name": "maas-servers",
"object_id": "719",
"colour": "#999900"
},
{
"name": "hrg-cube",
"object_id": "400",
"colour": "#660033"
}
],
"links": [
{
"source": 0,
"target": "531"
},
{
"source": 0,
"target": "719"
},
{
"source": "719",
"target": "400"
}
]
}
节点包含用于链接和颜色的对象id,用于显示节点的状态(OK =绿色,WARNING =黄色等)链接具有节点的源对象ID和目标对象ID。在监控系统中添加或删除新主机时,节点和链接可能会发生变化
我有以下代码设置初始SVG,然后每10秒
强制启动
$ .ajaxSetup({cache:false}); 宽度= 960, 身高= 500; node = []; link = []; force = d3.layout.force() .charge(-1000) .linkDistance(1) .size([width,height]);
svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
setInterval(function(){
$.ajax({
url: "<?php echo $url;?>",
type: "post",
async: false,
datatype: "json",
success: function(json, textStatus, XMLHttpRequest)
{
json = $.parseJSON(json);
var nodeMap = {};
json.nodes.forEach(function(x) { nodeMap[x.object_id] = x; });
json.links = json.links.map(function(x) {
return {
source: nodeMap[x.source],
target: nodeMap[x.target],
};
});
link = svg.selectAll("line")
.data(json.links);
node = svg.selectAll("circle")
.data(json.nodes,function(d){return d.object_id})
link.enter().append("line").attr("stroke-width",1).attr('stroke','#999');
link.exit().remove();
node.enter().append("circle").attr("r",5);
node.exit().remove();
node.attr("fill",function(d){return d.colour});
node.append("title")
.text(function(d) { return d.name; });
node.call(force.drag);
force
.nodes(node.data())
.links(link.data())
.start()
force.on("tick", function() {
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 = Math.max(5, Math.min(width - 5, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(5, Math.min(height - 5, d.y)); });
});
}
});
},10000);
以上所有操作都正常,但每次代码循环时都会导致可视化重新启动,节点会一直反弹直到它们稳定下来。我需要的是任何当前项目保持不变,但任何新节点和链接都添加到可视化中,并且可点击和拖动等。
如果有人可以提供帮助,我将永远感激。
答案 0 :(得分:5)
我已经设法使用上述所有建议的混合物找到问题的解决方案,下面是我使用的代码
var width = $(document).width();
var height = $(document).height();
var outer = d3.select("#chart")
.append("svg:svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all");
var vis = outer
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", rescale))
.on("dblclick.zoom", null)
.append('svg:g')
vis.append('svg:rect')
.attr('width', width)
.attr('height', height)
.attr('fill', 'white');
var force = d3.layout.force()
.size([width, height])
.nodes([]) // initialize with a single node
.linkDistance(1)
.charge(-500)
.on("tick", tick);
nodes = force.nodes(),
links = force.links();
var node = vis.selectAll(".node"),
link = vis.selectAll(".link");
redraw();
setInterval(function(){
$.ajax({
url: "<?php echo $url;?>",
type: "post",
async: false,
datatype: "json",
success: function(json, textStatus, XMLHttpRequest)
{
var current_nodes = [];
var delete_nodes = [];
var json = $.parseJSON(json);
$.each(json.nodes, function (i,data){
result = $.grep(nodes, function(e){ return e.object_id == data.object_id; });
if (!result.length)
{
nodes.push(data);
}
else
{
pos = nodes.map(function(e) { return e.object_id; }).indexOf(data.object_id);
nodes[pos].colour = data.colour;
}
current_nodes.push(data.object_id);
});
$.each(nodes,function(i,data){
if(current_nodes.indexOf(data.object_id) == -1)
{
delete_nodes.push(data.index);
}
});
$.each(delete_nodes,function(i,data){
nodes.splice(data,1);
});
var nodeMap = {};
nodes.forEach(function(x) { nodeMap[x.object_id] = x; });
links = json.links.map(function(x) {
return {
source: nodeMap[x.source],
target: nodeMap[x.target],
colour: x.colour,
};
});
redraw();
}
});
},2000);
function redraw()
{
node = node.data(nodes,function(d){ return d.object_id;});
node.enter().insert("circle")
.attr("r", 5)
node.attr("fill", function(d){return d.colour})
node.exit().remove();
link = link.data(links);
link.enter().append("line")
.attr("stroke-width",1)
link.attr('stroke',function(d){return d.colour});
link.exit().remove();
force.start();
}
function tick() {
link.attr("x1", function(d) { return Math.round(d.source.x); })
.attr("y1", function(d) { return Math.round(d.source.y); })
.attr("x2", function(d) { return Math.round(d.target.x); })
.attr("y2", function(d) { return Math.round(d.target.y); });
node.attr("cx", function(d) { return Math.round(d.x); })
.attr("cy", function(d) { return Math.round(d.y); });
}
function rescale() {
trans=d3.event.translate;
scale=d3.event.scale;
vis.attr("transform",
"translate(" + trans + ")"
+ " scale(" + scale + ")");
}
答案 1 :(得分:2)
看看这个答案。您需要一个唯一的节点标识符,您可以看到它。
Updating links on a force directed graph from dynamic json data
答案 2 :(得分:2)
我最近尝试做同样的事情,这是我提出的解决方案。我所做的是使用links.php
加载第一批数据,然后使用newlinks.php
更新它们,两者都返回带有属性sender
和receiver
的对象列表的JSON。在此示例中,newlinks每次都返回一个新的发送方,我将接收方设置为随机选择的旧节点。
$.post("links.php", function(data) {
// Functions as an "initializer", loads the first data
// Then newlinks.php will add more data to this first batch (see below)
var w = 1400,
h = 1400;
var svg = d3.select("#networkviz")
.append("svg")
.attr("width", w)
.attr("height", h);
var links = [];
var nodes = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([w, h])
.linkDistance(50)
.charge(-50)
.on("tick", tick);
svg.append("g").attr("class", "links");
svg.append("g").attr("class", "nodes");
var linkSVG = svg.select(".links").selectAll(".link"),
nodeSVG = svg.select(".nodes").selectAll(".node");
handleData(data);
update();
// This is the server call
var interval = 5; // set the frequency of server calls (in seconds)
setInterval(function() {
var currentDate = new Date();
var beforeDate = new Date(currentDate.setSeconds(currentDate.getSeconds()-interval));
$.post("newlinks.php", {begin: beforeDate, end: new Date()}, function(newlinks) {
// newlinks.php returns a JSON file with my new transactions (the one that happened between now and 5 seconds ago)
if (newlinks.length != 0) { // If nothing happened, then I don't need to do anything, the graph will stay as it was
// here I decide to add any new node and never remove any of the old ones
// so eventually my graph will grow extra large, but that's up to you to decide what you want to do with your nodes
newlinks = JSON.parse(newlinks);
// Adds a node to a randomly selected node (completely useless, but a good example)
var r = getRandomInt(0, nodes.length-1);
newlinks[0].receiver = nodes[r].id;
handleData(newlinks);
update();
}
});
}, interval*1000);
function update() {
// enter, update and exit
force.start();
linkSVG = linkSVG.data(force.links(), function(d) { return d.source.id+"-"+d.target.id; });
linkSVG.enter().append("line").attr("class", "link").attr("stroke", "#ccc").attr("stroke-width", 2);
linkSVG.exit().remove();
var r = d3.scale.sqrt().domain(d3.extent(force.nodes(), function(d) {return d.weight; })).range([5, 20]);
var c = d3.scale.sqrt().domain(d3.extent(force.nodes(), function(d) {return d.weight; })).range([0, 270]);
nodeSVG = nodeSVG.data(force.nodes(), function(d) { return d.id; });
nodeSVG.enter()
.append("circle")
.attr("class", "node")
// Color of the nodes depends on their weight
nodeSVG.attr("r", function(d) { return r(d.weight); })
.attr("fill", function(d) {
return "hsl("+c(d.weight)+", 83%, 60%)";
});
nodeSVG.exit().remove();
}
function handleData(data) {
// This is where you create nodes and links from the data you receive
// In my implementation I have a list of transactions with a sender and a receiver that I use as id
// You'll have to customize that part depending on your data
for (var i = 0, c = data.length; i<c; i++) {
var sender = {id: data[i].sender};
var receiver = {id: data[i].receiver};
sender = addNode(sender);
receiver = addNode(receiver);
addLink({source: sender, target: receiver});
}
}
// Checks whether node already exists in nodes or not
function addNode(node) {
var i = nodes.map(function(d) { return d.id; }).indexOf(node.id);
if (i == -1) {
nodes.push(node);
return node;
} else {
return nodes[i];
}
}
// Checks whether link already exists in links or not
function addLink(link) {
if (links.map(function(d) { return d.source.id+"-"+d.target.id; }).indexOf(link.source.id+"-"+link.target.id) == -1
&& links.map(function(d) { return d.target.id+"-"+d.source.id; }).indexOf(link.source.id+"-"+link.target.id) == -1)
links.push(link);
}
function tick() {
linkSVG.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;});
nodeSVG.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y});
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}, "json");
这是一个非常具体的实现,因此您应根据服务器输出填写必要的漏洞。但我相信D3主干是正确的,你在寻找:) 这是一个玩它的JSFiddle:http://jsfiddle.net/bTyh5/2/
This code非常有用,并激发了这里介绍的一些部分。
答案 3 :(得分:1)
您实际上不需要将任何内容传递回服务器,只要服务器端,您就可以告诉新的nodes
和links
正在生成。然后,不是重新加载整个d3脚本,而是加载一次,然后在force.on("tick", function())
中,进行10秒超时AJAX调用,从服务器获取要添加的新data
,无论是nodes
还是links
。
例如,假设您最初在服务器中拥有此JSON:
[
{
"nodes": [
{
"name": "MaaS",
"object_id": 0
},
{
"name": "Convergence",
"object_id": "531",
"colour": "#999900"
}
]
},
{
"links": [
{
"source": 0,
"target": "531"
}
]
}
]
您可以使用AJAX从服务器获取它并使用json = $.parseJSON(json);
解析它。
然后,编写timeout
,以便在<{1}}中运行,而不是在计算布局后运行。然后,再次在success
上解析您从服务器获得的新JSON,并将the_new_ success
和nodes
分别添加到已存在的links
和force.nodes
请注意,我没有对此进行测试,我不确定它是如何工作和/或执行的,但我认为一般方法是可行的。