-
Notifications
You must be signed in to change notification settings - Fork 57
D3可视化模块
D3是js的库,借助CSS3,HTML5以及svg,有一些比较赞的example。我选取了里面的WordCloud、带fisheye效果的力导向图和矩阵图。其实词云的效果并不好,后两者尝试了下还好,算是对可视化的初步探索,关键还是在于你想表达什么关系,套用什么图来表现,而难点就是在于数据串的计算,最后传给前端js代码生成就可以了。
词云相对简单,需要传递给前端的是一个String list或者String[]。在action包的WordCloudAction里,示意了使用方法。
public String[] abs;
public String execute() throws Exception {
if (user == null && name != null) {
SolrjHelper solrj = new SolrjHelper(1);
List<String> list = solrj.getAuthorAbstraction(name, start, rows);
abs = StringUtil.removeDupl(list);
} else if (user != null && name == null) {
ArrayList<String> list = DAOfactory.getUserDAO().getUserKeys(user);
abs = list.toArray(new String[list.size()]);
}
return SUCCESS;
}
通过别的类的接口计算得到list,然后处理一下变成abs这个String[]就可以了。
共生关系采用的是力导向图和矩阵图两个可视化效果,两者可以共用一份计算结果。生成符合力导向图和矩阵图的数据串比较复杂。下面举Andrew Y. Ng与若干个学者共同发表论文的数据关系为例。这是一组json数据对,生成自后台java代码,会传送给前端js模块来生成两个可视化关系图。主要由nodes和links两部分组成。Nodes代表力导向图中每个点,group用以区分学者的不同工作地,在图中会成为颜色区分的根据。Nodes在矩阵图中则代表矩阵中横竖两列坐标。Links代表的是在nodes中某两个node有关系,他们的关系强度由value的相对值来比较区分。在力导向图中,links表现为两点之间是否有连线且连线的深浅。在矩阵图中,links表现为矩阵某个坐标小方格内是否上色且颜色有多深。
{
"nodes":[
{"name":"Adam Coates","group":0},
{"name":"Andrew Y. Ng","group":0},
{"name":"Anna Petrovskaya","group":0},
{"name":"Ashutosh Saxena","group":1},
{"name":"Chuong Do","group":2},
{"name":"Honglak Lee","group":3},
{"name":"J. Zico Kolter","group":4},
{"name":"Pieter Abbeel","group":5}
],
"links":[
{"source":0,"target":1,"value":1},
{"source":3,"target":1,"value":1},
{"source":1,"target":5,"value":1},
{"source":7,"target":1,"value":3},
{"source":2,"target":1,"value":1},
{"source":1,"target":7,"value":3},
{"source":1,"target":0,"value":1},
{"source":1,"target":3,"value":1},
{"source":1,"target":6,"value":5},
{"source":1,"target":2,"value":1},
{"source":6,"target":1,"value":5},
{"source":1,"target":4,"value":1},
{"source":5,"target":1,"value":1},
{"source":4,"target":1,"value":1}
]
}
从计算角度看,后台link的生成由solr请求完成,即nodes中每两人是否共同发表论文或共同发表过几次论文都会是一次solr搜索请求,所以将进行O(n*n)次计算,在数据量很大的情况下将花费很长时间。我做了两方面优化考虑。一方面我限定了图中nodes个数,最大为50,另一方面,计算过的json串都将存储在mysql表中,避免下一次重复计算。
详细计算过程在dcd.academic.recommend的BtwAuthor.java内。具体计算上面这份json数据对的代码在coauthorNodes()和coauthorLinks()两个函数内,且通过getCoauthorJson()将两份结果整合起来。当然这个.java里还有四个findCoAuthorsByXXX()的函数,对应的是discover.jsp里的四种共生关系的发现接口,这几个函数在计算过程中都会被使用,主要是打通了solr的搜索请求。具体在dcd.academic.action里有四个对应的action,分别是CoauthorAction,CofieldAction,CopaperAction和CoplaceAction。比如拿CoauthorAction举个例子,其他几个都是相似的处理方法:
@Override
public String execute() throws Exception {
DAOfactory factory = new DAOfactory();
SaveDAO dao = factory.getSaveDAO();
String s = dao.getDiscover(name, "author");
if ( s != null) {
json = JSONObject.fromObject(s);
} else {
BtwAuthor ba = new BtwAuthor();
ArrayList<String> list = ba.findCoAuthorsByName(name, 0, 50);
json = ba.getCoauthorJson(list);
dao.saveDiscover(name, "author", json.toString());
}
return SUCCESS;
}
重点关注else{}内action是怎么使用BtwAuthor这个类,把name这个参数传进去,最后直接用ba.getCoauthorJson(list)得到了json计算结果,然后就回传给前端了。整个计算过程都封装在了BtwAuthor内,调用的时候就像上面那么简单。
词云的js生成代码单独在cloud.js里,我们不需要知道cloud.js里是怎么用d3代码生成图形的,我们只需要知道怎么调用这个js并做好传参工作。在index.js里有这个函数:
function wordcloud(str) {
var WIDTH = 850;
var HEIGHT = 300;
var ANGLE = 30;
var fill = d3.scale.category20();
d3.layout.cloud().size([WIDTH, HEIGHT]).words(str.map(function(d) {
return {text: d, size: 20 + Math.random() * 50};
})).rotate(function() {
return ~~(Math.random() * 2) * ANGLE;
}).font("Impact").fontSize(function(d) {
return d.size;
}).on("end", draw).start();
$('#load_three').remove();
$('#load_tag').remove();
function draw(words) {
d3.select(".wordcloud").append("svg").attr("width", WIDTH).attr("height", HEIGHT)
.append("g").attr("transform", "translate(450,150)").selectAll("text").data(words)
.enter().append("text").style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact").style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle").attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
}).text(function(d) { return d.text; });
}
}
WIDTH,HEIGHT定义了整个svg图的长宽尺寸,ANGLE定义了word的最小旋转角度。传参str来自后台action,完成这个绘制工作,我们关心的也就是传参和调用工作罢了。
说明一下生成共生关系的整个逻辑。首先在discover.jsp里有四种生成方式,分别是coauthor,cofield,copaper和coplace,这四个输入框和button,会对应四个action,coauthorAction,cofieldAction,copaperAction和coplaceAction。在index.js的入口处,设置了四个button的event触发效果:
//discover.jsp
$('.coauthor_btn').on("click", function(e) {
showVisualization("coauthor", $("#dname").val());
});
$('.cofield_btn').on("click", function(e) {
showVisualization("cofield", $("#dfield").val());
});
$('.coplace_btn').on("click", function(e) {
showVisualization("coplace", $("#dplace").val());
});
$('.copaper_btn').on("click", function(e) {
showVisualization("copaper", $("#dpaper").val());
});
而在showVisualization()里其实是设置了其他一些内容,然后主要做可视化工作的是coauthorOne()和coauthorTwo()两个函数,
function showWordCloud(user) {
var the_url = "";
if (user != undefined) {
the_url = "wordcloud?user="+user;
} else {
the_url = "wordcloud?name="+$("#rname").val();
}
alert(the_url);
$.ajax({
type : 'GET',
url : the_url,
dataType : 'json',
success : function(data) {
wordcloud(data.abs);
},
error : function(XmlHttpRequest, textStatus, errorThrown) {
alert("showWordCloud ajax error!");
}
});
}
分别对应力导向图的生成和矩阵图的生成,其生成数据上面介绍过了,就是后台的json计算方式。在两个coauthorXXX()里,我简单包装了下,具体两段代码比较长,就不帖出来了。