/*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; });