datavjs/lib/charts/scatterplotMatrix.js

615 lines
23 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 Raphael, d3, $, define */
/*!
* ScatterplotMatrix的兼容性定义
*/
;(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];});
}
})('ScatterplotMatrix', function (require) {
var DataV = require('DataV');
var Brush = require('Brush');
var ScatterplotMatrix = DataV.extend(DataV.Chart, {
initialize: function (node, options) {
this.type = "ScatterplotMatrix";
this.node = this.checkContainer(node);
this.defaults = {};
// Properties
this.defaults.allDimensions = [];
this.defaults.dimensionsX = []; //dimension of X axis(horizonal). array type
this.defaults.demensionsY = []; //dimension of Y axis(vertical). array type
this.defaults.dimensionDomain = {};
this.defaults.typeNames = [];
// canvas parameters
this.defaults.width = 522;
this.defaults.height = 522;
this.defaults.margin = 50;
this.defaults.gap = 15;
this.defaults.squareWidth = 150;
this.defaults.circleR = 3;
//图例区域的左上顶点坐标xy
this.defaults.legendArea = [20, (this.defaults.height - 20 - 220), 200, 220];
//简介区域的左上角顶点坐标xy
this.defaults.introArea = [20, 20, 200, 200];
//散点矩阵区域的左上顶点坐标xy
this.defaults.diagramArea = [240, 20, (this.defaults.width - 260), (this.defaults.height - 40)];
this.defaults.typeName = "NoTypeDefinition"; //默认情况是没有分类
this.defaults.legendDimen = "NoTagDimen";
this.setOptions(options);
this.createCanvas();
}
});
/**
* 设置X轴的维度
*/
ScatterplotMatrix.prototype.setDimensionsX = function (dimen) {
if (!dimen) {
throw new Error("Please specify the dimensions.");
}
var conf = this.defaults;
conf.dimensionsX = [];
var i = 0,
l = 0;
for (i = 0, l = dimen.length; i < l; i++) {
if (_.indexOf(conf.allDimensions, dimen[i]) !== -1) {
conf.dimensionsX.push(dimen[i]);
}
}
};
/**
* 设置Y轴的维度
*/
ScatterplotMatrix.prototype.setDimensionsY = function (dimen) {
if (!dimen) {
throw new Error("Please specify the dimensions.");
}
var conf = this.defaults;
conf.dimensionsY = [];
var i = 0,
l = 0;
for (i = 0, l = dimen.length; i < l; i++) {
if (_.indexOf(conf.allDimensions, dimen[i]) !== -1) {
conf.dimensionsY.push(dimen[i]);
}
}
};
//设置类型的名字
ScatterplotMatrix.prototype.setTypeName = function (types) {
this.defaults.typeNames = types;
};
//设置源数据
ScatterplotMatrix.prototype.setSource = function (source) {
var i, j, l, ll;
var conf = this.defaults;
var xTemp = [],
yTemp = [];
for (i = 1; i < source[0].length; i++) {
xTemp[i - 1] = source[0][i];
yTemp[i - 1] = source[0][i];
}
conf.allDimensions = source[0];
// 默认情况下,所有维度都显示
conf.dimensionsX = xTemp;
conf.dimensionsY = yTemp;
// this.source is array of line; key is dimension, value is line's value in that dimension
this.source = [];
for (i = 1, l = source.length; i < l; i++) {
var line = {}, dimenT = conf.allDimensions;
for (j = 0, ll = dimenT.length; j < ll; j++) {
line[dimenT[j]] = source[i][j]; //each line is an array, contains value for each dimension
}
this.source.push(line);
}
// 设置默认的定义域
var getExtent = function (s, dimen) {
return d3.extent(s, function (p) {
return +p[dimen];
});
};
var dimen;
for (i = 0, l = conf.allDimensions.length; i < l; i++) {
dimen = conf.allDimensions[i];
conf.dimensionDomain[dimen] = getExtent(this.source, dimen);
}
};
/**
* 设置X轴和Y轴
*/
ScatterplotMatrix.prototype.setAxis = function () {
var conf = this.defaults;
conf.legendArea = [20, (conf.height - 20 - 220), 200, 220];
conf.introArea = [20, 20, 200, 200];
conf.diagramArea = [240, 20, (conf.width - 260), (conf.height - 40)];
var w = conf.diagramArea[2] - 2 * conf.margin,
h = conf.diagramArea[3] - conf.margin,
g = conf.gap,
nX = conf.dimensionsX.length,
nY = conf.dimensionsY.length,
wX = d3.round((w - (nX - 1) * g) / nX),
wY = d3.round((h - (nY - 1) * g) / nY),
sw = d3.min([wX, wY]);
this.defaults.squareWidth = sw;
this.defaults.dX = conf.dimensionsX[0];
this.defaults.dY = conf.dimensionsY[0];
this.x = {};
this.y = {};
var x = this.x,
y = this.y;
var tickAr = [5];
//设置X轴
var i, l, dimen, begin, end;
for (i = 0, l = conf.dimensionsX.length; i < l; i++) {
dimen = conf.dimensionsX[i];
begin = i * (sw + g) + conf.diagramArea[0] + 30;
end = begin + sw;
x[dimen] = d3.scale.linear().domain(conf.dimensionDomain[dimen]).range([begin, end]);
x[dimen].ticks = x[dimen].ticks.apply(x[dimen], tickAr);
}
//设置Y轴
for (i = 0, l = conf.dimensionsY.length; i < l; i++) {
dimen = conf.dimensionsY[i];
end = i * (sw + g) + conf.diagramArea[1] + 30;
begin = end + sw;
y[dimen] = d3.scale.linear().domain(conf.dimensionDomain[dimen]).range([begin, end]);
y[dimen].ticks = y[dimen].ticks.apply(y[dimen], tickAr);
}
};
//画散点矩阵
ScatterplotMatrix.prototype.drawDiagram = function () {
var i, j, k, z, ticks;
var conf = this.defaults,
x = this.x,
y = this.y,
sw = conf.squareWidth,
g = conf.gap,
cR = conf.circleR;
var paper = this.canvas;
var sourceData = this.source;
var dimensionsX = conf.dimensionsX,
dimensionsY = conf.dimensionsY,
lx = dimensionsX.length,
ly = dimensionsY.length;
var browserName = navigator.appName;
var that = this;
$(this.node).append(this.floatTag);
//画背景点
var circlesBg = paper.set(); //背景点
var centerPos;
if (browserName !== "Microsoft Internet Explorer") {
for (k = 0; k < sourceData.length; k++) {
for (i = 0; i < lx; i++) {
for (j = 0; j < ly; j++) {
centerPos = this.circleCenter(k, dimensionsX[i], dimensionsY[j]);
circlesBg.push(paper.circle(centerPos[0], centerPos[1], cR).attr({
"fill": "gray",
"stroke": "none",
"opacity": 0.2
}));
}
}
}
}
// 画矩形框
var squares = paper.set();
var x1, y1;
for (i = 0; i < lx; i++) {
for (j = 0; j < ly; j++) {
x1 = x[dimensionsX[i]].range()[0];
y1 = y[dimensionsY[j]].range()[1];
squares.push(paper.rect(x1 - 1, y1 - 1, sw + 2, sw + 2));
}
}
squares.attr({
"fill": "white",
"fill-opacity": 0.5, //背景点的蒙版
"stroke": "#d6d6d6",
"stroke-width": '1px'
});
//画虚线
var reLines = paper.set(),
tickText = paper.set();
var tickAr = [10], //set the number of ticks
leftPos = x[dimensionsX[0]].range()[0],
rightPos = x[dimensionsX[lx - 1]].range()[1],
upPos = y[dimensionsY[0]].range()[1],
downPos = y[dimensionsY[ly - 1]].range()[0];
var reLineGap = sw / 7; //每个矩形框中画6条虚线
var reLinePos;
//画纵向的虚线
for (i = 0; i < lx; i++) {
ticks = x[dimensionsX[i]].ticks;
for (j = 0; j < ticks.length; j++) {
tickText.push(paper.text((x[dimensionsX[i]](ticks[j])), downPos + 6, ticks[j]).attr({
"fill": "#aaaaaa",
"fill-opacity": 0.7,
"font-family": "雅黑",
"font-size": 12
}).attr({
"text-anchor": "end"
}).rotate(-45, x[dimensionsX[i]](ticks[j]), downPos + 6));
}
for (z = 1; z < 7; z++) {
reLinePos = x[dimensionsX[i]].range()[0] + z * reLineGap;
reLines.push(paper.path("M" + (reLinePos) + "," + (upPos) + "L" + (reLinePos) + "," + (downPos)).attr({
"stroke": "#ebebeb",
"stroke-dasharray": "-"
}));
}
}
//画横向的虚线
for (i = 0; i < ly; i++) {
//draw reference lines
ticks = y[dimensionsY[i]].ticks;
for (j = 0; j < ticks.length; j++) {
tickText.push(paper.text(rightPos + 6, y[dimensionsY[i]](ticks[j]), ticks[j]).attr({
"fill": "#aaaaaa",
"fill-opacity": 0.7,
"font-family": "雅黑",
"font-size": 12
}).attr({
"text-anchor": "start"
}).rotate(315, rightPos + 6, y[dimensionsY[i]](ticks[j])));
}
for (z = 1; z < 7; z++) {
reLinePos = y[dimensionsY[i]].range()[1] + z * reLineGap;
reLines.push(paper.path("M" + (leftPos) + "," + (reLinePos) + "L" + (rightPos) + "," + (reLinePos)).attr({
"stroke": "#ebebeb",
"stroke-dasharray": "-"
}));
}
}
//坐标轴名称
var axText = paper.set();
var xPos, yPos;
var pos = y[dimensionsY[0]].range()[1] - 10;
for (i = 0; i < lx; i++) {
xPos = x[dimensionsX[i]].range()[0] + sw / 2;
axText.push(paper.text(xPos, pos, dimensionsX[i]).attr({
"fill": "#000000",
"fill-opacity": 0.7,
"font-family": "Verdana",
//"font-weight": "bold",
"font-size": 12
}).attr({
"text-anchor": "middle"
}));
}
pos = x[dimensionsX[0]].range()[0] - 10;
for (i = 0; i < ly; i++) {
yPos = y[dimensionsY[i]].range()[1] + sw / 2;
axText.push(paper.text(pos, yPos, dimensionsY[i]).attr({
"fill": "#000000",
"fill-opacity": 0.7,
"font-family": "Verdana",
//"font-weight": "bold",
"font-size": 12
}).attr({
"text-anchor": "middle"
}).rotate(-90, pos, yPos));
}
// 画前景点
var circlesFg = []; //circles in foreground
var circleType = -1;
var typeMax = -1;
this.preIndex = "start";
this.linePosition = [0,0];
//水平虚线
that.lineH = paper.path("M" + (leftPos) + "," + (0) + "L" + (rightPos) + "," + (0)).attr({
"stroke-dasharray": "- ",
'stroke': '#000000'
}).hide();
//垂直虚线
that.lineV = paper.path("M" + (0) + "," + (upPos) + "L" + (0) + "," + (downPos)).attr({
"stroke-dasharray": "- ",
'stroke': '#000000'
}).hide();
var hoverTag;
var circle;
for (k = 0; k < sourceData.length; k++) {
if (conf.typeName !== "NoTypeDefinition") { //classify the circles according to their types
circleType = sourceData[k][conf.typeName] - 1;
typeMax = Math.max(typeMax, circleType);
} else {
circleType = 0;
}
for (i = 0; i < lx; i++) {
for (j = 0; j < ly; j++) {
centerPos = this.circleCenter(k, dimensionsX[i], dimensionsY[j]);
//前景点
circle = paper.circle(centerPos[0], centerPos[1], cR)
.data("type", circleType)
.data("canHover", 0)
.data("position", centerPos)
.data('colorType', circleType)
.attr({
"fill": "#800",
"stroke": "none",
"opacity": 0.5
}).attr({
"fill": this.getColor(circleType)
});
//如果制定了hover要显示的文字则hover显示的文字
if (conf.legendDimen !== "NoTagDimen") {
hoverTag = conf.legendDimen + ": " + sourceData[k][conf.legendDimen];
circle.data("legend", hoverTag);
}
circlesFg.push(circle);
}
}
}
//图例
var legendArea = this.defaults.legendArea;
var rectBn = paper.set();
var underBn = [];
for (i = 0; i <= typeMax; i++) {
//底框
underBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
"fill": "#ebebeb",
"stroke": "none"
}).hide());
//色框
paper.rect(legendArea[0] + 10 + 3, legendArea[1] + 10 + (20 + 3) * i + 6, 16, 8).attr({
"fill": this.getColor(i),
"stroke": "none"
});
//文字
paper.text(legendArea[0] + 10 + 3 + 16 + 8, legendArea[1] + 10 + (20 + 3) * i + 10, conf.typeNames[i]).attr({
"fill": "black",
"fill-opacity": 1,
"font-family": "Verdana",
"font-size": 12
}).attr({
"text-anchor": "start"
});
//选框
rectBn.push(paper.rect(legendArea[0] + 10, legendArea[1] + 10 + (20 + 3) * i, 180, 20).attr({
"fill": "white",
"fill-opacity": 0,
"stroke": "none"
//"r": 3
}).data("type", i)).data("clicked", 0);
}
if (browserName !== "Microsoft Internet Explorer") {
rectBn.forEach(function (d, i) {
underBn[i].data('legendclicked', false);
d.mouseover(function () {
if (underBn[i].data('legendclicked') === false) {
underBn[i].attr('opacity', 0.5).show();
}
}).mouseout(function () {
if (underBn[i].data('legendclicked') === false) {
underBn[i].hide();
}
});
d.click(function () {
for (j = 0; j < underBn.length; j++) {
if (j === i) {
underBn[j].show();
} else {
underBn[j].hide();
}
}
rectBn.forEach(function (eachBn) {
if (eachBn !== d) {
eachBn.data("clicked", 0);
}
});
if (d.data("clicked") === 0) {
underBn[i].attr('opacity', 1).show();
underBn[i].data('legendclicked', true);
circlesFg.forEach(function (ec) {
if (ec.data("type") !== d.data("type")) {
ec.hide();
ec.data("canHover", 0);
} else {
ec.show();
ec.data("canHover", 1);
}
});
d.data("clicked", 1);
} else if (d.data("clicked") === 1) {
underBn[i].hide();
underBn[i].data('legendclicked', false);
d.data("clicked", 0);
circlesFg.forEach(function (ec) {
ec.show();
ec.data("canHover", 0);
});
}
});
});
//Bursh函数定义
var curBrush;
function brushstart() {
if (curBrush !== undefined && curBrush !== d3.event.target) {
curBrush.clear();
}
var i;
for (i = 0; i < circlesFg.length; i++) {
circlesFg[i].hide();
circlesFg[i].data("canHover", 0);
}
underBn.forEach(function (ub) {
ub.hide();
});
rectBn.forEach(function (rb) {
rb.data("clicked", 0);
});
}
function brush() {
curBrush = d3.event.target;
var e = curBrush.extent(),
dimX = d3.event.target.dimX,
dimY = d3.event.target.dimY,
tempX,
tempY,
count = lx * ly,
i,
z;
for (i = 0; i < sourceData.length; i++) {
tempX = sourceData[i][dimX];
tempY = sourceData[i][dimY];
if (e[0][0] - 1 <= tempX && tempX <= e[1][0] + 1 && e[0][1] - 1 <= tempY && tempY <= e[1][1] + 1) {
for (z = 0; z < count; z++) {
circlesFg[i * count + z].show();
}
} else {
for (z = 0; z < count; z++) {
circlesFg[i * count + z].hide();
}
}
}
}
function brushend() {
if (d3.event.target.empty()) {
circlesFg.forEach(function (d) {
d.show();
});
}
}
//Brush交互
var brushes = [];
var b;
for (i = 0; i < lx; i++) {
for (j = 0; j < ly; j++) {
b = Brush().x(x[dimensionsX[i]]).y(y[dimensionsY[j]]).backgroundAttr({
"opacity": 0, //背景颜色:白色、全透明
"fill": "white"
}).foregroundAttr({ //选框颜色
"opacity": 0.2,
"fill": "#fff700"
}).on("brushstart", brushstart).on("brush", brush).on("brushend", brushend);
b(paper);
b.dimX = dimensionsX[i];
b.dimY = dimensionsY[j];
brushes.push(b);
}
}
//hover交互
//var preIndex = "start";
var floatTag = this.floatTag;
$(paper.canvas).bind("mousemove", function (e) {
var bgOffset = $(this).parent().offset();
var mouse = [e.pageX - bgOffset.left, e.pageY - bgOffset.top];
var location = [Math.floor((mouse[0] - leftPos) / (sw + g)), Math.floor((mouse[1] - upPos) / (sw + g))];
if (that.preIndex !== "start") {
that.lineV.hide();
that.lineH.hide();
if (conf.legendDimen !== "NoTagDimen") {
floatTag.css({"visibility" : "hidden"});
}
}
if (location[0] >= 0 && location[0] <= lx && location[1] >= 0 && location[1] <= ly) {
for (i = location[0] * ly + location[1]; i < circlesFg.length; i = i + lx * ly) {
var center = circlesFg[i].data("position");
var canHover = circlesFg[i].data("canHover");
if ((canHover === 1) && (Math.abs(mouse[0] - center[0]) <= cR) && (Math.abs(mouse[1] - center[1]) <= cR)) {
that.lineV.translate(center[0] - that.linePosition[0], 0).attr('stroke', that.getColor(circlesFg[i].data('colorType'))).show();
that.lineH.translate(0, center[1] - that.linePosition[1]).attr('stroke', that.getColor(circlesFg[i].data('colorType'))).show();
that.linePosition = center;
if (conf.legendDimen !== "NoTagDimen") {
floatTag.html('<div style="text-align: center;margin:auto;color:#ffffff">' + circlesFg[i].data("legend") + '</div>');
floatTag.css({"visibility" : "visible"});
}
that.preIndex = i;
break;
}
}
}
});
}
};
/**
* 创建canvas
*/
ScatterplotMatrix.prototype.createCanvas = function () {
var conf = this.defaults;
this.node.style.position = "relative";
this.canvas = new Raphael(this.node, conf.width, conf.height);
this.floatTag = DataV.FloatTag()(this.node);
this.floatTag.css({"visibility": "hidden"});
};
//根据不同类别得到颜色值
ScatterplotMatrix.prototype.getColor = function (circleType) {
var color = DataV.getColor();
return color[circleType % color.length][0];
};
//绘制函数
ScatterplotMatrix.prototype.render = function (options) {
this.setOptions(options);
this.canvas.clear();
this.setAxis();
this.drawDiagram();
//var dEnd = new Date();
//alert(dEnd.getTime() - dBegin.getTime());
};
//计算每个circle的圆心位置
ScatterplotMatrix.prototype.circleCenter = function (index, xDimen, yDimen) {
var conf = this.defaults,
source = this.source,
y = this.y,
x = this.x,
dimensionsX = conf.dimensionsX,
dimensionsY = conf.dimensionsY,
dimensionType = conf.dimensionType;
var xPos = x[xDimen](source[index][xDimen]),
yPos = y[yDimen](source[index][yDimen]);
return [xPos, yPos];
};
return ScatterplotMatrix;
});