/*global Raphael, d3, $, _ */
/*!
* Bubble的兼容性定义
*/
;(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];});
}
})('Bubble', function (require) {
var DataV = require('DataV');
var Axis = require('Axis');
/**
* Recently, bubble graph can represent five dimensions by xaxis,yaxis,size,color and time.
* You can stop animation by pause() method, start animation by initControls method;
* you can change animation start time by using global variable this.startTime;
* you can visualize a time point's data by generatePaths(time point) method;
* an inside method drawAllTime(key) is designed for interaction.
* Options:
* - `width`, 图表宽度,默认800
* - `height`, 图表高度, 默认600
* - `minRadius`, 最小圆角值
* - `meshInterval`, 背景网格的间距, 默认20
* - `showLegend`, 是否显示图例,默认显示
* - `margin`, 主图的间距,[上, 右, 下, 左], 默认为[30, 0, 80, 200]。当不显示图例时,可调节此处
* - `tipStyle`, 提示框样式
* - `skeletonLineAttr`, 龙骨线属性
* - `skeletonCircleAttr`, 龙骨点属性
*/
var Bubble = DataV.extend(DataV.Chart, {
initialize: function (node, options) {
this.type = "Bubble";
this.node = this.checkContainer(node);
// setting display width and height, also they can be changed by options
this.defaults.width = 600;
this.defaults.height = 360;
this.defaults.minRadius = 10;
this.defaults.maxRadius = 40;
this.defaults.meshInterval = 20;
this.defaults.margin = [30, 0, 80, 100];
this.defaults.allDimensions = [];
this.defaults.dimensions = [];
this.defaults.dimensionType = {};
this.defaults.dimensionDomain = {};
this.defaults.dotStrokeColor = {"stroke": "#fff"};
this.defaults.colorBarHeight = 27;
this.defaults.showLegend = true;
this.defaults.skeletonCircleAttr = {
"fill": "#000",
"fill-opacity": 0.6,
"stroke-width": 0
};
this.defaults.skeletonLineAttr = {
"stroke": "#000",
"stroke-width": 0.5,
"stroke-opacity": 0.5
};
this.defaults.tipStyle = {
"textAlign": "left",
"margin": "auto",
"color": "#ffffff"
};
/**
* @param {String} key 关键字
* @param {Number} x0 横轴值
* @param {Number} y0 纵轴值
* @param {Number} r0 半径值
* @param {Number} c0 分组值
* @param {String} time 时间值
*/
this.formatter.tipFormat = function (key, x0, y0, r0, c0, time) {
var tpl = '' + this.keyDimen + ':{key}
' +
'' + this.xDimen + ':{xDimen}
' +
'' + this.yDimen + ':{yDimen}
' +
'' + this.sizeDimen + ':{sizeDimen}
' +
'' + this.colorDimen + ':{colorDimen}
' +
'' + this.timeDimen + ':{timeDimen}';
var tip = tpl.replace('{key}', key);
tip = tip.replace('{xDimen}', x0);
tip = tip.replace('{yDimen}', y0);
tip = tip.replace('{sizeDimen}', r0);
tip = tip.replace('{colorDimen}', c0);
tip = tip.replace('{timeDimen}', time);
return tip;
};
this.setOptions(options);
this.createCanvas();
}
});
Bubble.prototype.getTip = function (key, index) {
var timeKeys = this.timeKeys;
var value = timeKeys[index];
var x = this.interpolateData(value, timeKeys, this.getKeyData(this.xDimen, key)),
y = this.interpolateData(value, timeKeys, this.getKeyData(this.yDimen, key)),
size = this.interpolateData(value, timeKeys, this.getKeyData(this.sizeDimen, key)),
color = this.getColorData(key);
var formatter = this.getFormatter("tipFormat");
return formatter.call(this, key, x, y, size, color, this.times[value]);
};
/**
* Creates a backCanvas for the visualization
*/
Bubble.prototype.createCanvas = function () {
var conf = this.defaults;
var margin = conf.margin;
this.backCanvas = new Raphael(this.node, conf.width, conf.height);
this.foreCanvas = new Raphael(this.node, conf.width, conf.height);
$(this.node).css("position", "relative");
$(this.foreCanvas.canvas).css({
"position": "absolute",
"zIndex": 2,
"left": margin[3],
"top": margin[0]
});
this.floatTag = DataV.FloatTag()(this.node);
this.floatTag.css({"visibility": "hidden"});
$(this.node).append(this.floatTag);
};
/**
* Chooses bubble graph setted visualization dimens orderly
*/
Bubble.prototype.chooseDimensions = function (dimen) {
var conf = this.defaults;
conf.dimensions = dimen.filter(function (item) {
return _.indexOf(conf.allDimensions, item) !== -1;
});
this.timeDimen = conf.dimensions[0];
this.keyDimen = conf.dimensions[1];
this.xDimen = conf.dimensions[2];
this.yDimen = conf.dimensions[3];
this.sizeDimen = conf.dimensions[4];
this.colorDimen = conf.dimensions[5];
this.keys = [];
this.times = [];
this.timeKeys = [];
for (var i = 0, l = this.source.length; i < l; i++) {
this.keys.push(this.source[i][this.keyDimen]);
this.times.push(this.source[i][this.timeDimen]);
}
this.times = _.uniq(this.times);
this.keys = _.uniq(this.keys);
this.timeKeys = _.range(this.times.length);
this.startTime = 0;
};
/**
* set source, get dimensions data, dimension type, and dimension domain
* default visualization dimension is setted here
*/
Bubble.prototype.setSource = function (source) {
var conf = this.defaults;
conf.allDimensions = source[0];
// by default all dimensions show
conf.dimensions = source[0];
this.source = [];
for (var i = 1, l = source.length; i < l; i++){
var dot = {},
dimen = conf.allDimensions;
for (var j=0, ll=dimen.length; j < ll; j++){
dot[dimen[j]] = source[i][j];
}
this.source.push(dot);
}
// judge dimesions type auto
conf.dimensionType = {};
for (var i = 0, l = conf.allDimensions.length; i < l; i++){
var type = "quantitative";
for (var j = 1, ll = source.length; j < ll; j++){
var d = source[j][i];
if(d && (!DataV.isNumeric(d))){
type = "ordinal";
break;
}
}
conf.dimensionType[conf.allDimensions[i]] = type;
}
// set default dimensionDomain
for (var i = 0, l = conf.allDimensions.length; i < l; i++){
var dimen = conf.allDimensions[i];
if (conf.dimensionType[dimen] === "quantitative") {
conf.dimensionDomain[dimen] = d3.extent(this.source, function (p) {
return Math.abs(p[dimen]);
});
} else {
conf.dimensionDomain[dimen] = this.source.map(function(p){
return p[dimen];
});
}
}
this.timeDimen = conf.dimensions[0];
this.keyDimen = conf.dimensions[1];
this.xDimen = conf.dimensions[2];
this.yDimen = conf.dimensions[3];
this.sizeDimen = conf.dimensions[4];
this.colorDimen = conf.dimensions[5];
this.keys = [];
this.times = [];
this.timeKeys = [];
for (var i = 0, l = this.source.length; i < l; i++) {
this.keys.push(this.source[i][this.keyDimen]);
this.times.push(this.source[i][this.timeDimen]);
}
this.times = _.uniq(this.times);
this.keys = _.uniq(this.keys);
for (var i = 0, l = this.times.length; i < l; i++) {
this.timeKeys.push(i);
}
this.startTime = 0;
};
/**
* 绘制图例
*/
Bubble.prototype.drawLegend = function () {
var conf = this.defaults;
var that = this;
var colorData = _.uniq(this.keys.map(function (key) {
return that.getColorData(key);
}));
// draw colorbar
var tagArea = [20, (conf.height - conf.margin[2] - colorData.length * 23), 200, 220];
var backCanvas = this.backCanvas;
var rectBn = this.backCanvas.set();
var underBn = [];
for (var i = 0, l = colorData.length; i < l; i++) {
var c = this.c[this.colorDimen](colorData[i]);
// background to add interaction
underBn.push(backCanvas.rect(tagArea[0] + 10, tagArea[1] + 10 + (20 + 3) * i, 120, 20)
.attr({"fill": "#ebebeb", "stroke": "none"}).hide());
// real colorbar
backCanvas.rect(tagArea[0] + 10 + 3, tagArea[1] + 10 + (20 + 3) * i + 6, 16, 8)
.attr({"fill": c, "stroke": "none"});
// colorbar text
backCanvas.text(tagArea[0] + 10 + 3 + 16 + 8, tagArea[1] + 10 + (20 + 3) * i + 10, colorData[i])
.attr({"fill": "black", "fill-opacity": 1, "font-family": "Verdana", "font-size": 12})
.attr({"text-anchor": "start"});
// just for interaction -- selction
rectBn.push(backCanvas.rect(tagArea[0] + 10, tagArea[1] + 10 + (20 + 3) * i, 50, 20)
.attr({"fill": "white", "fill-opacity": 0, "stroke": "none"})
.data("type", i).data("colorType", colorData[i]));
}
// add interaction for colorbar
this.interactionType = null;
rectBn.forEach(function (d) {
d.hover(function () {
if (!that.interval) {
for (var i = 0, l = underBn.length; i < l; i++) {
if (i === d.data("type")) {
underBn[i].show();
that.interactionType = d.data("colorType");
that.generatePaths(Math.ceil(that.startTime));
}
}
}
},
function () {
for (var i = 0, l = underBn.length; i < l; i++) {
if (i === d.data("type")) {
underBn[i].hide();
that.interactionType = null;
}
}
});
});
};
/**
* different visualization scale is defined here
*/
Bubble.prototype.getScale = function() {
var that = this;
var conf = this.defaults,
margin = conf.margin,
w = conf.width - margin[3] - margin[1],
h = conf.height - margin[0] - margin[2],
maxRadius = conf.maxRadius,
minRadius = conf.minRadius;
var backCanvas = this.backCanvas,
xDimen = this.xDimen,
yDimen = this.yDimen,
sizeDimen = this.sizeDimen,
colorDimen = this.colorDimen,
xMin = conf.dimensionDomain[xDimen][0],
yMin = conf.dimensionDomain[yDimen][0],
xMax = conf.dimensionDomain[xDimen][1],
yMax = conf.dimensionDomain[yDimen][1],
xBorder = (maxRadius + 30) * (xMax - xMin)/w,
yBorder = (maxRadius + 30) * (yMax - yMin)/h,
xDomain = [xMin - xBorder, xMax + xBorder],
yDomain = [yMin - yBorder, yMax + yBorder];
this.x = {};
this.x[xDimen] = d3.scale.linear()
.domain(xDomain).range([margin[3], margin[3] + w]);
this.y = {};
this.y[yDimen] = d3.scale.linear()
.domain(yDomain).range([h, 0]);
this.z = {};
this.z[sizeDimen] = d3.scale.linear()
.domain(conf.dimensionDomain[sizeDimen]).range([minRadius, maxRadius]);
this.c = {};
this.c[colorDimen] = this.colorDB({mode: "random", ratio: 0.5});
if (conf.showLegend) {
this.drawLegend();
}
var playButtonBack = backCanvas.rect(0,0,24,24,2).attr({"stroke": "none","fill": "#d6d6d6"});
var startPatternPath = "M7,18L19,12L7,6V18z";
var stopPatternPathL = "M7,7sh4v10sh-4z";
var stopPatternPathR = "M13,7sh4v10sh-4z";
var startPattern = backCanvas.path(startPatternPath).attr({
"stroke-width": 0,
"stroke-linejoin": "round",
"fill": "#606060"
});
var stopPattern = backCanvas.set();
stopPattern.push(backCanvas.path(stopPatternPathL));
stopPattern.push(backCanvas.path(stopPatternPathR));
stopPattern.attr({
"stroke-width": 0,
"stroke-linejoin": "round",
"fill": "#606060"
});
playButtonBack.transform("t" + (margin[3] - conf.colorBarHeight) + "," + (margin[0] + h + 33));
startPattern.transform("t" + (margin[3] - conf.colorBarHeight) + "," + (margin[0] + h + 33));
stopPattern.transform("t" + (margin[3] - conf.colorBarHeight) + "," + (margin[0] + h + 33));
startPattern.attr({
"stroke-width": 0,
"stroke-linejoin": "round",
"fill-opacity": 0
});
var playButton = backCanvas.set();
playButton.push(playButtonBack);
playButton.push(startPattern);
playButton.push(stopPattern);
playButton.dblclick(function() {
that.clearAnimation();
that.render();
});
playButton.click(function() {
if (that.interval) {
stopPattern.attr({"fill-opacity": 0});
startPattern.attr({"fill-opacity": 1});
that.pause();
} else {
startPattern.attr({"fill-opacity": 0});
stopPattern.attr({"fill-opacity": 1});
that.initControls();
}
});
playButton.hover(
function() {
startPattern.attr({"fill": "#ffffff"});
stopPattern.attr({"fill": "#ffffff"});
},
function() {
startPattern.attr({"fill": "#606060"});
stopPattern.attr({"fill": "#606060"});
}
);
};
/**
* draw x-axis, y-axis and related parts
*/
Bubble.prototype.renderAxis = function () {
var conf = this.defaults,
margin = conf.margin,
w = conf.width - margin[3] - margin[1],
h = conf.height - margin[0] - margin[2],
maxRadius = conf.maxRadius,
yaxis = Axis().orient("left"),
xaxis = Axis().orient("bottom"),
backCanvas = this.backCanvas,
xDimen = this.xDimen,
yDimen = this.yDimen,
xMin = conf.dimensionDomain[xDimen][0],
yMin = conf.dimensionDomain[yDimen][0],
xMax = conf.dimensionDomain[xDimen][1],
yMax = conf.dimensionDomain[yDimen][1],
xBorder = (maxRadius + 30) * (xMax - xMin)/w,
yBorder = (maxRadius + 30) * (yMax - yMin)/h,
xDomain = [xMin - xBorder, xMax + xBorder],
yDomain = [yMin - yBorder, yMax + yBorder],
axixX = d3.scale.linear().domain(xDomain).range([0, w]),
axixY = d3.scale.linear().domain(yDomain).range([h, 0]);
backCanvas.clear();
xaxis.scale(axixX)
.tickSubdivide(1)
.tickSize(6, 3, 0)
.tickPadding(5)
.tickAttr({"stroke": "#929292"})
.tickTextAttr({"font-size": "10px", "fill": "#929292"})
.minorTickAttr({"stroke": "#929292"})
.domainAttr({"stroke-width": 1, "stroke": "#929292"})
(backCanvas).attr({transform: "t" + margin[3] + "," + (margin[0] + h)});
yaxis.scale(axixY)
.tickSubdivide(1)
.tickSize(6, 3, 0)
.tickPadding(5)
.tickAttr({"stroke": "#929292"})
.tickTextAttr({"font-size": "10px", "fill": "#929292"})
.minorTickAttr({"stroke": "#929292"})
.domainAttr({"stroke-width": 1, "stroke": "#929292"})
(backCanvas).attr({transform: "t" + margin[3] + "," + margin[0]});
var xText = backCanvas.text(margin[3] + w/2, margin[0] + h + 40, this.xDimen);
xText.attr({"font-size": "15px", "font-family": "Arial", "fill": "#000000"});
var yText = backCanvas.text(margin[3] - 50, margin[0] + h/2, this.yDimen);
yText.attr({"font-size": "15px", "font-family": "Arial", "fill": "#000000"}).transform("r-90");
};
/**
* color database
*/
Bubble.prototype.colorDB = function (colorJson) {
var colorMatrix = DataV.getColor();
var color;
var colorStyle = colorJson || {};
var colorMode = colorStyle.mode || 'default';
var i, l;
switch (colorMode) {
case "gradient":
var index = colorJson.index || 0;
index = index < 0 ? 0 : Math.min(index, colorMatrix.length - 1);
color = d3.interpolateRgb.apply(null, [colorMatrix[index][0], colorMatrix[index][1]]);
break;
case "random":
case "default":
var ratio = colorStyle.ratio || 0;
if (ratio < 0) { ratio = 0; }
if (ratio > 1) { ratio = 1; }
var colorArray = [];
for (i = 0, l = colorMatrix.length; i < l; i++) {
var colorFunc = d3.interpolateRgb.apply(null, [colorMatrix[i][0], colorMatrix[i][1]]);
colorArray.push(colorFunc(ratio));
}
color = d3.scale.ordinal().range(colorArray);
break;
}
return color;
};
/**
* main visualization method where bubble is drawed inside
* a time point is the method's only parameter
*/
Bubble.prototype.generatePaths = function (time) {
var conf = this.defaults,
margin = conf.margin,
meshInterval = conf.meshInterval,
realWidth = conf.width - margin[3] - margin[1],
realHeight = conf.height - margin[0] - margin[2],
labelSize = 18,
foreCanvas = this.foreCanvas,
skeletonRadius = 2,
timeKeys = this.timeKeys,
keys = this.keys,
dotBubbleSet = [];
var that = this;
if (time < this.times.length - 1) {
this.startTime = time;
} else {
this.startTime = 0;
}
foreCanvas.clear();
// draw mesh
var meshes = foreCanvas.set(),
verticleMeshNum = realWidth / meshInterval,
horizontalMeshNUm = realHeight / meshInterval;
var i;
for (i = 1;i < verticleMeshNum;i++) {
meshes.push(foreCanvas.path("M"+(i * meshInterval)+" "+0+"L"+(i * meshInterval)+
" "+(realHeight-1)).attr({"stroke": "#ebebeb", "stroke-width": 1}));
}
for (i = 1; i < horizontalMeshNUm; i++) {
meshes.push(foreCanvas.path("M"+1+" "+(realHeight - (i * meshInterval))+"L"+realWidth+
" "+(realHeight - (i * meshInterval))).attr({"stroke": "#ebebeb", "stroke-dasharray": "-", "stroke-width": 0.5}));
}
var dots = [];
// get all data by time and key dimension data
for (var i = 0, l = keys.length; i < l; i++) {
var x0 = this.interpolateData(time, timeKeys, this.getKeyData(this.xDimen, keys[i]));
var y0 = this.interpolateData(time, timeKeys, this.getKeyData(this.yDimen, keys[i]));
var r0 = this.interpolateData(time, timeKeys, this.getKeyData(this.sizeDimen, keys[i]));
var c0 = this.getColorData(keys[i]);
var dot = {key: keys[i], x0: x0, y0: y0, r0: r0, c0: c0};
dots.push(dot);
}
var floatTag = this.floatTag;
// control the time label
var label = foreCanvas.text(20, margin[0] + realHeight + 15, this.times[Math.floor(time)]);
label.attr({"font-size": labelSize, "fill": "#606060", "text-anchor": "start"});
dots.sort(function(b,a) { return a.r0 < b.r0 ? -1 : a.r0 > b.r0 ? 1 : 0; });
// draw the circles
for (var i = 0, l = dots.length; i < l; i++) {
var dot = dots[i],
x = this.x[this.xDimen](dot.x0) - margin[3],
y = this.y[this.yDimen](dot.y0),
r = this.z[this.sizeDimen](dot.r0),
c = this.c[this.colorDimen](dot.c0),
dotBubble = foreCanvas.circle(x, y, r);
dotBubble.attr({"stroke-width":0, "fill": c, "fill-opacity": 0.5})
.data("key", dot.key).data("colorType", dot.c0);
dotBubbleSet.push(dotBubble);
}
// add hover and click effect for all circles
dotBubbleSet.forEach(function (d, i) {
(function (d, i) {
d.hover(function () {
floatTag.html(that.getTip(d.data("key"), Math.floor(time))).css(conf.tipStyle);
floatTag.css({"visibility" : "visible"});
if (!that.choose) {
d.attr({"stroke-width": 1, "stroke": "#f00", "fill-opacity": 0.8});
meshes.attr({"stroke": "#d6d6d6", "stroke-dasharray": "-", "stroke-width": 1});
for (var j = 0, l = dotBubbleSet.length; j < l ; j++) {
if (j !== i) {
dotBubbleSet[j].attr({"stroke-width": 0, "fill-opacity": 0.2});
}
}
}
}, function () {
floatTag.css({"visibility" : "hidden"});
if (!that.choose) {
d.attr({"stroke-width": 0, "fill-opacity": 0.5});
meshes.attr({"stroke": "#ebebeb", "stroke-dasharray": "-", "stroke-width": 1});
for (var j = 0, l = dotBubbleSet.length; j < l ; j++) {
if (j !== i) {
dotBubbleSet[j].attr({"stroke-width": 0, "fill-opacity": 0.5});
}
}
}
});
d.click(function() {
if (time === Math.ceil(time)) {
drawAllTime(this.data("key"), i);
} else {
drawAllTime(this.data("key"), i);
this.remove();
}
});
}(d, i));
});
// colorbar interaction for showing all same color history data
if (this.interactionType) {
dotBubbleSet.forEach(function (d) {
if (d.data("colorType") === that.interactionType) {
drawAllTime(d.data("key"));
}
});
}
// an inside method to visualize a key's all time data
function drawAllTime (key, num) {
if (!that.interval) {
that.choose = true;
var floatTag = that.floatTag;
for (var j = 0, l = dotBubbleSet.length; j < l ; j++) {
if (j !== num) {
dotBubbleSet[j].attr({"stroke-width": 0, "fill-opacity": 0.2});
}
}
meshes.attr({"stroke": "#d6d6d6", "stroke-dasharray": "-"});
var i;
for (i = 0, l = timeKeys.length; i < l; i++) {
(function (i) {
var x0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.xDimen, key)),
y0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.yDimen, key)),
r0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.sizeDimen, key)),
c0 = that.getColorData(key),
x = that.x[that.xDimen](x0) - margin[3],
y = that.y[that.yDimen](y0),
r = that.z[that.sizeDimen](r0),
c = that.c[that.colorDimen](c0),
fOpacity = 0.1 + Math.pow(1.5, i)/Math.pow(1.5, l);
var historyBubble = foreCanvas.circle(x, y, r);
historyBubble.attr({"stroke-width": 0, "fill": c, "fill-opacity": fOpacity});
if (timeKeys[i] === Math.ceil(time)) {
historyBubble.attr({"stroke-width": 1, "stroke": "#f00"});
historyBubble.hover(function () {
floatTag.html(that.getTip(key, i)).css(conf.tipStyle);
floatTag.css({"visibility" : "visible"});
}, function () {
floatTag.css({"visibility" : "hidden"});
});
} else {
historyBubble.hover(function () {
this.attr({"stroke-width": 1, "stroke": "#f00"});
floatTag.html(that.getTip(key, i)).css(conf.tipStyle);
floatTag.css({"visibility" : "visible"});
}, function () {
this.attr({"stroke-width": 0});
floatTag.css({"visibility" : "hidden"});
});
}
historyBubble.click(function () {
that.generatePaths(Math.ceil(time));
that.choose = false;
});
}(i));
}
var skeletonLineSet = foreCanvas.set();
for (i = 1, l = timeKeys.length; i < l; i++) {
var x0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.xDimen, key)),
y0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.yDimen, key)),
x = that.x[that.xDimen](x0) - margin[3],
y = that.y[that.yDimen](y0),
x1 = that.interpolateData(timeKeys[i-1], timeKeys, that.getKeyData(that.xDimen, key)),
y1 = that.interpolateData(timeKeys[i-1], timeKeys, that.getKeyData(that.yDimen, key)),
x2 = that.x[that.xDimen](x1) - margin[3],
y2 = that.y[that.yDimen](y1);
var skeletonLine = foreCanvas.path("M"+x2+" "+y2+"L"+x+" "+y);
skeletonLine.attr(conf.skeletonLineAttr);
skeletonLineSet.push(skeletonLine);
}
var skeletonCircleSet = foreCanvas.set();
for (i = 0, l = timeKeys.length; i < l; i++) {
(function (i) {
var x0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.xDimen, key)),
y0 = that.interpolateData(timeKeys[i], timeKeys, that.getKeyData(that.yDimen, key)),
c0 = that.getColorData(key),
x = that.x[that.xDimen](x0) - margin[3],
y = that.y[that.yDimen](y0),
c = that.c[that.colorDimen](c0);
var skeletonCircle = foreCanvas.circle(x,y,skeletonRadius);
skeletonCircle.attr(conf.skeletonCircleAttr).attr({"stroke": c});
skeletonCircleSet.push(skeletonCircle);
if (timeKeys[i] === Math.ceil(time)) {
skeletonCircle.attr({"fill": "#f00"});
skeletonCircle.click(function () {
that.generatePaths(Math.ceil(time));
});
skeletonCircle.hover(function () {
floatTag.html(that.getTip(key, i)).css(conf.tipStyle);
floatTag.css({"visibility" : "visible"});
skeletonCircleSet.attr({"fill-opacity": 0.35});
this.attr({"fill-opacity": 1, "r": 5});
skeletonLineSet.attr({"opacity": 0.35});
}, function () {
floatTag.css({"visibility" : "hidden"});
this.attr(conf.dotStrokeColor);
skeletonCircleSet.attr({"fill-opacity": 0.7});
this.attr({"r": skeletonRadius});
// meshes.attr({"stroke": "#ebebeb", "stroke-dasharray": "-"});
skeletonLineSet.attr({"opacity": 0.7});
});
} else {
skeletonCircle.hover(function () {
floatTag.html(that.getTip(key, i)).css(conf.tipStyle);
floatTag.css({"visibility" : "visible"});
skeletonCircleSet.attr({"fill-opacity": 0.35});
this.attr({"fill-opacity": 1, "r": 5});
skeletonLineSet.attr({"opacity": 0.35});
}, function () {
floatTag.css({"visibility" : "hidden"});
this.attr(conf.dotStrokeColor);
skeletonCircleSet.attr({"fill-opacity": 0.7});
this.attr({"r": skeletonRadius});
skeletonLineSet.attr({"opacity": 0.7});
});
}
}(i));
}
}
}
};
/**
* get key's specific dimension data which include all time points
*/
Bubble.prototype.getKeyData = function(dimen, key) {
var that = this;
return _.map(_.filter(this.source, function (item) {
return item[that.keyDimen] === key;
}), function (item) {
return item[dimen];
});
};
/**
* get a unique color specified by key
*/
Bubble.prototype.getColorData = function(key) {
for (var i = 0; i < this.source.length; i++) {
if (this.source[i][this.keyDimen] === key) {
return this.source[i][this.colorDimen];
}
}
};
/**
* set up an animation
*/
Bubble.prototype.initControls = function() {
var that = this,
len = this.times.length -1;
var value = this.startTime;
this.interval = setInterval(function() {
if (value <= len) {
that.generatePaths(value);
value += 0.25;
} else {
clearInterval(that.interval);
that.interval = 0;
}
}, 250);
};
/**
* interpolated some data between neibourh data point for the animation
*/
Bubble.prototype.interpolateData = function(year, years, values) {
var index = Math.ceil(year);
if (year === years[index]) {
return values[index];
}
var lowerIndex = Math.max(0,index-1);
var lower = values[lowerIndex];
var higherIndex = index;
var higher = values[higherIndex];
var lowYear = years[lowerIndex];
var highYear = years[higherIndex];
var p = (year - lowYear) / (highYear - lowYear);
var value = +lower + (higher - lower) * p;
return value;
};
/**
* clear animation and related artifacts
*/
Bubble.prototype.clearAnimation = function () {
clearInterval(this.interval);
this.interval = 0;
this.backCanvas.clear();
this.foreCanvas.clear();
};
/**
* pause the interval
*/
Bubble.prototype.pause = function () {
clearInterval(this.interval);
this.interval = 0;
};
/**
* set the rendering process
*/
Bubble.prototype.render = function (options) {
clearInterval(this.interval);
this.setOptions(options);
if (!this.interval) {
this.renderAxis();
}
this.foreCanvas.clear();
this.getScale();
this.initControls();
};
return Bubble;
});