datavjs/lib/charts/bundle.js
2012-11-26 18:31:44 +08:00

425 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*global EventProxy, d3, Raphael, self, packages, $ */
/*!
* Bundle的兼容性定义
*/
;(function (name, definition) {
if (typeof define === 'function') { // Module
define(definition);
} else { // Assign to common namespaces or simply the global object (window)
this[name] = definition(function (id) { return this[id];});
}
})('Bundle', function (require) {
var DataV = require('DataV');
/**
* 构造函数node参数表示在html的哪个容器中绘制该组件
* options对象为用户自定义的组件的属性比如画布大小
*/
var Bundle = DataV.extend(DataV.Chart, {
initialize: function (node, options) {
this.type = "Bundle";
this.node = this.checkContainer(node);
this.json = {};
// 图的半径
this.defaults.diameter = 960;
this.defaults.radius = this.defaults.diameter / 2;
this.defaults.innerRadius = this.defaults.radius - 120;
this.defaults.tension = 0.85;
this.defaults.color = {
defaultLineColor: "#4065AF",
defaultWordColor: "#000000",
lineHoverColor: "#02B0ED",
nodeHoverColor: "#02B0ED",
importNodesColor: "#5DA714", //被引用的节点
exportNodesColor: "#FE3919" //引用当前节点的节点
};
this.setOptions(options);
this.createCanvas();
}
});
/**
* 设置用户自定义属性
*/
Bundle.prototype.setOptions = function (options) {
if (options) {
var prop;
for (prop in options) {
if (options.hasOwnProperty(prop)) {
this.defaults[prop] = options[prop];
if (prop === "diameter") {
this.defaults.radius = this.defaults.diameter / 2;
this.defaults.innerRadius = this.defaults.radius - 120;
} else if (prop === "radius") {
this.defaults.diameter = this.defaults.radius * 2;
this.defaults.innerRadius = this.defaults.radius - 120;
} else if (prop === "innerRadius") {
this.defaults.radius = this.defaults.innerRadius + 120;
this.defaults.diameter = this.defaults.radius * 2;
} else if (prop === "width") {
this.defaults.diameter = this.defaults.width;
this.defaults.radius = this.defaults.diameter / 2;
this.defaults.innerRadius = this.defaults.radius - 120;
}
}
}
}
};
/**
* 对原始数据进行处理
* TODO: 改进为获取值时运算
*/
Bundle.prototype.setSource = function (source) {
if (source[0] && source[0] instanceof Array) {
// csv or 2d array source
if (source[0][0] === "name") {
source = source.slice(1); // 从第一行开始第0行舍去
}
var nData = [];
var imports = [];
//var isNode = true;
var nodeNum;
var that = this;
source.forEach(function (d, i) {
if (d[0] === "") {
throw new Error("name can not be empty(line:" + (i + 1) + ").");
}
if (d[1] !== "") {
imports = d[1].split(" ");
}
nData[i] = {
name: d[0],
imports: imports
};
});
this.json = nData;
} else {
// json source
this.json = source;
}
};
/**
* 创建画布
*/
Bundle.prototype.createCanvas = function () {
var conf = this.defaults;
this.canvas = new Raphael(this.node, conf.diameter, conf.diameter);
//var c = this.canvas.circle(50, 50, 40);
};
/**
* 布局
*/
Bundle.prototype.layout = function () {
var packages = {
// Lazily construct the package hierarchy from class names.
root: function (classes) {
var map = {};
function construct(name, data) {
var node = map[name], i;
if (!node) {
node = map[name] = data || {name: name, children: []};
if (name.length) {
node.parent = construct(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function (d) {
construct(d.name, d);
});
return map[""];
},
// Return a list of imports for the given array of nodes.
imports: function (nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function (d) {
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function (d) {
if (d.imports) {
d.imports.forEach(function (i) {imports.push({source: map[d.name], target: map[i]});
});
}
});
return imports;
}
};
var cluster = d3.layout.cluster()
.size([360, this.defaults.innerRadius]) //.size(角度,半径)
.sort(null)
.value(function (d) {
return d.size;
});
this.nodes = cluster.nodes(packages.root(this.json));
this.links = packages.imports(this.nodes);
};
/**
* 渲染弦图
*/
Bundle.prototype.render = function () {
this.layout();
this.generatePaths();
};
/**
* 生成路径
*/
Bundle.prototype.generatePaths = function (options) {
var that = this;
var canvas = this.canvas;
var rNodes = canvas.set();
var rLinks = canvas.set();
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(this.defaults.tension)
.radius(function (d) {
return d.y;
})
.angle(function (d) {
return d.x / 180 * Math.PI;
});
//定义图中的弦和节点
var nodes = this.nodes;
var links = this.links;
var linksCount = links.length;
var paths = bundle(links);
var locateStr = ""; //对文字进行平移和旋转
var locateBBox = ""; //对文字的bounding box进行平移和旋转
var r = 0;
var angle = 0;
var xTrans = 0;
var yTrans = 0;
var anchor; //text-anchor: start or end
var rotateStr = "";
//element data cache
var nodeRelatedElements = {};// {key: {targetLink: [], sourceLink: [], targetNode: [], sourceNode: []}}
var nodeElements = {}; //{key: Els}
var bBoxElements = {}; //{key: Els}
var i,
j,
key,
textEl,
bBox,
bBoxNew,
tCenterX,
tCenterY,
bBoxEl,
linkEl;
var mouseoverLink = function () {
var current = this;
//var color = that.data("color");
if (rLinks.preLink) {
rLinks.preLink.attr("stroke", that.defaults.color.defaultLineColor)
.attr("stroke-width", 1)
.attr("stroke-opacity", 0.6);
}
rLinks.preLink = this;
current.attr("stroke", that.defaults.color.lineHoverColor)
.attr("stroke-width", 2)
.attr("stroke-opacity", 1.0)
.toFront(); //把当前弦移到画布最上层
};
var mouseoverNode = function () {
var relatedEl = this.data("relatedElements");
//高亮所选节点的文字颜色
this.data("relatedNode").attr({"fill": that.defaults.color.nodeHoverColor,
"fill-opacity": 1.0, "font-weight": "600"});
//将包围盒颜色设为透明
this.attr({"fill": that.defaults.color.nodeHoverColor, "fill-opacity": 0.0/*, "font-weight": "600"*/});
relatedEl.sourceLink.forEach(function (d) { //set green
d.attr({"stroke": that.defaults.color.importNodesColor, "stroke-width": 1, "stroke-opacity": 0.9})
.toFront();
});
relatedEl.sourceNode.forEach(function (d) {
d.attr({"fill": that.defaults.color.importNodesColor, "font-weight": "600"});
});
relatedEl.targetLink.forEach(function (d) { //set red
d.attr({"stroke": that.defaults.color.exportNodesColor, "stroke-width": 1, "stroke-opacity": 0.9})
.toFront();
});
relatedEl.targetNode.forEach(function (d) {
d.attr({"fill": that.defaults.color.exportNodesColor, "font-weight": "600"});
});
};
var mouseoutNode = function () {
var relatedEl = this.data("relatedElements");
this.data("relatedNode").attr({"fill": that.defaults.color.defaultWordColor,
"font-weight": "400", "fill-opacity": 1.0});
relatedEl.targetLink.forEach(function (d) {
d.attr({"stroke": that.defaults.color.defaultLineColor, "stroke-width": 1, "stroke-opacity": 0.6});
});
relatedEl.targetNode.forEach(function (d) {
d.attr({"fill": that.defaults.color.defaultWordColor, "font-weight": "400"});
});
relatedEl.sourceLink.forEach(function (d) {
d.attr({"stroke": that.defaults.color.defaultLineColor, "stroke-width": 1, "stroke-opacity": 0.6});
});
relatedEl.sourceNode.forEach(function (d) {
d.attr({"fill": that.defaults.color.defaultWordColor, "font-weight": "400"});
});
};
for (j = 0; j < nodes.length; j++) {
//若为叶子节点
if (!nodes[j].children) {
locateStr = "T" + that.defaults.radius + "," + that.defaults.radius + "R"; //使用大写T、R、S--绝对not相对
//半径: add a padding between lines and words
r = nodes[j].y + 20;
//计算旋转角度和水平、竖直方向所需平移的距离
angle = (nodes[j].x - 90) * Math.PI / 180;
xTrans = r * Math.cos(angle);
yTrans = r * Math.sin(angle);
//计算text-anchor
if (nodes[j].x < 180) {
anchor = "start";
} else {
anchor = "end";
}
//计算文字方向是否需要旋转180度
if (nodes[j].x < 180) {
rotateStr = "";
} else {
rotateStr = "R180";
}
//计算文字需要如何经过平移和旋转被排列在圆周上
locateStr += (nodes[j].x - 90) + rotateStr + "T" + xTrans + "," + yTrans;
//绘制文字
textEl = canvas.text()
.attr("font", "11px arial")
.data("color", that.defaults.color)
.attr("text", nodes[j].key)
//.attr("title", nodes[j].size)
.transform(locateStr)
.attr("text-anchor", anchor)
.attr("fill", that.defaults.color.defaultWordColor);
//获取旋转平移之前文字的bounding box
bBox = textEl.getBBox(true);
//canvas.rect(bBox.x, bBox.y, bBox.width, bBox.height);
//获取旋转平移之后文字的bounding box
bBoxNew = textEl.getBBox();
//adjust vml box center
if (Raphael.vml) {
//vml's word bbox is not related to text-anchor, always middle;
//svg's word bbox is related to text-anchor;
bBoxNew.x = bBoxNew.x + bBox.width / 2 * Math.cos(angle);
bBoxNew.y = bBoxNew.y + bBox.width / 2 * Math.sin(angle);
}
//canvas.rect(bBoxNew.x, bBoxNew.y, bBoxNew.width, bBoxNew.height);
//新旧bounding box的中心坐标变化
tCenterX = bBoxNew.x + bBoxNew.width / 2 - bBox.x - bBox.width / 2;
tCenterY = bBoxNew.y + bBoxNew.height / 2 - bBox.y - bBox.height / 2;
//对bounding box进行平移和旋转
locateBBox = "T" + tCenterX + "," + tCenterY + "R" + (nodes[j].x - 90) + rotateStr;
// 包围盒
bBoxEl = canvas.rect(bBox.x, bBox.y, bBox.width, bBox.height)
.transform(locateBBox)
.data("relatedNode", textEl)
.attr({"fill": "#fff", "opacity": 0.01});
key = nodes[j].key;
nodeElements[key] = textEl;
bBoxElements[key] = bBoxEl;
nodeRelatedElements[key] = {targetLink: [], sourceLink: [], targetNode: [], sourceNode: []};
rNodes.push(textEl);
}
}
//绘制曲线
for (i = 0; i < linksCount; i++) {
var l = paths[i];
//对paths数组中的每一项进行计算由路径节点信息得到坐标值
var spline = line(l);
var sourceKey = links[i].source.key;
var targetKey = links[i].target.key;
var tips = "link source: " + sourceKey + "\n"
+ "link target: " + targetKey;
linkEl = canvas.path(spline)
//.attr("stroke", that.defaults.defaultLineColor)
.attr("stroke-opacity", 0.6)
.attr("title", tips)
.attr("d", spline)
.attr("stroke", that.defaults.color.defaultLineColor)
.translate(that.defaults.radius, that.defaults.radius)
.mouseover(mouseoverLink);
//.mouseout(mouseoutLink);
linkEl[0].el = linkEl;
nodeRelatedElements[sourceKey].targetLink.push(linkEl);
nodeRelatedElements[sourceKey].targetNode.push(nodeElements[targetKey]);
nodeRelatedElements[targetKey].sourceLink.push(linkEl);
nodeRelatedElements[targetKey].sourceNode.push(nodeElements[sourceKey]);
rLinks.push(linkEl);
}
$(this.canvas.canvas).mousemove(function (e) {
if(!e.target.el && rLinks.preLink){
rLinks.preLink.attr("stroke", that.defaults.color.defaultLineColor)
.attr("stroke-width", 1)
.attr("stroke-opacity", 0.6);
rLinks.preLink = undefined;
//console.log("a");
}
});
//bind text words hover event
for (key in bBoxElements) {
if (bBoxElements.hasOwnProperty(key)) {
bBoxElements[key].data("relatedElements", nodeRelatedElements[key])
.mouseover(mouseoverNode)
.mouseout(mouseoutNode);
}
}
};
return Bundle;
});