最近毕设基本告一段落,题目是数据可视化相关的项目,用到了d3.js,这里集中回顾记录下遇到的一些问题。
毕设要求基于Web显示柑橘蛋白质分支间相互做用的关系,蛋白质总数共8195个,共有12491组不同关系。当然这些蛋白质的检测工作与我无关,数据是现成的,我的任务是拿到它们并把它们之间的关系直观的显示出来。
所给数据是csv格式的,而且信息是分散到多个不同的csv文件中的,考虑到最终的显示的是网络图,所以我决定将csv转换成关系型数据存储,最后根据查询条件动态生成自定义符合d3.js force graph
要求的json格式。基于上面的需求,我的实施方案如下:
前端 JQuery D3.js Bootstrap 后端 Python Tornado Sqlite3(后边会转移到Mysql上)
####Step 1 格式化csv文件,清洗数据 后端语言用的Python,当然脚本也用Python写的,用到了csv,sqlite3模块。处理文件较大约30MB的样子,电脑比较渣,耗时大概3个小时才建起数据库 囧…
####Step 2 定义Json生成接口
按照D3里Force Layout所要求的JSON格式做定义,因为仅仅是一对多的关系,所以仍然是nodes与links结构,只是多了些附加字段用来做其它功能的扩展。比如name,exp,summ,score1,score2
等。
{
"nodes": [
{
"summ": {
"bin": "30.7",
"mapman": "signalling.14-3-3 proteins",
"annotation": "annotation"
},
"name": "Cs2g04850.2",
"exp": {
"flower": 95.2894,
"leaf": 65.7036,
"fruit": 34.7638,
"mixture1": 64.9308,
"mixture2": 40.8747,
"mixture3": 56.1705,
"callus": 17.1658
},
"center": "y"
},
...
],
"links": [
{
"source": "Cs2g04850.2",
"target": "Cs6g10590.1",
"score2": 1,
"score1": 0
},
...
]
}
####Step 3 D3渲染可视化效果 这是本次毕设的核心部分,除了D3还用到了JQuery,Ajax技术,JQuery与Ajax主要做为前端交换使用,用于提交查询,异步获取Json数据。当然这里面也遇到不少问题。
解决办法: node中有一个fixed属性,取值为boolean值,true代表位置固定,false代表不固定,在遍历所有nodes时候,判断出中心点,为其设置固定坐标(x,y),同时设置fixed属性即可
解决办法: 这是个浏览器兼容问题,之所以firefox中会有问题,因为Firefox中form表单的默认行为,影响了d3.json
方法的异步请求,同样的使用JQery的jquery.getjson()
也会有问题。有两个方案可行,一是不要用表单提交,直接用input
,或者改变表单的默认行为,后者比较麻烦。另外动态生成的json数据要设置Content-Type
为application/json
解决办法: 对svg容器添加d3.behavior.zoom()
行为,并设置zoom事件监听器。最关键的地方是要记得,在svg后追加一个g
容器,代码如下
var vis = d3.select("body")
.append("svg:svg")
.attr("class", "stage")
.attr("width", w)
.attr("height", h)
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", zoom))
.append('svg:g');
vis.append("svg:rect")
.attr("class", "overlay")
.attr("width", w)
.attr("height", h);
function zoom() {
vis.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
解决办法: 修改json中links的映射关系,source与target分别引用到nodes中的点,映射函数用的是d3.map()
,因为中心点的关系在nodes中用两个,所以使用d3.set()
建立nodes的map结构时会发生覆盖问题,导致后面建立link关系时会出现孤立的点,这时就需要在建立map时,对map中是否已经存在该键做判断,代码如下
function mapNodes(nodes){
nodesMap = d3.map()
nodes.forEach(function(n){
if (!nodesMap.has(n.name)){
nodesMap.set(n.name,n)
}
});
return nodesMap;
}
nodesMap = mapNodes(ppi_json.nodes);
ppi_json.links.forEach(function(l){
l.source = nodesMap.get(l.source)
l.target = nodesMap.get(l.target)
});
SVG,PATH用法可以参考mozilla的MDN
####总结 以上是这次毕设的总体思路与一些自认为关键的细节点,因为之前不是专门做js的,所以有罗嗦了一点,总的来说这个项目还是很锻炼人的,让我又把web相关的东西(前后台)理了一遍,后续我会把这个项目搭建到SAE上,并把源码放到github上